blob: c6e3677b8461751b7489dea445ff88cc27707356 [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
11#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h"
12
Tze Kwang Chin307a0922016-03-21 13:57:40 -070013#include "webrtc/base/atomicops.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080014#include "webrtc/base/checks.h"
tkchin0ce3bf92016-03-12 16:52:04 -080015#include "webrtc/base/criticalsection.h"
tkchine54467f2016-03-15 16:54:03 -070016#include "webrtc/modules/audio_device/ios/audio_device_ios.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080017
18#import "webrtc/base/objc/RTCLogging.h"
19#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h"
20
21NSString * const kRTCAudioSessionErrorDomain = @"org.webrtc.RTCAudioSession";
22NSInteger const kRTCAudioSessionErrorLockRequired = -1;
tkchin9f987d32016-03-12 20:06:28 -080023NSInteger const kRTCAudioSessionErrorConfiguration = -2;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080024
25// This class needs to be thread-safe because it is accessed from many threads.
26// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
27// lock contention so coarse locks should be fine for now.
28@implementation RTCAudioSession {
tkchin0ce3bf92016-03-12 16:52:04 -080029 rtc::CriticalSection _crit;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080030 AVAudioSession *_session;
Tze Kwang Chin307a0922016-03-21 13:57:40 -070031 volatile int _activationCount;
32 volatile int _lockRecursionCount;
33 volatile int _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080034 BOOL _isActive;
tkchin9f987d32016-03-12 20:06:28 -080035 BOOL _shouldDelayAudioConfiguration;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080036}
37
38@synthesize session = _session;
tkchine54467f2016-03-15 16:54:03 -070039@synthesize delegates = _delegates;
Tze Kwang Chin307a0922016-03-21 13:57:40 -070040@synthesize savedConfiguration = _savedConfiguration;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080041
42+ (instancetype)sharedInstance {
43 static dispatch_once_t onceToken;
44 static RTCAudioSession *sharedInstance = nil;
45 dispatch_once(&onceToken, ^{
Tze Kwang Chin307a0922016-03-21 13:57:40 -070046 sharedInstance = [[self alloc] init];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080047 });
48 return sharedInstance;
49}
50
51- (instancetype)init {
52 if (self = [super init]) {
53 _session = [AVAudioSession sharedInstance];
tkchin0ce3bf92016-03-12 16:52:04 -080054
Zeke Chinb3fb71c2016-02-18 15:44:07 -080055 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
56 [center addObserver:self
57 selector:@selector(handleInterruptionNotification:)
58 name:AVAudioSessionInterruptionNotification
59 object:nil];
60 [center addObserver:self
61 selector:@selector(handleRouteChangeNotification:)
62 name:AVAudioSessionRouteChangeNotification
63 object:nil];
64 // TODO(tkchin): Maybe listen to SilenceSecondaryAudioHintNotification.
65 [center addObserver:self
66 selector:@selector(handleMediaServicesWereLost:)
67 name:AVAudioSessionMediaServicesWereLostNotification
68 object:nil];
69 [center addObserver:self
70 selector:@selector(handleMediaServicesWereReset:)
71 name:AVAudioSessionMediaServicesWereResetNotification
72 object:nil];
73 }
74 return self;
75}
76
77- (void)dealloc {
78 [[NSNotificationCenter defaultCenter] removeObserver:self];
79}
80
Zeke Chin1300caa2016-03-18 14:39:11 -070081- (NSString *)description {
82 NSString *format =
83 @"RTCAudioSession: {\n"
84 " isActive: %d\n"
85 " sampleRate: %.2f\n"
86 " IOBufferDuration: %f\n"
87 " outputNumberOfChannels: %ld\n"
88 " inputNumberOfChannels: %ld\n"
89 " outputLatency: %f\n"
90 " inputLatency: %f\n"
91 "}";
92 NSString *description = [NSString stringWithFormat:format,
93 self.isActive, self.sampleRate, self.IOBufferDuration,
94 self.outputNumberOfChannels, self.inputNumberOfChannels,
95 self.outputLatency, self.inputLatency];
96 return description;
97}
98
Zeke Chinb3fb71c2016-02-18 15:44:07 -080099- (void)setIsActive:(BOOL)isActive {
100 @synchronized(self) {
101 _isActive = isActive;
102 }
103}
104
105- (BOOL)isActive {
106 @synchronized(self) {
107 return _isActive;
108 }
109}
110
111- (BOOL)isLocked {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700112 return _lockRecursionCount > 0;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800113}
114
tkchin9f987d32016-03-12 20:06:28 -0800115- (void)setShouldDelayAudioConfiguration:(BOOL)shouldDelayAudioConfiguration {
116 @synchronized(self) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700117 // No one should be changing this while an audio device is active.
118 RTC_DCHECK(!self.isConfiguredForWebRTC);
tkchin9f987d32016-03-12 20:06:28 -0800119 if (_shouldDelayAudioConfiguration == shouldDelayAudioConfiguration) {
120 return;
121 }
122 _shouldDelayAudioConfiguration = shouldDelayAudioConfiguration;
123 }
124}
125
126- (BOOL)shouldDelayAudioConfiguration {
127 @synchronized(self) {
128 return _shouldDelayAudioConfiguration;
129 }
130}
131
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700132// TODO(tkchin): Check for duplicates.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800133- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700134 if (!delegate) {
135 return;
136 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800137 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700138 _delegates.push_back(delegate);
139 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800140 }
141}
142
143- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700144 if (!delegate) {
145 return;
146 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800147 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700148 _delegates.erase(std::remove(_delegates.begin(),
149 _delegates.end(),
150 delegate));
151 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800152 }
153}
154
155- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800156 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700157 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800158}
159
160- (void)unlockForConfiguration {
161 // Don't let threads other than the one that called lockForConfiguration
162 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800163 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700164 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800165 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800166 // was called without anyone calling lock, we will hit an assertion.
167 _crit.Leave();
168 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800169 }
170}
171
172#pragma mark - AVAudioSession proxy methods
173
174- (NSString *)category {
175 return self.session.category;
176}
177
178- (AVAudioSessionCategoryOptions)categoryOptions {
179 return self.session.categoryOptions;
180}
181
182- (NSString *)mode {
183 return self.session.mode;
184}
185
186- (BOOL)secondaryAudioShouldBeSilencedHint {
187 return self.session.secondaryAudioShouldBeSilencedHint;
188}
189
190- (AVAudioSessionRouteDescription *)currentRoute {
191 return self.session.currentRoute;
192}
193
194- (NSInteger)maximumInputNumberOfChannels {
195 return self.session.maximumInputNumberOfChannels;
196}
197
198- (NSInteger)maximumOutputNumberOfChannels {
199 return self.session.maximumOutputNumberOfChannels;
200}
201
202- (float)inputGain {
203 return self.session.inputGain;
204}
205
206- (BOOL)inputGainSettable {
207 return self.session.inputGainSettable;
208}
209
210- (BOOL)inputAvailable {
211 return self.session.inputAvailable;
212}
213
214- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
215 return self.session.inputDataSources;
216}
217
218- (AVAudioSessionDataSourceDescription *)inputDataSource {
219 return self.session.inputDataSource;
220}
221
222- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
223 return self.session.outputDataSources;
224}
225
226- (AVAudioSessionDataSourceDescription *)outputDataSource {
227 return self.session.outputDataSource;
228}
229
230- (double)sampleRate {
231 return self.session.sampleRate;
232}
233
234- (NSInteger)inputNumberOfChannels {
235 return self.session.inputNumberOfChannels;
236}
237
238- (NSInteger)outputNumberOfChannels {
239 return self.session.outputNumberOfChannels;
240}
241
242- (float)outputVolume {
243 return self.session.outputVolume;
244}
245
246- (NSTimeInterval)inputLatency {
247 return self.session.inputLatency;
248}
249
250- (NSTimeInterval)outputLatency {
251 return self.session.outputLatency;
252}
253
254- (NSTimeInterval)IOBufferDuration {
255 return self.session.IOBufferDuration;
256}
257
tkchine54467f2016-03-15 16:54:03 -0700258// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
259// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800260- (BOOL)setActive:(BOOL)active
261 error:(NSError **)outError {
262 if (![self checkLock:outError]) {
263 return NO;
264 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700265 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800266 if (!active && activationCount == 0) {
267 RTCLogWarning(@"Attempting to deactivate without prior activation.");
268 }
269 BOOL success = YES;
270 BOOL isActive = self.isActive;
271 // Keep a local error so we can log it.
272 NSError *error = nil;
273 BOOL shouldSetActive =
274 (active && !isActive) || (!active && isActive && activationCount == 1);
275 // Attempt to activate if we're not active.
276 // Attempt to deactivate if we're active and it's the last unbalanced call.
277 if (shouldSetActive) {
278 AVAudioSession *session = self.session;
279 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
280 // that other audio sessions that were interrupted by our session can return
281 // to their active state. It is recommended for VoIP apps to use this
282 // option.
283 AVAudioSessionSetActiveOptions options =
284 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
285 success = [session setActive:active
286 withOptions:options
287 error:&error];
288 if (outError) {
289 *outError = error;
290 }
291 }
292 if (success) {
293 if (shouldSetActive) {
294 self.isActive = active;
295 }
296 if (active) {
297 [self incrementActivationCount];
298 }
299 } else {
tkchin9f987d32016-03-12 20:06:28 -0800300 RTCLogError(@"Failed to setActive:%d. Error: %@",
301 active, error.localizedDescription);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800302 }
303 // Decrement activation count on deactivation whether or not it succeeded.
304 if (!active) {
305 [self decrementActivationCount];
306 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700307 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800308 return success;
309}
310
311- (BOOL)setCategory:(NSString *)category
312 withOptions:(AVAudioSessionCategoryOptions)options
313 error:(NSError **)outError {
314 if (![self checkLock:outError]) {
315 return NO;
316 }
317 return [self.session setCategory:category withOptions:options error:outError];
318}
319
320- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
321 if (![self checkLock:outError]) {
322 return NO;
323 }
324 return [self.session setMode:mode error:outError];
325}
326
327- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
328 if (![self checkLock:outError]) {
329 return NO;
330 }
331 return [self.session setInputGain:gain error:outError];
332}
333
334- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
335 if (![self checkLock:outError]) {
336 return NO;
337 }
338 return [self.session setPreferredSampleRate:sampleRate error:outError];
339}
340
341- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
342 error:(NSError **)outError {
343 if (![self checkLock:outError]) {
344 return NO;
345 }
346 return [self.session setPreferredIOBufferDuration:duration error:outError];
347}
348
349- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
350 error:(NSError **)outError {
351 if (![self checkLock:outError]) {
352 return NO;
353 }
354 return [self.session setPreferredInputNumberOfChannels:count error:outError];
355}
356- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
357 error:(NSError **)outError {
358 if (![self checkLock:outError]) {
359 return NO;
360 }
361 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
362}
363
364- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
365 error:(NSError **)outError {
366 if (![self checkLock:outError]) {
367 return NO;
368 }
369 return [self.session overrideOutputAudioPort:portOverride error:outError];
370}
371
372- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
373 error:(NSError **)outError {
374 if (![self checkLock:outError]) {
375 return NO;
376 }
377 return [self.session setPreferredInput:inPort error:outError];
378}
379
380- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
381 error:(NSError **)outError {
382 if (![self checkLock:outError]) {
383 return NO;
384 }
385 return [self.session setInputDataSource:dataSource error:outError];
386}
387
388- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
389 error:(NSError **)outError {
390 if (![self checkLock:outError]) {
391 return NO;
392 }
393 return [self.session setOutputDataSource:dataSource error:outError];
394}
395
396#pragma mark - Notifications
397
398- (void)handleInterruptionNotification:(NSNotification *)notification {
399 NSNumber* typeNumber =
400 notification.userInfo[AVAudioSessionInterruptionTypeKey];
401 AVAudioSessionInterruptionType type =
402 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
403 switch (type) {
404 case AVAudioSessionInterruptionTypeBegan:
405 RTCLog(@"Audio session interruption began.");
406 self.isActive = NO;
407 [self notifyDidBeginInterruption];
408 break;
409 case AVAudioSessionInterruptionTypeEnded: {
410 RTCLog(@"Audio session interruption ended.");
411 [self updateAudioSessionAfterEvent];
412 NSNumber *optionsNumber =
413 notification.userInfo[AVAudioSessionInterruptionOptionKey];
414 AVAudioSessionInterruptionOptions options =
415 optionsNumber.unsignedIntegerValue;
416 BOOL shouldResume =
417 options & AVAudioSessionInterruptionOptionShouldResume;
418 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
419 break;
420 }
421 }
422}
423
424- (void)handleRouteChangeNotification:(NSNotification *)notification {
425 // Get reason for current route change.
426 NSNumber* reasonNumber =
427 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
428 AVAudioSessionRouteChangeReason reason =
429 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
430 RTCLog(@"Audio route changed:");
431 switch (reason) {
432 case AVAudioSessionRouteChangeReasonUnknown:
433 RTCLog(@"Audio route changed: ReasonUnknown");
434 break;
435 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
436 RTCLog(@"Audio route changed: NewDeviceAvailable");
437 break;
438 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
439 RTCLog(@"Audio route changed: OldDeviceUnavailable");
440 break;
441 case AVAudioSessionRouteChangeReasonCategoryChange:
442 RTCLog(@"Audio route changed: CategoryChange to :%@",
443 self.session.category);
444 break;
445 case AVAudioSessionRouteChangeReasonOverride:
446 RTCLog(@"Audio route changed: Override");
447 break;
448 case AVAudioSessionRouteChangeReasonWakeFromSleep:
449 RTCLog(@"Audio route changed: WakeFromSleep");
450 break;
451 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
452 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
453 break;
454 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
455 RTCLog(@"Audio route changed: RouteConfigurationChange");
456 break;
457 }
458 AVAudioSessionRouteDescription* previousRoute =
459 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
460 // Log previous route configuration.
461 RTCLog(@"Previous route: %@\nCurrent route:%@",
462 previousRoute, self.session.currentRoute);
463 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
464}
465
466- (void)handleMediaServicesWereLost:(NSNotification *)notification {
467 RTCLog(@"Media services were lost.");
468 [self updateAudioSessionAfterEvent];
469 [self notifyMediaServicesWereLost];
470}
471
472- (void)handleMediaServicesWereReset:(NSNotification *)notification {
473 RTCLog(@"Media services were reset.");
474 [self updateAudioSessionAfterEvent];
475 [self notifyMediaServicesWereReset];
476}
477
478#pragma mark - Private
479
480+ (NSError *)lockError {
481 NSDictionary *userInfo = @{
482 NSLocalizedDescriptionKey:
483 @"Must call lockForConfiguration before calling this method."
484 };
485 NSError *error =
486 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
487 code:kRTCAudioSessionErrorLockRequired
488 userInfo:userInfo];
489 return error;
490}
491
tkchine54467f2016-03-15 16:54:03 -0700492- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800493 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700494 // Note: this returns a copy.
495 return _delegates;
496 }
497}
498
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700499- (void)setSavedConfiguration:(RTCAudioSessionConfiguration *)configuration {
500 @synchronized(self) {
501 if (_savedConfiguration == configuration) {
502 return;
503 }
504 _savedConfiguration = configuration;
505 }
506}
507
508- (RTCAudioSessionConfiguration *)savedConfiguration {
509 @synchronized(self) {
510 return _savedConfiguration;
511 }
512}
513
514// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700515- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
516 @synchronized(self) {
517 _delegates.insert(_delegates.begin(), delegate);
518 }
519}
520
521- (void)removeZeroedDelegates {
522 @synchronized(self) {
523 for (auto it = _delegates.begin(); it != _delegates.end(); ++it) {
524 if (!*it) {
525 _delegates.erase(it);
526 }
527 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800528 }
529}
530
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700531- (int)activationCount {
532 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800533}
534
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700535- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800536 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700537 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800538}
539
540- (NSInteger)decrementActivationCount {
541 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700542 return rtc::AtomicOps::Decrement(&_activationCount);
543}
544
545- (int)webRTCSessionCount {
546 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800547}
548
tkchin9f987d32016-03-12 20:06:28 -0800549- (BOOL)checkLock:(NSError **)outError {
550 // Check ivar instead of trying to acquire lock so that we won't accidentally
551 // acquire lock if it hasn't already been called.
552 if (!self.isLocked) {
553 if (outError) {
554 *outError = [RTCAudioSession lockError];
555 }
556 return NO;
557 }
558 return YES;
559}
560
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700561- (BOOL)beginWebRTCSession:(NSError **)outError {
562 if (outError) {
563 *outError = nil;
564 }
565 if (![self checkLock:outError]) {
566 return NO;
567 }
568 NSInteger sessionCount = rtc::AtomicOps::Increment(&_webRTCSessionCount);
569 if (sessionCount > 1) {
570 // Should already be configured.
571 RTC_DCHECK(self.isConfiguredForWebRTC);
572 return YES;
573 }
574
575 // Only perform configuration steps once. Application might have already
576 // configured the session.
577 if (self.isConfiguredForWebRTC) {
578 // Nothing more to do, already configured.
579 return YES;
580 }
581
582 // If application has prevented automatic configuration, return here and wait
583 // for application to call configureWebRTCSession.
584 if (self.shouldDelayAudioConfiguration) {
585 [self notifyShouldConfigure];
586 return YES;
587 }
588
589 // Configure audio session.
590 NSError *error = nil;
591 if (![self configureWebRTCSession:&error]) {
592 RTCLogError(@"Error configuring audio session: %@",
593 error.localizedDescription);
594 if (outError) {
595 *outError = error;
596 }
597 return NO;
598 }
599
600 return YES;
601}
602
603- (BOOL)endWebRTCSession:(NSError **)outError {
604 if (outError) {
605 *outError = nil;
606 }
607 if (![self checkLock:outError]) {
608 return NO;
609 }
610 int sessionCount = rtc::AtomicOps::Decrement(&_webRTCSessionCount);
611 RTC_DCHECK_GE(sessionCount, 0);
612 if (sessionCount != 0) {
613 // Should still be configured.
614 RTC_DCHECK(self.isConfiguredForWebRTC);
615 return YES;
616 }
617
618 // Only unconfigure if application has not done it.
619 if (!self.isConfiguredForWebRTC) {
620 // Nothing more to do, already unconfigured.
621 return YES;
622 }
623
624 // If application has prevented automatic configuration, return here and wait
625 // for application to call unconfigureWebRTCSession.
626 if (self.shouldDelayAudioConfiguration) {
627 [self notifyShouldUnconfigure];
628 return YES;
629 }
630
631 // Unconfigure audio session.
632 NSError *error = nil;
633 if (![self unconfigureWebRTCSession:&error]) {
634 RTCLogError(@"Error unconfiguring audio session: %@",
635 error.localizedDescription);
636 if (outError) {
637 *outError = error;
638 }
639 return NO;
640 }
641
642 return YES;
643}
644
645- (NSError *)configurationErrorWithDescription:(NSString *)description {
646 NSDictionary* userInfo = @{
647 NSLocalizedDescriptionKey: description,
648 };
649 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
650 code:kRTCAudioSessionErrorConfiguration
651 userInfo:userInfo];
652}
653
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800654- (void)updateAudioSessionAfterEvent {
655 BOOL shouldActivate = self.activationCount > 0;
656 AVAudioSessionSetActiveOptions options = shouldActivate ?
657 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
658 NSError *error = nil;
659 if ([self.session setActive:shouldActivate
660 withOptions:options
661 error:&error]) {
662 self.isActive = shouldActivate;
663 } else {
664 RTCLogError(@"Failed to set session active to %d. Error:%@",
665 shouldActivate, error.localizedDescription);
666 }
667}
668
669- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700670 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700671 SEL sel = @selector(audioSessionDidBeginInterruption:);
672 if ([delegate respondsToSelector:sel]) {
673 [delegate audioSessionDidBeginInterruption:self];
674 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800675 }
676}
677
678- (void)notifyDidEndInterruptionWithShouldResumeSession:
679 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700680 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700681 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
682 if ([delegate respondsToSelector:sel]) {
683 [delegate audioSessionDidEndInterruption:self
684 shouldResumeSession:shouldResumeSession];
685 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800686 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800687}
688
689- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
690 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700691 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700692 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
693 if ([delegate respondsToSelector:sel]) {
694 [delegate audioSessionDidChangeRoute:self
695 reason:reason
696 previousRoute:previousRoute];
697 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800698 }
699}
700
701- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700702 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700703 SEL sel = @selector(audioSessionMediaServicesWereLost:);
704 if ([delegate respondsToSelector:sel]) {
705 [delegate audioSessionMediaServicesWereLost:self];
706 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800707 }
708}
709
710- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700711 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700712 SEL sel = @selector(audioSessionMediaServicesWereReset:);
713 if ([delegate respondsToSelector:sel]) {
714 [delegate audioSessionMediaServicesWereReset:self];
715 }
716 }
717}
718
719- (void)notifyShouldConfigure {
720 for (auto delegate : self.delegates) {
721 SEL sel = @selector(audioSessionShouldConfigure:);
722 if ([delegate respondsToSelector:sel]) {
723 [delegate audioSessionShouldConfigure:self];
724 }
725 }
726}
727
728- (void)notifyShouldUnconfigure {
729 for (auto delegate : self.delegates) {
730 SEL sel = @selector(audioSessionShouldUnconfigure:);
731 if ([delegate respondsToSelector:sel]) {
732 [delegate audioSessionShouldUnconfigure:self];
733 }
734 }
735}
736
737- (void)notifyDidConfigure {
738 for (auto delegate : self.delegates) {
739 SEL sel = @selector(audioSessionDidConfigure:);
740 if ([delegate respondsToSelector:sel]) {
741 [delegate audioSessionDidConfigure:self];
742 }
743 }
744}
745
746- (void)notifyDidUnconfigure {
747 for (auto delegate : self.delegates) {
748 SEL sel = @selector(audioSessionDidUnconfigure:);
749 if ([delegate respondsToSelector:sel]) {
750 [delegate audioSessionDidUnconfigure:self];
751 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800752 }
753}
754
755@end