Index: webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm |
diff --git a/webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm b/webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm |
index 05e9cb50c5070328323de70930956a06ad406ba3..c6e3677b8461751b7489dea445ff88cc27707356 100644 |
--- a/webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm |
+++ b/webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm |
@@ -10,6 +10,7 @@ |
#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h" |
+#include "webrtc/base/atomicops.h" |
#include "webrtc/base/checks.h" |
#include "webrtc/base/criticalsection.h" |
#include "webrtc/modules/audio_device/ios/audio_device_ios.h" |
@@ -27,20 +28,22 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
@implementation RTCAudioSession { |
rtc::CriticalSection _crit; |
AVAudioSession *_session; |
- NSInteger _activationCount; |
- NSInteger _lockRecursionCount; |
+ volatile int _activationCount; |
+ volatile int _lockRecursionCount; |
+ volatile int _webRTCSessionCount; |
BOOL _isActive; |
BOOL _shouldDelayAudioConfiguration; |
} |
@synthesize session = _session; |
@synthesize delegates = _delegates; |
+@synthesize savedConfiguration = _savedConfiguration; |
+ (instancetype)sharedInstance { |
static dispatch_once_t onceToken; |
static RTCAudioSession *sharedInstance = nil; |
dispatch_once(&onceToken, ^{ |
- sharedInstance = [[RTCAudioSession alloc] init]; |
+ sharedInstance = [[self alloc] init]; |
}); |
return sharedInstance; |
} |
@@ -106,13 +109,13 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
} |
- (BOOL)isLocked { |
- @synchronized(self) { |
- return _lockRecursionCount > 0; |
- } |
+ return _lockRecursionCount > 0; |
} |
- (void)setShouldDelayAudioConfiguration:(BOOL)shouldDelayAudioConfiguration { |
@synchronized(self) { |
+ // No one should be changing this while an audio device is active. |
+ RTC_DCHECK(!self.isConfiguredForWebRTC); |
if (_shouldDelayAudioConfiguration == shouldDelayAudioConfiguration) { |
return; |
} |
@@ -126,6 +129,7 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
} |
} |
+// TODO(tkchin): Check for duplicates. |
- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate { |
if (!delegate) { |
return; |
@@ -150,18 +154,14 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
- (void)lockForConfiguration { |
_crit.Enter(); |
- @synchronized(self) { |
- ++_lockRecursionCount; |
- } |
+ rtc::AtomicOps::Increment(&_lockRecursionCount); |
} |
- (void)unlockForConfiguration { |
// Don't let threads other than the one that called lockForConfiguration |
// unlock. |
if (_crit.TryEnter()) { |
- @synchronized(self) { |
- --_lockRecursionCount; |
- } |
+ rtc::AtomicOps::Decrement(&_lockRecursionCount); |
// One unlock for the tryLock, and another one to actually unlock. If this |
// was called without anyone calling lock, we will hit an assertion. |
_crit.Leave(); |
@@ -262,7 +262,7 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
if (![self checkLock:outError]) { |
return NO; |
} |
- NSInteger activationCount = self.activationCount; |
+ int activationCount = _activationCount; |
if (!active && activationCount == 0) { |
RTCLogWarning(@"Attempting to deactivate without prior activation."); |
} |
@@ -304,7 +304,7 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
if (!active) { |
[self decrementActivationCount]; |
} |
- RTCLog(@"Number of current activations: %ld", (long)self.activationCount); |
+ RTCLog(@"Number of current activations: %d", _activationCount); |
return success; |
} |
@@ -496,6 +496,22 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
} |
} |
+- (void)setSavedConfiguration:(RTCAudioSessionConfiguration *)configuration { |
+ @synchronized(self) { |
+ if (_savedConfiguration == configuration) { |
+ return; |
+ } |
+ _savedConfiguration = configuration; |
+ } |
+} |
+ |
+- (RTCAudioSessionConfiguration *)savedConfiguration { |
+ @synchronized(self) { |
+ return _savedConfiguration; |
+ } |
+} |
+ |
+// TODO(tkchin): check for duplicates. |
- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate { |
@synchronized(self) { |
_delegates.insert(_delegates.begin(), delegate); |
@@ -512,24 +528,22 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
} |
} |
-- (NSInteger)activationCount { |
- @synchronized(self) { |
- return _activationCount; |
- } |
+- (int)activationCount { |
+ return _activationCount; |
} |
-- (NSInteger)incrementActivationCount { |
+- (int)incrementActivationCount { |
RTCLog(@"Incrementing activation count."); |
- @synchronized(self) { |
- return ++_activationCount; |
- } |
+ return rtc::AtomicOps::Increment(&_activationCount); |
} |
- (NSInteger)decrementActivationCount { |
RTCLog(@"Decrementing activation count."); |
- @synchronized(self) { |
- return --_activationCount; |
- } |
+ return rtc::AtomicOps::Decrement(&_activationCount); |
+} |
+ |
+- (int)webRTCSessionCount { |
+ return _webRTCSessionCount; |
} |
- (BOOL)checkLock:(NSError **)outError { |
@@ -544,6 +558,99 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
return YES; |
} |
+- (BOOL)beginWebRTCSession:(NSError **)outError { |
+ if (outError) { |
+ *outError = nil; |
+ } |
+ if (![self checkLock:outError]) { |
+ return NO; |
+ } |
+ NSInteger sessionCount = rtc::AtomicOps::Increment(&_webRTCSessionCount); |
+ if (sessionCount > 1) { |
+ // Should already be configured. |
+ RTC_DCHECK(self.isConfiguredForWebRTC); |
+ return YES; |
+ } |
+ |
+ // Only perform configuration steps once. Application might have already |
+ // configured the session. |
+ if (self.isConfiguredForWebRTC) { |
+ // Nothing more to do, already configured. |
+ return YES; |
+ } |
+ |
+ // If application has prevented automatic configuration, return here and wait |
+ // for application to call configureWebRTCSession. |
+ if (self.shouldDelayAudioConfiguration) { |
+ [self notifyShouldConfigure]; |
+ return YES; |
+ } |
+ |
+ // Configure audio session. |
+ NSError *error = nil; |
+ if (![self configureWebRTCSession:&error]) { |
+ RTCLogError(@"Error configuring audio session: %@", |
+ error.localizedDescription); |
+ if (outError) { |
+ *outError = error; |
+ } |
+ return NO; |
+ } |
+ |
+ return YES; |
+} |
+ |
+- (BOOL)endWebRTCSession:(NSError **)outError { |
+ if (outError) { |
+ *outError = nil; |
+ } |
+ if (![self checkLock:outError]) { |
+ return NO; |
+ } |
+ int sessionCount = rtc::AtomicOps::Decrement(&_webRTCSessionCount); |
+ RTC_DCHECK_GE(sessionCount, 0); |
+ if (sessionCount != 0) { |
+ // Should still be configured. |
+ RTC_DCHECK(self.isConfiguredForWebRTC); |
+ return YES; |
+ } |
+ |
+ // Only unconfigure if application has not done it. |
+ if (!self.isConfiguredForWebRTC) { |
+ // Nothing more to do, already unconfigured. |
+ return YES; |
+ } |
+ |
+ // If application has prevented automatic configuration, return here and wait |
+ // for application to call unconfigureWebRTCSession. |
+ if (self.shouldDelayAudioConfiguration) { |
+ [self notifyShouldUnconfigure]; |
+ return YES; |
+ } |
+ |
+ // Unconfigure audio session. |
+ NSError *error = nil; |
+ if (![self unconfigureWebRTCSession:&error]) { |
+ RTCLogError(@"Error unconfiguring audio session: %@", |
+ error.localizedDescription); |
+ if (outError) { |
+ *outError = error; |
+ } |
+ return NO; |
+ } |
+ |
+ return YES; |
+} |
+ |
+- (NSError *)configurationErrorWithDescription:(NSString *)description { |
+ NSDictionary* userInfo = @{ |
+ NSLocalizedDescriptionKey: description, |
+ }; |
+ return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain |
+ code:kRTCAudioSessionErrorConfiguration |
+ userInfo:userInfo]; |
+} |
+ |
- (void)updateAudioSessionAfterEvent { |
BOOL shouldActivate = self.activationCount > 0; |
AVAudioSessionSetActiveOptions options = shouldActivate ? |
@@ -561,37 +668,87 @@ NSInteger const kRTCAudioSessionErrorConfiguration = -2; |
- (void)notifyDidBeginInterruption { |
for (auto delegate : self.delegates) { |
- [delegate audioSessionDidBeginInterruption:self]; |
+ SEL sel = @selector(audioSessionDidBeginInterruption:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionDidBeginInterruption:self]; |
+ } |
} |
} |
- (void)notifyDidEndInterruptionWithShouldResumeSession: |
(BOOL)shouldResumeSession { |
for (auto delegate : self.delegates) { |
- [delegate audioSessionDidEndInterruption:self |
- shouldResumeSession:shouldResumeSession]; |
+ SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionDidEndInterruption:self |
+ shouldResumeSession:shouldResumeSession]; |
+ } |
} |
- |
} |
- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason |
previousRoute:(AVAudioSessionRouteDescription *)previousRoute { |
for (auto delegate : self.delegates) { |
- [delegate audioSessionDidChangeRoute:self |
- reason:reason |
- previousRoute:previousRoute]; |
+ SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionDidChangeRoute:self |
+ reason:reason |
+ previousRoute:previousRoute]; |
+ } |
} |
} |
- (void)notifyMediaServicesWereLost { |
for (auto delegate : self.delegates) { |
- [delegate audioSessionMediaServicesWereLost:self]; |
+ SEL sel = @selector(audioSessionMediaServicesWereLost:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionMediaServicesWereLost:self]; |
+ } |
} |
} |
- (void)notifyMediaServicesWereReset { |
for (auto delegate : self.delegates) { |
- [delegate audioSessionMediaServicesWereReset:self]; |
+ SEL sel = @selector(audioSessionMediaServicesWereReset:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionMediaServicesWereReset:self]; |
+ } |
+ } |
+} |
+ |
+- (void)notifyShouldConfigure { |
+ for (auto delegate : self.delegates) { |
+ SEL sel = @selector(audioSessionShouldConfigure:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionShouldConfigure:self]; |
+ } |
+ } |
+} |
+ |
+- (void)notifyShouldUnconfigure { |
+ for (auto delegate : self.delegates) { |
+ SEL sel = @selector(audioSessionShouldUnconfigure:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionShouldUnconfigure:self]; |
+ } |
+ } |
+} |
+ |
+- (void)notifyDidConfigure { |
+ for (auto delegate : self.delegates) { |
+ SEL sel = @selector(audioSessionDidConfigure:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionDidConfigure:self]; |
+ } |
+ } |
+} |
+ |
+- (void)notifyDidUnconfigure { |
+ for (auto delegate : self.delegates) { |
+ SEL sel = @selector(audioSessionDidUnconfigure:); |
+ if ([delegate respondsToSelector:sel]) { |
+ [delegate audioSessionDidUnconfigure:self]; |
+ } |
} |
} |