1*d9f75844SAndroid Build Coastguard Worker/* 2*d9f75844SAndroid Build Coastguard Worker * Copyright 2016 The WebRTC Project Authors. All rights reserved. 3*d9f75844SAndroid Build Coastguard Worker * 4*d9f75844SAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license 5*d9f75844SAndroid Build Coastguard Worker * that can be found in the LICENSE file in the root of the source 6*d9f75844SAndroid Build Coastguard Worker * tree. An additional intellectual property rights grant can be found 7*d9f75844SAndroid Build Coastguard Worker * in the file PATENTS. All contributing project authors may 8*d9f75844SAndroid Build Coastguard Worker * be found in the AUTHORS file in the root of the source tree. 9*d9f75844SAndroid Build Coastguard Worker */ 10*d9f75844SAndroid Build Coastguard Worker 11*d9f75844SAndroid Build Coastguard Worker#import "RTCAudioSession+Private.h" 12*d9f75844SAndroid Build Coastguard Worker 13*d9f75844SAndroid Build Coastguard Worker#import <UIKit/UIKit.h> 14*d9f75844SAndroid Build Coastguard Worker 15*d9f75844SAndroid Build Coastguard Worker#include <atomic> 16*d9f75844SAndroid Build Coastguard Worker#include <vector> 17*d9f75844SAndroid Build Coastguard Worker 18*d9f75844SAndroid Build Coastguard Worker#include "absl/base/attributes.h" 19*d9f75844SAndroid Build Coastguard Worker#include "rtc_base/checks.h" 20*d9f75844SAndroid Build Coastguard Worker#include "rtc_base/synchronization/mutex.h" 21*d9f75844SAndroid Build Coastguard Worker 22*d9f75844SAndroid Build Coastguard Worker#import "RTCAudioSessionConfiguration.h" 23*d9f75844SAndroid Build Coastguard Worker#import "base/RTCLogging.h" 24*d9f75844SAndroid Build Coastguard Worker 25*d9f75844SAndroid Build Coastguard Worker#if !defined(ABSL_HAVE_THREAD_LOCAL) 26*d9f75844SAndroid Build Coastguard Worker#error ABSL_HAVE_THREAD_LOCAL should be defined for MacOS / iOS Targets. 27*d9f75844SAndroid Build Coastguard Worker#endif 28*d9f75844SAndroid Build Coastguard Worker 29*d9f75844SAndroid Build Coastguard WorkerNSString *const kRTCAudioSessionErrorDomain = @"org.webrtc.RTC_OBJC_TYPE(RTCAudioSession)"; 30*d9f75844SAndroid Build Coastguard WorkerNSInteger const kRTCAudioSessionErrorLockRequired = -1; 31*d9f75844SAndroid Build Coastguard WorkerNSInteger const kRTCAudioSessionErrorConfiguration = -2; 32*d9f75844SAndroid Build Coastguard WorkerNSString * const kRTCAudioSessionOutputVolumeSelector = @"outputVolume"; 33*d9f75844SAndroid Build Coastguard Worker 34*d9f75844SAndroid Build Coastguard Workernamespace { 35*d9f75844SAndroid Build Coastguard Worker// Since webrtc::Mutex is not a reentrant lock and cannot check if the mutex is locked, 36*d9f75844SAndroid Build Coastguard Worker// we need a separate variable to check that the mutex is locked in the RTCAudioSession. 37*d9f75844SAndroid Build Coastguard WorkerABSL_CONST_INIT thread_local bool mutex_locked = false; 38*d9f75844SAndroid Build Coastguard Worker} // namespace 39*d9f75844SAndroid Build Coastguard Worker 40*d9f75844SAndroid Build Coastguard Worker@interface RTC_OBJC_TYPE (RTCAudioSession) 41*d9f75844SAndroid Build Coastguard Worker() @property(nonatomic, 42*d9f75844SAndroid Build Coastguard Worker readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates; 43*d9f75844SAndroid Build Coastguard Worker@end 44*d9f75844SAndroid Build Coastguard Worker 45*d9f75844SAndroid Build Coastguard Worker// This class needs to be thread-safe because it is accessed from many threads. 46*d9f75844SAndroid Build Coastguard Worker// TODO(tkchin): Consider more granular locking. We're not expecting a lot of 47*d9f75844SAndroid Build Coastguard Worker// lock contention so coarse locks should be fine for now. 48*d9f75844SAndroid Build Coastguard Worker@implementation RTC_OBJC_TYPE (RTCAudioSession) { 49*d9f75844SAndroid Build Coastguard Worker webrtc::Mutex _mutex; 50*d9f75844SAndroid Build Coastguard Worker AVAudioSession *_session; 51*d9f75844SAndroid Build Coastguard Worker std::atomic<int> _activationCount; 52*d9f75844SAndroid Build Coastguard Worker std::atomic<int> _webRTCSessionCount; 53*d9f75844SAndroid Build Coastguard Worker BOOL _isActive; 54*d9f75844SAndroid Build Coastguard Worker BOOL _useManualAudio; 55*d9f75844SAndroid Build Coastguard Worker BOOL _isAudioEnabled; 56*d9f75844SAndroid Build Coastguard Worker BOOL _canPlayOrRecord; 57*d9f75844SAndroid Build Coastguard Worker BOOL _isInterrupted; 58*d9f75844SAndroid Build Coastguard Worker} 59*d9f75844SAndroid Build Coastguard Worker 60*d9f75844SAndroid Build Coastguard Worker@synthesize session = _session; 61*d9f75844SAndroid Build Coastguard Worker@synthesize delegates = _delegates; 62*d9f75844SAndroid Build Coastguard Worker@synthesize ignoresPreferredAttributeConfigurationErrors = 63*d9f75844SAndroid Build Coastguard Worker _ignoresPreferredAttributeConfigurationErrors; 64*d9f75844SAndroid Build Coastguard Worker 65*d9f75844SAndroid Build Coastguard Worker+ (instancetype)sharedInstance { 66*d9f75844SAndroid Build Coastguard Worker static dispatch_once_t onceToken; 67*d9f75844SAndroid Build Coastguard Worker static RTC_OBJC_TYPE(RTCAudioSession) *sharedInstance = nil; 68*d9f75844SAndroid Build Coastguard Worker dispatch_once(&onceToken, ^{ 69*d9f75844SAndroid Build Coastguard Worker sharedInstance = [[self alloc] init]; 70*d9f75844SAndroid Build Coastguard Worker }); 71*d9f75844SAndroid Build Coastguard Worker return sharedInstance; 72*d9f75844SAndroid Build Coastguard Worker} 73*d9f75844SAndroid Build Coastguard Worker 74*d9f75844SAndroid Build Coastguard Worker- (instancetype)init { 75*d9f75844SAndroid Build Coastguard Worker return [self initWithAudioSession:[AVAudioSession sharedInstance]]; 76*d9f75844SAndroid Build Coastguard Worker} 77*d9f75844SAndroid Build Coastguard Worker 78*d9f75844SAndroid Build Coastguard Worker/** This initializer provides a way for unit tests to inject a fake/mock audio session. */ 79*d9f75844SAndroid Build Coastguard Worker- (instancetype)initWithAudioSession:(id)audioSession { 80*d9f75844SAndroid Build Coastguard Worker if (self = [super init]) { 81*d9f75844SAndroid Build Coastguard Worker _session = audioSession; 82*d9f75844SAndroid Build Coastguard Worker 83*d9f75844SAndroid Build Coastguard Worker NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 84*d9f75844SAndroid Build Coastguard Worker [center addObserver:self 85*d9f75844SAndroid Build Coastguard Worker selector:@selector(handleInterruptionNotification:) 86*d9f75844SAndroid Build Coastguard Worker name:AVAudioSessionInterruptionNotification 87*d9f75844SAndroid Build Coastguard Worker object:nil]; 88*d9f75844SAndroid Build Coastguard Worker [center addObserver:self 89*d9f75844SAndroid Build Coastguard Worker selector:@selector(handleRouteChangeNotification:) 90*d9f75844SAndroid Build Coastguard Worker name:AVAudioSessionRouteChangeNotification 91*d9f75844SAndroid Build Coastguard Worker object:nil]; 92*d9f75844SAndroid Build Coastguard Worker [center addObserver:self 93*d9f75844SAndroid Build Coastguard Worker selector:@selector(handleMediaServicesWereLost:) 94*d9f75844SAndroid Build Coastguard Worker name:AVAudioSessionMediaServicesWereLostNotification 95*d9f75844SAndroid Build Coastguard Worker object:nil]; 96*d9f75844SAndroid Build Coastguard Worker [center addObserver:self 97*d9f75844SAndroid Build Coastguard Worker selector:@selector(handleMediaServicesWereReset:) 98*d9f75844SAndroid Build Coastguard Worker name:AVAudioSessionMediaServicesWereResetNotification 99*d9f75844SAndroid Build Coastguard Worker object:nil]; 100*d9f75844SAndroid Build Coastguard Worker // Posted on the main thread when the primary audio from other applications 101*d9f75844SAndroid Build Coastguard Worker // starts and stops. Foreground applications may use this notification as a 102*d9f75844SAndroid Build Coastguard Worker // hint to enable or disable audio that is secondary. 103*d9f75844SAndroid Build Coastguard Worker [center addObserver:self 104*d9f75844SAndroid Build Coastguard Worker selector:@selector(handleSilenceSecondaryAudioHintNotification:) 105*d9f75844SAndroid Build Coastguard Worker name:AVAudioSessionSilenceSecondaryAudioHintNotification 106*d9f75844SAndroid Build Coastguard Worker object:nil]; 107*d9f75844SAndroid Build Coastguard Worker // Also track foreground event in order to deal with interruption ended situation. 108*d9f75844SAndroid Build Coastguard Worker [center addObserver:self 109*d9f75844SAndroid Build Coastguard Worker selector:@selector(handleApplicationDidBecomeActive:) 110*d9f75844SAndroid Build Coastguard Worker name:UIApplicationDidBecomeActiveNotification 111*d9f75844SAndroid Build Coastguard Worker object:nil]; 112*d9f75844SAndroid Build Coastguard Worker [_session addObserver:self 113*d9f75844SAndroid Build Coastguard Worker forKeyPath:kRTCAudioSessionOutputVolumeSelector 114*d9f75844SAndroid Build Coastguard Worker options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 115*d9f75844SAndroid Build Coastguard Worker context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class]; 116*d9f75844SAndroid Build Coastguard Worker 117*d9f75844SAndroid Build Coastguard Worker RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): init.", self); 118*d9f75844SAndroid Build Coastguard Worker } 119*d9f75844SAndroid Build Coastguard Worker return self; 120*d9f75844SAndroid Build Coastguard Worker} 121*d9f75844SAndroid Build Coastguard Worker 122*d9f75844SAndroid Build Coastguard Worker- (void)dealloc { 123*d9f75844SAndroid Build Coastguard Worker [[NSNotificationCenter defaultCenter] removeObserver:self]; 124*d9f75844SAndroid Build Coastguard Worker [_session removeObserver:self 125*d9f75844SAndroid Build Coastguard Worker forKeyPath:kRTCAudioSessionOutputVolumeSelector 126*d9f75844SAndroid Build Coastguard Worker context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class]; 127*d9f75844SAndroid Build Coastguard Worker RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): dealloc.", self); 128*d9f75844SAndroid Build Coastguard Worker} 129*d9f75844SAndroid Build Coastguard Worker 130*d9f75844SAndroid Build Coastguard Worker- (NSString *)description { 131*d9f75844SAndroid Build Coastguard Worker NSString *format = @"RTC_OBJC_TYPE(RTCAudioSession): {\n" 132*d9f75844SAndroid Build Coastguard Worker " category: %@\n" 133*d9f75844SAndroid Build Coastguard Worker " categoryOptions: %ld\n" 134*d9f75844SAndroid Build Coastguard Worker " mode: %@\n" 135*d9f75844SAndroid Build Coastguard Worker " isActive: %d\n" 136*d9f75844SAndroid Build Coastguard Worker " sampleRate: %.2f\n" 137*d9f75844SAndroid Build Coastguard Worker " IOBufferDuration: %f\n" 138*d9f75844SAndroid Build Coastguard Worker " outputNumberOfChannels: %ld\n" 139*d9f75844SAndroid Build Coastguard Worker " inputNumberOfChannels: %ld\n" 140*d9f75844SAndroid Build Coastguard Worker " outputLatency: %f\n" 141*d9f75844SAndroid Build Coastguard Worker " inputLatency: %f\n" 142*d9f75844SAndroid Build Coastguard Worker " outputVolume: %f\n" 143*d9f75844SAndroid Build Coastguard Worker "}"; 144*d9f75844SAndroid Build Coastguard Worker NSString *description = [NSString stringWithFormat:format, 145*d9f75844SAndroid Build Coastguard Worker self.category, (long)self.categoryOptions, self.mode, 146*d9f75844SAndroid Build Coastguard Worker self.isActive, self.sampleRate, self.IOBufferDuration, 147*d9f75844SAndroid Build Coastguard Worker self.outputNumberOfChannels, self.inputNumberOfChannels, 148*d9f75844SAndroid Build Coastguard Worker self.outputLatency, self.inputLatency, self.outputVolume]; 149*d9f75844SAndroid Build Coastguard Worker return description; 150*d9f75844SAndroid Build Coastguard Worker} 151*d9f75844SAndroid Build Coastguard Worker 152*d9f75844SAndroid Build Coastguard Worker- (void)setIsActive:(BOOL)isActive { 153*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 154*d9f75844SAndroid Build Coastguard Worker _isActive = isActive; 155*d9f75844SAndroid Build Coastguard Worker } 156*d9f75844SAndroid Build Coastguard Worker} 157*d9f75844SAndroid Build Coastguard Worker 158*d9f75844SAndroid Build Coastguard Worker- (BOOL)isActive { 159*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 160*d9f75844SAndroid Build Coastguard Worker return _isActive; 161*d9f75844SAndroid Build Coastguard Worker } 162*d9f75844SAndroid Build Coastguard Worker} 163*d9f75844SAndroid Build Coastguard Worker 164*d9f75844SAndroid Build Coastguard Worker- (void)setUseManualAudio:(BOOL)useManualAudio { 165*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 166*d9f75844SAndroid Build Coastguard Worker if (_useManualAudio == useManualAudio) { 167*d9f75844SAndroid Build Coastguard Worker return; 168*d9f75844SAndroid Build Coastguard Worker } 169*d9f75844SAndroid Build Coastguard Worker _useManualAudio = useManualAudio; 170*d9f75844SAndroid Build Coastguard Worker } 171*d9f75844SAndroid Build Coastguard Worker [self updateCanPlayOrRecord]; 172*d9f75844SAndroid Build Coastguard Worker} 173*d9f75844SAndroid Build Coastguard Worker 174*d9f75844SAndroid Build Coastguard Worker- (BOOL)useManualAudio { 175*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 176*d9f75844SAndroid Build Coastguard Worker return _useManualAudio; 177*d9f75844SAndroid Build Coastguard Worker } 178*d9f75844SAndroid Build Coastguard Worker} 179*d9f75844SAndroid Build Coastguard Worker 180*d9f75844SAndroid Build Coastguard Worker- (void)setIsAudioEnabled:(BOOL)isAudioEnabled { 181*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 182*d9f75844SAndroid Build Coastguard Worker if (_isAudioEnabled == isAudioEnabled) { 183*d9f75844SAndroid Build Coastguard Worker return; 184*d9f75844SAndroid Build Coastguard Worker } 185*d9f75844SAndroid Build Coastguard Worker _isAudioEnabled = isAudioEnabled; 186*d9f75844SAndroid Build Coastguard Worker } 187*d9f75844SAndroid Build Coastguard Worker [self updateCanPlayOrRecord]; 188*d9f75844SAndroid Build Coastguard Worker} 189*d9f75844SAndroid Build Coastguard Worker 190*d9f75844SAndroid Build Coastguard Worker- (BOOL)isAudioEnabled { 191*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 192*d9f75844SAndroid Build Coastguard Worker return _isAudioEnabled; 193*d9f75844SAndroid Build Coastguard Worker } 194*d9f75844SAndroid Build Coastguard Worker} 195*d9f75844SAndroid Build Coastguard Worker 196*d9f75844SAndroid Build Coastguard Worker- (void)setIgnoresPreferredAttributeConfigurationErrors: 197*d9f75844SAndroid Build Coastguard Worker (BOOL)ignoresPreferredAttributeConfigurationErrors { 198*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 199*d9f75844SAndroid Build Coastguard Worker if (_ignoresPreferredAttributeConfigurationErrors == 200*d9f75844SAndroid Build Coastguard Worker ignoresPreferredAttributeConfigurationErrors) { 201*d9f75844SAndroid Build Coastguard Worker return; 202*d9f75844SAndroid Build Coastguard Worker } 203*d9f75844SAndroid Build Coastguard Worker _ignoresPreferredAttributeConfigurationErrors = ignoresPreferredAttributeConfigurationErrors; 204*d9f75844SAndroid Build Coastguard Worker } 205*d9f75844SAndroid Build Coastguard Worker} 206*d9f75844SAndroid Build Coastguard Worker 207*d9f75844SAndroid Build Coastguard Worker- (BOOL)ignoresPreferredAttributeConfigurationErrors { 208*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 209*d9f75844SAndroid Build Coastguard Worker return _ignoresPreferredAttributeConfigurationErrors; 210*d9f75844SAndroid Build Coastguard Worker } 211*d9f75844SAndroid Build Coastguard Worker} 212*d9f75844SAndroid Build Coastguard Worker 213*d9f75844SAndroid Build Coastguard Worker// TODO(tkchin): Check for duplicates. 214*d9f75844SAndroid Build Coastguard Worker- (void)addDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate { 215*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Adding delegate: (%p)", delegate); 216*d9f75844SAndroid Build Coastguard Worker if (!delegate) { 217*d9f75844SAndroid Build Coastguard Worker return; 218*d9f75844SAndroid Build Coastguard Worker } 219*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 220*d9f75844SAndroid Build Coastguard Worker _delegates.push_back(delegate); 221*d9f75844SAndroid Build Coastguard Worker [self removeZeroedDelegates]; 222*d9f75844SAndroid Build Coastguard Worker } 223*d9f75844SAndroid Build Coastguard Worker} 224*d9f75844SAndroid Build Coastguard Worker 225*d9f75844SAndroid Build Coastguard Worker- (void)removeDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate { 226*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Removing delegate: (%p)", delegate); 227*d9f75844SAndroid Build Coastguard Worker if (!delegate) { 228*d9f75844SAndroid Build Coastguard Worker return; 229*d9f75844SAndroid Build Coastguard Worker } 230*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 231*d9f75844SAndroid Build Coastguard Worker _delegates.erase(std::remove(_delegates.begin(), 232*d9f75844SAndroid Build Coastguard Worker _delegates.end(), 233*d9f75844SAndroid Build Coastguard Worker delegate), 234*d9f75844SAndroid Build Coastguard Worker _delegates.end()); 235*d9f75844SAndroid Build Coastguard Worker [self removeZeroedDelegates]; 236*d9f75844SAndroid Build Coastguard Worker } 237*d9f75844SAndroid Build Coastguard Worker} 238*d9f75844SAndroid Build Coastguard Worker 239*d9f75844SAndroid Build Coastguard Worker#pragma clang diagnostic push 240*d9f75844SAndroid Build Coastguard Worker#pragma clang diagnostic ignored "-Wthread-safety-analysis" 241*d9f75844SAndroid Build Coastguard Worker 242*d9f75844SAndroid Build Coastguard Worker- (void)lockForConfiguration { 243*d9f75844SAndroid Build Coastguard Worker RTC_CHECK(!mutex_locked); 244*d9f75844SAndroid Build Coastguard Worker _mutex.Lock(); 245*d9f75844SAndroid Build Coastguard Worker mutex_locked = true; 246*d9f75844SAndroid Build Coastguard Worker} 247*d9f75844SAndroid Build Coastguard Worker 248*d9f75844SAndroid Build Coastguard Worker- (void)unlockForConfiguration { 249*d9f75844SAndroid Build Coastguard Worker mutex_locked = false; 250*d9f75844SAndroid Build Coastguard Worker _mutex.Unlock(); 251*d9f75844SAndroid Build Coastguard Worker} 252*d9f75844SAndroid Build Coastguard Worker 253*d9f75844SAndroid Build Coastguard Worker#pragma clang diagnostic pop 254*d9f75844SAndroid Build Coastguard Worker 255*d9f75844SAndroid Build Coastguard Worker#pragma mark - AVAudioSession proxy methods 256*d9f75844SAndroid Build Coastguard Worker 257*d9f75844SAndroid Build Coastguard Worker- (NSString *)category { 258*d9f75844SAndroid Build Coastguard Worker return self.session.category; 259*d9f75844SAndroid Build Coastguard Worker} 260*d9f75844SAndroid Build Coastguard Worker 261*d9f75844SAndroid Build Coastguard Worker- (AVAudioSessionCategoryOptions)categoryOptions { 262*d9f75844SAndroid Build Coastguard Worker return self.session.categoryOptions; 263*d9f75844SAndroid Build Coastguard Worker} 264*d9f75844SAndroid Build Coastguard Worker 265*d9f75844SAndroid Build Coastguard Worker- (NSString *)mode { 266*d9f75844SAndroid Build Coastguard Worker return self.session.mode; 267*d9f75844SAndroid Build Coastguard Worker} 268*d9f75844SAndroid Build Coastguard Worker 269*d9f75844SAndroid Build Coastguard Worker- (BOOL)secondaryAudioShouldBeSilencedHint { 270*d9f75844SAndroid Build Coastguard Worker return self.session.secondaryAudioShouldBeSilencedHint; 271*d9f75844SAndroid Build Coastguard Worker} 272*d9f75844SAndroid Build Coastguard Worker 273*d9f75844SAndroid Build Coastguard Worker- (AVAudioSessionRouteDescription *)currentRoute { 274*d9f75844SAndroid Build Coastguard Worker return self.session.currentRoute; 275*d9f75844SAndroid Build Coastguard Worker} 276*d9f75844SAndroid Build Coastguard Worker 277*d9f75844SAndroid Build Coastguard Worker- (NSInteger)maximumInputNumberOfChannels { 278*d9f75844SAndroid Build Coastguard Worker return self.session.maximumInputNumberOfChannels; 279*d9f75844SAndroid Build Coastguard Worker} 280*d9f75844SAndroid Build Coastguard Worker 281*d9f75844SAndroid Build Coastguard Worker- (NSInteger)maximumOutputNumberOfChannels { 282*d9f75844SAndroid Build Coastguard Worker return self.session.maximumOutputNumberOfChannels; 283*d9f75844SAndroid Build Coastguard Worker} 284*d9f75844SAndroid Build Coastguard Worker 285*d9f75844SAndroid Build Coastguard Worker- (float)inputGain { 286*d9f75844SAndroid Build Coastguard Worker return self.session.inputGain; 287*d9f75844SAndroid Build Coastguard Worker} 288*d9f75844SAndroid Build Coastguard Worker 289*d9f75844SAndroid Build Coastguard Worker- (BOOL)inputGainSettable { 290*d9f75844SAndroid Build Coastguard Worker return self.session.inputGainSettable; 291*d9f75844SAndroid Build Coastguard Worker} 292*d9f75844SAndroid Build Coastguard Worker 293*d9f75844SAndroid Build Coastguard Worker- (BOOL)inputAvailable { 294*d9f75844SAndroid Build Coastguard Worker return self.session.inputAvailable; 295*d9f75844SAndroid Build Coastguard Worker} 296*d9f75844SAndroid Build Coastguard Worker 297*d9f75844SAndroid Build Coastguard Worker- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources { 298*d9f75844SAndroid Build Coastguard Worker return self.session.inputDataSources; 299*d9f75844SAndroid Build Coastguard Worker} 300*d9f75844SAndroid Build Coastguard Worker 301*d9f75844SAndroid Build Coastguard Worker- (AVAudioSessionDataSourceDescription *)inputDataSource { 302*d9f75844SAndroid Build Coastguard Worker return self.session.inputDataSource; 303*d9f75844SAndroid Build Coastguard Worker} 304*d9f75844SAndroid Build Coastguard Worker 305*d9f75844SAndroid Build Coastguard Worker- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources { 306*d9f75844SAndroid Build Coastguard Worker return self.session.outputDataSources; 307*d9f75844SAndroid Build Coastguard Worker} 308*d9f75844SAndroid Build Coastguard Worker 309*d9f75844SAndroid Build Coastguard Worker- (AVAudioSessionDataSourceDescription *)outputDataSource { 310*d9f75844SAndroid Build Coastguard Worker return self.session.outputDataSource; 311*d9f75844SAndroid Build Coastguard Worker} 312*d9f75844SAndroid Build Coastguard Worker 313*d9f75844SAndroid Build Coastguard Worker- (double)sampleRate { 314*d9f75844SAndroid Build Coastguard Worker return self.session.sampleRate; 315*d9f75844SAndroid Build Coastguard Worker} 316*d9f75844SAndroid Build Coastguard Worker 317*d9f75844SAndroid Build Coastguard Worker- (double)preferredSampleRate { 318*d9f75844SAndroid Build Coastguard Worker return self.session.preferredSampleRate; 319*d9f75844SAndroid Build Coastguard Worker} 320*d9f75844SAndroid Build Coastguard Worker 321*d9f75844SAndroid Build Coastguard Worker- (NSInteger)inputNumberOfChannels { 322*d9f75844SAndroid Build Coastguard Worker return self.session.inputNumberOfChannels; 323*d9f75844SAndroid Build Coastguard Worker} 324*d9f75844SAndroid Build Coastguard Worker 325*d9f75844SAndroid Build Coastguard Worker- (NSInteger)outputNumberOfChannels { 326*d9f75844SAndroid Build Coastguard Worker return self.session.outputNumberOfChannels; 327*d9f75844SAndroid Build Coastguard Worker} 328*d9f75844SAndroid Build Coastguard Worker 329*d9f75844SAndroid Build Coastguard Worker- (float)outputVolume { 330*d9f75844SAndroid Build Coastguard Worker return self.session.outputVolume; 331*d9f75844SAndroid Build Coastguard Worker} 332*d9f75844SAndroid Build Coastguard Worker 333*d9f75844SAndroid Build Coastguard Worker- (NSTimeInterval)inputLatency { 334*d9f75844SAndroid Build Coastguard Worker return self.session.inputLatency; 335*d9f75844SAndroid Build Coastguard Worker} 336*d9f75844SAndroid Build Coastguard Worker 337*d9f75844SAndroid Build Coastguard Worker- (NSTimeInterval)outputLatency { 338*d9f75844SAndroid Build Coastguard Worker return self.session.outputLatency; 339*d9f75844SAndroid Build Coastguard Worker} 340*d9f75844SAndroid Build Coastguard Worker 341*d9f75844SAndroid Build Coastguard Worker- (NSTimeInterval)IOBufferDuration { 342*d9f75844SAndroid Build Coastguard Worker return self.session.IOBufferDuration; 343*d9f75844SAndroid Build Coastguard Worker} 344*d9f75844SAndroid Build Coastguard Worker 345*d9f75844SAndroid Build Coastguard Worker- (NSTimeInterval)preferredIOBufferDuration { 346*d9f75844SAndroid Build Coastguard Worker return self.session.preferredIOBufferDuration; 347*d9f75844SAndroid Build Coastguard Worker} 348*d9f75844SAndroid Build Coastguard Worker 349*d9f75844SAndroid Build Coastguard Worker- (BOOL)setActive:(BOOL)active 350*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 351*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 352*d9f75844SAndroid Build Coastguard Worker return NO; 353*d9f75844SAndroid Build Coastguard Worker } 354*d9f75844SAndroid Build Coastguard Worker int activationCount = _activationCount.load(); 355*d9f75844SAndroid Build Coastguard Worker if (!active && activationCount == 0) { 356*d9f75844SAndroid Build Coastguard Worker RTCLogWarning(@"Attempting to deactivate without prior activation."); 357*d9f75844SAndroid Build Coastguard Worker } 358*d9f75844SAndroid Build Coastguard Worker [self notifyWillSetActive:active]; 359*d9f75844SAndroid Build Coastguard Worker BOOL success = YES; 360*d9f75844SAndroid Build Coastguard Worker BOOL isActive = self.isActive; 361*d9f75844SAndroid Build Coastguard Worker // Keep a local error so we can log it. 362*d9f75844SAndroid Build Coastguard Worker NSError *error = nil; 363*d9f75844SAndroid Build Coastguard Worker BOOL shouldSetActive = 364*d9f75844SAndroid Build Coastguard Worker (active && !isActive) || (!active && isActive && activationCount == 1); 365*d9f75844SAndroid Build Coastguard Worker // Attempt to activate if we're not active. 366*d9f75844SAndroid Build Coastguard Worker // Attempt to deactivate if we're active and it's the last unbalanced call. 367*d9f75844SAndroid Build Coastguard Worker if (shouldSetActive) { 368*d9f75844SAndroid Build Coastguard Worker AVAudioSession *session = self.session; 369*d9f75844SAndroid Build Coastguard Worker // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure 370*d9f75844SAndroid Build Coastguard Worker // that other audio sessions that were interrupted by our session can return 371*d9f75844SAndroid Build Coastguard Worker // to their active state. It is recommended for VoIP apps to use this 372*d9f75844SAndroid Build Coastguard Worker // option. 373*d9f75844SAndroid Build Coastguard Worker AVAudioSessionSetActiveOptions options = 374*d9f75844SAndroid Build Coastguard Worker active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation; 375*d9f75844SAndroid Build Coastguard Worker success = [session setActive:active 376*d9f75844SAndroid Build Coastguard Worker withOptions:options 377*d9f75844SAndroid Build Coastguard Worker error:&error]; 378*d9f75844SAndroid Build Coastguard Worker if (outError) { 379*d9f75844SAndroid Build Coastguard Worker *outError = error; 380*d9f75844SAndroid Build Coastguard Worker } 381*d9f75844SAndroid Build Coastguard Worker } 382*d9f75844SAndroid Build Coastguard Worker if (success) { 383*d9f75844SAndroid Build Coastguard Worker if (active) { 384*d9f75844SAndroid Build Coastguard Worker if (shouldSetActive) { 385*d9f75844SAndroid Build Coastguard Worker self.isActive = active; 386*d9f75844SAndroid Build Coastguard Worker if (self.isInterrupted) { 387*d9f75844SAndroid Build Coastguard Worker self.isInterrupted = NO; 388*d9f75844SAndroid Build Coastguard Worker [self notifyDidEndInterruptionWithShouldResumeSession:YES]; 389*d9f75844SAndroid Build Coastguard Worker } 390*d9f75844SAndroid Build Coastguard Worker } 391*d9f75844SAndroid Build Coastguard Worker [self incrementActivationCount]; 392*d9f75844SAndroid Build Coastguard Worker [self notifyDidSetActive:active]; 393*d9f75844SAndroid Build Coastguard Worker } 394*d9f75844SAndroid Build Coastguard Worker } else { 395*d9f75844SAndroid Build Coastguard Worker RTCLogError(@"Failed to setActive:%d. Error: %@", 396*d9f75844SAndroid Build Coastguard Worker active, error.localizedDescription); 397*d9f75844SAndroid Build Coastguard Worker [self notifyFailedToSetActive:active error:error]; 398*d9f75844SAndroid Build Coastguard Worker } 399*d9f75844SAndroid Build Coastguard Worker // Set isActive and decrement activation count on deactivation 400*d9f75844SAndroid Build Coastguard Worker // whether or not it succeeded. 401*d9f75844SAndroid Build Coastguard Worker if (!active) { 402*d9f75844SAndroid Build Coastguard Worker self.isActive = active; 403*d9f75844SAndroid Build Coastguard Worker [self notifyDidSetActive:active]; 404*d9f75844SAndroid Build Coastguard Worker [self decrementActivationCount]; 405*d9f75844SAndroid Build Coastguard Worker } 406*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Number of current activations: %d", _activationCount.load()); 407*d9f75844SAndroid Build Coastguard Worker return success; 408*d9f75844SAndroid Build Coastguard Worker} 409*d9f75844SAndroid Build Coastguard Worker 410*d9f75844SAndroid Build Coastguard Worker- (BOOL)setCategory:(NSString *)category 411*d9f75844SAndroid Build Coastguard Worker withOptions:(AVAudioSessionCategoryOptions)options 412*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 413*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 414*d9f75844SAndroid Build Coastguard Worker return NO; 415*d9f75844SAndroid Build Coastguard Worker } 416*d9f75844SAndroid Build Coastguard Worker return [self.session setCategory:category withOptions:options error:outError]; 417*d9f75844SAndroid Build Coastguard Worker} 418*d9f75844SAndroid Build Coastguard Worker 419*d9f75844SAndroid Build Coastguard Worker- (BOOL)setMode:(NSString *)mode error:(NSError **)outError { 420*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 421*d9f75844SAndroid Build Coastguard Worker return NO; 422*d9f75844SAndroid Build Coastguard Worker } 423*d9f75844SAndroid Build Coastguard Worker return [self.session setMode:mode error:outError]; 424*d9f75844SAndroid Build Coastguard Worker} 425*d9f75844SAndroid Build Coastguard Worker 426*d9f75844SAndroid Build Coastguard Worker- (BOOL)setInputGain:(float)gain error:(NSError **)outError { 427*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 428*d9f75844SAndroid Build Coastguard Worker return NO; 429*d9f75844SAndroid Build Coastguard Worker } 430*d9f75844SAndroid Build Coastguard Worker return [self.session setInputGain:gain error:outError]; 431*d9f75844SAndroid Build Coastguard Worker} 432*d9f75844SAndroid Build Coastguard Worker 433*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError { 434*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 435*d9f75844SAndroid Build Coastguard Worker return NO; 436*d9f75844SAndroid Build Coastguard Worker } 437*d9f75844SAndroid Build Coastguard Worker return [self.session setPreferredSampleRate:sampleRate error:outError]; 438*d9f75844SAndroid Build Coastguard Worker} 439*d9f75844SAndroid Build Coastguard Worker 440*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration 441*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 442*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 443*d9f75844SAndroid Build Coastguard Worker return NO; 444*d9f75844SAndroid Build Coastguard Worker } 445*d9f75844SAndroid Build Coastguard Worker return [self.session setPreferredIOBufferDuration:duration error:outError]; 446*d9f75844SAndroid Build Coastguard Worker} 447*d9f75844SAndroid Build Coastguard Worker 448*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count 449*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 450*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 451*d9f75844SAndroid Build Coastguard Worker return NO; 452*d9f75844SAndroid Build Coastguard Worker } 453*d9f75844SAndroid Build Coastguard Worker return [self.session setPreferredInputNumberOfChannels:count error:outError]; 454*d9f75844SAndroid Build Coastguard Worker} 455*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count 456*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 457*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 458*d9f75844SAndroid Build Coastguard Worker return NO; 459*d9f75844SAndroid Build Coastguard Worker } 460*d9f75844SAndroid Build Coastguard Worker return [self.session setPreferredOutputNumberOfChannels:count error:outError]; 461*d9f75844SAndroid Build Coastguard Worker} 462*d9f75844SAndroid Build Coastguard Worker 463*d9f75844SAndroid Build Coastguard Worker- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride 464*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 465*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 466*d9f75844SAndroid Build Coastguard Worker return NO; 467*d9f75844SAndroid Build Coastguard Worker } 468*d9f75844SAndroid Build Coastguard Worker return [self.session overrideOutputAudioPort:portOverride error:outError]; 469*d9f75844SAndroid Build Coastguard Worker} 470*d9f75844SAndroid Build Coastguard Worker 471*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort 472*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 473*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 474*d9f75844SAndroid Build Coastguard Worker return NO; 475*d9f75844SAndroid Build Coastguard Worker } 476*d9f75844SAndroid Build Coastguard Worker return [self.session setPreferredInput:inPort error:outError]; 477*d9f75844SAndroid Build Coastguard Worker} 478*d9f75844SAndroid Build Coastguard Worker 479*d9f75844SAndroid Build Coastguard Worker- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource 480*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 481*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 482*d9f75844SAndroid Build Coastguard Worker return NO; 483*d9f75844SAndroid Build Coastguard Worker } 484*d9f75844SAndroid Build Coastguard Worker return [self.session setInputDataSource:dataSource error:outError]; 485*d9f75844SAndroid Build Coastguard Worker} 486*d9f75844SAndroid Build Coastguard Worker 487*d9f75844SAndroid Build Coastguard Worker- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource 488*d9f75844SAndroid Build Coastguard Worker error:(NSError **)outError { 489*d9f75844SAndroid Build Coastguard Worker if (![self checkLock:outError]) { 490*d9f75844SAndroid Build Coastguard Worker return NO; 491*d9f75844SAndroid Build Coastguard Worker } 492*d9f75844SAndroid Build Coastguard Worker return [self.session setOutputDataSource:dataSource error:outError]; 493*d9f75844SAndroid Build Coastguard Worker} 494*d9f75844SAndroid Build Coastguard Worker 495*d9f75844SAndroid Build Coastguard Worker#pragma mark - Notifications 496*d9f75844SAndroid Build Coastguard Worker 497*d9f75844SAndroid Build Coastguard Worker- (void)handleInterruptionNotification:(NSNotification *)notification { 498*d9f75844SAndroid Build Coastguard Worker NSNumber* typeNumber = 499*d9f75844SAndroid Build Coastguard Worker notification.userInfo[AVAudioSessionInterruptionTypeKey]; 500*d9f75844SAndroid Build Coastguard Worker AVAudioSessionInterruptionType type = 501*d9f75844SAndroid Build Coastguard Worker (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue; 502*d9f75844SAndroid Build Coastguard Worker switch (type) { 503*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionInterruptionTypeBegan: 504*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio session interruption began."); 505*d9f75844SAndroid Build Coastguard Worker self.isActive = NO; 506*d9f75844SAndroid Build Coastguard Worker self.isInterrupted = YES; 507*d9f75844SAndroid Build Coastguard Worker [self notifyDidBeginInterruption]; 508*d9f75844SAndroid Build Coastguard Worker break; 509*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionInterruptionTypeEnded: { 510*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio session interruption ended."); 511*d9f75844SAndroid Build Coastguard Worker self.isInterrupted = NO; 512*d9f75844SAndroid Build Coastguard Worker [self updateAudioSessionAfterEvent]; 513*d9f75844SAndroid Build Coastguard Worker NSNumber *optionsNumber = 514*d9f75844SAndroid Build Coastguard Worker notification.userInfo[AVAudioSessionInterruptionOptionKey]; 515*d9f75844SAndroid Build Coastguard Worker AVAudioSessionInterruptionOptions options = 516*d9f75844SAndroid Build Coastguard Worker optionsNumber.unsignedIntegerValue; 517*d9f75844SAndroid Build Coastguard Worker BOOL shouldResume = 518*d9f75844SAndroid Build Coastguard Worker options & AVAudioSessionInterruptionOptionShouldResume; 519*d9f75844SAndroid Build Coastguard Worker [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume]; 520*d9f75844SAndroid Build Coastguard Worker break; 521*d9f75844SAndroid Build Coastguard Worker } 522*d9f75844SAndroid Build Coastguard Worker } 523*d9f75844SAndroid Build Coastguard Worker} 524*d9f75844SAndroid Build Coastguard Worker 525*d9f75844SAndroid Build Coastguard Worker- (void)handleRouteChangeNotification:(NSNotification *)notification { 526*d9f75844SAndroid Build Coastguard Worker // Get reason for current route change. 527*d9f75844SAndroid Build Coastguard Worker NSNumber* reasonNumber = 528*d9f75844SAndroid Build Coastguard Worker notification.userInfo[AVAudioSessionRouteChangeReasonKey]; 529*d9f75844SAndroid Build Coastguard Worker AVAudioSessionRouteChangeReason reason = 530*d9f75844SAndroid Build Coastguard Worker (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue; 531*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed:"); 532*d9f75844SAndroid Build Coastguard Worker switch (reason) { 533*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionRouteChangeReasonUnknown: 534*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed: ReasonUnknown"); 535*d9f75844SAndroid Build Coastguard Worker break; 536*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionRouteChangeReasonNewDeviceAvailable: 537*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed: NewDeviceAvailable"); 538*d9f75844SAndroid Build Coastguard Worker break; 539*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: 540*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed: OldDeviceUnavailable"); 541*d9f75844SAndroid Build Coastguard Worker break; 542*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionRouteChangeReasonCategoryChange: 543*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed: CategoryChange to :%@", 544*d9f75844SAndroid Build Coastguard Worker self.session.category); 545*d9f75844SAndroid Build Coastguard Worker break; 546*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionRouteChangeReasonOverride: 547*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed: Override"); 548*d9f75844SAndroid Build Coastguard Worker break; 549*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionRouteChangeReasonWakeFromSleep: 550*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed: WakeFromSleep"); 551*d9f75844SAndroid Build Coastguard Worker break; 552*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: 553*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed: NoSuitableRouteForCategory"); 554*d9f75844SAndroid Build Coastguard Worker break; 555*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionRouteChangeReasonRouteConfigurationChange: 556*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio route changed: RouteConfigurationChange"); 557*d9f75844SAndroid Build Coastguard Worker break; 558*d9f75844SAndroid Build Coastguard Worker } 559*d9f75844SAndroid Build Coastguard Worker AVAudioSessionRouteDescription* previousRoute = 560*d9f75844SAndroid Build Coastguard Worker notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey]; 561*d9f75844SAndroid Build Coastguard Worker // Log previous route configuration. 562*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Previous route: %@\nCurrent route:%@", 563*d9f75844SAndroid Build Coastguard Worker previousRoute, self.session.currentRoute); 564*d9f75844SAndroid Build Coastguard Worker [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute]; 565*d9f75844SAndroid Build Coastguard Worker} 566*d9f75844SAndroid Build Coastguard Worker 567*d9f75844SAndroid Build Coastguard Worker- (void)handleMediaServicesWereLost:(NSNotification *)notification { 568*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Media services were lost."); 569*d9f75844SAndroid Build Coastguard Worker [self updateAudioSessionAfterEvent]; 570*d9f75844SAndroid Build Coastguard Worker [self notifyMediaServicesWereLost]; 571*d9f75844SAndroid Build Coastguard Worker} 572*d9f75844SAndroid Build Coastguard Worker 573*d9f75844SAndroid Build Coastguard Worker- (void)handleMediaServicesWereReset:(NSNotification *)notification { 574*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Media services were reset."); 575*d9f75844SAndroid Build Coastguard Worker [self updateAudioSessionAfterEvent]; 576*d9f75844SAndroid Build Coastguard Worker [self notifyMediaServicesWereReset]; 577*d9f75844SAndroid Build Coastguard Worker} 578*d9f75844SAndroid Build Coastguard Worker 579*d9f75844SAndroid Build Coastguard Worker- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification { 580*d9f75844SAndroid Build Coastguard Worker // TODO(henrika): just adding logs here for now until we know if we are ever 581*d9f75844SAndroid Build Coastguard Worker // see this notification and might be affected by it or if further actions 582*d9f75844SAndroid Build Coastguard Worker // are required. 583*d9f75844SAndroid Build Coastguard Worker NSNumber *typeNumber = 584*d9f75844SAndroid Build Coastguard Worker notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey]; 585*d9f75844SAndroid Build Coastguard Worker AVAudioSessionSilenceSecondaryAudioHintType type = 586*d9f75844SAndroid Build Coastguard Worker (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue; 587*d9f75844SAndroid Build Coastguard Worker switch (type) { 588*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionSilenceSecondaryAudioHintTypeBegin: 589*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Another application's primary audio has started."); 590*d9f75844SAndroid Build Coastguard Worker break; 591*d9f75844SAndroid Build Coastguard Worker case AVAudioSessionSilenceSecondaryAudioHintTypeEnd: 592*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Another application's primary audio has stopped."); 593*d9f75844SAndroid Build Coastguard Worker break; 594*d9f75844SAndroid Build Coastguard Worker } 595*d9f75844SAndroid Build Coastguard Worker} 596*d9f75844SAndroid Build Coastguard Worker 597*d9f75844SAndroid Build Coastguard Worker- (void)handleApplicationDidBecomeActive:(NSNotification *)notification { 598*d9f75844SAndroid Build Coastguard Worker BOOL isInterrupted = self.isInterrupted; 599*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Application became active after an interruption. Treating as interruption " 600*d9f75844SAndroid Build Coastguard Worker "end. isInterrupted changed from %d to 0.", 601*d9f75844SAndroid Build Coastguard Worker isInterrupted); 602*d9f75844SAndroid Build Coastguard Worker if (isInterrupted) { 603*d9f75844SAndroid Build Coastguard Worker self.isInterrupted = NO; 604*d9f75844SAndroid Build Coastguard Worker [self updateAudioSessionAfterEvent]; 605*d9f75844SAndroid Build Coastguard Worker } 606*d9f75844SAndroid Build Coastguard Worker // Always treat application becoming active as an interruption end event. 607*d9f75844SAndroid Build Coastguard Worker [self notifyDidEndInterruptionWithShouldResumeSession:YES]; 608*d9f75844SAndroid Build Coastguard Worker} 609*d9f75844SAndroid Build Coastguard Worker 610*d9f75844SAndroid Build Coastguard Worker#pragma mark - Private 611*d9f75844SAndroid Build Coastguard Worker 612*d9f75844SAndroid Build Coastguard Worker+ (NSError *)lockError { 613*d9f75844SAndroid Build Coastguard Worker NSDictionary *userInfo = 614*d9f75844SAndroid Build Coastguard Worker @{NSLocalizedDescriptionKey : @"Must call lockForConfiguration before calling this method."}; 615*d9f75844SAndroid Build Coastguard Worker NSError *error = [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain 616*d9f75844SAndroid Build Coastguard Worker code:kRTCAudioSessionErrorLockRequired 617*d9f75844SAndroid Build Coastguard Worker userInfo:userInfo]; 618*d9f75844SAndroid Build Coastguard Worker return error; 619*d9f75844SAndroid Build Coastguard Worker} 620*d9f75844SAndroid Build Coastguard Worker 621*d9f75844SAndroid Build Coastguard Worker- (std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> >)delegates { 622*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 623*d9f75844SAndroid Build Coastguard Worker // Note: this returns a copy. 624*d9f75844SAndroid Build Coastguard Worker return _delegates; 625*d9f75844SAndroid Build Coastguard Worker } 626*d9f75844SAndroid Build Coastguard Worker} 627*d9f75844SAndroid Build Coastguard Worker 628*d9f75844SAndroid Build Coastguard Worker// TODO(tkchin): check for duplicates. 629*d9f75844SAndroid Build Coastguard Worker- (void)pushDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate { 630*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 631*d9f75844SAndroid Build Coastguard Worker _delegates.insert(_delegates.begin(), delegate); 632*d9f75844SAndroid Build Coastguard Worker } 633*d9f75844SAndroid Build Coastguard Worker} 634*d9f75844SAndroid Build Coastguard Worker 635*d9f75844SAndroid Build Coastguard Worker- (void)removeZeroedDelegates { 636*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 637*d9f75844SAndroid Build Coastguard Worker _delegates.erase( 638*d9f75844SAndroid Build Coastguard Worker std::remove_if(_delegates.begin(), 639*d9f75844SAndroid Build Coastguard Worker _delegates.end(), 640*d9f75844SAndroid Build Coastguard Worker [](id delegate) -> bool { return delegate == nil; }), 641*d9f75844SAndroid Build Coastguard Worker _delegates.end()); 642*d9f75844SAndroid Build Coastguard Worker } 643*d9f75844SAndroid Build Coastguard Worker} 644*d9f75844SAndroid Build Coastguard Worker 645*d9f75844SAndroid Build Coastguard Worker- (int)activationCount { 646*d9f75844SAndroid Build Coastguard Worker return _activationCount.load(); 647*d9f75844SAndroid Build Coastguard Worker} 648*d9f75844SAndroid Build Coastguard Worker 649*d9f75844SAndroid Build Coastguard Worker- (int)incrementActivationCount { 650*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Incrementing activation count."); 651*d9f75844SAndroid Build Coastguard Worker return _activationCount.fetch_add(1) + 1; 652*d9f75844SAndroid Build Coastguard Worker} 653*d9f75844SAndroid Build Coastguard Worker 654*d9f75844SAndroid Build Coastguard Worker- (NSInteger)decrementActivationCount { 655*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Decrementing activation count."); 656*d9f75844SAndroid Build Coastguard Worker return _activationCount.fetch_sub(1) - 1; 657*d9f75844SAndroid Build Coastguard Worker} 658*d9f75844SAndroid Build Coastguard Worker 659*d9f75844SAndroid Build Coastguard Worker- (int)webRTCSessionCount { 660*d9f75844SAndroid Build Coastguard Worker return _webRTCSessionCount.load(); 661*d9f75844SAndroid Build Coastguard Worker} 662*d9f75844SAndroid Build Coastguard Worker 663*d9f75844SAndroid Build Coastguard Worker- (BOOL)canPlayOrRecord { 664*d9f75844SAndroid Build Coastguard Worker return !self.useManualAudio || self.isAudioEnabled; 665*d9f75844SAndroid Build Coastguard Worker} 666*d9f75844SAndroid Build Coastguard Worker 667*d9f75844SAndroid Build Coastguard Worker- (BOOL)isInterrupted { 668*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 669*d9f75844SAndroid Build Coastguard Worker return _isInterrupted; 670*d9f75844SAndroid Build Coastguard Worker } 671*d9f75844SAndroid Build Coastguard Worker} 672*d9f75844SAndroid Build Coastguard Worker 673*d9f75844SAndroid Build Coastguard Worker- (void)setIsInterrupted:(BOOL)isInterrupted { 674*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 675*d9f75844SAndroid Build Coastguard Worker if (_isInterrupted == isInterrupted) { 676*d9f75844SAndroid Build Coastguard Worker return; 677*d9f75844SAndroid Build Coastguard Worker } 678*d9f75844SAndroid Build Coastguard Worker _isInterrupted = isInterrupted; 679*d9f75844SAndroid Build Coastguard Worker } 680*d9f75844SAndroid Build Coastguard Worker} 681*d9f75844SAndroid Build Coastguard Worker 682*d9f75844SAndroid Build Coastguard Worker- (BOOL)checkLock:(NSError **)outError { 683*d9f75844SAndroid Build Coastguard Worker if (!mutex_locked) { 684*d9f75844SAndroid Build Coastguard Worker if (outError) { 685*d9f75844SAndroid Build Coastguard Worker *outError = [RTC_OBJC_TYPE(RTCAudioSession) lockError]; 686*d9f75844SAndroid Build Coastguard Worker } 687*d9f75844SAndroid Build Coastguard Worker return NO; 688*d9f75844SAndroid Build Coastguard Worker } 689*d9f75844SAndroid Build Coastguard Worker return YES; 690*d9f75844SAndroid Build Coastguard Worker} 691*d9f75844SAndroid Build Coastguard Worker 692*d9f75844SAndroid Build Coastguard Worker- (BOOL)beginWebRTCSession:(NSError **)outError { 693*d9f75844SAndroid Build Coastguard Worker if (outError) { 694*d9f75844SAndroid Build Coastguard Worker *outError = nil; 695*d9f75844SAndroid Build Coastguard Worker } 696*d9f75844SAndroid Build Coastguard Worker _webRTCSessionCount.fetch_add(1); 697*d9f75844SAndroid Build Coastguard Worker [self notifyDidStartPlayOrRecord]; 698*d9f75844SAndroid Build Coastguard Worker return YES; 699*d9f75844SAndroid Build Coastguard Worker} 700*d9f75844SAndroid Build Coastguard Worker 701*d9f75844SAndroid Build Coastguard Worker- (BOOL)endWebRTCSession:(NSError **)outError { 702*d9f75844SAndroid Build Coastguard Worker if (outError) { 703*d9f75844SAndroid Build Coastguard Worker *outError = nil; 704*d9f75844SAndroid Build Coastguard Worker } 705*d9f75844SAndroid Build Coastguard Worker _webRTCSessionCount.fetch_sub(1); 706*d9f75844SAndroid Build Coastguard Worker [self notifyDidStopPlayOrRecord]; 707*d9f75844SAndroid Build Coastguard Worker return YES; 708*d9f75844SAndroid Build Coastguard Worker} 709*d9f75844SAndroid Build Coastguard Worker 710*d9f75844SAndroid Build Coastguard Worker- (BOOL)configureWebRTCSession:(NSError **)outError { 711*d9f75844SAndroid Build Coastguard Worker if (outError) { 712*d9f75844SAndroid Build Coastguard Worker *outError = nil; 713*d9f75844SAndroid Build Coastguard Worker } 714*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Configuring audio session for WebRTC."); 715*d9f75844SAndroid Build Coastguard Worker 716*d9f75844SAndroid Build Coastguard Worker // Configure the AVAudioSession and activate it. 717*d9f75844SAndroid Build Coastguard Worker // Provide an error even if there isn't one so we can log it. 718*d9f75844SAndroid Build Coastguard Worker NSError *error = nil; 719*d9f75844SAndroid Build Coastguard Worker RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *webRTCConfig = 720*d9f75844SAndroid Build Coastguard Worker [RTC_OBJC_TYPE(RTCAudioSessionConfiguration) webRTCConfiguration]; 721*d9f75844SAndroid Build Coastguard Worker if (![self setConfiguration:webRTCConfig active:YES error:&error]) { 722*d9f75844SAndroid Build Coastguard Worker RTCLogError(@"Failed to set WebRTC audio configuration: %@", 723*d9f75844SAndroid Build Coastguard Worker error.localizedDescription); 724*d9f75844SAndroid Build Coastguard Worker // Do not call setActive:NO if setActive:YES failed. 725*d9f75844SAndroid Build Coastguard Worker if (outError) { 726*d9f75844SAndroid Build Coastguard Worker *outError = error; 727*d9f75844SAndroid Build Coastguard Worker } 728*d9f75844SAndroid Build Coastguard Worker return NO; 729*d9f75844SAndroid Build Coastguard Worker } 730*d9f75844SAndroid Build Coastguard Worker 731*d9f75844SAndroid Build Coastguard Worker // Ensure that the device currently supports audio input. 732*d9f75844SAndroid Build Coastguard Worker // TODO(tkchin): Figure out if this is really necessary. 733*d9f75844SAndroid Build Coastguard Worker if (!self.inputAvailable) { 734*d9f75844SAndroid Build Coastguard Worker RTCLogError(@"No audio input path is available!"); 735*d9f75844SAndroid Build Coastguard Worker [self unconfigureWebRTCSession:nil]; 736*d9f75844SAndroid Build Coastguard Worker if (outError) { 737*d9f75844SAndroid Build Coastguard Worker *outError = [self configurationErrorWithDescription:@"No input path."]; 738*d9f75844SAndroid Build Coastguard Worker } 739*d9f75844SAndroid Build Coastguard Worker return NO; 740*d9f75844SAndroid Build Coastguard Worker } 741*d9f75844SAndroid Build Coastguard Worker 742*d9f75844SAndroid Build Coastguard Worker // It can happen (e.g. in combination with BT devices) that the attempt to set 743*d9f75844SAndroid Build Coastguard Worker // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new 744*d9f75844SAndroid Build Coastguard Worker // configuration attempt using the sample rate that worked using the active 745*d9f75844SAndroid Build Coastguard Worker // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in 746*d9f75844SAndroid Build Coastguard Worker // combination with BT headsets. Using this "trick" seems to avoid a state 747*d9f75844SAndroid Build Coastguard Worker // where Core Audio asks for a different number of audio frames than what the 748*d9f75844SAndroid Build Coastguard Worker // session's I/O buffer duration corresponds to. 749*d9f75844SAndroid Build Coastguard Worker // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been 750*d9f75844SAndroid Build Coastguard Worker // tested on a limited set of iOS devices and BT devices. 751*d9f75844SAndroid Build Coastguard Worker double sessionSampleRate = self.sampleRate; 752*d9f75844SAndroid Build Coastguard Worker double preferredSampleRate = webRTCConfig.sampleRate; 753*d9f75844SAndroid Build Coastguard Worker if (sessionSampleRate != preferredSampleRate) { 754*d9f75844SAndroid Build Coastguard Worker RTCLogWarning( 755*d9f75844SAndroid Build Coastguard Worker @"Current sample rate (%.2f) is not the preferred rate (%.2f)", 756*d9f75844SAndroid Build Coastguard Worker sessionSampleRate, preferredSampleRate); 757*d9f75844SAndroid Build Coastguard Worker if (![self setPreferredSampleRate:sessionSampleRate 758*d9f75844SAndroid Build Coastguard Worker error:&error]) { 759*d9f75844SAndroid Build Coastguard Worker RTCLogError(@"Failed to set preferred sample rate: %@", 760*d9f75844SAndroid Build Coastguard Worker error.localizedDescription); 761*d9f75844SAndroid Build Coastguard Worker if (outError) { 762*d9f75844SAndroid Build Coastguard Worker *outError = error; 763*d9f75844SAndroid Build Coastguard Worker } 764*d9f75844SAndroid Build Coastguard Worker } 765*d9f75844SAndroid Build Coastguard Worker } 766*d9f75844SAndroid Build Coastguard Worker 767*d9f75844SAndroid Build Coastguard Worker return YES; 768*d9f75844SAndroid Build Coastguard Worker} 769*d9f75844SAndroid Build Coastguard Worker 770*d9f75844SAndroid Build Coastguard Worker- (BOOL)unconfigureWebRTCSession:(NSError **)outError { 771*d9f75844SAndroid Build Coastguard Worker if (outError) { 772*d9f75844SAndroid Build Coastguard Worker *outError = nil; 773*d9f75844SAndroid Build Coastguard Worker } 774*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Unconfiguring audio session for WebRTC."); 775*d9f75844SAndroid Build Coastguard Worker [self setActive:NO error:outError]; 776*d9f75844SAndroid Build Coastguard Worker 777*d9f75844SAndroid Build Coastguard Worker return YES; 778*d9f75844SAndroid Build Coastguard Worker} 779*d9f75844SAndroid Build Coastguard Worker 780*d9f75844SAndroid Build Coastguard Worker- (NSError *)configurationErrorWithDescription:(NSString *)description { 781*d9f75844SAndroid Build Coastguard Worker NSDictionary* userInfo = @{ 782*d9f75844SAndroid Build Coastguard Worker NSLocalizedDescriptionKey: description, 783*d9f75844SAndroid Build Coastguard Worker }; 784*d9f75844SAndroid Build Coastguard Worker return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain 785*d9f75844SAndroid Build Coastguard Worker code:kRTCAudioSessionErrorConfiguration 786*d9f75844SAndroid Build Coastguard Worker userInfo:userInfo]; 787*d9f75844SAndroid Build Coastguard Worker} 788*d9f75844SAndroid Build Coastguard Worker 789*d9f75844SAndroid Build Coastguard Worker- (void)updateAudioSessionAfterEvent { 790*d9f75844SAndroid Build Coastguard Worker BOOL shouldActivate = self.activationCount > 0; 791*d9f75844SAndroid Build Coastguard Worker AVAudioSessionSetActiveOptions options = shouldActivate ? 792*d9f75844SAndroid Build Coastguard Worker 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation; 793*d9f75844SAndroid Build Coastguard Worker NSError *error = nil; 794*d9f75844SAndroid Build Coastguard Worker if ([self.session setActive:shouldActivate 795*d9f75844SAndroid Build Coastguard Worker withOptions:options 796*d9f75844SAndroid Build Coastguard Worker error:&error]) { 797*d9f75844SAndroid Build Coastguard Worker self.isActive = shouldActivate; 798*d9f75844SAndroid Build Coastguard Worker } else { 799*d9f75844SAndroid Build Coastguard Worker RTCLogError(@"Failed to set session active to %d. Error:%@", 800*d9f75844SAndroid Build Coastguard Worker shouldActivate, error.localizedDescription); 801*d9f75844SAndroid Build Coastguard Worker } 802*d9f75844SAndroid Build Coastguard Worker} 803*d9f75844SAndroid Build Coastguard Worker 804*d9f75844SAndroid Build Coastguard Worker- (void)updateCanPlayOrRecord { 805*d9f75844SAndroid Build Coastguard Worker BOOL canPlayOrRecord = NO; 806*d9f75844SAndroid Build Coastguard Worker BOOL shouldNotify = NO; 807*d9f75844SAndroid Build Coastguard Worker @synchronized(self) { 808*d9f75844SAndroid Build Coastguard Worker canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled; 809*d9f75844SAndroid Build Coastguard Worker if (_canPlayOrRecord == canPlayOrRecord) { 810*d9f75844SAndroid Build Coastguard Worker return; 811*d9f75844SAndroid Build Coastguard Worker } 812*d9f75844SAndroid Build Coastguard Worker _canPlayOrRecord = canPlayOrRecord; 813*d9f75844SAndroid Build Coastguard Worker shouldNotify = YES; 814*d9f75844SAndroid Build Coastguard Worker } 815*d9f75844SAndroid Build Coastguard Worker if (shouldNotify) { 816*d9f75844SAndroid Build Coastguard Worker [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord]; 817*d9f75844SAndroid Build Coastguard Worker } 818*d9f75844SAndroid Build Coastguard Worker} 819*d9f75844SAndroid Build Coastguard Worker 820*d9f75844SAndroid Build Coastguard Worker- (void)audioSessionDidActivate:(AVAudioSession *)session { 821*d9f75844SAndroid Build Coastguard Worker if (_session != session) { 822*d9f75844SAndroid Build Coastguard Worker RTCLogError(@"audioSessionDidActivate called on different AVAudioSession"); 823*d9f75844SAndroid Build Coastguard Worker } 824*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio session was externally activated."); 825*d9f75844SAndroid Build Coastguard Worker [self incrementActivationCount]; 826*d9f75844SAndroid Build Coastguard Worker self.isActive = YES; 827*d9f75844SAndroid Build Coastguard Worker // When a CallKit call begins, it's possible that we receive an interruption 828*d9f75844SAndroid Build Coastguard Worker // begin without a corresponding end. Since we know that we have an activated 829*d9f75844SAndroid Build Coastguard Worker // audio session at this point, just clear any saved interruption flag since 830*d9f75844SAndroid Build Coastguard Worker // the app may never be foregrounded during the duration of the call. 831*d9f75844SAndroid Build Coastguard Worker if (self.isInterrupted) { 832*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Clearing interrupted state due to external activation."); 833*d9f75844SAndroid Build Coastguard Worker self.isInterrupted = NO; 834*d9f75844SAndroid Build Coastguard Worker } 835*d9f75844SAndroid Build Coastguard Worker // Treat external audio session activation as an end interruption event. 836*d9f75844SAndroid Build Coastguard Worker [self notifyDidEndInterruptionWithShouldResumeSession:YES]; 837*d9f75844SAndroid Build Coastguard Worker} 838*d9f75844SAndroid Build Coastguard Worker 839*d9f75844SAndroid Build Coastguard Worker- (void)audioSessionDidDeactivate:(AVAudioSession *)session { 840*d9f75844SAndroid Build Coastguard Worker if (_session != session) { 841*d9f75844SAndroid Build Coastguard Worker RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession"); 842*d9f75844SAndroid Build Coastguard Worker } 843*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Audio session was externally deactivated."); 844*d9f75844SAndroid Build Coastguard Worker self.isActive = NO; 845*d9f75844SAndroid Build Coastguard Worker [self decrementActivationCount]; 846*d9f75844SAndroid Build Coastguard Worker} 847*d9f75844SAndroid Build Coastguard Worker 848*d9f75844SAndroid Build Coastguard Worker- (void)observeValueForKeyPath:(NSString *)keyPath 849*d9f75844SAndroid Build Coastguard Worker ofObject:(id)object 850*d9f75844SAndroid Build Coastguard Worker change:(NSDictionary *)change 851*d9f75844SAndroid Build Coastguard Worker context:(void *)context { 852*d9f75844SAndroid Build Coastguard Worker if (context == (__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class) { 853*d9f75844SAndroid Build Coastguard Worker if (object == _session) { 854*d9f75844SAndroid Build Coastguard Worker NSNumber *newVolume = change[NSKeyValueChangeNewKey]; 855*d9f75844SAndroid Build Coastguard Worker RTCLog(@"OutputVolumeDidChange to %f", newVolume.floatValue); 856*d9f75844SAndroid Build Coastguard Worker [self notifyDidChangeOutputVolume:newVolume.floatValue]; 857*d9f75844SAndroid Build Coastguard Worker } 858*d9f75844SAndroid Build Coastguard Worker } else { 859*d9f75844SAndroid Build Coastguard Worker [super observeValueForKeyPath:keyPath 860*d9f75844SAndroid Build Coastguard Worker ofObject:object 861*d9f75844SAndroid Build Coastguard Worker change:change 862*d9f75844SAndroid Build Coastguard Worker context:context]; 863*d9f75844SAndroid Build Coastguard Worker } 864*d9f75844SAndroid Build Coastguard Worker} 865*d9f75844SAndroid Build Coastguard Worker 866*d9f75844SAndroid Build Coastguard Worker- (void)notifyAudioUnitStartFailedWithError:(OSStatus)error { 867*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 868*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSession:audioUnitStartFailedWithError:); 869*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 870*d9f75844SAndroid Build Coastguard Worker [delegate audioSession:self 871*d9f75844SAndroid Build Coastguard Worker audioUnitStartFailedWithError:[NSError errorWithDomain:kRTCAudioSessionErrorDomain 872*d9f75844SAndroid Build Coastguard Worker code:error 873*d9f75844SAndroid Build Coastguard Worker userInfo:nil]]; 874*d9f75844SAndroid Build Coastguard Worker } 875*d9f75844SAndroid Build Coastguard Worker } 876*d9f75844SAndroid Build Coastguard Worker} 877*d9f75844SAndroid Build Coastguard Worker 878*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidBeginInterruption { 879*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 880*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSessionDidBeginInterruption:); 881*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 882*d9f75844SAndroid Build Coastguard Worker [delegate audioSessionDidBeginInterruption:self]; 883*d9f75844SAndroid Build Coastguard Worker } 884*d9f75844SAndroid Build Coastguard Worker } 885*d9f75844SAndroid Build Coastguard Worker} 886*d9f75844SAndroid Build Coastguard Worker 887*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidEndInterruptionWithShouldResumeSession: 888*d9f75844SAndroid Build Coastguard Worker (BOOL)shouldResumeSession { 889*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 890*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:); 891*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 892*d9f75844SAndroid Build Coastguard Worker [delegate audioSessionDidEndInterruption:self 893*d9f75844SAndroid Build Coastguard Worker shouldResumeSession:shouldResumeSession]; 894*d9f75844SAndroid Build Coastguard Worker } 895*d9f75844SAndroid Build Coastguard Worker } 896*d9f75844SAndroid Build Coastguard Worker} 897*d9f75844SAndroid Build Coastguard Worker 898*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason 899*d9f75844SAndroid Build Coastguard Worker previousRoute:(AVAudioSessionRouteDescription *)previousRoute { 900*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 901*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:); 902*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 903*d9f75844SAndroid Build Coastguard Worker [delegate audioSessionDidChangeRoute:self 904*d9f75844SAndroid Build Coastguard Worker reason:reason 905*d9f75844SAndroid Build Coastguard Worker previousRoute:previousRoute]; 906*d9f75844SAndroid Build Coastguard Worker } 907*d9f75844SAndroid Build Coastguard Worker } 908*d9f75844SAndroid Build Coastguard Worker} 909*d9f75844SAndroid Build Coastguard Worker 910*d9f75844SAndroid Build Coastguard Worker- (void)notifyMediaServicesWereLost { 911*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 912*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSessionMediaServerTerminated:); 913*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 914*d9f75844SAndroid Build Coastguard Worker [delegate audioSessionMediaServerTerminated:self]; 915*d9f75844SAndroid Build Coastguard Worker } 916*d9f75844SAndroid Build Coastguard Worker } 917*d9f75844SAndroid Build Coastguard Worker} 918*d9f75844SAndroid Build Coastguard Worker 919*d9f75844SAndroid Build Coastguard Worker- (void)notifyMediaServicesWereReset { 920*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 921*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSessionMediaServerReset:); 922*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 923*d9f75844SAndroid Build Coastguard Worker [delegate audioSessionMediaServerReset:self]; 924*d9f75844SAndroid Build Coastguard Worker } 925*d9f75844SAndroid Build Coastguard Worker } 926*d9f75844SAndroid Build Coastguard Worker} 927*d9f75844SAndroid Build Coastguard Worker 928*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord { 929*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 930*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:); 931*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 932*d9f75844SAndroid Build Coastguard Worker [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord]; 933*d9f75844SAndroid Build Coastguard Worker } 934*d9f75844SAndroid Build Coastguard Worker } 935*d9f75844SAndroid Build Coastguard Worker} 936*d9f75844SAndroid Build Coastguard Worker 937*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidStartPlayOrRecord { 938*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 939*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSessionDidStartPlayOrRecord:); 940*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 941*d9f75844SAndroid Build Coastguard Worker [delegate audioSessionDidStartPlayOrRecord:self]; 942*d9f75844SAndroid Build Coastguard Worker } 943*d9f75844SAndroid Build Coastguard Worker } 944*d9f75844SAndroid Build Coastguard Worker} 945*d9f75844SAndroid Build Coastguard Worker 946*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidStopPlayOrRecord { 947*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 948*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSessionDidStopPlayOrRecord:); 949*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 950*d9f75844SAndroid Build Coastguard Worker [delegate audioSessionDidStopPlayOrRecord:self]; 951*d9f75844SAndroid Build Coastguard Worker } 952*d9f75844SAndroid Build Coastguard Worker } 953*d9f75844SAndroid Build Coastguard Worker} 954*d9f75844SAndroid Build Coastguard Worker 955*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidChangeOutputVolume:(float)volume { 956*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 957*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSession:didChangeOutputVolume:); 958*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 959*d9f75844SAndroid Build Coastguard Worker [delegate audioSession:self didChangeOutputVolume:volume]; 960*d9f75844SAndroid Build Coastguard Worker } 961*d9f75844SAndroid Build Coastguard Worker } 962*d9f75844SAndroid Build Coastguard Worker} 963*d9f75844SAndroid Build Coastguard Worker 964*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches { 965*d9f75844SAndroid Build Coastguard Worker for (auto delegate : self.delegates) { 966*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSession:didDetectPlayoutGlitch:); 967*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 968*d9f75844SAndroid Build Coastguard Worker [delegate audioSession:self didDetectPlayoutGlitch:totalNumberOfGlitches]; 969*d9f75844SAndroid Build Coastguard Worker } 970*d9f75844SAndroid Build Coastguard Worker } 971*d9f75844SAndroid Build Coastguard Worker} 972*d9f75844SAndroid Build Coastguard Worker 973*d9f75844SAndroid Build Coastguard Worker- (void)notifyWillSetActive:(BOOL)active { 974*d9f75844SAndroid Build Coastguard Worker for (id delegate : self.delegates) { 975*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSession:willSetActive:); 976*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 977*d9f75844SAndroid Build Coastguard Worker [delegate audioSession:self willSetActive:active]; 978*d9f75844SAndroid Build Coastguard Worker } 979*d9f75844SAndroid Build Coastguard Worker } 980*d9f75844SAndroid Build Coastguard Worker} 981*d9f75844SAndroid Build Coastguard Worker 982*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidSetActive:(BOOL)active { 983*d9f75844SAndroid Build Coastguard Worker for (id delegate : self.delegates) { 984*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSession:didSetActive:); 985*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 986*d9f75844SAndroid Build Coastguard Worker [delegate audioSession:self didSetActive:active]; 987*d9f75844SAndroid Build Coastguard Worker } 988*d9f75844SAndroid Build Coastguard Worker } 989*d9f75844SAndroid Build Coastguard Worker} 990*d9f75844SAndroid Build Coastguard Worker 991*d9f75844SAndroid Build Coastguard Worker- (void)notifyFailedToSetActive:(BOOL)active error:(NSError *)error { 992*d9f75844SAndroid Build Coastguard Worker for (id delegate : self.delegates) { 993*d9f75844SAndroid Build Coastguard Worker SEL sel = @selector(audioSession:failedToSetActive:error:); 994*d9f75844SAndroid Build Coastguard Worker if ([delegate respondsToSelector:sel]) { 995*d9f75844SAndroid Build Coastguard Worker [delegate audioSession:self failedToSetActive:active error:error]; 996*d9f75844SAndroid Build Coastguard Worker } 997*d9f75844SAndroid Build Coastguard Worker } 998*d9f75844SAndroid Build Coastguard Worker} 999*d9f75844SAndroid Build Coastguard Worker 1000*d9f75844SAndroid Build Coastguard Worker@end 1001