Skip to content

Commit

Permalink
Support AirPods Mute/Unmute toggle.
Browse files Browse the repository at this point in the history
  • Loading branch information
john-preston committed Mar 15, 2024
1 parent 5493af6 commit 1cbf5fa
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 1 deletion.
37 changes: 36 additions & 1 deletion webrtc/platform/mac/webrtc_environment_mac.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,24 @@
//
#pragma once

#include "base/weak_ptr.h"
#include "webrtc/platform/webrtc_platform_environment.h"

#include <media/engine/webrtc_media_engine.h>

namespace rtc {
template <class T>
class scoped_refptr;
} // namespace rtc

namespace webrtc {
class TaskQueueFactory;
class AudioDeviceModule;
} // namespace webrtc

namespace Webrtc::Platform {

class EnvironmentMac final : public Environment {
class EnvironmentMac final : public Environment, public base::has_weak_ptr {
public:
explicit EnvironmentMac(not_null<EnvironmentDelegate*> delegate);
~EnvironmentMac();
Expand All @@ -27,13 +40,35 @@ class EnvironmentMac final : public Environment {
void defaultIdRequested(DeviceType type) override;
void devicesRequested(DeviceType type) override;

void setCaptureMuted(bool muted) override;
void setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track) override;

void defaultPlaybackDeviceChanged();
void defaultCaptureDeviceChanged();
void audioDeviceListChanged();

private:
void captureMuteSubscribe();
void captureMuteUnsubscribe();
void captureMuteRestartAdm();

const not_null<EnvironmentDelegate*> _delegate;

CaptureMuteTracker *_captureMuteTracker = nullptr;
bool _captureMuteNotification = false;
bool _captureMuted = false;

std::unique_ptr<webrtc::TaskQueueFactory> _admTaskQueueFactory;
rtc::scoped_refptr<webrtc::AudioDeviceModule> _adm;
Fn<void(DeviceResolvedId)> _admSetDeviceIdCallback;
DeviceResolvedId _admCaptureDeviceId;

rpl::lifetime _captureMuteTrackerLifetime;
rpl::lifetime _captureMuteSubscriptionLifetime;
rpl::lifetime _lifetime;

};

} // namespace Webrtc::Platform
134 changes: 134 additions & 0 deletions webrtc/platform/mac/webrtc_environment_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,37 @@

#include "base/weak_ptr.h"
#include "webrtc/webrtc_environment.h"
#include "webrtc/webrtc_create_adm.h"

#include <modules/audio_device/include/audio_device_defines.h>
#include <api/task_queue/default_task_queue_factory.h>

#import <AVFoundation/AVFoundation.h>
#import <IOKit/hidsystem/IOHIDLib.h>
#import <CoreAudio/CoreAudio.h>
#import <Cocoa/Cocoa.h>

@interface InputMuteObserver : NSObject {
}

- (id) init;
- (void) inputMuteStateChange:(NSNotification *)aNotification;

@end // @interface InputMuteObserver

@implementation InputMuteObserver {
}

- (id) init {
if (self = [super init]) {
}
return self;
}

- (void) inputMuteStateChange:(NSNotification *)aNotification {
}

@end // @implementation InputMuteObserver

namespace Webrtc::Platform {
namespace {
Expand Down Expand Up @@ -338,6 +363,30 @@ static OSStatus Callback(
DefaultPlaybackDeviceChangedMonitor.registerEnvironment(this);
DefaultCaptureDeviceChangedMonitor.registerEnvironment(this);
AudioDeviceListChangedMonitor.registerEnvironment(this);

if (@available(macOS 14.0, *)) {
const auto weak = base::make_weak(this);
id block = [^(BOOL shouldBeMuted){
crl::on_main([weak, mute = shouldBeMuted ? true : false] {
if (const auto strong = weak.get()) {
if (const auto tracker = strong->_captureMuteTracker) {
strong->_captureMuted = mute;
strong->_captureMuteNotification = true;
tracker->captureMuteChanged(mute);
if (const auto strong = weak.get()) {
strong->_captureMuteNotification = false;
}
}
}
});
return YES;
} copy];
_lifetime.add([block] { [block release]; });

[[AVAudioApplication sharedInstance]
setInputMuteStateChangeHandler:block
error:nil];
}
}

EnvironmentMac::~EnvironmentMac() {
Expand Down Expand Up @@ -487,6 +536,91 @@ static OSStatus Callback(
void EnvironmentMac::devicesRequested(DeviceType type) {
}

void EnvironmentMac::setCaptureMuted(bool muted) {
if (@available(macOS 14.0, *)) {
if (!_captureMuteNotification) {
const auto value = muted ? YES : NO;
[[AVAudioApplication sharedInstance] setInputMuted:value error:nil];
}
}
}

void EnvironmentMac::captureMuteSubscribe() {
if (@available(macOS 14.0, *)) {
id observer = [[InputMuteObserver alloc] init];
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:observer
selector:@selector(inputMuteStateChange:)
name:AVAudioApplicationInputMuteStateChangeNotification
object:nil];

_admTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory();
const auto saveSetDeviceIdCallback = [=](
Fn<void(DeviceResolvedId)> setDeviceIdCallback) {
_admSetDeviceIdCallback = std::move(setDeviceIdCallback);
if (!_admCaptureDeviceId.isDefault()) {
_admSetDeviceIdCallback(_admCaptureDeviceId);
}
};
_adm = CreateAudioDeviceModule(
_admTaskQueueFactory.get(),
saveSetDeviceIdCallback);

_captureMuteSubscriptionLifetime.add([=] {
_admSetDeviceIdCallback = nullptr;
_adm = nullptr;
_admTaskQueueFactory = nullptr;

[[[NSWorkspace sharedWorkspace] notificationCenter]
removeObserver:observer
name:AVAudioApplicationInputMuteStateChangeNotification
object:nil];
[observer release];
});
}
}

void EnvironmentMac::captureMuteUnsubscribe() {
_captureMuteSubscriptionLifetime.destroy();
}

void EnvironmentMac::captureMuteRestartAdm() {
_adm->StopRecording();
_adm->SetRecordingDevice(0);
if (_adm->InitRecording() == 0) {
_adm->StartRecording();
}
}

void EnvironmentMac::setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track) {
if (@available(macOS 14.0, *)) {
if (track) {
if (!_captureMuteTracker) {
captureMuteSubscribe();
} else if (_captureMuteTracker == tracker) {
return;
}
_captureMuteTrackerLifetime.destroy();
_captureMuteTracker = tracker;
_captureMuteTracker->captureMuteDeviceId(
) | rpl::start_with_next([=](DeviceResolvedId deviceId) {
_admSetDeviceIdCallback(deviceId);
captureMuteRestartAdm();
}, _captureMuteTrackerLifetime);
} else if (_captureMuteTracker == tracker) {
_captureMuteTrackerLifetime.destroy();
_captureMuteTracker = nullptr;
captureMuteUnsubscribe();
if (!_captureMuted) {
_captureMuted = true;
setCaptureMuted(true);
}
}
}
}

std::unique_ptr<Environment> CreateEnvironment(
not_null<EnvironmentDelegate*> delegate) {
return std::make_unique<EnvironmentMac>(delegate);
Expand Down
7 changes: 7 additions & 0 deletions webrtc/platform/webrtc_platform_environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ class Environment {
return lastResolvedId;
}

virtual void setCaptureMuted(bool muted) {
}
virtual void setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track) {
}

};

[[nodiscard]] std::unique_ptr<Environment> CreateEnvironment(
Expand Down
6 changes: 6 additions & 0 deletions webrtc/webrtc_device_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,10 @@ struct DeviceResolvedId {
const DeviceResolvedId &b) = default;
};

class CaptureMuteTracker {
public:
virtual void captureMuteChanged(bool muted) = 0;
[[nodiscard]] virtual rpl::producer<DeviceResolvedId> captureMuteDeviceId() = 0;
};

} // namespace Webrtc
10 changes: 10 additions & 0 deletions webrtc/webrtc_environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ DeviceResolvedId Environment::threadSafeResolveId(
return _platform->threadSafeResolveId(lastResolvedId, savedId);
}

void Environment::setCaptureMuted(bool muted) {
_platform->setCaptureMuted(muted);
}

void Environment::setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track) {
_platform->setCaptureMuteTracker(tracker, track);
}

void Environment::defaultChanged(
DeviceType type,
DeviceChangeReason reason,
Expand Down
5 changes: 5 additions & 0 deletions webrtc/webrtc_environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ class Environment final : private Platform::EnvironmentDelegate {
const DeviceResolvedId &lastResolvedId,
const QString &savedId) const;

void setCaptureMuted(bool muted);
void setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track);

private:
static constexpr auto kTypeCount = 3;

Expand Down

0 comments on commit 1cbf5fa

Please sign in to comment.