blob: 7ef5110f476a9cf950eaa3cda86dcb721e3ae6e1 [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
tkchin9eeb6242016-04-27 01:54:20 -070018#import "WebRTC/RTCLogging.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080019#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h"
tkchind2511962016-05-06 18:54:15 -070020#import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080021
22NSString * const kRTCAudioSessionErrorDomain = @"org.webrtc.RTCAudioSession";
23NSInteger const kRTCAudioSessionErrorLockRequired = -1;
tkchin9f987d32016-03-12 20:06:28 -080024NSInteger const kRTCAudioSessionErrorConfiguration = -2;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080025
26// This class needs to be thread-safe because it is accessed from many threads.
27// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
28// lock contention so coarse locks should be fine for now.
29@implementation RTCAudioSession {
tkchin0ce3bf92016-03-12 16:52:04 -080030 rtc::CriticalSection _crit;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080031 AVAudioSession *_session;
Tze Kwang Chin307a0922016-03-21 13:57:40 -070032 volatile int _activationCount;
33 volatile int _lockRecursionCount;
34 volatile int _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080035 BOOL _isActive;
tkchind2511962016-05-06 18:54:15 -070036 BOOL _useManualAudio;
37 BOOL _isAudioEnabled;
38 BOOL _canPlayOrRecord;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080039}
40
41@synthesize session = _session;
tkchine54467f2016-03-15 16:54:03 -070042@synthesize delegates = _delegates;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080043
44+ (instancetype)sharedInstance {
45 static dispatch_once_t onceToken;
46 static RTCAudioSession *sharedInstance = nil;
47 dispatch_once(&onceToken, ^{
Tze Kwang Chin307a0922016-03-21 13:57:40 -070048 sharedInstance = [[self alloc] init];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080049 });
50 return sharedInstance;
51}
52
53- (instancetype)init {
54 if (self = [super init]) {
55 _session = [AVAudioSession sharedInstance];
tkchin0ce3bf92016-03-12 16:52:04 -080056
Zeke Chinb3fb71c2016-02-18 15:44:07 -080057 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
58 [center addObserver:self
59 selector:@selector(handleInterruptionNotification:)
60 name:AVAudioSessionInterruptionNotification
61 object:nil];
62 [center addObserver:self
63 selector:@selector(handleRouteChangeNotification:)
64 name:AVAudioSessionRouteChangeNotification
65 object:nil];
66 // TODO(tkchin): Maybe listen to SilenceSecondaryAudioHintNotification.
67 [center addObserver:self
68 selector:@selector(handleMediaServicesWereLost:)
69 name:AVAudioSessionMediaServicesWereLostNotification
70 object:nil];
71 [center addObserver:self
72 selector:@selector(handleMediaServicesWereReset:)
73 name:AVAudioSessionMediaServicesWereResetNotification
74 object:nil];
75 }
76 return self;
77}
78
79- (void)dealloc {
80 [[NSNotificationCenter defaultCenter] removeObserver:self];
81}
82
Zeke Chin1300caa2016-03-18 14:39:11 -070083- (NSString *)description {
84 NSString *format =
85 @"RTCAudioSession: {\n"
tkchind2511962016-05-06 18:54:15 -070086 " category: %@\n"
87 " categoryOptions: %ld\n"
88 " mode: %@\n"
Zeke Chin1300caa2016-03-18 14:39:11 -070089 " isActive: %d\n"
90 " sampleRate: %.2f\n"
91 " IOBufferDuration: %f\n"
92 " outputNumberOfChannels: %ld\n"
93 " inputNumberOfChannels: %ld\n"
94 " outputLatency: %f\n"
95 " inputLatency: %f\n"
96 "}";
97 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -070098 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -070099 self.isActive, self.sampleRate, self.IOBufferDuration,
100 self.outputNumberOfChannels, self.inputNumberOfChannels,
101 self.outputLatency, self.inputLatency];
102 return description;
103}
104
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800105- (void)setIsActive:(BOOL)isActive {
106 @synchronized(self) {
107 _isActive = isActive;
108 }
109}
110
111- (BOOL)isActive {
112 @synchronized(self) {
113 return _isActive;
114 }
115}
116
117- (BOOL)isLocked {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700118 return _lockRecursionCount > 0;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800119}
120
tkchind2511962016-05-06 18:54:15 -0700121- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800122 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700123 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800124 return;
125 }
tkchind2511962016-05-06 18:54:15 -0700126 _useManualAudio = useManualAudio;
127 }
128 [self updateCanPlayOrRecord];
129}
130
131- (BOOL)useManualAudio {
132 @synchronized(self) {
133 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800134 }
135}
136
tkchind2511962016-05-06 18:54:15 -0700137- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800138 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700139 if (_isAudioEnabled == isAudioEnabled) {
140 return;
141 }
142 _isAudioEnabled = isAudioEnabled;
143 }
144 [self updateCanPlayOrRecord];
145}
146
147- (BOOL)isAudioEnabled {
148 @synchronized(self) {
149 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800150 }
151}
152
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700153// TODO(tkchin): Check for duplicates.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800154- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700155 if (!delegate) {
156 return;
157 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800158 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700159 _delegates.push_back(delegate);
160 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800161 }
162}
163
164- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700165 if (!delegate) {
166 return;
167 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800168 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700169 _delegates.erase(std::remove(_delegates.begin(),
170 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700171 delegate),
172 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700173 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800174 }
175}
176
177- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800178 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700179 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800180}
181
182- (void)unlockForConfiguration {
183 // Don't let threads other than the one that called lockForConfiguration
184 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800185 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700186 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800187 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800188 // was called without anyone calling lock, we will hit an assertion.
189 _crit.Leave();
190 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800191 }
192}
193
194#pragma mark - AVAudioSession proxy methods
195
196- (NSString *)category {
197 return self.session.category;
198}
199
200- (AVAudioSessionCategoryOptions)categoryOptions {
201 return self.session.categoryOptions;
202}
203
204- (NSString *)mode {
205 return self.session.mode;
206}
207
208- (BOOL)secondaryAudioShouldBeSilencedHint {
209 return self.session.secondaryAudioShouldBeSilencedHint;
210}
211
212- (AVAudioSessionRouteDescription *)currentRoute {
213 return self.session.currentRoute;
214}
215
216- (NSInteger)maximumInputNumberOfChannels {
217 return self.session.maximumInputNumberOfChannels;
218}
219
220- (NSInteger)maximumOutputNumberOfChannels {
221 return self.session.maximumOutputNumberOfChannels;
222}
223
224- (float)inputGain {
225 return self.session.inputGain;
226}
227
228- (BOOL)inputGainSettable {
229 return self.session.inputGainSettable;
230}
231
232- (BOOL)inputAvailable {
233 return self.session.inputAvailable;
234}
235
236- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
237 return self.session.inputDataSources;
238}
239
240- (AVAudioSessionDataSourceDescription *)inputDataSource {
241 return self.session.inputDataSource;
242}
243
244- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
245 return self.session.outputDataSources;
246}
247
248- (AVAudioSessionDataSourceDescription *)outputDataSource {
249 return self.session.outputDataSource;
250}
251
252- (double)sampleRate {
253 return self.session.sampleRate;
254}
255
tkchind2511962016-05-06 18:54:15 -0700256- (double)preferredSampleRate {
257 return self.session.preferredSampleRate;
258}
259
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800260- (NSInteger)inputNumberOfChannels {
261 return self.session.inputNumberOfChannels;
262}
263
264- (NSInteger)outputNumberOfChannels {
265 return self.session.outputNumberOfChannels;
266}
267
268- (float)outputVolume {
269 return self.session.outputVolume;
270}
271
272- (NSTimeInterval)inputLatency {
273 return self.session.inputLatency;
274}
275
276- (NSTimeInterval)outputLatency {
277 return self.session.outputLatency;
278}
279
280- (NSTimeInterval)IOBufferDuration {
281 return self.session.IOBufferDuration;
282}
283
tkchind2511962016-05-06 18:54:15 -0700284- (NSTimeInterval)preferredIOBufferDuration {
285 return self.session.preferredIOBufferDuration;
286}
287
tkchine54467f2016-03-15 16:54:03 -0700288// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
289// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800290- (BOOL)setActive:(BOOL)active
291 error:(NSError **)outError {
292 if (![self checkLock:outError]) {
293 return NO;
294 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700295 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800296 if (!active && activationCount == 0) {
297 RTCLogWarning(@"Attempting to deactivate without prior activation.");
298 }
299 BOOL success = YES;
300 BOOL isActive = self.isActive;
301 // Keep a local error so we can log it.
302 NSError *error = nil;
303 BOOL shouldSetActive =
304 (active && !isActive) || (!active && isActive && activationCount == 1);
305 // Attempt to activate if we're not active.
306 // Attempt to deactivate if we're active and it's the last unbalanced call.
307 if (shouldSetActive) {
308 AVAudioSession *session = self.session;
309 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
310 // that other audio sessions that were interrupted by our session can return
311 // to their active state. It is recommended for VoIP apps to use this
312 // option.
313 AVAudioSessionSetActiveOptions options =
314 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
315 success = [session setActive:active
316 withOptions:options
317 error:&error];
318 if (outError) {
319 *outError = error;
320 }
321 }
322 if (success) {
323 if (shouldSetActive) {
324 self.isActive = active;
325 }
326 if (active) {
327 [self incrementActivationCount];
328 }
329 } else {
tkchin9f987d32016-03-12 20:06:28 -0800330 RTCLogError(@"Failed to setActive:%d. Error: %@",
331 active, error.localizedDescription);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800332 }
333 // Decrement activation count on deactivation whether or not it succeeded.
334 if (!active) {
335 [self decrementActivationCount];
336 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700337 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800338 return success;
339}
340
341- (BOOL)setCategory:(NSString *)category
342 withOptions:(AVAudioSessionCategoryOptions)options
343 error:(NSError **)outError {
344 if (![self checkLock:outError]) {
345 return NO;
346 }
347 return [self.session setCategory:category withOptions:options error:outError];
348}
349
350- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
351 if (![self checkLock:outError]) {
352 return NO;
353 }
354 return [self.session setMode:mode error:outError];
355}
356
357- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
358 if (![self checkLock:outError]) {
359 return NO;
360 }
361 return [self.session setInputGain:gain error:outError];
362}
363
364- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
365 if (![self checkLock:outError]) {
366 return NO;
367 }
368 return [self.session setPreferredSampleRate:sampleRate error:outError];
369}
370
371- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
372 error:(NSError **)outError {
373 if (![self checkLock:outError]) {
374 return NO;
375 }
376 return [self.session setPreferredIOBufferDuration:duration error:outError];
377}
378
379- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
380 error:(NSError **)outError {
381 if (![self checkLock:outError]) {
382 return NO;
383 }
384 return [self.session setPreferredInputNumberOfChannels:count error:outError];
385}
386- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
387 error:(NSError **)outError {
388 if (![self checkLock:outError]) {
389 return NO;
390 }
391 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
392}
393
394- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
395 error:(NSError **)outError {
396 if (![self checkLock:outError]) {
397 return NO;
398 }
399 return [self.session overrideOutputAudioPort:portOverride error:outError];
400}
401
402- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
403 error:(NSError **)outError {
404 if (![self checkLock:outError]) {
405 return NO;
406 }
407 return [self.session setPreferredInput:inPort error:outError];
408}
409
410- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
411 error:(NSError **)outError {
412 if (![self checkLock:outError]) {
413 return NO;
414 }
415 return [self.session setInputDataSource:dataSource error:outError];
416}
417
418- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
419 error:(NSError **)outError {
420 if (![self checkLock:outError]) {
421 return NO;
422 }
423 return [self.session setOutputDataSource:dataSource error:outError];
424}
425
426#pragma mark - Notifications
427
428- (void)handleInterruptionNotification:(NSNotification *)notification {
429 NSNumber* typeNumber =
430 notification.userInfo[AVAudioSessionInterruptionTypeKey];
431 AVAudioSessionInterruptionType type =
432 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
433 switch (type) {
434 case AVAudioSessionInterruptionTypeBegan:
435 RTCLog(@"Audio session interruption began.");
436 self.isActive = NO;
437 [self notifyDidBeginInterruption];
438 break;
439 case AVAudioSessionInterruptionTypeEnded: {
440 RTCLog(@"Audio session interruption ended.");
441 [self updateAudioSessionAfterEvent];
442 NSNumber *optionsNumber =
443 notification.userInfo[AVAudioSessionInterruptionOptionKey];
444 AVAudioSessionInterruptionOptions options =
445 optionsNumber.unsignedIntegerValue;
446 BOOL shouldResume =
447 options & AVAudioSessionInterruptionOptionShouldResume;
448 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
449 break;
450 }
451 }
452}
453
454- (void)handleRouteChangeNotification:(NSNotification *)notification {
455 // Get reason for current route change.
456 NSNumber* reasonNumber =
457 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
458 AVAudioSessionRouteChangeReason reason =
459 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
460 RTCLog(@"Audio route changed:");
461 switch (reason) {
462 case AVAudioSessionRouteChangeReasonUnknown:
463 RTCLog(@"Audio route changed: ReasonUnknown");
464 break;
465 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
466 RTCLog(@"Audio route changed: NewDeviceAvailable");
467 break;
468 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
469 RTCLog(@"Audio route changed: OldDeviceUnavailable");
470 break;
471 case AVAudioSessionRouteChangeReasonCategoryChange:
472 RTCLog(@"Audio route changed: CategoryChange to :%@",
473 self.session.category);
474 break;
475 case AVAudioSessionRouteChangeReasonOverride:
476 RTCLog(@"Audio route changed: Override");
477 break;
478 case AVAudioSessionRouteChangeReasonWakeFromSleep:
479 RTCLog(@"Audio route changed: WakeFromSleep");
480 break;
481 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
482 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
483 break;
484 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
485 RTCLog(@"Audio route changed: RouteConfigurationChange");
486 break;
487 }
488 AVAudioSessionRouteDescription* previousRoute =
489 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
490 // Log previous route configuration.
491 RTCLog(@"Previous route: %@\nCurrent route:%@",
492 previousRoute, self.session.currentRoute);
493 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
494}
495
496- (void)handleMediaServicesWereLost:(NSNotification *)notification {
497 RTCLog(@"Media services were lost.");
498 [self updateAudioSessionAfterEvent];
499 [self notifyMediaServicesWereLost];
500}
501
502- (void)handleMediaServicesWereReset:(NSNotification *)notification {
503 RTCLog(@"Media services were reset.");
504 [self updateAudioSessionAfterEvent];
505 [self notifyMediaServicesWereReset];
506}
507
508#pragma mark - Private
509
510+ (NSError *)lockError {
511 NSDictionary *userInfo = @{
512 NSLocalizedDescriptionKey:
513 @"Must call lockForConfiguration before calling this method."
514 };
515 NSError *error =
516 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
517 code:kRTCAudioSessionErrorLockRequired
518 userInfo:userInfo];
519 return error;
520}
521
tkchine54467f2016-03-15 16:54:03 -0700522- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800523 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700524 // Note: this returns a copy.
525 return _delegates;
526 }
527}
528
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700529// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700530- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
531 @synchronized(self) {
532 _delegates.insert(_delegates.begin(), delegate);
533 }
534}
535
536- (void)removeZeroedDelegates {
537 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700538 _delegates.erase(
539 std::remove_if(_delegates.begin(),
540 _delegates.end(),
541 [](id delegate) -> bool { return delegate == nil; }),
542 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800543 }
544}
545
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700546- (int)activationCount {
547 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800548}
549
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700550- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800551 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700552 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800553}
554
555- (NSInteger)decrementActivationCount {
556 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700557 return rtc::AtomicOps::Decrement(&_activationCount);
558}
559
560- (int)webRTCSessionCount {
561 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800562}
563
tkchind2511962016-05-06 18:54:15 -0700564- (BOOL)canPlayOrRecord {
565 return !self.useManualAudio || self.isAudioEnabled;
566}
567
tkchin9f987d32016-03-12 20:06:28 -0800568- (BOOL)checkLock:(NSError **)outError {
569 // Check ivar instead of trying to acquire lock so that we won't accidentally
570 // acquire lock if it hasn't already been called.
571 if (!self.isLocked) {
572 if (outError) {
573 *outError = [RTCAudioSession lockError];
574 }
575 return NO;
576 }
577 return YES;
578}
579
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700580- (BOOL)beginWebRTCSession:(NSError **)outError {
581 if (outError) {
582 *outError = nil;
583 }
584 if (![self checkLock:outError]) {
585 return NO;
586 }
tkchind2511962016-05-06 18:54:15 -0700587 rtc::AtomicOps::Increment(&_webRTCSessionCount);
588 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700589 return YES;
590}
591
592- (BOOL)endWebRTCSession:(NSError **)outError {
593 if (outError) {
594 *outError = nil;
595 }
596 if (![self checkLock:outError]) {
597 return NO;
598 }
tkchind2511962016-05-06 18:54:15 -0700599 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
600 [self notifyDidStopPlayOrRecord];
601 return YES;
602}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700603
tkchind2511962016-05-06 18:54:15 -0700604- (BOOL)configureWebRTCSession:(NSError **)outError {
605 if (outError) {
606 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700607 }
tkchind2511962016-05-06 18:54:15 -0700608 if (![self checkLock:outError]) {
609 return NO;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700610 }
tkchind2511962016-05-06 18:54:15 -0700611 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700612
tkchind2511962016-05-06 18:54:15 -0700613 // Configure the AVAudioSession and activate it.
614 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700615 NSError *error = nil;
tkchind2511962016-05-06 18:54:15 -0700616 RTCAudioSessionConfiguration *webRTCConfig =
617 [RTCAudioSessionConfiguration webRTCConfiguration];
618 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
619 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700620 error.localizedDescription);
tkchind2511962016-05-06 18:54:15 -0700621 [self unconfigureWebRTCSession:nil];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700622 if (outError) {
623 *outError = error;
624 }
625 return NO;
626 }
627
tkchind2511962016-05-06 18:54:15 -0700628 // Ensure that the device currently supports audio input.
629 // TODO(tkchin): Figure out if this is really necessary.
630 if (!self.inputAvailable) {
631 RTCLogError(@"No audio input path is available!");
632 [self unconfigureWebRTCSession:nil];
633 if (outError) {
634 *outError = [self configurationErrorWithDescription:@"No input path."];
635 }
636 return NO;
637 }
638
639 return YES;
640}
641
642- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
643 if (outError) {
644 *outError = nil;
645 }
646 if (![self checkLock:outError]) {
647 return NO;
648 }
649 RTCLog(@"Unconfiguring audio session for WebRTC.");
650 [self setActive:NO error:outError];
651
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700652 return YES;
653}
654
655- (NSError *)configurationErrorWithDescription:(NSString *)description {
656 NSDictionary* userInfo = @{
657 NSLocalizedDescriptionKey: description,
658 };
659 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
660 code:kRTCAudioSessionErrorConfiguration
661 userInfo:userInfo];
662}
663
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800664- (void)updateAudioSessionAfterEvent {
665 BOOL shouldActivate = self.activationCount > 0;
666 AVAudioSessionSetActiveOptions options = shouldActivate ?
667 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
668 NSError *error = nil;
669 if ([self.session setActive:shouldActivate
670 withOptions:options
671 error:&error]) {
672 self.isActive = shouldActivate;
673 } else {
674 RTCLogError(@"Failed to set session active to %d. Error:%@",
675 shouldActivate, error.localizedDescription);
676 }
677}
678
tkchind2511962016-05-06 18:54:15 -0700679- (void)updateCanPlayOrRecord {
680 BOOL canPlayOrRecord = NO;
681 BOOL shouldNotify = NO;
682 @synchronized(self) {
683 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
684 if (_canPlayOrRecord == canPlayOrRecord) {
685 return;
686 }
687 _canPlayOrRecord = canPlayOrRecord;
688 shouldNotify = YES;
689 }
690 if (shouldNotify) {
691 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
692 }
693}
694
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800695- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700696 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700697 SEL sel = @selector(audioSessionDidBeginInterruption:);
698 if ([delegate respondsToSelector:sel]) {
699 [delegate audioSessionDidBeginInterruption:self];
700 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800701 }
702}
703
704- (void)notifyDidEndInterruptionWithShouldResumeSession:
705 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700706 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700707 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
708 if ([delegate respondsToSelector:sel]) {
709 [delegate audioSessionDidEndInterruption:self
710 shouldResumeSession:shouldResumeSession];
711 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800712 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800713}
714
715- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
716 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700717 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700718 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
719 if ([delegate respondsToSelector:sel]) {
720 [delegate audioSessionDidChangeRoute:self
721 reason:reason
722 previousRoute:previousRoute];
723 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800724 }
725}
726
727- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700728 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700729 SEL sel = @selector(audioSessionMediaServicesWereLost:);
730 if ([delegate respondsToSelector:sel]) {
731 [delegate audioSessionMediaServicesWereLost:self];
732 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800733 }
734}
735
736- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700737 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700738 SEL sel = @selector(audioSessionMediaServicesWereReset:);
739 if ([delegate respondsToSelector:sel]) {
740 [delegate audioSessionMediaServicesWereReset:self];
741 }
742 }
743}
744
tkchind2511962016-05-06 18:54:15 -0700745- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700746 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700747 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700748 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700749 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700750 }
751 }
752}
753
tkchind2511962016-05-06 18:54:15 -0700754- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700755 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700756 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700757 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700758 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700759 }
760 }
761}
762
tkchind2511962016-05-06 18:54:15 -0700763- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700764 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700765 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700766 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700767 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700768 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800769 }
770}
771
772@end