blob: 74b57acd613c61feb0171f8e9a55313361386bf0 [file] [log] [blame]
Zeke Chinb3fb71c2016-02-18 15:44:07 -08001/*
2 * Copyright 2016 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020011#import "RTCAudioSession+Private.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080012
tkchin93dd6342016-07-27 10:17:14 -070013#import <UIKit/UIKit.h>
14
Joe Chen0b3a6e32019-12-26 23:01:42 -080015#include <vector>
16
Steve Anton10542f22019-01-11 09:11:00 -080017#include "rtc_base/atomic_ops.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020018#include "rtc_base/checks.h"
Steve Anton10542f22019-01-11 09:11:00 -080019#include "rtc_base/critical_section.h"
denicija59ee91b2017-06-05 05:48:47 -070020
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020021#import "RTCAudioSessionConfiguration.h"
22#import "base/RTCLogging.h"
denicija59ee91b2017-06-05 05:48:47 -070023
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020024NSString *const kRTCAudioSessionErrorDomain = @"org.webrtc.RTC_OBJC_TYPE(RTCAudioSession)";
Zeke Chinb3fb71c2016-02-18 15:44:07 -080025NSInteger const kRTCAudioSessionErrorLockRequired = -1;
tkchin9f987d32016-03-12 20:06:28 -080026NSInteger const kRTCAudioSessionErrorConfiguration = -2;
jtteh13ae11a2017-05-25 17:52:20 -070027NSString * const kRTCAudioSessionOutputVolumeSelector = @"outputVolume";
Zeke Chinb3fb71c2016-02-18 15:44:07 -080028
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020029@interface RTC_OBJC_TYPE (RTCAudioSession)
30() @property(nonatomic,
31 readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates;
Joe Chen0b3a6e32019-12-26 23:01:42 -080032@end
33
Zeke Chinb3fb71c2016-02-18 15:44:07 -080034// This class needs to be thread-safe because it is accessed from many threads.
35// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
36// lock contention so coarse locks should be fine for now.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020037@implementation RTC_OBJC_TYPE (RTCAudioSession) {
tkchin0ce3bf92016-03-12 16:52:04 -080038 rtc::CriticalSection _crit;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080039 AVAudioSession *_session;
Tze Kwang Chin307a0922016-03-21 13:57:40 -070040 volatile int _activationCount;
41 volatile int _lockRecursionCount;
42 volatile int _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080043 BOOL _isActive;
tkchind2511962016-05-06 18:54:15 -070044 BOOL _useManualAudio;
45 BOOL _isAudioEnabled;
46 BOOL _canPlayOrRecord;
tkchin93dd6342016-07-27 10:17:14 -070047 BOOL _isInterrupted;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080048}
49
50@synthesize session = _session;
tkchine54467f2016-03-15 16:54:03 -070051@synthesize delegates = _delegates;
Joe Chen0c05b1a2019-05-07 10:46:22 -070052@synthesize ignoresPreferredAttributeConfigurationErrors =
53 _ignoresPreferredAttributeConfigurationErrors;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080054
55+ (instancetype)sharedInstance {
56 static dispatch_once_t onceToken;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020057 static RTC_OBJC_TYPE(RTCAudioSession) *sharedInstance = nil;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080058 dispatch_once(&onceToken, ^{
Tze Kwang Chin307a0922016-03-21 13:57:40 -070059 sharedInstance = [[self alloc] init];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080060 });
61 return sharedInstance;
62}
63
64- (instancetype)init {
Peter Hanspers47217362017-10-05 11:39:15 +020065 return [self initWithAudioSession:[AVAudioSession sharedInstance]];
66}
67
68/** This initializer provides a way for unit tests to inject a fake/mock audio session. */
69- (instancetype)initWithAudioSession:(id)audioSession {
Zeke Chinb3fb71c2016-02-18 15:44:07 -080070 if (self = [super init]) {
Peter Hanspers47217362017-10-05 11:39:15 +020071 _session = audioSession;
tkchin0ce3bf92016-03-12 16:52:04 -080072
Zeke Chinb3fb71c2016-02-18 15:44:07 -080073 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
74 [center addObserver:self
75 selector:@selector(handleInterruptionNotification:)
76 name:AVAudioSessionInterruptionNotification
77 object:nil];
78 [center addObserver:self
79 selector:@selector(handleRouteChangeNotification:)
80 name:AVAudioSessionRouteChangeNotification
81 object:nil];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080082 [center addObserver:self
83 selector:@selector(handleMediaServicesWereLost:)
84 name:AVAudioSessionMediaServicesWereLostNotification
85 object:nil];
86 [center addObserver:self
87 selector:@selector(handleMediaServicesWereReset:)
88 name:AVAudioSessionMediaServicesWereResetNotification
89 object:nil];
henrikaf1363fd2016-09-27 06:06:44 -070090 // Posted on the main thread when the primary audio from other applications
91 // starts and stops. Foreground applications may use this notification as a
92 // hint to enable or disable audio that is secondary.
93 [center addObserver:self
94 selector:@selector(handleSilenceSecondaryAudioHintNotification:)
95 name:AVAudioSessionSilenceSecondaryAudioHintNotification
96 object:nil];
tkchin93dd6342016-07-27 10:17:14 -070097 // Also track foreground event in order to deal with interruption ended situation.
98 [center addObserver:self
99 selector:@selector(handleApplicationDidBecomeActive:)
100 name:UIApplicationDidBecomeActiveNotification
101 object:nil];
jtteh13ae11a2017-05-25 17:52:20 -0700102 [_session addObserver:self
103 forKeyPath:kRTCAudioSessionOutputVolumeSelector
104 options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200105 context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
jtteh13ae11a2017-05-25 17:52:20 -0700106
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200107 RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): init.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800108 }
109 return self;
110}
111
112- (void)dealloc {
113 [[NSNotificationCenter defaultCenter] removeObserver:self];
Peter Hanspers47217362017-10-05 11:39:15 +0200114 [_session removeObserver:self
115 forKeyPath:kRTCAudioSessionOutputVolumeSelector
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200116 context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
117 RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): dealloc.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800118}
119
Zeke Chin1300caa2016-03-18 14:39:11 -0700120- (NSString *)description {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200121 NSString *format = @"RTC_OBJC_TYPE(RTCAudioSession): {\n"
122 " category: %@\n"
123 " categoryOptions: %ld\n"
124 " mode: %@\n"
125 " isActive: %d\n"
126 " sampleRate: %.2f\n"
127 " IOBufferDuration: %f\n"
128 " outputNumberOfChannels: %ld\n"
129 " inputNumberOfChannels: %ld\n"
130 " outputLatency: %f\n"
131 " inputLatency: %f\n"
132 " outputVolume: %f\n"
133 "}";
Zeke Chin1300caa2016-03-18 14:39:11 -0700134 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -0700135 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -0700136 self.isActive, self.sampleRate, self.IOBufferDuration,
137 self.outputNumberOfChannels, self.inputNumberOfChannels,
henrikac5aea652016-09-21 07:45:55 -0700138 self.outputLatency, self.inputLatency, self.outputVolume];
Zeke Chin1300caa2016-03-18 14:39:11 -0700139 return description;
140}
141
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800142- (void)setIsActive:(BOOL)isActive {
143 @synchronized(self) {
144 _isActive = isActive;
145 }
146}
147
148- (BOOL)isActive {
149 @synchronized(self) {
150 return _isActive;
151 }
152}
153
154- (BOOL)isLocked {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700155 return _lockRecursionCount > 0;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800156}
157
tkchind2511962016-05-06 18:54:15 -0700158- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800159 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700160 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800161 return;
162 }
tkchind2511962016-05-06 18:54:15 -0700163 _useManualAudio = useManualAudio;
164 }
165 [self updateCanPlayOrRecord];
166}
167
168- (BOOL)useManualAudio {
169 @synchronized(self) {
170 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800171 }
172}
173
tkchind2511962016-05-06 18:54:15 -0700174- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800175 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700176 if (_isAudioEnabled == isAudioEnabled) {
177 return;
178 }
179 _isAudioEnabled = isAudioEnabled;
180 }
181 [self updateCanPlayOrRecord];
182}
183
184- (BOOL)isAudioEnabled {
185 @synchronized(self) {
186 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800187 }
188}
189
Joe Chen0c05b1a2019-05-07 10:46:22 -0700190- (void)setIgnoresPreferredAttributeConfigurationErrors:
191 (BOOL)ignoresPreferredAttributeConfigurationErrors {
192 @synchronized(self) {
193 if (_ignoresPreferredAttributeConfigurationErrors ==
194 ignoresPreferredAttributeConfigurationErrors) {
195 return;
196 }
197 _ignoresPreferredAttributeConfigurationErrors = ignoresPreferredAttributeConfigurationErrors;
198 }
199}
200
201- (BOOL)ignoresPreferredAttributeConfigurationErrors {
202 @synchronized(self) {
203 return _ignoresPreferredAttributeConfigurationErrors;
204 }
205}
206
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700207// TODO(tkchin): Check for duplicates.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200208- (void)addDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700209 RTCLog(@"Adding delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700210 if (!delegate) {
211 return;
212 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800213 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700214 _delegates.push_back(delegate);
215 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800216 }
217}
218
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200219- (void)removeDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700220 RTCLog(@"Removing delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700221 if (!delegate) {
222 return;
223 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800224 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700225 _delegates.erase(std::remove(_delegates.begin(),
226 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700227 delegate),
228 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700229 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800230 }
231}
232
kthelgasonee8b8612017-03-31 04:50:27 -0700233#pragma clang diagnostic push
234#pragma clang diagnostic ignored "-Wthread-safety-analysis"
235
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800236- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800237 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700238 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800239}
240
241- (void)unlockForConfiguration {
242 // Don't let threads other than the one that called lockForConfiguration
243 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800244 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700245 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800246 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800247 // was called without anyone calling lock, we will hit an assertion.
248 _crit.Leave();
249 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800250 }
251}
252
kthelgasonee8b8612017-03-31 04:50:27 -0700253#pragma clang diagnostic pop
254
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800255#pragma mark - AVAudioSession proxy methods
256
257- (NSString *)category {
258 return self.session.category;
259}
260
261- (AVAudioSessionCategoryOptions)categoryOptions {
262 return self.session.categoryOptions;
263}
264
265- (NSString *)mode {
266 return self.session.mode;
267}
268
269- (BOOL)secondaryAudioShouldBeSilencedHint {
270 return self.session.secondaryAudioShouldBeSilencedHint;
271}
272
273- (AVAudioSessionRouteDescription *)currentRoute {
274 return self.session.currentRoute;
275}
276
277- (NSInteger)maximumInputNumberOfChannels {
278 return self.session.maximumInputNumberOfChannels;
279}
280
281- (NSInteger)maximumOutputNumberOfChannels {
282 return self.session.maximumOutputNumberOfChannels;
283}
284
285- (float)inputGain {
286 return self.session.inputGain;
287}
288
289- (BOOL)inputGainSettable {
290 return self.session.inputGainSettable;
291}
292
293- (BOOL)inputAvailable {
294 return self.session.inputAvailable;
295}
296
297- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
298 return self.session.inputDataSources;
299}
300
301- (AVAudioSessionDataSourceDescription *)inputDataSource {
302 return self.session.inputDataSource;
303}
304
305- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
306 return self.session.outputDataSources;
307}
308
309- (AVAudioSessionDataSourceDescription *)outputDataSource {
310 return self.session.outputDataSource;
311}
312
313- (double)sampleRate {
314 return self.session.sampleRate;
315}
316
tkchind2511962016-05-06 18:54:15 -0700317- (double)preferredSampleRate {
318 return self.session.preferredSampleRate;
319}
320
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800321- (NSInteger)inputNumberOfChannels {
322 return self.session.inputNumberOfChannels;
323}
324
325- (NSInteger)outputNumberOfChannels {
326 return self.session.outputNumberOfChannels;
327}
328
329- (float)outputVolume {
330 return self.session.outputVolume;
331}
332
333- (NSTimeInterval)inputLatency {
334 return self.session.inputLatency;
335}
336
337- (NSTimeInterval)outputLatency {
338 return self.session.outputLatency;
339}
340
341- (NSTimeInterval)IOBufferDuration {
342 return self.session.IOBufferDuration;
343}
344
tkchind2511962016-05-06 18:54:15 -0700345- (NSTimeInterval)preferredIOBufferDuration {
346 return self.session.preferredIOBufferDuration;
347}
348
tkchine54467f2016-03-15 16:54:03 -0700349// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
350// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800351- (BOOL)setActive:(BOOL)active
352 error:(NSError **)outError {
353 if (![self checkLock:outError]) {
354 return NO;
355 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700356 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800357 if (!active && activationCount == 0) {
358 RTCLogWarning(@"Attempting to deactivate without prior activation.");
359 }
JT Tehc1f083d2018-04-25 09:19:35 -0700360 [self notifyWillSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800361 BOOL success = YES;
362 BOOL isActive = self.isActive;
363 // Keep a local error so we can log it.
364 NSError *error = nil;
365 BOOL shouldSetActive =
366 (active && !isActive) || (!active && isActive && activationCount == 1);
367 // Attempt to activate if we're not active.
368 // Attempt to deactivate if we're active and it's the last unbalanced call.
369 if (shouldSetActive) {
370 AVAudioSession *session = self.session;
371 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
372 // that other audio sessions that were interrupted by our session can return
373 // to their active state. It is recommended for VoIP apps to use this
374 // option.
375 AVAudioSessionSetActiveOptions options =
376 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
377 success = [session setActive:active
378 withOptions:options
379 error:&error];
380 if (outError) {
381 *outError = error;
382 }
383 }
384 if (success) {
385 if (shouldSetActive) {
386 self.isActive = active;
Joe Chen81dcfda2019-12-23 11:18:02 -0800387 if (active && self.isInterrupted) {
388 self.isInterrupted = NO;
389 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
390 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800391 }
392 if (active) {
393 [self incrementActivationCount];
394 }
JT Tehc1f083d2018-04-25 09:19:35 -0700395 [self notifyDidSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800396 } else {
tkchin9f987d32016-03-12 20:06:28 -0800397 RTCLogError(@"Failed to setActive:%d. Error: %@",
398 active, error.localizedDescription);
JT Tehc1f083d2018-04-25 09:19:35 -0700399 [self notifyFailedToSetActive:active error:error];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800400 }
401 // Decrement activation count on deactivation whether or not it succeeded.
402 if (!active) {
403 [self decrementActivationCount];
404 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700405 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800406 return success;
407}
408
409- (BOOL)setCategory:(NSString *)category
410 withOptions:(AVAudioSessionCategoryOptions)options
411 error:(NSError **)outError {
412 if (![self checkLock:outError]) {
413 return NO;
414 }
415 return [self.session setCategory:category withOptions:options error:outError];
416}
417
418- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
419 if (![self checkLock:outError]) {
420 return NO;
421 }
422 return [self.session setMode:mode error:outError];
423}
424
425- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
426 if (![self checkLock:outError]) {
427 return NO;
428 }
429 return [self.session setInputGain:gain error:outError];
430}
431
432- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
433 if (![self checkLock:outError]) {
434 return NO;
435 }
436 return [self.session setPreferredSampleRate:sampleRate error:outError];
437}
438
439- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
440 error:(NSError **)outError {
441 if (![self checkLock:outError]) {
442 return NO;
443 }
444 return [self.session setPreferredIOBufferDuration:duration error:outError];
445}
446
447- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
448 error:(NSError **)outError {
449 if (![self checkLock:outError]) {
450 return NO;
451 }
452 return [self.session setPreferredInputNumberOfChannels:count error:outError];
453}
454- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
455 error:(NSError **)outError {
456 if (![self checkLock:outError]) {
457 return NO;
458 }
459 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
460}
461
462- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
463 error:(NSError **)outError {
464 if (![self checkLock:outError]) {
465 return NO;
466 }
467 return [self.session overrideOutputAudioPort:portOverride error:outError];
468}
469
470- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
471 error:(NSError **)outError {
472 if (![self checkLock:outError]) {
473 return NO;
474 }
475 return [self.session setPreferredInput:inPort error:outError];
476}
477
478- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
479 error:(NSError **)outError {
480 if (![self checkLock:outError]) {
481 return NO;
482 }
483 return [self.session setInputDataSource:dataSource error:outError];
484}
485
486- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
487 error:(NSError **)outError {
488 if (![self checkLock:outError]) {
489 return NO;
490 }
491 return [self.session setOutputDataSource:dataSource error:outError];
492}
493
494#pragma mark - Notifications
495
496- (void)handleInterruptionNotification:(NSNotification *)notification {
497 NSNumber* typeNumber =
498 notification.userInfo[AVAudioSessionInterruptionTypeKey];
499 AVAudioSessionInterruptionType type =
500 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
501 switch (type) {
502 case AVAudioSessionInterruptionTypeBegan:
503 RTCLog(@"Audio session interruption began.");
504 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700505 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800506 [self notifyDidBeginInterruption];
507 break;
508 case AVAudioSessionInterruptionTypeEnded: {
509 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700510 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800511 [self updateAudioSessionAfterEvent];
512 NSNumber *optionsNumber =
513 notification.userInfo[AVAudioSessionInterruptionOptionKey];
514 AVAudioSessionInterruptionOptions options =
515 optionsNumber.unsignedIntegerValue;
516 BOOL shouldResume =
517 options & AVAudioSessionInterruptionOptionShouldResume;
518 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
519 break;
520 }
521 }
522}
523
524- (void)handleRouteChangeNotification:(NSNotification *)notification {
525 // Get reason for current route change.
526 NSNumber* reasonNumber =
527 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
528 AVAudioSessionRouteChangeReason reason =
529 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
530 RTCLog(@"Audio route changed:");
531 switch (reason) {
532 case AVAudioSessionRouteChangeReasonUnknown:
533 RTCLog(@"Audio route changed: ReasonUnknown");
534 break;
535 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
536 RTCLog(@"Audio route changed: NewDeviceAvailable");
537 break;
538 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
539 RTCLog(@"Audio route changed: OldDeviceUnavailable");
540 break;
541 case AVAudioSessionRouteChangeReasonCategoryChange:
542 RTCLog(@"Audio route changed: CategoryChange to :%@",
543 self.session.category);
544 break;
545 case AVAudioSessionRouteChangeReasonOverride:
546 RTCLog(@"Audio route changed: Override");
547 break;
548 case AVAudioSessionRouteChangeReasonWakeFromSleep:
549 RTCLog(@"Audio route changed: WakeFromSleep");
550 break;
551 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
552 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
553 break;
554 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
555 RTCLog(@"Audio route changed: RouteConfigurationChange");
556 break;
557 }
558 AVAudioSessionRouteDescription* previousRoute =
559 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
560 // Log previous route configuration.
561 RTCLog(@"Previous route: %@\nCurrent route:%@",
562 previousRoute, self.session.currentRoute);
563 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
564}
565
566- (void)handleMediaServicesWereLost:(NSNotification *)notification {
567 RTCLog(@"Media services were lost.");
568 [self updateAudioSessionAfterEvent];
569 [self notifyMediaServicesWereLost];
570}
571
572- (void)handleMediaServicesWereReset:(NSNotification *)notification {
573 RTCLog(@"Media services were reset.");
574 [self updateAudioSessionAfterEvent];
575 [self notifyMediaServicesWereReset];
576}
577
henrikaf1363fd2016-09-27 06:06:44 -0700578- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
579 // TODO(henrika): just adding logs here for now until we know if we are ever
580 // see this notification and might be affected by it or if further actions
581 // are required.
582 NSNumber *typeNumber =
583 notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
584 AVAudioSessionSilenceSecondaryAudioHintType type =
585 (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
586 switch (type) {
587 case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
588 RTCLog(@"Another application's primary audio has started.");
589 break;
590 case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
591 RTCLog(@"Another application's primary audio has stopped.");
592 break;
593 }
594}
595
tkchin93dd6342016-07-27 10:17:14 -0700596- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
Zeke Chin8280a562018-07-10 13:53:55 -0700597 BOOL isInterrupted = self.isInterrupted;
haysc7735b1e2017-03-29 14:53:32 -0700598 RTCLog(@"Application became active after an interruption. Treating as interruption "
Zeke Chin8280a562018-07-10 13:53:55 -0700599 "end. isInterrupted changed from %d to 0.",
600 isInterrupted);
601 if (isInterrupted) {
tkchin93dd6342016-07-27 10:17:14 -0700602 self.isInterrupted = NO;
603 [self updateAudioSessionAfterEvent];
tkchin93dd6342016-07-27 10:17:14 -0700604 }
haysc7735b1e2017-03-29 14:53:32 -0700605 // Always treat application becoming active as an interruption end event.
606 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
tkchin93dd6342016-07-27 10:17:14 -0700607}
608
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800609#pragma mark - Private
610
611+ (NSError *)lockError {
612 NSDictionary *userInfo = @{
613 NSLocalizedDescriptionKey:
614 @"Must call lockForConfiguration before calling this method."
615 };
616 NSError *error =
617 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
618 code:kRTCAudioSessionErrorLockRequired
619 userInfo:userInfo];
620 return error;
621}
622
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200623- (std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800624 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700625 // Note: this returns a copy.
626 return _delegates;
627 }
628}
629
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700630// TODO(tkchin): check for duplicates.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200631- (void)pushDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700632 @synchronized(self) {
633 _delegates.insert(_delegates.begin(), delegate);
634 }
635}
636
637- (void)removeZeroedDelegates {
638 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700639 _delegates.erase(
640 std::remove_if(_delegates.begin(),
641 _delegates.end(),
642 [](id delegate) -> bool { return delegate == nil; }),
643 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800644 }
645}
646
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700647- (int)activationCount {
648 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800649}
650
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700651- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800652 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700653 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800654}
655
656- (NSInteger)decrementActivationCount {
657 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700658 return rtc::AtomicOps::Decrement(&_activationCount);
659}
660
661- (int)webRTCSessionCount {
662 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800663}
664
tkchind2511962016-05-06 18:54:15 -0700665- (BOOL)canPlayOrRecord {
666 return !self.useManualAudio || self.isAudioEnabled;
667}
668
tkchin93dd6342016-07-27 10:17:14 -0700669- (BOOL)isInterrupted {
670 @synchronized(self) {
671 return _isInterrupted;
672 }
673}
674
675- (void)setIsInterrupted:(BOOL)isInterrupted {
676 @synchronized(self) {
677 if (_isInterrupted == isInterrupted) {
678 return;
679 }
680 _isInterrupted = isInterrupted;
681 }
682}
683
tkchin9f987d32016-03-12 20:06:28 -0800684- (BOOL)checkLock:(NSError **)outError {
685 // Check ivar instead of trying to acquire lock so that we won't accidentally
686 // acquire lock if it hasn't already been called.
687 if (!self.isLocked) {
688 if (outError) {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200689 *outError = [RTC_OBJC_TYPE(RTCAudioSession) lockError];
tkchin9f987d32016-03-12 20:06:28 -0800690 }
691 return NO;
692 }
693 return YES;
694}
695
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700696- (BOOL)beginWebRTCSession:(NSError **)outError {
697 if (outError) {
698 *outError = nil;
699 }
700 if (![self checkLock:outError]) {
701 return NO;
702 }
tkchind2511962016-05-06 18:54:15 -0700703 rtc::AtomicOps::Increment(&_webRTCSessionCount);
704 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700705 return YES;
706}
707
708- (BOOL)endWebRTCSession:(NSError **)outError {
709 if (outError) {
710 *outError = nil;
711 }
712 if (![self checkLock:outError]) {
713 return NO;
714 }
tkchind2511962016-05-06 18:54:15 -0700715 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
716 [self notifyDidStopPlayOrRecord];
717 return YES;
718}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700719
tkchind2511962016-05-06 18:54:15 -0700720- (BOOL)configureWebRTCSession:(NSError **)outError {
721 if (outError) {
722 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700723 }
tkchind2511962016-05-06 18:54:15 -0700724 if (![self checkLock:outError]) {
725 return NO;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700726 }
tkchind2511962016-05-06 18:54:15 -0700727 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700728
tkchind2511962016-05-06 18:54:15 -0700729 // Configure the AVAudioSession and activate it.
730 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700731 NSError *error = nil;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200732 RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *webRTCConfig =
733 [RTC_OBJC_TYPE(RTCAudioSessionConfiguration) webRTCConfiguration];
tkchind2511962016-05-06 18:54:15 -0700734 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
735 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700736 error.localizedDescription);
jttehf84c1d62017-04-21 13:56:39 -0700737 // Do not call setActive:NO if setActive:YES failed.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700738 if (outError) {
739 *outError = error;
740 }
741 return NO;
742 }
743
tkchind2511962016-05-06 18:54:15 -0700744 // Ensure that the device currently supports audio input.
745 // TODO(tkchin): Figure out if this is really necessary.
746 if (!self.inputAvailable) {
747 RTCLogError(@"No audio input path is available!");
748 [self unconfigureWebRTCSession:nil];
749 if (outError) {
750 *outError = [self configurationErrorWithDescription:@"No input path."];
751 }
752 return NO;
753 }
754
henrika2d014be2016-06-16 14:26:55 +0200755 // It can happen (e.g. in combination with BT devices) that the attempt to set
756 // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
757 // configuration attempt using the sample rate that worked using the active
758 // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
759 // combination with BT headsets. Using this "trick" seems to avoid a state
760 // where Core Audio asks for a different number of audio frames than what the
761 // session's I/O buffer duration corresponds to.
762 // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
763 // tested on a limited set of iOS devices and BT devices.
764 double sessionSampleRate = self.sampleRate;
765 double preferredSampleRate = webRTCConfig.sampleRate;
766 if (sessionSampleRate != preferredSampleRate) {
767 RTCLogWarning(
768 @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
769 sessionSampleRate, preferredSampleRate);
770 if (![self setPreferredSampleRate:sessionSampleRate
771 error:&error]) {
772 RTCLogError(@"Failed to set preferred sample rate: %@",
773 error.localizedDescription);
774 if (outError) {
775 *outError = error;
776 }
777 }
778 }
779
tkchind2511962016-05-06 18:54:15 -0700780 return YES;
781}
782
783- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
784 if (outError) {
785 *outError = nil;
786 }
787 if (![self checkLock:outError]) {
788 return NO;
789 }
790 RTCLog(@"Unconfiguring audio session for WebRTC.");
791 [self setActive:NO error:outError];
792
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700793 return YES;
794}
795
796- (NSError *)configurationErrorWithDescription:(NSString *)description {
797 NSDictionary* userInfo = @{
798 NSLocalizedDescriptionKey: description,
799 };
800 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
801 code:kRTCAudioSessionErrorConfiguration
802 userInfo:userInfo];
803}
804
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800805- (void)updateAudioSessionAfterEvent {
806 BOOL shouldActivate = self.activationCount > 0;
807 AVAudioSessionSetActiveOptions options = shouldActivate ?
808 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
809 NSError *error = nil;
810 if ([self.session setActive:shouldActivate
811 withOptions:options
812 error:&error]) {
813 self.isActive = shouldActivate;
814 } else {
815 RTCLogError(@"Failed to set session active to %d. Error:%@",
816 shouldActivate, error.localizedDescription);
817 }
818}
819
tkchind2511962016-05-06 18:54:15 -0700820- (void)updateCanPlayOrRecord {
821 BOOL canPlayOrRecord = NO;
822 BOOL shouldNotify = NO;
823 @synchronized(self) {
824 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
825 if (_canPlayOrRecord == canPlayOrRecord) {
826 return;
827 }
828 _canPlayOrRecord = canPlayOrRecord;
829 shouldNotify = YES;
830 }
831 if (shouldNotify) {
832 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
833 }
834}
835
jtteh3c9a6c02017-04-18 09:09:35 -0700836- (void)audioSessionDidActivate:(AVAudioSession *)session {
837 if (_session != session) {
838 RTCLogError(@"audioSessionDidActivate called on different AVAudioSession");
839 }
Zeke Chin8280a562018-07-10 13:53:55 -0700840 RTCLog(@"Audio session was externally activated.");
jtteh3c9a6c02017-04-18 09:09:35 -0700841 [self incrementActivationCount];
842 self.isActive = YES;
Zeke Chin8280a562018-07-10 13:53:55 -0700843 // When a CallKit call begins, it's possible that we receive an interruption
844 // begin without a corresponding end. Since we know that we have an activated
845 // audio session at this point, just clear any saved interruption flag since
846 // the app may never be foregrounded during the duration of the call.
847 if (self.isInterrupted) {
848 RTCLog(@"Clearing interrupted state due to external activation.");
849 self.isInterrupted = NO;
850 }
851 // Treat external audio session activation as an end interruption event.
852 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
jtteh3c9a6c02017-04-18 09:09:35 -0700853}
854
855- (void)audioSessionDidDeactivate:(AVAudioSession *)session {
856 if (_session != session) {
857 RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession");
858 }
Zeke Chin8280a562018-07-10 13:53:55 -0700859 RTCLog(@"Audio session was externally deactivated.");
jtteh3c9a6c02017-04-18 09:09:35 -0700860 self.isActive = NO;
861 [self decrementActivationCount];
862}
863
jtteh13ae11a2017-05-25 17:52:20 -0700864- (void)observeValueForKeyPath:(NSString *)keyPath
865 ofObject:(id)object
866 change:(NSDictionary *)change
867 context:(void *)context {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200868 if (context == (__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class) {
Peter Hanspers47217362017-10-05 11:39:15 +0200869 if (object == _session) {
870 NSNumber *newVolume = change[NSKeyValueChangeNewKey];
871 RTCLog(@"OutputVolumeDidChange to %f", newVolume.floatValue);
872 [self notifyDidChangeOutputVolume:newVolume.floatValue];
873 }
jtteh13ae11a2017-05-25 17:52:20 -0700874 } else {
875 [super observeValueForKeyPath:keyPath
876 ofObject:object
877 change:change
878 context:context];
879 }
880}
881
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800882- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700883 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700884 SEL sel = @selector(audioSessionDidBeginInterruption:);
885 if ([delegate respondsToSelector:sel]) {
886 [delegate audioSessionDidBeginInterruption:self];
887 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800888 }
889}
890
891- (void)notifyDidEndInterruptionWithShouldResumeSession:
892 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700893 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700894 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
895 if ([delegate respondsToSelector:sel]) {
896 [delegate audioSessionDidEndInterruption:self
897 shouldResumeSession:shouldResumeSession];
898 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800899 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800900}
901
902- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
903 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700904 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700905 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
906 if ([delegate respondsToSelector:sel]) {
907 [delegate audioSessionDidChangeRoute:self
908 reason:reason
909 previousRoute:previousRoute];
910 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800911 }
912}
913
914- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700915 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800916 SEL sel = @selector(audioSessionMediaServerTerminated:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700917 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800918 [delegate audioSessionMediaServerTerminated:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700919 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800920 }
921}
922
923- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700924 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800925 SEL sel = @selector(audioSessionMediaServerReset:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700926 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800927 [delegate audioSessionMediaServerReset:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700928 }
929 }
930}
931
tkchind2511962016-05-06 18:54:15 -0700932- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700933 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700934 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700935 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700936 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700937 }
938 }
939}
940
tkchind2511962016-05-06 18:54:15 -0700941- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700942 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700943 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700944 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700945 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700946 }
947 }
948}
949
tkchind2511962016-05-06 18:54:15 -0700950- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700951 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700952 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700953 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700954 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700955 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800956 }
957}
958
jtteh13ae11a2017-05-25 17:52:20 -0700959- (void)notifyDidChangeOutputVolume:(float)volume {
960 for (auto delegate : self.delegates) {
961 SEL sel = @selector(audioSession:didChangeOutputVolume:);
962 if ([delegate respondsToSelector:sel]) {
963 [delegate audioSession:self didChangeOutputVolume:volume];
964 }
965 }
966}
967
Anders Carlsson121ea322017-06-26 15:34:47 +0200968- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches {
969 for (auto delegate : self.delegates) {
970 SEL sel = @selector(audioSession:didDetectPlayoutGlitch:);
971 if ([delegate respondsToSelector:sel]) {
972 [delegate audioSession:self didDetectPlayoutGlitch:totalNumberOfGlitches];
973 }
974 }
975}
976
JT Tehc1f083d2018-04-25 09:19:35 -0700977- (void)notifyWillSetActive:(BOOL)active {
978 for (id delegate : self.delegates) {
979 SEL sel = @selector(audioSession:willSetActive:);
980 if ([delegate respondsToSelector:sel]) {
981 [delegate audioSession:self willSetActive:active];
982 }
983 }
984}
985
986- (void)notifyDidSetActive:(BOOL)active {
987 for (id delegate : self.delegates) {
988 SEL sel = @selector(audioSession:didSetActive:);
989 if ([delegate respondsToSelector:sel]) {
990 [delegate audioSession:self didSetActive:active];
991 }
992 }
993}
994
995- (void)notifyFailedToSetActive:(BOOL)active error:(NSError *)error {
996 for (id delegate : self.delegates) {
997 SEL sel = @selector(audioSession:failedToSetActive:error:);
998 if ([delegate respondsToSelector:sel]) {
999 [delegate audioSession:self failedToSetActive:active error:error];
1000 }
1001 }
1002}
1003
Zeke Chinb3fb71c2016-02-18 15:44:07 -08001004@end