| Index: webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm
|
| diff --git a/webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm b/webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm
|
| deleted file mode 100644
|
| index 763f1fae54d4b41f4679e4dcbba16702d6284dfb..0000000000000000000000000000000000000000
|
| --- a/webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm
|
| +++ /dev/null
|
| @@ -1,915 +0,0 @@
|
| -/*
|
| - * Copyright 2016 The WebRTC Project Authors. All rights reserved.
|
| - *
|
| - * Use of this source code is governed by a BSD-style license
|
| - * that can be found in the LICENSE file in the root of the source
|
| - * tree. An additional intellectual property rights grant can be found
|
| - * in the file PATENTS. All contributing project authors may
|
| - * be found in the AUTHORS file in the root of the source tree.
|
| - */
|
| -
|
| -#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h"
|
| -
|
| -#import <UIKit/UIKit.h>
|
| -
|
| -#include "webrtc/base/atomicops.h"
|
| -#include "webrtc/base/checks.h"
|
| -#include "webrtc/base/criticalsection.h"
|
| -#include "webrtc/modules/audio_device/ios/audio_device_ios.h"
|
| -
|
| -#import "WebRTC/RTCLogging.h"
|
| -#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h"
|
| -#import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h"
|
| -
|
| -NSString * const kRTCAudioSessionErrorDomain = @"org.webrtc.RTCAudioSession";
|
| -NSInteger const kRTCAudioSessionErrorLockRequired = -1;
|
| -NSInteger const kRTCAudioSessionErrorConfiguration = -2;
|
| -NSString * const kRTCAudioSessionOutputVolumeSelector = @"outputVolume";
|
| -
|
| -// This class needs to be thread-safe because it is accessed from many threads.
|
| -// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
|
| -// lock contention so coarse locks should be fine for now.
|
| -@implementation RTCAudioSession {
|
| - rtc::CriticalSection _crit;
|
| - AVAudioSession *_session;
|
| - volatile int _activationCount;
|
| - volatile int _lockRecursionCount;
|
| - volatile int _webRTCSessionCount;
|
| - BOOL _isActive;
|
| - BOOL _useManualAudio;
|
| - BOOL _isAudioEnabled;
|
| - BOOL _canPlayOrRecord;
|
| - BOOL _isInterrupted;
|
| -}
|
| -
|
| -@synthesize session = _session;
|
| -@synthesize delegates = _delegates;
|
| -
|
| -+ (instancetype)sharedInstance {
|
| - static dispatch_once_t onceToken;
|
| - static RTCAudioSession *sharedInstance = nil;
|
| - dispatch_once(&onceToken, ^{
|
| - sharedInstance = [[self alloc] init];
|
| - });
|
| - return sharedInstance;
|
| -}
|
| -
|
| -- (instancetype)init {
|
| - if (self = [super init]) {
|
| - _session = [AVAudioSession sharedInstance];
|
| -
|
| - NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
| - [center addObserver:self
|
| - selector:@selector(handleInterruptionNotification:)
|
| - name:AVAudioSessionInterruptionNotification
|
| - object:nil];
|
| - [center addObserver:self
|
| - selector:@selector(handleRouteChangeNotification:)
|
| - name:AVAudioSessionRouteChangeNotification
|
| - object:nil];
|
| - [center addObserver:self
|
| - selector:@selector(handleMediaServicesWereLost:)
|
| - name:AVAudioSessionMediaServicesWereLostNotification
|
| - object:nil];
|
| - [center addObserver:self
|
| - selector:@selector(handleMediaServicesWereReset:)
|
| - name:AVAudioSessionMediaServicesWereResetNotification
|
| - object:nil];
|
| - // Posted on the main thread when the primary audio from other applications
|
| - // starts and stops. Foreground applications may use this notification as a
|
| - // hint to enable or disable audio that is secondary.
|
| - [center addObserver:self
|
| - selector:@selector(handleSilenceSecondaryAudioHintNotification:)
|
| - name:AVAudioSessionSilenceSecondaryAudioHintNotification
|
| - object:nil];
|
| - // Also track foreground event in order to deal with interruption ended situation.
|
| - [center addObserver:self
|
| - selector:@selector(handleApplicationDidBecomeActive:)
|
| - name:UIApplicationDidBecomeActiveNotification
|
| - object:nil];
|
| - [_session addObserver:self
|
| - forKeyPath:kRTCAudioSessionOutputVolumeSelector
|
| - options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
|
| - context:nil];
|
| -
|
| - RTCLog(@"RTCAudioSession (%p): init.", self);
|
| - }
|
| - return self;
|
| -}
|
| -
|
| -- (void)dealloc {
|
| - [[NSNotificationCenter defaultCenter] removeObserver:self];
|
| - [_session removeObserver:self forKeyPath:kRTCAudioSessionOutputVolumeSelector context:nil];
|
| - RTCLog(@"RTCAudioSession (%p): dealloc.", self);
|
| -}
|
| -
|
| -- (NSString *)description {
|
| - NSString *format =
|
| - @"RTCAudioSession: {\n"
|
| - " category: %@\n"
|
| - " categoryOptions: %ld\n"
|
| - " mode: %@\n"
|
| - " isActive: %d\n"
|
| - " sampleRate: %.2f\n"
|
| - " IOBufferDuration: %f\n"
|
| - " outputNumberOfChannels: %ld\n"
|
| - " inputNumberOfChannels: %ld\n"
|
| - " outputLatency: %f\n"
|
| - " inputLatency: %f\n"
|
| - " outputVolume: %f\n"
|
| - "}";
|
| - NSString *description = [NSString stringWithFormat:format,
|
| - self.category, (long)self.categoryOptions, self.mode,
|
| - self.isActive, self.sampleRate, self.IOBufferDuration,
|
| - self.outputNumberOfChannels, self.inputNumberOfChannels,
|
| - self.outputLatency, self.inputLatency, self.outputVolume];
|
| - return description;
|
| -}
|
| -
|
| -- (void)setIsActive:(BOOL)isActive {
|
| - @synchronized(self) {
|
| - _isActive = isActive;
|
| - }
|
| -}
|
| -
|
| -- (BOOL)isActive {
|
| - @synchronized(self) {
|
| - return _isActive;
|
| - }
|
| -}
|
| -
|
| -- (BOOL)isLocked {
|
| - return _lockRecursionCount > 0;
|
| -}
|
| -
|
| -- (void)setUseManualAudio:(BOOL)useManualAudio {
|
| - @synchronized(self) {
|
| - if (_useManualAudio == useManualAudio) {
|
| - return;
|
| - }
|
| - _useManualAudio = useManualAudio;
|
| - }
|
| - [self updateCanPlayOrRecord];
|
| -}
|
| -
|
| -- (BOOL)useManualAudio {
|
| - @synchronized(self) {
|
| - return _useManualAudio;
|
| - }
|
| -}
|
| -
|
| -- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
|
| - @synchronized(self) {
|
| - if (_isAudioEnabled == isAudioEnabled) {
|
| - return;
|
| - }
|
| - _isAudioEnabled = isAudioEnabled;
|
| - }
|
| - [self updateCanPlayOrRecord];
|
| -}
|
| -
|
| -- (BOOL)isAudioEnabled {
|
| - @synchronized(self) {
|
| - return _isAudioEnabled;
|
| - }
|
| -}
|
| -
|
| -// TODO(tkchin): Check for duplicates.
|
| -- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
|
| - RTCLog(@"Adding delegate: (%p)", delegate);
|
| - if (!delegate) {
|
| - return;
|
| - }
|
| - @synchronized(self) {
|
| - _delegates.push_back(delegate);
|
| - [self removeZeroedDelegates];
|
| - }
|
| -}
|
| -
|
| -- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
|
| - RTCLog(@"Removing delegate: (%p)", delegate);
|
| - if (!delegate) {
|
| - return;
|
| - }
|
| - @synchronized(self) {
|
| - _delegates.erase(std::remove(_delegates.begin(),
|
| - _delegates.end(),
|
| - delegate),
|
| - _delegates.end());
|
| - [self removeZeroedDelegates];
|
| - }
|
| -}
|
| -
|
| -#pragma clang diagnostic push
|
| -#pragma clang diagnostic ignored "-Wthread-safety-analysis"
|
| -
|
| -- (void)lockForConfiguration {
|
| - _crit.Enter();
|
| - rtc::AtomicOps::Increment(&_lockRecursionCount);
|
| -}
|
| -
|
| -- (void)unlockForConfiguration {
|
| - // Don't let threads other than the one that called lockForConfiguration
|
| - // unlock.
|
| - if (_crit.TryEnter()) {
|
| - rtc::AtomicOps::Decrement(&_lockRecursionCount);
|
| - // One unlock for the tryLock, and another one to actually unlock. If this
|
| - // was called without anyone calling lock, we will hit an assertion.
|
| - _crit.Leave();
|
| - _crit.Leave();
|
| - }
|
| -}
|
| -
|
| -#pragma clang diagnostic pop
|
| -
|
| -#pragma mark - AVAudioSession proxy methods
|
| -
|
| -- (NSString *)category {
|
| - return self.session.category;
|
| -}
|
| -
|
| -- (AVAudioSessionCategoryOptions)categoryOptions {
|
| - return self.session.categoryOptions;
|
| -}
|
| -
|
| -- (NSString *)mode {
|
| - return self.session.mode;
|
| -}
|
| -
|
| -- (BOOL)secondaryAudioShouldBeSilencedHint {
|
| - return self.session.secondaryAudioShouldBeSilencedHint;
|
| -}
|
| -
|
| -- (AVAudioSessionRouteDescription *)currentRoute {
|
| - return self.session.currentRoute;
|
| -}
|
| -
|
| -- (NSInteger)maximumInputNumberOfChannels {
|
| - return self.session.maximumInputNumberOfChannels;
|
| -}
|
| -
|
| -- (NSInteger)maximumOutputNumberOfChannels {
|
| - return self.session.maximumOutputNumberOfChannels;
|
| -}
|
| -
|
| -- (float)inputGain {
|
| - return self.session.inputGain;
|
| -}
|
| -
|
| -- (BOOL)inputGainSettable {
|
| - return self.session.inputGainSettable;
|
| -}
|
| -
|
| -- (BOOL)inputAvailable {
|
| - return self.session.inputAvailable;
|
| -}
|
| -
|
| -- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
|
| - return self.session.inputDataSources;
|
| -}
|
| -
|
| -- (AVAudioSessionDataSourceDescription *)inputDataSource {
|
| - return self.session.inputDataSource;
|
| -}
|
| -
|
| -- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
|
| - return self.session.outputDataSources;
|
| -}
|
| -
|
| -- (AVAudioSessionDataSourceDescription *)outputDataSource {
|
| - return self.session.outputDataSource;
|
| -}
|
| -
|
| -- (double)sampleRate {
|
| - return self.session.sampleRate;
|
| -}
|
| -
|
| -- (double)preferredSampleRate {
|
| - return self.session.preferredSampleRate;
|
| -}
|
| -
|
| -- (NSInteger)inputNumberOfChannels {
|
| - return self.session.inputNumberOfChannels;
|
| -}
|
| -
|
| -- (NSInteger)outputNumberOfChannels {
|
| - return self.session.outputNumberOfChannels;
|
| -}
|
| -
|
| -- (float)outputVolume {
|
| - return self.session.outputVolume;
|
| -}
|
| -
|
| -- (NSTimeInterval)inputLatency {
|
| - return self.session.inputLatency;
|
| -}
|
| -
|
| -- (NSTimeInterval)outputLatency {
|
| - return self.session.outputLatency;
|
| -}
|
| -
|
| -- (NSTimeInterval)IOBufferDuration {
|
| - return self.session.IOBufferDuration;
|
| -}
|
| -
|
| -- (NSTimeInterval)preferredIOBufferDuration {
|
| - return self.session.preferredIOBufferDuration;
|
| -}
|
| -
|
| -// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
|
| -// can just do atomic increments / decrements.
|
| -- (BOOL)setActive:(BOOL)active
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - int activationCount = _activationCount;
|
| - if (!active && activationCount == 0) {
|
| - RTCLogWarning(@"Attempting to deactivate without prior activation.");
|
| - }
|
| - BOOL success = YES;
|
| - BOOL isActive = self.isActive;
|
| - // Keep a local error so we can log it.
|
| - NSError *error = nil;
|
| - BOOL shouldSetActive =
|
| - (active && !isActive) || (!active && isActive && activationCount == 1);
|
| - // Attempt to activate if we're not active.
|
| - // Attempt to deactivate if we're active and it's the last unbalanced call.
|
| - if (shouldSetActive) {
|
| - AVAudioSession *session = self.session;
|
| - // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
|
| - // that other audio sessions that were interrupted by our session can return
|
| - // to their active state. It is recommended for VoIP apps to use this
|
| - // option.
|
| - AVAudioSessionSetActiveOptions options =
|
| - active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
|
| - success = [session setActive:active
|
| - withOptions:options
|
| - error:&error];
|
| - if (outError) {
|
| - *outError = error;
|
| - }
|
| - }
|
| - if (success) {
|
| - if (shouldSetActive) {
|
| - self.isActive = active;
|
| - }
|
| - if (active) {
|
| - [self incrementActivationCount];
|
| - }
|
| - } else {
|
| - RTCLogError(@"Failed to setActive:%d. Error: %@",
|
| - active, error.localizedDescription);
|
| - }
|
| - // Decrement activation count on deactivation whether or not it succeeded.
|
| - if (!active) {
|
| - [self decrementActivationCount];
|
| - }
|
| - RTCLog(@"Number of current activations: %d", _activationCount);
|
| - return success;
|
| -}
|
| -
|
| -- (BOOL)setCategory:(NSString *)category
|
| - withOptions:(AVAudioSessionCategoryOptions)options
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setCategory:category withOptions:options error:outError];
|
| -}
|
| -
|
| -- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setMode:mode error:outError];
|
| -}
|
| -
|
| -- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setInputGain:gain error:outError];
|
| -}
|
| -
|
| -- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setPreferredSampleRate:sampleRate error:outError];
|
| -}
|
| -
|
| -- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setPreferredIOBufferDuration:duration error:outError];
|
| -}
|
| -
|
| -- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setPreferredInputNumberOfChannels:count error:outError];
|
| -}
|
| -- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setPreferredOutputNumberOfChannels:count error:outError];
|
| -}
|
| -
|
| -- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session overrideOutputAudioPort:portOverride error:outError];
|
| -}
|
| -
|
| -- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setPreferredInput:inPort error:outError];
|
| -}
|
| -
|
| -- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setInputDataSource:dataSource error:outError];
|
| -}
|
| -
|
| -- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
|
| - error:(NSError **)outError {
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - return [self.session setOutputDataSource:dataSource error:outError];
|
| -}
|
| -
|
| -#pragma mark - Notifications
|
| -
|
| -- (void)handleInterruptionNotification:(NSNotification *)notification {
|
| - NSNumber* typeNumber =
|
| - notification.userInfo[AVAudioSessionInterruptionTypeKey];
|
| - AVAudioSessionInterruptionType type =
|
| - (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
|
| - switch (type) {
|
| - case AVAudioSessionInterruptionTypeBegan:
|
| - RTCLog(@"Audio session interruption began.");
|
| - self.isActive = NO;
|
| - self.isInterrupted = YES;
|
| - [self notifyDidBeginInterruption];
|
| - break;
|
| - case AVAudioSessionInterruptionTypeEnded: {
|
| - RTCLog(@"Audio session interruption ended.");
|
| - self.isInterrupted = NO;
|
| - [self updateAudioSessionAfterEvent];
|
| - NSNumber *optionsNumber =
|
| - notification.userInfo[AVAudioSessionInterruptionOptionKey];
|
| - AVAudioSessionInterruptionOptions options =
|
| - optionsNumber.unsignedIntegerValue;
|
| - BOOL shouldResume =
|
| - options & AVAudioSessionInterruptionOptionShouldResume;
|
| - [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
|
| - break;
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)handleRouteChangeNotification:(NSNotification *)notification {
|
| - // Get reason for current route change.
|
| - NSNumber* reasonNumber =
|
| - notification.userInfo[AVAudioSessionRouteChangeReasonKey];
|
| - AVAudioSessionRouteChangeReason reason =
|
| - (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
|
| - RTCLog(@"Audio route changed:");
|
| - switch (reason) {
|
| - case AVAudioSessionRouteChangeReasonUnknown:
|
| - RTCLog(@"Audio route changed: ReasonUnknown");
|
| - break;
|
| - case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
|
| - RTCLog(@"Audio route changed: NewDeviceAvailable");
|
| - break;
|
| - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
|
| - RTCLog(@"Audio route changed: OldDeviceUnavailable");
|
| - break;
|
| - case AVAudioSessionRouteChangeReasonCategoryChange:
|
| - RTCLog(@"Audio route changed: CategoryChange to :%@",
|
| - self.session.category);
|
| - break;
|
| - case AVAudioSessionRouteChangeReasonOverride:
|
| - RTCLog(@"Audio route changed: Override");
|
| - break;
|
| - case AVAudioSessionRouteChangeReasonWakeFromSleep:
|
| - RTCLog(@"Audio route changed: WakeFromSleep");
|
| - break;
|
| - case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
|
| - RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
|
| - break;
|
| - case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
|
| - RTCLog(@"Audio route changed: RouteConfigurationChange");
|
| - break;
|
| - }
|
| - AVAudioSessionRouteDescription* previousRoute =
|
| - notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
|
| - // Log previous route configuration.
|
| - RTCLog(@"Previous route: %@\nCurrent route:%@",
|
| - previousRoute, self.session.currentRoute);
|
| - [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
|
| -}
|
| -
|
| -- (void)handleMediaServicesWereLost:(NSNotification *)notification {
|
| - RTCLog(@"Media services were lost.");
|
| - [self updateAudioSessionAfterEvent];
|
| - [self notifyMediaServicesWereLost];
|
| -}
|
| -
|
| -- (void)handleMediaServicesWereReset:(NSNotification *)notification {
|
| - RTCLog(@"Media services were reset.");
|
| - [self updateAudioSessionAfterEvent];
|
| - [self notifyMediaServicesWereReset];
|
| -}
|
| -
|
| -- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
|
| - // TODO(henrika): just adding logs here for now until we know if we are ever
|
| - // see this notification and might be affected by it or if further actions
|
| - // are required.
|
| - NSNumber *typeNumber =
|
| - notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
|
| - AVAudioSessionSilenceSecondaryAudioHintType type =
|
| - (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
|
| - switch (type) {
|
| - case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
|
| - RTCLog(@"Another application's primary audio has started.");
|
| - break;
|
| - case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
|
| - RTCLog(@"Another application's primary audio has stopped.");
|
| - break;
|
| - }
|
| -}
|
| -
|
| -- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
|
| - RTCLog(@"Application became active after an interruption. Treating as interruption "
|
| - " end. isInterrupted changed from %d to 0.", self.isInterrupted);
|
| - if (self.isInterrupted) {
|
| - self.isInterrupted = NO;
|
| - [self updateAudioSessionAfterEvent];
|
| - }
|
| - // Always treat application becoming active as an interruption end event.
|
| - [self notifyDidEndInterruptionWithShouldResumeSession:YES];
|
| -}
|
| -
|
| -#pragma mark - Private
|
| -
|
| -+ (NSError *)lockError {
|
| - NSDictionary *userInfo = @{
|
| - NSLocalizedDescriptionKey:
|
| - @"Must call lockForConfiguration before calling this method."
|
| - };
|
| - NSError *error =
|
| - [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
|
| - code:kRTCAudioSessionErrorLockRequired
|
| - userInfo:userInfo];
|
| - return error;
|
| -}
|
| -
|
| -- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
|
| - @synchronized(self) {
|
| - // Note: this returns a copy.
|
| - return _delegates;
|
| - }
|
| -}
|
| -
|
| -// TODO(tkchin): check for duplicates.
|
| -- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
|
| - @synchronized(self) {
|
| - _delegates.insert(_delegates.begin(), delegate);
|
| - }
|
| -}
|
| -
|
| -- (void)removeZeroedDelegates {
|
| - @synchronized(self) {
|
| - _delegates.erase(
|
| - std::remove_if(_delegates.begin(),
|
| - _delegates.end(),
|
| - [](id delegate) -> bool { return delegate == nil; }),
|
| - _delegates.end());
|
| - }
|
| -}
|
| -
|
| -- (int)activationCount {
|
| - return _activationCount;
|
| -}
|
| -
|
| -- (int)incrementActivationCount {
|
| - RTCLog(@"Incrementing activation count.");
|
| - return rtc::AtomicOps::Increment(&_activationCount);
|
| -}
|
| -
|
| -- (NSInteger)decrementActivationCount {
|
| - RTCLog(@"Decrementing activation count.");
|
| - return rtc::AtomicOps::Decrement(&_activationCount);
|
| -}
|
| -
|
| -- (int)webRTCSessionCount {
|
| - return _webRTCSessionCount;
|
| -}
|
| -
|
| -- (BOOL)canPlayOrRecord {
|
| - return !self.useManualAudio || self.isAudioEnabled;
|
| -}
|
| -
|
| -- (BOOL)isInterrupted {
|
| - @synchronized(self) {
|
| - return _isInterrupted;
|
| - }
|
| -}
|
| -
|
| -- (void)setIsInterrupted:(BOOL)isInterrupted {
|
| - @synchronized(self) {
|
| - if (_isInterrupted == isInterrupted) {
|
| - return;
|
| - }
|
| - _isInterrupted = isInterrupted;
|
| - }
|
| -}
|
| -
|
| -- (BOOL)checkLock:(NSError **)outError {
|
| - // Check ivar instead of trying to acquire lock so that we won't accidentally
|
| - // acquire lock if it hasn't already been called.
|
| - if (!self.isLocked) {
|
| - if (outError) {
|
| - *outError = [RTCAudioSession lockError];
|
| - }
|
| - return NO;
|
| - }
|
| - return YES;
|
| -}
|
| -
|
| -- (BOOL)beginWebRTCSession:(NSError **)outError {
|
| - if (outError) {
|
| - *outError = nil;
|
| - }
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - rtc::AtomicOps::Increment(&_webRTCSessionCount);
|
| - [self notifyDidStartPlayOrRecord];
|
| - return YES;
|
| -}
|
| -
|
| -- (BOOL)endWebRTCSession:(NSError **)outError {
|
| - if (outError) {
|
| - *outError = nil;
|
| - }
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - rtc::AtomicOps::Decrement(&_webRTCSessionCount);
|
| - [self notifyDidStopPlayOrRecord];
|
| - return YES;
|
| -}
|
| -
|
| -- (BOOL)configureWebRTCSession:(NSError **)outError {
|
| - if (outError) {
|
| - *outError = nil;
|
| - }
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - RTCLog(@"Configuring audio session for WebRTC.");
|
| -
|
| - // Configure the AVAudioSession and activate it.
|
| - // Provide an error even if there isn't one so we can log it.
|
| - NSError *error = nil;
|
| - RTCAudioSessionConfiguration *webRTCConfig =
|
| - [RTCAudioSessionConfiguration webRTCConfiguration];
|
| - if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
|
| - RTCLogError(@"Failed to set WebRTC audio configuration: %@",
|
| - error.localizedDescription);
|
| - // Do not call setActive:NO if setActive:YES failed.
|
| - if (outError) {
|
| - *outError = error;
|
| - }
|
| - return NO;
|
| - }
|
| -
|
| - // Ensure that the device currently supports audio input.
|
| - // TODO(tkchin): Figure out if this is really necessary.
|
| - if (!self.inputAvailable) {
|
| - RTCLogError(@"No audio input path is available!");
|
| - [self unconfigureWebRTCSession:nil];
|
| - if (outError) {
|
| - *outError = [self configurationErrorWithDescription:@"No input path."];
|
| - }
|
| - return NO;
|
| - }
|
| -
|
| - // It can happen (e.g. in combination with BT devices) that the attempt to set
|
| - // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
|
| - // configuration attempt using the sample rate that worked using the active
|
| - // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
|
| - // combination with BT headsets. Using this "trick" seems to avoid a state
|
| - // where Core Audio asks for a different number of audio frames than what the
|
| - // session's I/O buffer duration corresponds to.
|
| - // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
|
| - // tested on a limited set of iOS devices and BT devices.
|
| - double sessionSampleRate = self.sampleRate;
|
| - double preferredSampleRate = webRTCConfig.sampleRate;
|
| - if (sessionSampleRate != preferredSampleRate) {
|
| - RTCLogWarning(
|
| - @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
|
| - sessionSampleRate, preferredSampleRate);
|
| - if (![self setPreferredSampleRate:sessionSampleRate
|
| - error:&error]) {
|
| - RTCLogError(@"Failed to set preferred sample rate: %@",
|
| - error.localizedDescription);
|
| - if (outError) {
|
| - *outError = error;
|
| - }
|
| - }
|
| - }
|
| -
|
| - return YES;
|
| -}
|
| -
|
| -- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
|
| - if (outError) {
|
| - *outError = nil;
|
| - }
|
| - if (![self checkLock:outError]) {
|
| - return NO;
|
| - }
|
| - RTCLog(@"Unconfiguring audio session for WebRTC.");
|
| - [self setActive:NO error:outError];
|
| -
|
| - return YES;
|
| -}
|
| -
|
| -- (NSError *)configurationErrorWithDescription:(NSString *)description {
|
| - NSDictionary* userInfo = @{
|
| - NSLocalizedDescriptionKey: description,
|
| - };
|
| - return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
|
| - code:kRTCAudioSessionErrorConfiguration
|
| - userInfo:userInfo];
|
| -}
|
| -
|
| -- (void)updateAudioSessionAfterEvent {
|
| - BOOL shouldActivate = self.activationCount > 0;
|
| - AVAudioSessionSetActiveOptions options = shouldActivate ?
|
| - 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
|
| - NSError *error = nil;
|
| - if ([self.session setActive:shouldActivate
|
| - withOptions:options
|
| - error:&error]) {
|
| - self.isActive = shouldActivate;
|
| - } else {
|
| - RTCLogError(@"Failed to set session active to %d. Error:%@",
|
| - shouldActivate, error.localizedDescription);
|
| - }
|
| -}
|
| -
|
| -- (void)updateCanPlayOrRecord {
|
| - BOOL canPlayOrRecord = NO;
|
| - BOOL shouldNotify = NO;
|
| - @synchronized(self) {
|
| - canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
|
| - if (_canPlayOrRecord == canPlayOrRecord) {
|
| - return;
|
| - }
|
| - _canPlayOrRecord = canPlayOrRecord;
|
| - shouldNotify = YES;
|
| - }
|
| - if (shouldNotify) {
|
| - [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
|
| - }
|
| -}
|
| -
|
| -- (void)audioSessionDidActivate:(AVAudioSession *)session {
|
| - if (_session != session) {
|
| - RTCLogError(@"audioSessionDidActivate called on different AVAudioSession");
|
| - }
|
| - [self incrementActivationCount];
|
| - self.isActive = YES;
|
| -}
|
| -
|
| -- (void)audioSessionDidDeactivate:(AVAudioSession *)session {
|
| - if (_session != session) {
|
| - RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession");
|
| - }
|
| - self.isActive = NO;
|
| - [self decrementActivationCount];
|
| -}
|
| -
|
| -- (void)observeValueForKeyPath:(NSString *)keyPath
|
| - ofObject:(id)object
|
| - change:(NSDictionary *)change
|
| - context:(void *)context {
|
| - if (object == _session) {
|
| - NSNumber *newVolume = change[NSKeyValueChangeNewKey];
|
| - RTCLog(@"OutputVolumeDidChange to %f", newVolume.floatValue);
|
| - [self notifyDidChangeOutputVolume:newVolume.floatValue];
|
| - } else {
|
| - [super observeValueForKeyPath:keyPath
|
| - ofObject:object
|
| - change:change
|
| - context:context];
|
| - }
|
| -}
|
| -
|
| -- (void)notifyDidBeginInterruption {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSessionDidBeginInterruption:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSessionDidBeginInterruption:self];
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)notifyDidEndInterruptionWithShouldResumeSession:
|
| - (BOOL)shouldResumeSession {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSessionDidEndInterruption:self
|
| - shouldResumeSession:shouldResumeSession];
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
|
| - previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSessionDidChangeRoute:self
|
| - reason:reason
|
| - previousRoute:previousRoute];
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)notifyMediaServicesWereLost {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSessionMediaServerTerminated:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSessionMediaServerTerminated:self];
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)notifyMediaServicesWereReset {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSessionMediaServerReset:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSessionMediaServerReset:self];
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)notifyDidStartPlayOrRecord {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSessionDidStartPlayOrRecord:self];
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)notifyDidStopPlayOrRecord {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSessionDidStopPlayOrRecord:self];
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)notifyDidChangeOutputVolume:(float)volume {
|
| - for (auto delegate : self.delegates) {
|
| - SEL sel = @selector(audioSession:didChangeOutputVolume:);
|
| - if ([delegate respondsToSelector:sel]) {
|
| - [delegate audioSession:self didChangeOutputVolume:volume];
|
| - }
|
| - }
|
| -}
|
| -
|
| -@end
|
|
|