blob: 550a426d3655b3b6ec8ff5df922ddd709adeea81 [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
Niels Möller7a669002022-06-27 09:47:02 +020015#include <atomic>
Joe Chen0b3a6e32019-12-26 23:01:42 -080016#include <vector>
17
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +090018#include "absl/base/attributes.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020019#include "rtc_base/checks.h"
Niels Möller072c0082021-02-15 16:30:44 +010020#include "rtc_base/synchronization/mutex.h"
denicija59ee91b2017-06-05 05:48:47 -070021
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020022#import "RTCAudioSessionConfiguration.h"
23#import "base/RTCLogging.h"
denicija59ee91b2017-06-05 05:48:47 -070024
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +090025#if !defined(ABSL_HAVE_THREAD_LOCAL)
26#error ABSL_HAVE_THREAD_LOCAL should be defined for MacOS / iOS Targets.
27#endif
28
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020029NSString *const kRTCAudioSessionErrorDomain = @"org.webrtc.RTC_OBJC_TYPE(RTCAudioSession)";
Zeke Chinb3fb71c2016-02-18 15:44:07 -080030NSInteger const kRTCAudioSessionErrorLockRequired = -1;
tkchin9f987d32016-03-12 20:06:28 -080031NSInteger const kRTCAudioSessionErrorConfiguration = -2;
jtteh13ae11a2017-05-25 17:52:20 -070032NSString * const kRTCAudioSessionOutputVolumeSelector = @"outputVolume";
Zeke Chinb3fb71c2016-02-18 15:44:07 -080033
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +090034namespace {
35// Since webrtc::Mutex is not a reentrant lock and cannot check if the mutex is locked,
36// we need a separate variable to check that the mutex is locked in the RTCAudioSession.
37ABSL_CONST_INIT thread_local bool mutex_locked = false;
38} // namespace
39
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020040@interface RTC_OBJC_TYPE (RTCAudioSession)
41() @property(nonatomic,
42 readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates;
Joe Chen0b3a6e32019-12-26 23:01:42 -080043@end
44
Zeke Chinb3fb71c2016-02-18 15:44:07 -080045// This class needs to be thread-safe because it is accessed from many threads.
46// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
47// lock contention so coarse locks should be fine for now.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020048@implementation RTC_OBJC_TYPE (RTCAudioSession) {
Niels Möller072c0082021-02-15 16:30:44 +010049 webrtc::Mutex _mutex;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080050 AVAudioSession *_session;
Niels Möller7a669002022-06-27 09:47:02 +020051 std::atomic<int> _activationCount;
52 std::atomic<int> _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080053 BOOL _isActive;
tkchind2511962016-05-06 18:54:15 -070054 BOOL _useManualAudio;
55 BOOL _isAudioEnabled;
56 BOOL _canPlayOrRecord;
tkchin93dd6342016-07-27 10:17:14 -070057 BOOL _isInterrupted;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080058}
59
60@synthesize session = _session;
tkchine54467f2016-03-15 16:54:03 -070061@synthesize delegates = _delegates;
Joe Chen0c05b1a2019-05-07 10:46:22 -070062@synthesize ignoresPreferredAttributeConfigurationErrors =
63 _ignoresPreferredAttributeConfigurationErrors;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080064
65+ (instancetype)sharedInstance {
66 static dispatch_once_t onceToken;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020067 static RTC_OBJC_TYPE(RTCAudioSession) *sharedInstance = nil;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080068 dispatch_once(&onceToken, ^{
Tze Kwang Chin307a0922016-03-21 13:57:40 -070069 sharedInstance = [[self alloc] init];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080070 });
71 return sharedInstance;
72}
73
74- (instancetype)init {
Peter Hanspers47217362017-10-05 11:39:15 +020075 return [self initWithAudioSession:[AVAudioSession sharedInstance]];
76}
77
78/** This initializer provides a way for unit tests to inject a fake/mock audio session. */
79- (instancetype)initWithAudioSession:(id)audioSession {
Zeke Chinb3fb71c2016-02-18 15:44:07 -080080 if (self = [super init]) {
Peter Hanspers47217362017-10-05 11:39:15 +020081 _session = audioSession;
tkchin0ce3bf92016-03-12 16:52:04 -080082
Zeke Chinb3fb71c2016-02-18 15:44:07 -080083 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
84 [center addObserver:self
85 selector:@selector(handleInterruptionNotification:)
86 name:AVAudioSessionInterruptionNotification
87 object:nil];
88 [center addObserver:self
89 selector:@selector(handleRouteChangeNotification:)
90 name:AVAudioSessionRouteChangeNotification
91 object:nil];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080092 [center addObserver:self
93 selector:@selector(handleMediaServicesWereLost:)
94 name:AVAudioSessionMediaServicesWereLostNotification
95 object:nil];
96 [center addObserver:self
97 selector:@selector(handleMediaServicesWereReset:)
98 name:AVAudioSessionMediaServicesWereResetNotification
99 object:nil];
henrikaf1363fd2016-09-27 06:06:44 -0700100 // Posted on the main thread when the primary audio from other applications
101 // starts and stops. Foreground applications may use this notification as a
102 // hint to enable or disable audio that is secondary.
103 [center addObserver:self
104 selector:@selector(handleSilenceSecondaryAudioHintNotification:)
105 name:AVAudioSessionSilenceSecondaryAudioHintNotification
106 object:nil];
tkchin93dd6342016-07-27 10:17:14 -0700107 // Also track foreground event in order to deal with interruption ended situation.
108 [center addObserver:self
109 selector:@selector(handleApplicationDidBecomeActive:)
110 name:UIApplicationDidBecomeActiveNotification
111 object:nil];
jtteh13ae11a2017-05-25 17:52:20 -0700112 [_session addObserver:self
113 forKeyPath:kRTCAudioSessionOutputVolumeSelector
114 options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200115 context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
jtteh13ae11a2017-05-25 17:52:20 -0700116
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200117 RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): init.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800118 }
119 return self;
120}
121
122- (void)dealloc {
123 [[NSNotificationCenter defaultCenter] removeObserver:self];
Peter Hanspers47217362017-10-05 11:39:15 +0200124 [_session removeObserver:self
125 forKeyPath:kRTCAudioSessionOutputVolumeSelector
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200126 context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
127 RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): dealloc.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800128}
129
Zeke Chin1300caa2016-03-18 14:39:11 -0700130- (NSString *)description {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200131 NSString *format = @"RTC_OBJC_TYPE(RTCAudioSession): {\n"
132 " category: %@\n"
133 " categoryOptions: %ld\n"
134 " mode: %@\n"
135 " isActive: %d\n"
136 " sampleRate: %.2f\n"
137 " IOBufferDuration: %f\n"
138 " outputNumberOfChannels: %ld\n"
139 " inputNumberOfChannels: %ld\n"
140 " outputLatency: %f\n"
141 " inputLatency: %f\n"
142 " outputVolume: %f\n"
143 "}";
Zeke Chin1300caa2016-03-18 14:39:11 -0700144 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -0700145 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -0700146 self.isActive, self.sampleRate, self.IOBufferDuration,
147 self.outputNumberOfChannels, self.inputNumberOfChannels,
henrikac5aea652016-09-21 07:45:55 -0700148 self.outputLatency, self.inputLatency, self.outputVolume];
Zeke Chin1300caa2016-03-18 14:39:11 -0700149 return description;
150}
151
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800152- (void)setIsActive:(BOOL)isActive {
153 @synchronized(self) {
154 _isActive = isActive;
155 }
156}
157
158- (BOOL)isActive {
159 @synchronized(self) {
160 return _isActive;
161 }
162}
163
tkchind2511962016-05-06 18:54:15 -0700164- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800165 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700166 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800167 return;
168 }
tkchind2511962016-05-06 18:54:15 -0700169 _useManualAudio = useManualAudio;
170 }
171 [self updateCanPlayOrRecord];
172}
173
174- (BOOL)useManualAudio {
175 @synchronized(self) {
176 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800177 }
178}
179
tkchind2511962016-05-06 18:54:15 -0700180- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800181 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700182 if (_isAudioEnabled == isAudioEnabled) {
183 return;
184 }
185 _isAudioEnabled = isAudioEnabled;
186 }
187 [self updateCanPlayOrRecord];
188}
189
190- (BOOL)isAudioEnabled {
191 @synchronized(self) {
192 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800193 }
194}
195
Joe Chen0c05b1a2019-05-07 10:46:22 -0700196- (void)setIgnoresPreferredAttributeConfigurationErrors:
197 (BOOL)ignoresPreferredAttributeConfigurationErrors {
198 @synchronized(self) {
199 if (_ignoresPreferredAttributeConfigurationErrors ==
200 ignoresPreferredAttributeConfigurationErrors) {
201 return;
202 }
203 _ignoresPreferredAttributeConfigurationErrors = ignoresPreferredAttributeConfigurationErrors;
204 }
205}
206
207- (BOOL)ignoresPreferredAttributeConfigurationErrors {
208 @synchronized(self) {
209 return _ignoresPreferredAttributeConfigurationErrors;
210 }
211}
212
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700213// TODO(tkchin): Check for duplicates.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200214- (void)addDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700215 RTCLog(@"Adding delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700216 if (!delegate) {
217 return;
218 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800219 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700220 _delegates.push_back(delegate);
221 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800222 }
223}
224
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200225- (void)removeDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700226 RTCLog(@"Removing delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700227 if (!delegate) {
228 return;
229 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800230 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700231 _delegates.erase(std::remove(_delegates.begin(),
232 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700233 delegate),
234 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700235 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800236 }
237}
238
kthelgasonee8b8612017-03-31 04:50:27 -0700239#pragma clang diagnostic push
240#pragma clang diagnostic ignored "-Wthread-safety-analysis"
241
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800242- (void)lockForConfiguration {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900243 RTC_CHECK(!mutex_locked);
Niels Möller072c0082021-02-15 16:30:44 +0100244 _mutex.Lock();
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900245 mutex_locked = true;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800246}
247
248- (void)unlockForConfiguration {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900249 mutex_locked = false;
Niels Möller072c0082021-02-15 16:30:44 +0100250 _mutex.Unlock();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800251}
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
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800349- (BOOL)setActive:(BOOL)active
350 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900351 if (![self checkLock:outError]) {
352 return NO;
353 }
Niels Möller7a669002022-06-27 09:47:02 +0200354 int activationCount = _activationCount.load();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800355 if (!active && activationCount == 0) {
356 RTCLogWarning(@"Attempting to deactivate without prior activation.");
357 }
JT Tehc1f083d2018-04-25 09:19:35 -0700358 [self notifyWillSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800359 BOOL success = YES;
360 BOOL isActive = self.isActive;
361 // Keep a local error so we can log it.
362 NSError *error = nil;
363 BOOL shouldSetActive =
364 (active && !isActive) || (!active && isActive && activationCount == 1);
365 // Attempt to activate if we're not active.
366 // Attempt to deactivate if we're active and it's the last unbalanced call.
367 if (shouldSetActive) {
368 AVAudioSession *session = self.session;
369 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
370 // that other audio sessions that were interrupted by our session can return
371 // to their active state. It is recommended for VoIP apps to use this
372 // option.
373 AVAudioSessionSetActiveOptions options =
374 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
375 success = [session setActive:active
376 withOptions:options
377 error:&error];
378 if (outError) {
379 *outError = error;
380 }
381 }
382 if (success) {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800383 if (active) {
Abby Yeh31357722021-03-09 01:55:31 +0800384 if (shouldSetActive) {
385 self.isActive = active;
386 if (self.isInterrupted) {
387 self.isInterrupted = NO;
388 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
389 }
390 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800391 [self incrementActivationCount];
Abby Yeh31357722021-03-09 01:55:31 +0800392 [self notifyDidSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800393 }
394 } else {
tkchin9f987d32016-03-12 20:06:28 -0800395 RTCLogError(@"Failed to setActive:%d. Error: %@",
396 active, error.localizedDescription);
JT Tehc1f083d2018-04-25 09:19:35 -0700397 [self notifyFailedToSetActive:active error:error];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800398 }
Abby Yeh31357722021-03-09 01:55:31 +0800399 // Set isActive and decrement activation count on deactivation
400 // whether or not it succeeded.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800401 if (!active) {
Abby Yeh31357722021-03-09 01:55:31 +0800402 self.isActive = active;
403 [self notifyDidSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800404 [self decrementActivationCount];
405 }
Niels Möller7a669002022-06-27 09:47:02 +0200406 RTCLog(@"Number of current activations: %d", _activationCount.load());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800407 return success;
408}
409
410- (BOOL)setCategory:(NSString *)category
411 withOptions:(AVAudioSessionCategoryOptions)options
412 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900413 if (![self checkLock:outError]) {
414 return NO;
415 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800416 return [self.session setCategory:category withOptions:options error:outError];
417}
418
419- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900420 if (![self checkLock:outError]) {
421 return NO;
422 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800423 return [self.session setMode:mode error:outError];
424}
425
426- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900427 if (![self checkLock:outError]) {
428 return NO;
429 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800430 return [self.session setInputGain:gain error:outError];
431}
432
433- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900434 if (![self checkLock:outError]) {
435 return NO;
436 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800437 return [self.session setPreferredSampleRate:sampleRate error:outError];
438}
439
440- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
441 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900442 if (![self checkLock:outError]) {
443 return NO;
444 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800445 return [self.session setPreferredIOBufferDuration:duration error:outError];
446}
447
448- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
449 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900450 if (![self checkLock:outError]) {
451 return NO;
452 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800453 return [self.session setPreferredInputNumberOfChannels:count error:outError];
454}
455- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
456 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900457 if (![self checkLock:outError]) {
458 return NO;
459 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800460 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
461}
462
463- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
464 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900465 if (![self checkLock:outError]) {
466 return NO;
467 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800468 return [self.session overrideOutputAudioPort:portOverride error:outError];
469}
470
471- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
472 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900473 if (![self checkLock:outError]) {
474 return NO;
475 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800476 return [self.session setPreferredInput:inPort error:outError];
477}
478
479- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
480 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900481 if (![self checkLock:outError]) {
482 return NO;
483 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800484 return [self.session setInputDataSource:dataSource error:outError];
485}
486
487- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
488 error:(NSError **)outError {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900489 if (![self checkLock:outError]) {
490 return NO;
491 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800492 return [self.session setOutputDataSource:dataSource error:outError];
493}
494
495#pragma mark - Notifications
496
497- (void)handleInterruptionNotification:(NSNotification *)notification {
498 NSNumber* typeNumber =
499 notification.userInfo[AVAudioSessionInterruptionTypeKey];
500 AVAudioSessionInterruptionType type =
501 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
502 switch (type) {
503 case AVAudioSessionInterruptionTypeBegan:
504 RTCLog(@"Audio session interruption began.");
505 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700506 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800507 [self notifyDidBeginInterruption];
508 break;
509 case AVAudioSessionInterruptionTypeEnded: {
510 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700511 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800512 [self updateAudioSessionAfterEvent];
513 NSNumber *optionsNumber =
514 notification.userInfo[AVAudioSessionInterruptionOptionKey];
515 AVAudioSessionInterruptionOptions options =
516 optionsNumber.unsignedIntegerValue;
517 BOOL shouldResume =
518 options & AVAudioSessionInterruptionOptionShouldResume;
519 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
520 break;
521 }
522 }
523}
524
525- (void)handleRouteChangeNotification:(NSNotification *)notification {
526 // Get reason for current route change.
527 NSNumber* reasonNumber =
528 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
529 AVAudioSessionRouteChangeReason reason =
530 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
531 RTCLog(@"Audio route changed:");
532 switch (reason) {
533 case AVAudioSessionRouteChangeReasonUnknown:
534 RTCLog(@"Audio route changed: ReasonUnknown");
535 break;
536 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
537 RTCLog(@"Audio route changed: NewDeviceAvailable");
538 break;
539 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
540 RTCLog(@"Audio route changed: OldDeviceUnavailable");
541 break;
542 case AVAudioSessionRouteChangeReasonCategoryChange:
543 RTCLog(@"Audio route changed: CategoryChange to :%@",
544 self.session.category);
545 break;
546 case AVAudioSessionRouteChangeReasonOverride:
547 RTCLog(@"Audio route changed: Override");
548 break;
549 case AVAudioSessionRouteChangeReasonWakeFromSleep:
550 RTCLog(@"Audio route changed: WakeFromSleep");
551 break;
552 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
553 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
554 break;
555 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
556 RTCLog(@"Audio route changed: RouteConfigurationChange");
557 break;
558 }
559 AVAudioSessionRouteDescription* previousRoute =
560 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
561 // Log previous route configuration.
562 RTCLog(@"Previous route: %@\nCurrent route:%@",
563 previousRoute, self.session.currentRoute);
564 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
565}
566
567- (void)handleMediaServicesWereLost:(NSNotification *)notification {
568 RTCLog(@"Media services were lost.");
569 [self updateAudioSessionAfterEvent];
570 [self notifyMediaServicesWereLost];
571}
572
573- (void)handleMediaServicesWereReset:(NSNotification *)notification {
574 RTCLog(@"Media services were reset.");
575 [self updateAudioSessionAfterEvent];
576 [self notifyMediaServicesWereReset];
577}
578
henrikaf1363fd2016-09-27 06:06:44 -0700579- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
580 // TODO(henrika): just adding logs here for now until we know if we are ever
581 // see this notification and might be affected by it or if further actions
582 // are required.
583 NSNumber *typeNumber =
584 notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
585 AVAudioSessionSilenceSecondaryAudioHintType type =
586 (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
587 switch (type) {
588 case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
589 RTCLog(@"Another application's primary audio has started.");
590 break;
591 case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
592 RTCLog(@"Another application's primary audio has stopped.");
593 break;
594 }
595}
596
tkchin93dd6342016-07-27 10:17:14 -0700597- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
Zeke Chin8280a562018-07-10 13:53:55 -0700598 BOOL isInterrupted = self.isInterrupted;
haysc7735b1e2017-03-29 14:53:32 -0700599 RTCLog(@"Application became active after an interruption. Treating as interruption "
Zeke Chin8280a562018-07-10 13:53:55 -0700600 "end. isInterrupted changed from %d to 0.",
601 isInterrupted);
602 if (isInterrupted) {
tkchin93dd6342016-07-27 10:17:14 -0700603 self.isInterrupted = NO;
604 [self updateAudioSessionAfterEvent];
tkchin93dd6342016-07-27 10:17:14 -0700605 }
haysc7735b1e2017-03-29 14:53:32 -0700606 // Always treat application becoming active as an interruption end event.
607 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
tkchin93dd6342016-07-27 10:17:14 -0700608}
609
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800610#pragma mark - Private
611
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900612+ (NSError *)lockError {
613 NSDictionary *userInfo =
614 @{NSLocalizedDescriptionKey : @"Must call lockForConfiguration before calling this method."};
615 NSError *error = [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
616 code:kRTCAudioSessionErrorLockRequired
617 userInfo:userInfo];
618 return error;
619}
620
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200621- (std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800622 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700623 // Note: this returns a copy.
624 return _delegates;
625 }
626}
627
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700628// TODO(tkchin): check for duplicates.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200629- (void)pushDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700630 @synchronized(self) {
631 _delegates.insert(_delegates.begin(), delegate);
632 }
633}
634
635- (void)removeZeroedDelegates {
636 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700637 _delegates.erase(
638 std::remove_if(_delegates.begin(),
639 _delegates.end(),
640 [](id delegate) -> bool { return delegate == nil; }),
641 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800642 }
643}
644
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700645- (int)activationCount {
Niels Möller7a669002022-06-27 09:47:02 +0200646 return _activationCount.load();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800647}
648
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700649- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800650 RTCLog(@"Incrementing activation count.");
Niels Möller7a669002022-06-27 09:47:02 +0200651 return _activationCount.fetch_add(1) + 1;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800652}
653
654- (NSInteger)decrementActivationCount {
655 RTCLog(@"Decrementing activation count.");
Niels Möller7a669002022-06-27 09:47:02 +0200656 return _activationCount.fetch_sub(1) - 1;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700657}
658
659- (int)webRTCSessionCount {
Niels Möller7a669002022-06-27 09:47:02 +0200660 return _webRTCSessionCount.load();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800661}
662
tkchind2511962016-05-06 18:54:15 -0700663- (BOOL)canPlayOrRecord {
664 return !self.useManualAudio || self.isAudioEnabled;
665}
666
tkchin93dd6342016-07-27 10:17:14 -0700667- (BOOL)isInterrupted {
668 @synchronized(self) {
669 return _isInterrupted;
670 }
671}
672
673- (void)setIsInterrupted:(BOOL)isInterrupted {
674 @synchronized(self) {
675 if (_isInterrupted == isInterrupted) {
676 return;
677 }
678 _isInterrupted = isInterrupted;
679 }
680}
681
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900682- (BOOL)checkLock:(NSError **)outError {
683 if (!mutex_locked) {
684 if (outError) {
685 *outError = [RTC_OBJC_TYPE(RTCAudioSession) lockError];
686 }
687 return NO;
688 }
689 return YES;
690}
691
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700692- (BOOL)beginWebRTCSession:(NSError **)outError {
693 if (outError) {
694 *outError = nil;
695 }
Niels Möller7a669002022-06-27 09:47:02 +0200696 _webRTCSessionCount.fetch_add(1);
tkchind2511962016-05-06 18:54:15 -0700697 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700698 return YES;
699}
700
701- (BOOL)endWebRTCSession:(NSError **)outError {
702 if (outError) {
703 *outError = nil;
704 }
Niels Möller7a669002022-06-27 09:47:02 +0200705 _webRTCSessionCount.fetch_sub(1);
tkchind2511962016-05-06 18:54:15 -0700706 [self notifyDidStopPlayOrRecord];
707 return YES;
708}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700709
tkchind2511962016-05-06 18:54:15 -0700710- (BOOL)configureWebRTCSession:(NSError **)outError {
711 if (outError) {
712 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700713 }
tkchind2511962016-05-06 18:54:15 -0700714 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700715
tkchind2511962016-05-06 18:54:15 -0700716 // Configure the AVAudioSession and activate it.
717 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700718 NSError *error = nil;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200719 RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *webRTCConfig =
720 [RTC_OBJC_TYPE(RTCAudioSessionConfiguration) webRTCConfiguration];
tkchind2511962016-05-06 18:54:15 -0700721 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
722 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700723 error.localizedDescription);
jttehf84c1d62017-04-21 13:56:39 -0700724 // Do not call setActive:NO if setActive:YES failed.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700725 if (outError) {
726 *outError = error;
727 }
728 return NO;
729 }
730
tkchind2511962016-05-06 18:54:15 -0700731 // Ensure that the device currently supports audio input.
732 // TODO(tkchin): Figure out if this is really necessary.
733 if (!self.inputAvailable) {
734 RTCLogError(@"No audio input path is available!");
735 [self unconfigureWebRTCSession:nil];
736 if (outError) {
737 *outError = [self configurationErrorWithDescription:@"No input path."];
738 }
739 return NO;
740 }
741
henrika2d014be2016-06-16 14:26:55 +0200742 // It can happen (e.g. in combination with BT devices) that the attempt to set
743 // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
744 // configuration attempt using the sample rate that worked using the active
745 // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
746 // combination with BT headsets. Using this "trick" seems to avoid a state
747 // where Core Audio asks for a different number of audio frames than what the
748 // session's I/O buffer duration corresponds to.
749 // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
750 // tested on a limited set of iOS devices and BT devices.
751 double sessionSampleRate = self.sampleRate;
752 double preferredSampleRate = webRTCConfig.sampleRate;
753 if (sessionSampleRate != preferredSampleRate) {
754 RTCLogWarning(
755 @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
756 sessionSampleRate, preferredSampleRate);
757 if (![self setPreferredSampleRate:sessionSampleRate
758 error:&error]) {
759 RTCLogError(@"Failed to set preferred sample rate: %@",
760 error.localizedDescription);
761 if (outError) {
762 *outError = error;
763 }
764 }
765 }
766
tkchind2511962016-05-06 18:54:15 -0700767 return YES;
768}
769
770- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
771 if (outError) {
772 *outError = nil;
773 }
tkchind2511962016-05-06 18:54:15 -0700774 RTCLog(@"Unconfiguring audio session for WebRTC.");
775 [self setActive:NO error:outError];
776
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700777 return YES;
778}
779
780- (NSError *)configurationErrorWithDescription:(NSString *)description {
781 NSDictionary* userInfo = @{
782 NSLocalizedDescriptionKey: description,
783 };
784 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
785 code:kRTCAudioSessionErrorConfiguration
786 userInfo:userInfo];
787}
788
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800789- (void)updateAudioSessionAfterEvent {
790 BOOL shouldActivate = self.activationCount > 0;
791 AVAudioSessionSetActiveOptions options = shouldActivate ?
792 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
793 NSError *error = nil;
794 if ([self.session setActive:shouldActivate
795 withOptions:options
796 error:&error]) {
797 self.isActive = shouldActivate;
798 } else {
799 RTCLogError(@"Failed to set session active to %d. Error:%@",
800 shouldActivate, error.localizedDescription);
801 }
802}
803
tkchind2511962016-05-06 18:54:15 -0700804- (void)updateCanPlayOrRecord {
805 BOOL canPlayOrRecord = NO;
806 BOOL shouldNotify = NO;
807 @synchronized(self) {
808 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
809 if (_canPlayOrRecord == canPlayOrRecord) {
810 return;
811 }
812 _canPlayOrRecord = canPlayOrRecord;
813 shouldNotify = YES;
814 }
815 if (shouldNotify) {
816 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
817 }
818}
819
jtteh3c9a6c02017-04-18 09:09:35 -0700820- (void)audioSessionDidActivate:(AVAudioSession *)session {
821 if (_session != session) {
822 RTCLogError(@"audioSessionDidActivate called on different AVAudioSession");
823 }
Zeke Chin8280a562018-07-10 13:53:55 -0700824 RTCLog(@"Audio session was externally activated.");
jtteh3c9a6c02017-04-18 09:09:35 -0700825 [self incrementActivationCount];
826 self.isActive = YES;
Zeke Chin8280a562018-07-10 13:53:55 -0700827 // When a CallKit call begins, it's possible that we receive an interruption
828 // begin without a corresponding end. Since we know that we have an activated
829 // audio session at this point, just clear any saved interruption flag since
830 // the app may never be foregrounded during the duration of the call.
831 if (self.isInterrupted) {
832 RTCLog(@"Clearing interrupted state due to external activation.");
833 self.isInterrupted = NO;
834 }
835 // Treat external audio session activation as an end interruption event.
836 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
jtteh3c9a6c02017-04-18 09:09:35 -0700837}
838
839- (void)audioSessionDidDeactivate:(AVAudioSession *)session {
840 if (_session != session) {
841 RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession");
842 }
Zeke Chin8280a562018-07-10 13:53:55 -0700843 RTCLog(@"Audio session was externally deactivated.");
jtteh3c9a6c02017-04-18 09:09:35 -0700844 self.isActive = NO;
845 [self decrementActivationCount];
846}
847
jtteh13ae11a2017-05-25 17:52:20 -0700848- (void)observeValueForKeyPath:(NSString *)keyPath
849 ofObject:(id)object
850 change:(NSDictionary *)change
851 context:(void *)context {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200852 if (context == (__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class) {
Peter Hanspers47217362017-10-05 11:39:15 +0200853 if (object == _session) {
854 NSNumber *newVolume = change[NSKeyValueChangeNewKey];
855 RTCLog(@"OutputVolumeDidChange to %f", newVolume.floatValue);
856 [self notifyDidChangeOutputVolume:newVolume.floatValue];
857 }
jtteh13ae11a2017-05-25 17:52:20 -0700858 } else {
859 [super observeValueForKeyPath:keyPath
860 ofObject:object
861 change:change
862 context:context];
863 }
864}
865
Peter Hansperse5b4e942021-08-26 17:56:44 +0200866- (void)notifyAudioUnitStartFailedWithError:(OSStatus)error {
867 for (auto delegate : self.delegates) {
868 SEL sel = @selector(audioSession:audioUnitStartFailedWithError:);
869 if ([delegate respondsToSelector:sel]) {
870 [delegate audioSession:self
871 audioUnitStartFailedWithError:[NSError errorWithDomain:kRTCAudioSessionErrorDomain
872 code:error
873 userInfo:nil]];
874 }
875 }
876}
877
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800878- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700879 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700880 SEL sel = @selector(audioSessionDidBeginInterruption:);
881 if ([delegate respondsToSelector:sel]) {
882 [delegate audioSessionDidBeginInterruption:self];
883 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800884 }
885}
886
887- (void)notifyDidEndInterruptionWithShouldResumeSession:
888 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700889 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700890 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
891 if ([delegate respondsToSelector:sel]) {
892 [delegate audioSessionDidEndInterruption:self
893 shouldResumeSession:shouldResumeSession];
894 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800895 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800896}
897
898- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
899 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700900 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700901 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
902 if ([delegate respondsToSelector:sel]) {
903 [delegate audioSessionDidChangeRoute:self
904 reason:reason
905 previousRoute:previousRoute];
906 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800907 }
908}
909
910- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700911 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800912 SEL sel = @selector(audioSessionMediaServerTerminated:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700913 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800914 [delegate audioSessionMediaServerTerminated:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700915 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800916 }
917}
918
919- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700920 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800921 SEL sel = @selector(audioSessionMediaServerReset:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700922 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800923 [delegate audioSessionMediaServerReset:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700924 }
925 }
926}
927
tkchind2511962016-05-06 18:54:15 -0700928- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700929 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700930 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700931 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700932 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700933 }
934 }
935}
936
tkchind2511962016-05-06 18:54:15 -0700937- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700938 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700939 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700940 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700941 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700942 }
943 }
944}
945
tkchind2511962016-05-06 18:54:15 -0700946- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700947 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700948 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700949 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700950 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700951 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800952 }
953}
954
jtteh13ae11a2017-05-25 17:52:20 -0700955- (void)notifyDidChangeOutputVolume:(float)volume {
956 for (auto delegate : self.delegates) {
957 SEL sel = @selector(audioSession:didChangeOutputVolume:);
958 if ([delegate respondsToSelector:sel]) {
959 [delegate audioSession:self didChangeOutputVolume:volume];
960 }
961 }
962}
963
Anders Carlsson121ea322017-06-26 15:34:47 +0200964- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches {
965 for (auto delegate : self.delegates) {
966 SEL sel = @selector(audioSession:didDetectPlayoutGlitch:);
967 if ([delegate respondsToSelector:sel]) {
968 [delegate audioSession:self didDetectPlayoutGlitch:totalNumberOfGlitches];
969 }
970 }
971}
972
JT Tehc1f083d2018-04-25 09:19:35 -0700973- (void)notifyWillSetActive:(BOOL)active {
974 for (id delegate : self.delegates) {
975 SEL sel = @selector(audioSession:willSetActive:);
976 if ([delegate respondsToSelector:sel]) {
977 [delegate audioSession:self willSetActive:active];
978 }
979 }
980}
981
982- (void)notifyDidSetActive:(BOOL)active {
983 for (id delegate : self.delegates) {
984 SEL sel = @selector(audioSession:didSetActive:);
985 if ([delegate respondsToSelector:sel]) {
986 [delegate audioSession:self didSetActive:active];
987 }
988 }
989}
990
991- (void)notifyFailedToSetActive:(BOOL)active error:(NSError *)error {
992 for (id delegate : self.delegates) {
993 SEL sel = @selector(audioSession:failedToSetActive:error:);
994 if ([delegate respondsToSelector:sel]) {
995 [delegate audioSession:self failedToSetActive:active error:error];
996 }
997 }
998}
999
Zeke Chinb3fb71c2016-02-18 15:44:07 -08001000@end