blob: 3b6e05dea1687ba1253704188a1c7eb0b9dd60ca [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(),
tkchinefdd9302016-04-11 12:00:59 -0700150 delegate),
151 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700152 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800153 }
154}
155
156- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800157 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700158 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800159}
160
161- (void)unlockForConfiguration {
162 // Don't let threads other than the one that called lockForConfiguration
163 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800164 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700165 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800166 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800167 // was called without anyone calling lock, we will hit an assertion.
168 _crit.Leave();
169 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800170 }
171}
172
173#pragma mark - AVAudioSession proxy methods
174
175- (NSString *)category {
176 return self.session.category;
177}
178
179- (AVAudioSessionCategoryOptions)categoryOptions {
180 return self.session.categoryOptions;
181}
182
183- (NSString *)mode {
184 return self.session.mode;
185}
186
187- (BOOL)secondaryAudioShouldBeSilencedHint {
188 return self.session.secondaryAudioShouldBeSilencedHint;
189}
190
191- (AVAudioSessionRouteDescription *)currentRoute {
192 return self.session.currentRoute;
193}
194
195- (NSInteger)maximumInputNumberOfChannels {
196 return self.session.maximumInputNumberOfChannels;
197}
198
199- (NSInteger)maximumOutputNumberOfChannels {
200 return self.session.maximumOutputNumberOfChannels;
201}
202
203- (float)inputGain {
204 return self.session.inputGain;
205}
206
207- (BOOL)inputGainSettable {
208 return self.session.inputGainSettable;
209}
210
211- (BOOL)inputAvailable {
212 return self.session.inputAvailable;
213}
214
215- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
216 return self.session.inputDataSources;
217}
218
219- (AVAudioSessionDataSourceDescription *)inputDataSource {
220 return self.session.inputDataSource;
221}
222
223- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
224 return self.session.outputDataSources;
225}
226
227- (AVAudioSessionDataSourceDescription *)outputDataSource {
228 return self.session.outputDataSource;
229}
230
231- (double)sampleRate {
232 return self.session.sampleRate;
233}
234
235- (NSInteger)inputNumberOfChannels {
236 return self.session.inputNumberOfChannels;
237}
238
239- (NSInteger)outputNumberOfChannels {
240 return self.session.outputNumberOfChannels;
241}
242
243- (float)outputVolume {
244 return self.session.outputVolume;
245}
246
247- (NSTimeInterval)inputLatency {
248 return self.session.inputLatency;
249}
250
251- (NSTimeInterval)outputLatency {
252 return self.session.outputLatency;
253}
254
255- (NSTimeInterval)IOBufferDuration {
256 return self.session.IOBufferDuration;
257}
258
tkchine54467f2016-03-15 16:54:03 -0700259// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
260// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800261- (BOOL)setActive:(BOOL)active
262 error:(NSError **)outError {
263 if (![self checkLock:outError]) {
264 return NO;
265 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700266 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800267 if (!active && activationCount == 0) {
268 RTCLogWarning(@"Attempting to deactivate without prior activation.");
269 }
270 BOOL success = YES;
271 BOOL isActive = self.isActive;
272 // Keep a local error so we can log it.
273 NSError *error = nil;
274 BOOL shouldSetActive =
275 (active && !isActive) || (!active && isActive && activationCount == 1);
276 // Attempt to activate if we're not active.
277 // Attempt to deactivate if we're active and it's the last unbalanced call.
278 if (shouldSetActive) {
279 AVAudioSession *session = self.session;
280 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
281 // that other audio sessions that were interrupted by our session can return
282 // to their active state. It is recommended for VoIP apps to use this
283 // option.
284 AVAudioSessionSetActiveOptions options =
285 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
286 success = [session setActive:active
287 withOptions:options
288 error:&error];
289 if (outError) {
290 *outError = error;
291 }
292 }
293 if (success) {
294 if (shouldSetActive) {
295 self.isActive = active;
296 }
297 if (active) {
298 [self incrementActivationCount];
299 }
300 } else {
tkchin9f987d32016-03-12 20:06:28 -0800301 RTCLogError(@"Failed to setActive:%d. Error: %@",
302 active, error.localizedDescription);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800303 }
304 // Decrement activation count on deactivation whether or not it succeeded.
305 if (!active) {
306 [self decrementActivationCount];
307 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700308 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800309 return success;
310}
311
312- (BOOL)setCategory:(NSString *)category
313 withOptions:(AVAudioSessionCategoryOptions)options
314 error:(NSError **)outError {
315 if (![self checkLock:outError]) {
316 return NO;
317 }
318 return [self.session setCategory:category withOptions:options error:outError];
319}
320
321- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
322 if (![self checkLock:outError]) {
323 return NO;
324 }
325 return [self.session setMode:mode error:outError];
326}
327
328- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
329 if (![self checkLock:outError]) {
330 return NO;
331 }
332 return [self.session setInputGain:gain error:outError];
333}
334
335- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
336 if (![self checkLock:outError]) {
337 return NO;
338 }
339 return [self.session setPreferredSampleRate:sampleRate error:outError];
340}
341
342- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
343 error:(NSError **)outError {
344 if (![self checkLock:outError]) {
345 return NO;
346 }
347 return [self.session setPreferredIOBufferDuration:duration error:outError];
348}
349
350- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
351 error:(NSError **)outError {
352 if (![self checkLock:outError]) {
353 return NO;
354 }
355 return [self.session setPreferredInputNumberOfChannels:count error:outError];
356}
357- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
358 error:(NSError **)outError {
359 if (![self checkLock:outError]) {
360 return NO;
361 }
362 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
363}
364
365- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
366 error:(NSError **)outError {
367 if (![self checkLock:outError]) {
368 return NO;
369 }
370 return [self.session overrideOutputAudioPort:portOverride error:outError];
371}
372
373- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
374 error:(NSError **)outError {
375 if (![self checkLock:outError]) {
376 return NO;
377 }
378 return [self.session setPreferredInput:inPort error:outError];
379}
380
381- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
382 error:(NSError **)outError {
383 if (![self checkLock:outError]) {
384 return NO;
385 }
386 return [self.session setInputDataSource:dataSource error:outError];
387}
388
389- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
390 error:(NSError **)outError {
391 if (![self checkLock:outError]) {
392 return NO;
393 }
394 return [self.session setOutputDataSource:dataSource error:outError];
395}
396
397#pragma mark - Notifications
398
399- (void)handleInterruptionNotification:(NSNotification *)notification {
400 NSNumber* typeNumber =
401 notification.userInfo[AVAudioSessionInterruptionTypeKey];
402 AVAudioSessionInterruptionType type =
403 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
404 switch (type) {
405 case AVAudioSessionInterruptionTypeBegan:
406 RTCLog(@"Audio session interruption began.");
407 self.isActive = NO;
408 [self notifyDidBeginInterruption];
409 break;
410 case AVAudioSessionInterruptionTypeEnded: {
411 RTCLog(@"Audio session interruption ended.");
412 [self updateAudioSessionAfterEvent];
413 NSNumber *optionsNumber =
414 notification.userInfo[AVAudioSessionInterruptionOptionKey];
415 AVAudioSessionInterruptionOptions options =
416 optionsNumber.unsignedIntegerValue;
417 BOOL shouldResume =
418 options & AVAudioSessionInterruptionOptionShouldResume;
419 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
420 break;
421 }
422 }
423}
424
425- (void)handleRouteChangeNotification:(NSNotification *)notification {
426 // Get reason for current route change.
427 NSNumber* reasonNumber =
428 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
429 AVAudioSessionRouteChangeReason reason =
430 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
431 RTCLog(@"Audio route changed:");
432 switch (reason) {
433 case AVAudioSessionRouteChangeReasonUnknown:
434 RTCLog(@"Audio route changed: ReasonUnknown");
435 break;
436 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
437 RTCLog(@"Audio route changed: NewDeviceAvailable");
438 break;
439 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
440 RTCLog(@"Audio route changed: OldDeviceUnavailable");
441 break;
442 case AVAudioSessionRouteChangeReasonCategoryChange:
443 RTCLog(@"Audio route changed: CategoryChange to :%@",
444 self.session.category);
445 break;
446 case AVAudioSessionRouteChangeReasonOverride:
447 RTCLog(@"Audio route changed: Override");
448 break;
449 case AVAudioSessionRouteChangeReasonWakeFromSleep:
450 RTCLog(@"Audio route changed: WakeFromSleep");
451 break;
452 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
453 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
454 break;
455 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
456 RTCLog(@"Audio route changed: RouteConfigurationChange");
457 break;
458 }
459 AVAudioSessionRouteDescription* previousRoute =
460 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
461 // Log previous route configuration.
462 RTCLog(@"Previous route: %@\nCurrent route:%@",
463 previousRoute, self.session.currentRoute);
464 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
465}
466
467- (void)handleMediaServicesWereLost:(NSNotification *)notification {
468 RTCLog(@"Media services were lost.");
469 [self updateAudioSessionAfterEvent];
470 [self notifyMediaServicesWereLost];
471}
472
473- (void)handleMediaServicesWereReset:(NSNotification *)notification {
474 RTCLog(@"Media services were reset.");
475 [self updateAudioSessionAfterEvent];
476 [self notifyMediaServicesWereReset];
477}
478
479#pragma mark - Private
480
481+ (NSError *)lockError {
482 NSDictionary *userInfo = @{
483 NSLocalizedDescriptionKey:
484 @"Must call lockForConfiguration before calling this method."
485 };
486 NSError *error =
487 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
488 code:kRTCAudioSessionErrorLockRequired
489 userInfo:userInfo];
490 return error;
491}
492
tkchine54467f2016-03-15 16:54:03 -0700493- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800494 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700495 // Note: this returns a copy.
496 return _delegates;
497 }
498}
499
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700500- (void)setSavedConfiguration:(RTCAudioSessionConfiguration *)configuration {
501 @synchronized(self) {
502 if (_savedConfiguration == configuration) {
503 return;
504 }
505 _savedConfiguration = configuration;
506 }
507}
508
509- (RTCAudioSessionConfiguration *)savedConfiguration {
510 @synchronized(self) {
511 return _savedConfiguration;
512 }
513}
514
515// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700516- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
517 @synchronized(self) {
518 _delegates.insert(_delegates.begin(), delegate);
519 }
520}
521
522- (void)removeZeroedDelegates {
523 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700524 _delegates.erase(
525 std::remove_if(_delegates.begin(),
526 _delegates.end(),
527 [](id delegate) -> bool { return delegate == nil; }),
528 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800529 }
530}
531
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700532- (int)activationCount {
533 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800534}
535
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700536- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800537 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700538 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800539}
540
541- (NSInteger)decrementActivationCount {
542 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700543 return rtc::AtomicOps::Decrement(&_activationCount);
544}
545
546- (int)webRTCSessionCount {
547 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800548}
549
tkchin9f987d32016-03-12 20:06:28 -0800550- (BOOL)checkLock:(NSError **)outError {
551 // Check ivar instead of trying to acquire lock so that we won't accidentally
552 // acquire lock if it hasn't already been called.
553 if (!self.isLocked) {
554 if (outError) {
555 *outError = [RTCAudioSession lockError];
556 }
557 return NO;
558 }
559 return YES;
560}
561
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700562- (BOOL)beginWebRTCSession:(NSError **)outError {
563 if (outError) {
564 *outError = nil;
565 }
566 if (![self checkLock:outError]) {
567 return NO;
568 }
569 NSInteger sessionCount = rtc::AtomicOps::Increment(&_webRTCSessionCount);
570 if (sessionCount > 1) {
571 // Should already be configured.
572 RTC_DCHECK(self.isConfiguredForWebRTC);
573 return YES;
574 }
575
576 // Only perform configuration steps once. Application might have already
577 // configured the session.
578 if (self.isConfiguredForWebRTC) {
579 // Nothing more to do, already configured.
580 return YES;
581 }
582
583 // If application has prevented automatic configuration, return here and wait
584 // for application to call configureWebRTCSession.
585 if (self.shouldDelayAudioConfiguration) {
586 [self notifyShouldConfigure];
587 return YES;
588 }
589
590 // Configure audio session.
591 NSError *error = nil;
592 if (![self configureWebRTCSession:&error]) {
593 RTCLogError(@"Error configuring audio session: %@",
594 error.localizedDescription);
595 if (outError) {
596 *outError = error;
597 }
598 return NO;
599 }
600
601 return YES;
602}
603
604- (BOOL)endWebRTCSession:(NSError **)outError {
605 if (outError) {
606 *outError = nil;
607 }
608 if (![self checkLock:outError]) {
609 return NO;
610 }
611 int sessionCount = rtc::AtomicOps::Decrement(&_webRTCSessionCount);
612 RTC_DCHECK_GE(sessionCount, 0);
613 if (sessionCount != 0) {
614 // Should still be configured.
615 RTC_DCHECK(self.isConfiguredForWebRTC);
616 return YES;
617 }
618
619 // Only unconfigure if application has not done it.
620 if (!self.isConfiguredForWebRTC) {
621 // Nothing more to do, already unconfigured.
622 return YES;
623 }
624
625 // If application has prevented automatic configuration, return here and wait
626 // for application to call unconfigureWebRTCSession.
627 if (self.shouldDelayAudioConfiguration) {
628 [self notifyShouldUnconfigure];
629 return YES;
630 }
631
632 // Unconfigure audio session.
633 NSError *error = nil;
634 if (![self unconfigureWebRTCSession:&error]) {
635 RTCLogError(@"Error unconfiguring audio session: %@",
636 error.localizedDescription);
637 if (outError) {
638 *outError = error;
639 }
640 return NO;
641 }
642
643 return YES;
644}
645
646- (NSError *)configurationErrorWithDescription:(NSString *)description {
647 NSDictionary* userInfo = @{
648 NSLocalizedDescriptionKey: description,
649 };
650 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
651 code:kRTCAudioSessionErrorConfiguration
652 userInfo:userInfo];
653}
654
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800655- (void)updateAudioSessionAfterEvent {
656 BOOL shouldActivate = self.activationCount > 0;
657 AVAudioSessionSetActiveOptions options = shouldActivate ?
658 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
659 NSError *error = nil;
660 if ([self.session setActive:shouldActivate
661 withOptions:options
662 error:&error]) {
663 self.isActive = shouldActivate;
664 } else {
665 RTCLogError(@"Failed to set session active to %d. Error:%@",
666 shouldActivate, error.localizedDescription);
667 }
668}
669
670- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700671 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700672 SEL sel = @selector(audioSessionDidBeginInterruption:);
673 if ([delegate respondsToSelector:sel]) {
674 [delegate audioSessionDidBeginInterruption:self];
675 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800676 }
677}
678
679- (void)notifyDidEndInterruptionWithShouldResumeSession:
680 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700681 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700682 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
683 if ([delegate respondsToSelector:sel]) {
684 [delegate audioSessionDidEndInterruption:self
685 shouldResumeSession:shouldResumeSession];
686 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800687 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800688}
689
690- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
691 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700692 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700693 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
694 if ([delegate respondsToSelector:sel]) {
695 [delegate audioSessionDidChangeRoute:self
696 reason:reason
697 previousRoute:previousRoute];
698 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800699 }
700}
701
702- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700703 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700704 SEL sel = @selector(audioSessionMediaServicesWereLost:);
705 if ([delegate respondsToSelector:sel]) {
706 [delegate audioSessionMediaServicesWereLost:self];
707 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800708 }
709}
710
711- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700712 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700713 SEL sel = @selector(audioSessionMediaServicesWereReset:);
714 if ([delegate respondsToSelector:sel]) {
715 [delegate audioSessionMediaServicesWereReset:self];
716 }
717 }
718}
719
720- (void)notifyShouldConfigure {
721 for (auto delegate : self.delegates) {
722 SEL sel = @selector(audioSessionShouldConfigure:);
723 if ([delegate respondsToSelector:sel]) {
724 [delegate audioSessionShouldConfigure:self];
725 }
726 }
727}
728
729- (void)notifyShouldUnconfigure {
730 for (auto delegate : self.delegates) {
731 SEL sel = @selector(audioSessionShouldUnconfigure:);
732 if ([delegate respondsToSelector:sel]) {
733 [delegate audioSessionShouldUnconfigure:self];
734 }
735 }
736}
737
738- (void)notifyDidConfigure {
739 for (auto delegate : self.delegates) {
740 SEL sel = @selector(audioSessionDidConfigure:);
741 if ([delegate respondsToSelector:sel]) {
742 [delegate audioSessionDidConfigure:self];
743 }
744 }
745}
746
747- (void)notifyDidUnconfigure {
748 for (auto delegate : self.delegates) {
749 SEL sel = @selector(audioSessionDidUnconfigure:);
750 if ([delegate respondsToSelector:sel]) {
751 [delegate audioSessionDidUnconfigure:self];
752 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800753 }
754}
755
756@end