Add RTCAudioSession proxy class.
BUG= R=haysc@webrtc.org, henrika@webrtc.org Review URL: https://codereview.webrtc.org/1709853002 . Cr-Commit-Position: refs/heads/master@{#11676}
This commit is contained in:
parent
9ac4df1ba6
commit
b3fb71c101
@ -17,6 +17,14 @@ config("audio_device_config") {
|
||||
}
|
||||
|
||||
source_set("audio_device") {
|
||||
deps = [
|
||||
"../..:webrtc_common",
|
||||
"../../base:rtc_base_approved",
|
||||
"../../common_audio",
|
||||
"../../system_wrappers",
|
||||
"../utility",
|
||||
]
|
||||
|
||||
sources = [
|
||||
"audio_device_buffer.cc",
|
||||
"audio_device_buffer.h",
|
||||
@ -122,10 +130,14 @@ source_set("audio_device") {
|
||||
]
|
||||
}
|
||||
if (is_ios) {
|
||||
deps += [ "../../base:rtc_base_objc" ]
|
||||
sources += [
|
||||
"ios/audio_device_ios.h",
|
||||
"ios/audio_device_ios.mm",
|
||||
"ios/audio_device_not_implemented_ios.mm",
|
||||
"ios/objc/RTCAudioSession+Private.h",
|
||||
"ios/objc/RTCAudioSession.h",
|
||||
"ios/objc/RTCAudioSession.mm",
|
||||
]
|
||||
cflags += [ "-fobjc-arc" ] # CLANG_ENABLE_OBJC_ARC = YES.
|
||||
libs = [
|
||||
@ -175,12 +187,4 @@ source_set("audio_device") {
|
||||
# See http://code.google.com/p/webrtc/issues/detail?id=163 for details.
|
||||
configs -= [ "//build/config/clang:find_bad_constructs" ]
|
||||
}
|
||||
|
||||
deps = [
|
||||
"../..:webrtc_common",
|
||||
"../../base:rtc_base_approved",
|
||||
"../../common_audio",
|
||||
"../../system_wrappers",
|
||||
"../utility",
|
||||
]
|
||||
}
|
||||
|
||||
@ -166,10 +166,16 @@
|
||||
},
|
||||
}],
|
||||
['OS=="ios"', {
|
||||
'dependencies': [
|
||||
'<(webrtc_root)/base/base.gyp:rtc_base_objc',
|
||||
],
|
||||
'sources': [
|
||||
'ios/audio_device_ios.h',
|
||||
'ios/audio_device_ios.mm',
|
||||
'ios/audio_device_not_implemented_ios.mm',
|
||||
'ios/objc/RTCAudioSession+Private.h',
|
||||
'ios/objc/RTCAudioSession.h',
|
||||
'ios/objc/RTCAudioSession.mm',
|
||||
],
|
||||
'xcode_settings': {
|
||||
'CLANG_ENABLE_OBJC_ARC': 'YES',
|
||||
|
||||
@ -25,18 +25,10 @@
|
||||
#include "webrtc/modules/audio_device/fine_audio_buffer.h"
|
||||
#include "webrtc/modules/utility/include/helpers_ios.h"
|
||||
|
||||
#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Protects |g_audio_session_users|.
|
||||
static rtc::GlobalLockPod g_lock;
|
||||
|
||||
// Counts number of users (=instances of this object) who needs an active
|
||||
// audio session. This variable is used to ensure that we only activate an audio
|
||||
// session for the first user and deactivate it for the last.
|
||||
// Member is static to ensure that the value is counted for all instances
|
||||
// and not per instance.
|
||||
static int g_audio_session_users GUARDED_BY(g_lock) = 0;
|
||||
|
||||
#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::"
|
||||
|
||||
#define LOG_AND_RETURN_IF_ERROR(error, message) \
|
||||
@ -97,10 +89,10 @@ using ios::CheckAndLogError;
|
||||
|
||||
// Verifies that the current audio session supports input audio and that the
|
||||
// required category and mode are enabled.
|
||||
static bool VerifyAudioSession(AVAudioSession* session) {
|
||||
static bool VerifyAudioSession(RTCAudioSession* session) {
|
||||
LOG(LS_INFO) << "VerifyAudioSession";
|
||||
// Ensure that the device currently supports audio input.
|
||||
if (!session.isInputAvailable) {
|
||||
if (!session.inputAvailable) {
|
||||
LOG(LS_ERROR) << "No audio input path is available!";
|
||||
return false;
|
||||
}
|
||||
@ -121,93 +113,89 @@ static bool VerifyAudioSession(AVAudioSession* session) {
|
||||
// Activates an audio session suitable for full duplex VoIP sessions when
|
||||
// |activate| is true. Also sets the preferred sample rate and IO buffer
|
||||
// duration. Deactivates an active audio session if |activate| is set to false.
|
||||
static bool ActivateAudioSession(AVAudioSession* session, bool activate)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(g_lock) {
|
||||
static bool ActivateAudioSession(RTCAudioSession* session, bool activate) {
|
||||
LOG(LS_INFO) << "ActivateAudioSession(" << activate << ")";
|
||||
@autoreleasepool {
|
||||
NSError* error = nil;
|
||||
BOOL success = NO;
|
||||
|
||||
if (!activate) {
|
||||
// Deactivate the audio session using an extra option and then return.
|
||||
// AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to
|
||||
// ensure that other audio sessions that were interrupted by our session
|
||||
// can return to their active state. It is recommended for VoIP apps to
|
||||
// use this option.
|
||||
success = [session
|
||||
setActive:NO
|
||||
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
|
||||
error:&error];
|
||||
return CheckAndLogError(success, error);
|
||||
}
|
||||
NSError* error = nil;
|
||||
BOOL success = NO;
|
||||
|
||||
// Go ahead and active our own audio session since |activate| is true.
|
||||
// Use a category which supports simultaneous recording and playback.
|
||||
// By default, using this category implies that our app’s audio is
|
||||
// nonmixable, hence activating the session will interrupt any other
|
||||
// audio sessions which are also nonmixable.
|
||||
if (session.category != AVAudioSessionCategoryPlayAndRecord) {
|
||||
error = nil;
|
||||
success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
|
||||
withOptions:AVAudioSessionCategoryOptionAllowBluetooth
|
||||
error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
}
|
||||
|
||||
// Specify mode for two-way voice communication (e.g. VoIP).
|
||||
if (session.mode != AVAudioSessionModeVoiceChat) {
|
||||
error = nil;
|
||||
success = [session setMode:AVAudioSessionModeVoiceChat error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
}
|
||||
|
||||
// Set the session's sample rate or the hardware sample rate.
|
||||
// It is essential that we use the same sample rate as stream format
|
||||
// to ensure that the I/O unit does not have to do sample rate conversion.
|
||||
error = nil;
|
||||
success =
|
||||
[session setPreferredSampleRate:kPreferredSampleRate error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
|
||||
// Set the preferred audio I/O buffer duration, in seconds.
|
||||
error = nil;
|
||||
success = [session setPreferredIOBufferDuration:kPreferredIOBufferDuration
|
||||
error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
|
||||
// Activate the audio session. Activation can fail if another active audio
|
||||
// session (e.g. phone call) has higher priority than ours.
|
||||
error = nil;
|
||||
success = [session setActive:YES error:&error];
|
||||
if (!CheckAndLogError(success, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that the active audio session has the correct category and mode.
|
||||
if (!VerifyAudioSession(session)) {
|
||||
LOG(LS_ERROR) << "Failed to verify audio session category and mode";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to set the preferred number of hardware audio channels. These calls
|
||||
// must be done after setting the audio session’s category and mode and
|
||||
// activating the session.
|
||||
// We try to use mono in both directions to save resources and format
|
||||
// conversions in the audio unit. Some devices does only support stereo;
|
||||
// e.g. wired headset on iPhone 6.
|
||||
// TODO(henrika): add support for stereo if needed.
|
||||
error = nil;
|
||||
success =
|
||||
[session setPreferredInputNumberOfChannels:kPreferredNumberOfChannels
|
||||
error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
error = nil;
|
||||
success =
|
||||
[session setPreferredOutputNumberOfChannels:kPreferredNumberOfChannels
|
||||
error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
return true;
|
||||
[session lockForConfiguration];
|
||||
if (!activate) {
|
||||
success = [session setActive:NO
|
||||
error:&error];
|
||||
[session unlockForConfiguration];
|
||||
return CheckAndLogError(success, error);
|
||||
}
|
||||
|
||||
// Go ahead and active our own audio session since |activate| is true.
|
||||
// Use a category which supports simultaneous recording and playback.
|
||||
// By default, using this category implies that our app’s audio is
|
||||
// nonmixable, hence activating the session will interrupt any other
|
||||
// audio sessions which are also nonmixable.
|
||||
if (session.category != AVAudioSessionCategoryPlayAndRecord) {
|
||||
error = nil;
|
||||
success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
|
||||
withOptions:AVAudioSessionCategoryOptionAllowBluetooth
|
||||
error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
}
|
||||
|
||||
// Specify mode for two-way voice communication (e.g. VoIP).
|
||||
if (session.mode != AVAudioSessionModeVoiceChat) {
|
||||
error = nil;
|
||||
success = [session setMode:AVAudioSessionModeVoiceChat error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
}
|
||||
|
||||
// Set the session's sample rate or the hardware sample rate.
|
||||
// It is essential that we use the same sample rate as stream format
|
||||
// to ensure that the I/O unit does not have to do sample rate conversion.
|
||||
error = nil;
|
||||
success =
|
||||
[session setPreferredSampleRate:kPreferredSampleRate error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
|
||||
// Set the preferred audio I/O buffer duration, in seconds.
|
||||
error = nil;
|
||||
success = [session setPreferredIOBufferDuration:kPreferredIOBufferDuration
|
||||
error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
|
||||
// Activate the audio session. Activation can fail if another active audio
|
||||
// session (e.g. phone call) has higher priority than ours.
|
||||
error = nil;
|
||||
success = [session setActive:YES error:&error];
|
||||
if (!CheckAndLogError(success, error)) {
|
||||
[session unlockForConfiguration];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that the active audio session has the correct category and mode.
|
||||
if (!VerifyAudioSession(session)) {
|
||||
LOG(LS_ERROR) << "Failed to verify audio session category and mode";
|
||||
[session unlockForConfiguration];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to set the preferred number of hardware audio channels. These calls
|
||||
// must be done after setting the audio session’s category and mode and
|
||||
// activating the session.
|
||||
// We try to use mono in both directions to save resources and format
|
||||
// conversions in the audio unit. Some devices does only support stereo;
|
||||
// e.g. wired headset on iPhone 6.
|
||||
// TODO(henrika): add support for stereo if needed.
|
||||
error = nil;
|
||||
success =
|
||||
[session setPreferredInputNumberOfChannels:kPreferredNumberOfChannels
|
||||
error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
error = nil;
|
||||
success =
|
||||
[session setPreferredOutputNumberOfChannels:kPreferredNumberOfChannels
|
||||
error:&error];
|
||||
RTC_DCHECK(CheckAndLogError(success, error));
|
||||
[session unlockForConfiguration];
|
||||
return true;
|
||||
}
|
||||
|
||||
// An application can create more than one ADM and start audio streaming
|
||||
@ -215,24 +203,8 @@ static bool ActivateAudioSession(AVAudioSession* session, bool activate)
|
||||
// session once (for the first one) and deactivate it once (for the last).
|
||||
static bool ActivateAudioSession() {
|
||||
LOGI() << "ActivateAudioSession";
|
||||
rtc::GlobalLockScope ls(&g_lock);
|
||||
if (g_audio_session_users == 0) {
|
||||
// The system provides an audio session object upon launch of an
|
||||
// application. However, we must initialize the session in order to
|
||||
// handle interruptions. Implicit initialization occurs when obtaining
|
||||
// a reference to the AVAudioSession object.
|
||||
AVAudioSession* session = [AVAudioSession sharedInstance];
|
||||
// Try to activate the audio session and ask for a set of preferred audio
|
||||
// parameters.
|
||||
if (!ActivateAudioSession(session, true)) {
|
||||
LOG(LS_ERROR) << "Failed to activate the audio session";
|
||||
return false;
|
||||
}
|
||||
LOG(LS_INFO) << "The audio session is now activated";
|
||||
}
|
||||
++g_audio_session_users;
|
||||
LOG(LS_INFO) << "Number of audio session users: " << g_audio_session_users;
|
||||
return true;
|
||||
RTCAudioSession* session = [RTCAudioSession sharedInstance];
|
||||
return ActivateAudioSession(session, true);
|
||||
}
|
||||
|
||||
// If more than one object is using the audio session, ensure that only the
|
||||
@ -240,18 +212,8 @@ static bool ActivateAudioSession() {
|
||||
// only as needed and deactivate it when you are not using audio".
|
||||
static bool DeactivateAudioSession() {
|
||||
LOGI() << "DeactivateAudioSession";
|
||||
rtc::GlobalLockScope ls(&g_lock);
|
||||
if (g_audio_session_users == 1) {
|
||||
AVAudioSession* session = [AVAudioSession sharedInstance];
|
||||
if (!ActivateAudioSession(session, false)) {
|
||||
LOG(LS_ERROR) << "Failed to deactivate the audio session";
|
||||
return false;
|
||||
}
|
||||
LOG(LS_INFO) << "Our audio session is now deactivated";
|
||||
}
|
||||
--g_audio_session_users;
|
||||
LOG(LS_INFO) << "Number of audio session users: " << g_audio_session_users;
|
||||
return true;
|
||||
RTCAudioSession* session = [RTCAudioSession sharedInstance];
|
||||
return ActivateAudioSession(session, false);
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
@ -344,13 +306,6 @@ int32_t AudioDeviceIOS::Terminate() {
|
||||
StopPlayout();
|
||||
StopRecording();
|
||||
initialized_ = false;
|
||||
{
|
||||
rtc::GlobalLockScope ls(&g_lock);
|
||||
if (g_audio_session_users != 0) {
|
||||
LOG(LS_WARNING) << "Object is destructed with an active audio session";
|
||||
}
|
||||
RTC_DCHECK_GE(g_audio_session_users, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -456,7 +411,8 @@ int32_t AudioDeviceIOS::StopRecording() {
|
||||
int32_t AudioDeviceIOS::SetLoudspeakerStatus(bool enable) {
|
||||
LOGI() << "SetLoudspeakerStatus(" << enable << ")";
|
||||
|
||||
AVAudioSession* session = [AVAudioSession sharedInstance];
|
||||
RTCAudioSession* session = [RTCAudioSession sharedInstance];
|
||||
[session lockForConfiguration];
|
||||
NSString* category = session.category;
|
||||
AVAudioSessionCategoryOptions options = session.categoryOptions;
|
||||
// Respect old category options if category is
|
||||
@ -476,12 +432,13 @@ int32_t AudioDeviceIOS::SetLoudspeakerStatus(bool enable) {
|
||||
withOptions:options
|
||||
error:&error];
|
||||
ios::CheckAndLogError(success, error);
|
||||
[session unlockForConfiguration];
|
||||
return (error == nil) ? 0 : -1;
|
||||
}
|
||||
|
||||
int32_t AudioDeviceIOS::GetLoudspeakerStatus(bool& enabled) const {
|
||||
LOGI() << "GetLoudspeakerStatus";
|
||||
AVAudioSession* session = [AVAudioSession sharedInstance];
|
||||
RTCAudioSession* session = [RTCAudioSession sharedInstance];
|
||||
AVAudioSessionCategoryOptions options = session.categoryOptions;
|
||||
enabled = options & AVAudioSessionCategoryOptionDefaultToSpeaker;
|
||||
return 0;
|
||||
@ -618,7 +575,7 @@ void AudioDeviceIOS::RegisterNotificationObservers() {
|
||||
|
||||
// Only restart audio for a valid route change and if the
|
||||
// session sample rate has changed.
|
||||
AVAudioSession* session = [AVAudioSession sharedInstance];
|
||||
RTCAudioSession* session = [RTCAudioSession sharedInstance];
|
||||
const double session_sample_rate = session.sampleRate;
|
||||
LOG(LS_INFO) << "session sample rate: " << session_sample_rate;
|
||||
if (playout_parameters_.sample_rate() != session_sample_rate) {
|
||||
@ -672,7 +629,7 @@ void AudioDeviceIOS::UnregisterNotificationObservers() {
|
||||
void AudioDeviceIOS::SetupAudioBuffersForActiveAudioSession() {
|
||||
LOGI() << "SetupAudioBuffersForActiveAudioSession";
|
||||
// Verify the current values once the audio session has been activated.
|
||||
AVAudioSession* session = [AVAudioSession sharedInstance];
|
||||
RTCAudioSession* session = [RTCAudioSession sharedInstance];
|
||||
LOG(LS_INFO) << " sample rate: " << session.sampleRate;
|
||||
LOG(LS_INFO) << " IO buffer duration: " << session.IOBufferDuration;
|
||||
LOG(LS_INFO) << " output channels: " << session.outputNumberOfChannels;
|
||||
@ -954,7 +911,7 @@ bool AudioDeviceIOS::InitPlayOrRecord() {
|
||||
}
|
||||
|
||||
// Ensure that the active audio session has the correct category and mode.
|
||||
AVAudioSession* session = [AVAudioSession sharedInstance];
|
||||
RTCAudioSession* session = [RTCAudioSession sharedInstance];
|
||||
if (!VerifyAudioSession(session)) {
|
||||
DeactivateAudioSession();
|
||||
LOG(LS_ERROR) << "Failed to verify audio session category and mode";
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2016 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RTCAudioSession ()
|
||||
|
||||
/** The lock that guards access to AVAudioSession methods. */
|
||||
@property(nonatomic, strong) NSRecursiveLock *lock;
|
||||
|
||||
/** The delegates. */
|
||||
@property(nonatomic, readonly) NSSet *delegates;
|
||||
|
||||
/** Number of times setActive:YES has succeeded without a balanced call to
|
||||
* setActive:NO.
|
||||
*/
|
||||
@property(nonatomic, readonly) NSInteger activationCount;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
153
webrtc/modules/audio_device/ios/objc/RTCAudioSession.h
Normal file
153
webrtc/modules/audio_device/ios/objc/RTCAudioSession.h
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2016 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString * const kRTCAudioSessionErrorDomain;
|
||||
extern NSInteger const kRTCAudioSessionErrorLockRequired;
|
||||
|
||||
@class RTCAudioSession;
|
||||
|
||||
// Surfaces AVAudioSession events. WebRTC will listen directly for notifications
|
||||
// from AVAudioSession and handle them before calling these delegate methods,
|
||||
// at which point applications can perform additional processing if required.
|
||||
@protocol RTCAudioSessionDelegate <NSObject>
|
||||
|
||||
/** Called when AVAudioSession starts an interruption event. */
|
||||
- (void)audioSessionDidBeginInterruption:(RTCAudioSession *)session;
|
||||
|
||||
/** Called when AVAudioSession ends an interruption event. */
|
||||
- (void)audioSessionDidEndInterruption:(RTCAudioSession *)session
|
||||
shouldResumeSession:(BOOL)shouldResumeSession;
|
||||
|
||||
/** Called when AVAudioSession changes the route. */
|
||||
- (void)audioSessionDidChangeRoute:(RTCAudioSession *)session
|
||||
reason:(AVAudioSessionRouteChangeReason)reason
|
||||
previousRoute:(AVAudioSessionRouteDescription *)previousRoute;
|
||||
|
||||
/** Called when AVAudioSession media server terminates. */
|
||||
- (void)audioSessionMediaServicesWereLost:(RTCAudioSession *)session;
|
||||
|
||||
/** Called when AVAudioSession media server restarts. */
|
||||
- (void)audioSessionMediaServicesWereReset:(RTCAudioSession *)session;
|
||||
|
||||
// TODO(tkchin): Maybe handle SilenceSecondaryAudioHintNotification.
|
||||
|
||||
@end
|
||||
|
||||
/** Proxy class for AVAudioSession that adds a locking mechanism similar to
|
||||
* AVCaptureDevice. This is used to that interleaving configurations between
|
||||
* WebRTC and the application layer are avoided. Only setter methods are
|
||||
* currently proxied. Getters can be accessed directly off AVAudioSession.
|
||||
*
|
||||
* RTCAudioSession also coordinates activation so that the audio session is
|
||||
* activated only once. See |setActive:error:|.
|
||||
*/
|
||||
@interface RTCAudioSession : NSObject
|
||||
|
||||
/** Convenience property to access the AVAudioSession singleton. Callers should
|
||||
* not call setters on AVAudioSession directly, but other method invocations
|
||||
* are fine.
|
||||
*/
|
||||
@property(nonatomic, readonly) AVAudioSession *session;
|
||||
|
||||
/** Our best guess at whether the session is active based on results of calls to
|
||||
* AVAudioSession.
|
||||
*/
|
||||
@property(nonatomic, readonly) BOOL isActive;
|
||||
/** Whether RTCAudioSession is currently locked for configuration. */
|
||||
@property(nonatomic, readonly) BOOL isLocked;
|
||||
|
||||
// Proxy properties.
|
||||
@property(readonly) NSString *category;
|
||||
@property(readonly) AVAudioSessionCategoryOptions categoryOptions;
|
||||
@property(readonly) NSString *mode;
|
||||
@property(readonly) BOOL secondaryAudioShouldBeSilencedHint;
|
||||
@property(readonly) AVAudioSessionRouteDescription *currentRoute;
|
||||
@property(readonly) NSInteger maximumInputNumberOfChannels;
|
||||
@property(readonly) NSInteger maximumOutputNumberOfChannels;
|
||||
@property(readonly) float inputGain;
|
||||
@property(readonly) BOOL inputGainSettable;
|
||||
@property(readonly) BOOL inputAvailable;
|
||||
@property(readonly, nullable)
|
||||
NSArray<AVAudioSessionDataSourceDescription *> * inputDataSources;
|
||||
@property(readonly, nullable)
|
||||
AVAudioSessionDataSourceDescription *inputDataSource;
|
||||
@property(readonly, nullable)
|
||||
NSArray<AVAudioSessionDataSourceDescription *> * outputDataSources;
|
||||
@property(readonly, nullable)
|
||||
AVAudioSessionDataSourceDescription *outputDataSource;
|
||||
@property(readonly) double sampleRate;
|
||||
@property(readonly) NSInteger inputNumberOfChannels;
|
||||
@property(readonly) NSInteger outputNumberOfChannels;
|
||||
@property(readonly) float outputVolume;
|
||||
@property(readonly) NSTimeInterval inputLatency;
|
||||
@property(readonly) NSTimeInterval outputLatency;
|
||||
@property(readonly) NSTimeInterval IOBufferDuration;
|
||||
|
||||
/** Default constructor. Do not call init. */
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/** Adds a delegate, which is held weakly. Even though it's held weakly, callers
|
||||
* should still call |removeDelegate| when it's no longer required to ensure
|
||||
* proper dealloc. This is due to internal use of an NSHashTable.
|
||||
*/
|
||||
- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate;
|
||||
/** Removes an added delegate. */
|
||||
- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate;
|
||||
|
||||
/** Request exclusive access to the audio session for configuration. This call
|
||||
* will block if the lock is held by another object.
|
||||
*/
|
||||
- (void)lockForConfiguration;
|
||||
/** Relinquishes exclusive access to the audio session. */
|
||||
- (void)unlockForConfiguration;
|
||||
|
||||
/** If |active|, activates the audio session if it isn't already active.
|
||||
* Successful calls must be balanced with a setActive:NO when activation is no
|
||||
* longer required. If not |active|, deactivates the audio session if one is
|
||||
* active and this is the last balanced call. When deactivating, the
|
||||
* AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation option is passed to
|
||||
* AVAudioSession.
|
||||
*/
|
||||
- (BOOL)setActive:(BOOL)active
|
||||
error:(NSError **)outError;
|
||||
|
||||
// The following methods are proxies for the associated methods on
|
||||
// AVAudioSession. |lockForConfiguration| must be called before using them
|
||||
// otherwise they will fail with kRTCAudioSessionErrorLockRequired.
|
||||
|
||||
- (BOOL)setCategory:(NSString *)category
|
||||
withOptions:(AVAudioSessionCategoryOptions)options
|
||||
error:(NSError **)outError;
|
||||
- (BOOL)setMode:(NSString *)mode error:(NSError **)outError;
|
||||
- (BOOL)setInputGain:(float)gain error:(NSError **)outError;
|
||||
- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError;
|
||||
- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
|
||||
error:(NSError **)outError;
|
||||
- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
|
||||
error:(NSError **)outError;
|
||||
- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
|
||||
error:(NSError **)outError;
|
||||
- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
|
||||
error:(NSError **)outError;
|
||||
- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
|
||||
error:(NSError **)outError;
|
||||
- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
|
||||
error:(NSError **)outError;
|
||||
- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
|
||||
error:(NSError **)outError;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
533
webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm
Normal file
533
webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm
Normal file
@ -0,0 +1,533 @@
|
||||
/*
|
||||
* Copyright 2016 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h"
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
|
||||
#import "webrtc/base/objc/RTCLogging.h"
|
||||
#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h"
|
||||
|
||||
NSString * const kRTCAudioSessionErrorDomain = @"org.webrtc.RTCAudioSession";
|
||||
NSInteger const kRTCAudioSessionErrorLockRequired = -1;
|
||||
|
||||
// This class needs to be thread-safe because it is accessed from many threads.
|
||||
// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
|
||||
// lock contention so coarse locks should be fine for now.
|
||||
@implementation RTCAudioSession {
|
||||
AVAudioSession *_session;
|
||||
NSHashTable *_delegates;
|
||||
NSInteger _activationCount;
|
||||
BOOL _isActive;
|
||||
BOOL _isLocked;
|
||||
}
|
||||
|
||||
@synthesize session = _session;
|
||||
@synthesize lock = _lock;
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
static RTCAudioSession *sharedInstance = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[RTCAudioSession alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_session = [AVAudioSession sharedInstance];
|
||||
_delegates = [NSHashTable weakObjectsHashTable];
|
||||
_lock = [[NSRecursiveLock alloc] init];
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
[center addObserver:self
|
||||
selector:@selector(handleInterruptionNotification:)
|
||||
name:AVAudioSessionInterruptionNotification
|
||||
object:nil];
|
||||
[center addObserver:self
|
||||
selector:@selector(handleRouteChangeNotification:)
|
||||
name:AVAudioSessionRouteChangeNotification
|
||||
object:nil];
|
||||
// TODO(tkchin): Maybe listen to SilenceSecondaryAudioHintNotification.
|
||||
[center addObserver:self
|
||||
selector:@selector(handleMediaServicesWereLost:)
|
||||
name:AVAudioSessionMediaServicesWereLostNotification
|
||||
object:nil];
|
||||
[center addObserver:self
|
||||
selector:@selector(handleMediaServicesWereReset:)
|
||||
name:AVAudioSessionMediaServicesWereResetNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)setIsActive:(BOOL)isActive {
|
||||
@synchronized(self) {
|
||||
_isActive = isActive;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isActive {
|
||||
@synchronized(self) {
|
||||
return _isActive;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isLocked {
|
||||
@synchronized(self) {
|
||||
return _isLocked;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
|
||||
@synchronized(self) {
|
||||
[_delegates addObject:delegate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
|
||||
@synchronized(self) {
|
||||
[_delegates removeObject:delegate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)lockForConfiguration {
|
||||
[_lock lock];
|
||||
@synchronized(self) {
|
||||
_isLocked = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)unlockForConfiguration {
|
||||
// Don't let threads other than the one that called lockForConfiguration
|
||||
// unlock.
|
||||
if ([_lock tryLock]) {
|
||||
@synchronized(self) {
|
||||
_isLocked = NO;
|
||||
}
|
||||
// One unlock for the tryLock, and another one to actually unlock. If this
|
||||
// was called without anyone calling lock, the underlying NSRecursiveLock
|
||||
// should spit out an error.
|
||||
[_lock unlock];
|
||||
[_lock unlock];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - AVAudioSession proxy methods
|
||||
|
||||
- (NSString *)category {
|
||||
return self.session.category;
|
||||
}
|
||||
|
||||
- (AVAudioSessionCategoryOptions)categoryOptions {
|
||||
return self.session.categoryOptions;
|
||||
}
|
||||
|
||||
- (NSString *)mode {
|
||||
return self.session.mode;
|
||||
}
|
||||
|
||||
- (BOOL)secondaryAudioShouldBeSilencedHint {
|
||||
return self.session.secondaryAudioShouldBeSilencedHint;
|
||||
}
|
||||
|
||||
- (AVAudioSessionRouteDescription *)currentRoute {
|
||||
return self.session.currentRoute;
|
||||
}
|
||||
|
||||
- (NSInteger)maximumInputNumberOfChannels {
|
||||
return self.session.maximumInputNumberOfChannels;
|
||||
}
|
||||
|
||||
- (NSInteger)maximumOutputNumberOfChannels {
|
||||
return self.session.maximumOutputNumberOfChannels;
|
||||
}
|
||||
|
||||
- (float)inputGain {
|
||||
return self.session.inputGain;
|
||||
}
|
||||
|
||||
- (BOOL)inputGainSettable {
|
||||
return self.session.inputGainSettable;
|
||||
}
|
||||
|
||||
- (BOOL)inputAvailable {
|
||||
return self.session.inputAvailable;
|
||||
}
|
||||
|
||||
- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
|
||||
return self.session.inputDataSources;
|
||||
}
|
||||
|
||||
- (AVAudioSessionDataSourceDescription *)inputDataSource {
|
||||
return self.session.inputDataSource;
|
||||
}
|
||||
|
||||
- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
|
||||
return self.session.outputDataSources;
|
||||
}
|
||||
|
||||
- (AVAudioSessionDataSourceDescription *)outputDataSource {
|
||||
return self.session.outputDataSource;
|
||||
}
|
||||
|
||||
- (double)sampleRate {
|
||||
return self.session.sampleRate;
|
||||
}
|
||||
|
||||
- (NSInteger)inputNumberOfChannels {
|
||||
return self.session.inputNumberOfChannels;
|
||||
}
|
||||
|
||||
- (NSInteger)outputNumberOfChannels {
|
||||
return self.session.outputNumberOfChannels;
|
||||
}
|
||||
|
||||
- (float)outputVolume {
|
||||
return self.session.outputVolume;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)inputLatency {
|
||||
return self.session.inputLatency;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)outputLatency {
|
||||
return self.session.outputLatency;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)IOBufferDuration {
|
||||
return self.session.IOBufferDuration;
|
||||
}
|
||||
|
||||
- (BOOL)setActive:(BOOL)active
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
NSInteger activationCount = self.activationCount;
|
||||
if (!active && activationCount == 0) {
|
||||
RTCLogWarning(@"Attempting to deactivate without prior activation.");
|
||||
}
|
||||
BOOL success = YES;
|
||||
BOOL isActive = self.isActive;
|
||||
// Keep a local error so we can log it.
|
||||
NSError *error = nil;
|
||||
BOOL shouldSetActive =
|
||||
(active && !isActive) || (!active && isActive && activationCount == 1);
|
||||
// Attempt to activate if we're not active.
|
||||
// Attempt to deactivate if we're active and it's the last unbalanced call.
|
||||
if (shouldSetActive) {
|
||||
AVAudioSession *session = self.session;
|
||||
// AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
|
||||
// that other audio sessions that were interrupted by our session can return
|
||||
// to their active state. It is recommended for VoIP apps to use this
|
||||
// option.
|
||||
AVAudioSessionSetActiveOptions options =
|
||||
active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
|
||||
success = [session setActive:active
|
||||
withOptions:options
|
||||
error:&error];
|
||||
if (outError) {
|
||||
*outError = error;
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
if (shouldSetActive) {
|
||||
self.isActive = active;
|
||||
}
|
||||
if (active) {
|
||||
[self incrementActivationCount];
|
||||
}
|
||||
} else {
|
||||
RTCLogError(@"Failed to setActive:%d. Error: %@", active, error);
|
||||
}
|
||||
// Decrement activation count on deactivation whether or not it succeeded.
|
||||
if (!active) {
|
||||
[self decrementActivationCount];
|
||||
}
|
||||
RTCLog(@"Number of current activations: %ld", (long)self.activationCount);
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)setCategory:(NSString *)category
|
||||
withOptions:(AVAudioSessionCategoryOptions)options
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setCategory:category withOptions:options error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setMode:mode error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setInputGain:gain error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setPreferredSampleRate:sampleRate error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setPreferredIOBufferDuration:duration error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setPreferredInputNumberOfChannels:count error:outError];
|
||||
}
|
||||
- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setPreferredOutputNumberOfChannels:count error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session overrideOutputAudioPort:portOverride error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setPreferredInput:inPort error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setInputDataSource:dataSource error:outError];
|
||||
}
|
||||
|
||||
- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
|
||||
error:(NSError **)outError {
|
||||
if (![self checkLock:outError]) {
|
||||
return NO;
|
||||
}
|
||||
return [self.session setOutputDataSource:dataSource error:outError];
|
||||
}
|
||||
|
||||
#pragma mark - Notifications
|
||||
|
||||
- (void)handleInterruptionNotification:(NSNotification *)notification {
|
||||
NSNumber* typeNumber =
|
||||
notification.userInfo[AVAudioSessionInterruptionTypeKey];
|
||||
AVAudioSessionInterruptionType type =
|
||||
(AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
|
||||
switch (type) {
|
||||
case AVAudioSessionInterruptionTypeBegan:
|
||||
RTCLog(@"Audio session interruption began.");
|
||||
self.isActive = NO;
|
||||
[self notifyDidBeginInterruption];
|
||||
break;
|
||||
case AVAudioSessionInterruptionTypeEnded: {
|
||||
RTCLog(@"Audio session interruption ended.");
|
||||
[self updateAudioSessionAfterEvent];
|
||||
NSNumber *optionsNumber =
|
||||
notification.userInfo[AVAudioSessionInterruptionOptionKey];
|
||||
AVAudioSessionInterruptionOptions options =
|
||||
optionsNumber.unsignedIntegerValue;
|
||||
BOOL shouldResume =
|
||||
options & AVAudioSessionInterruptionOptionShouldResume;
|
||||
[self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleRouteChangeNotification:(NSNotification *)notification {
|
||||
// Get reason for current route change.
|
||||
NSNumber* reasonNumber =
|
||||
notification.userInfo[AVAudioSessionRouteChangeReasonKey];
|
||||
AVAudioSessionRouteChangeReason reason =
|
||||
(AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
|
||||
RTCLog(@"Audio route changed:");
|
||||
switch (reason) {
|
||||
case AVAudioSessionRouteChangeReasonUnknown:
|
||||
RTCLog(@"Audio route changed: ReasonUnknown");
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
|
||||
RTCLog(@"Audio route changed: NewDeviceAvailable");
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
|
||||
RTCLog(@"Audio route changed: OldDeviceUnavailable");
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonCategoryChange:
|
||||
RTCLog(@"Audio route changed: CategoryChange to :%@",
|
||||
self.session.category);
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonOverride:
|
||||
RTCLog(@"Audio route changed: Override");
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonWakeFromSleep:
|
||||
RTCLog(@"Audio route changed: WakeFromSleep");
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
|
||||
RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
|
||||
RTCLog(@"Audio route changed: RouteConfigurationChange");
|
||||
break;
|
||||
}
|
||||
AVAudioSessionRouteDescription* previousRoute =
|
||||
notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
|
||||
// Log previous route configuration.
|
||||
RTCLog(@"Previous route: %@\nCurrent route:%@",
|
||||
previousRoute, self.session.currentRoute);
|
||||
[self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
|
||||
}
|
||||
|
||||
- (void)handleMediaServicesWereLost:(NSNotification *)notification {
|
||||
RTCLog(@"Media services were lost.");
|
||||
[self updateAudioSessionAfterEvent];
|
||||
[self notifyMediaServicesWereLost];
|
||||
}
|
||||
|
||||
- (void)handleMediaServicesWereReset:(NSNotification *)notification {
|
||||
RTCLog(@"Media services were reset.");
|
||||
[self updateAudioSessionAfterEvent];
|
||||
[self notifyMediaServicesWereReset];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
+ (NSError *)lockError {
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey:
|
||||
@"Must call lockForConfiguration before calling this method."
|
||||
};
|
||||
NSError *error =
|
||||
[[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
|
||||
code:kRTCAudioSessionErrorLockRequired
|
||||
userInfo:userInfo];
|
||||
return error;
|
||||
}
|
||||
|
||||
- (BOOL)checkLock:(NSError **)outError {
|
||||
// Check ivar instead of trying to acquire lock so that we won't accidentally
|
||||
// acquire lock if it hasn't already been called.
|
||||
if (!self.isLocked) {
|
||||
if (outError) {
|
||||
*outError = [RTCAudioSession lockError];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSSet *)delegates {
|
||||
@synchronized(self) {
|
||||
return _delegates.setRepresentation;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)activationCount {
|
||||
@synchronized(self) {
|
||||
return _activationCount;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)incrementActivationCount {
|
||||
RTCLog(@"Incrementing activation count.");
|
||||
@synchronized(self) {
|
||||
return ++_activationCount;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)decrementActivationCount {
|
||||
RTCLog(@"Decrementing activation count.");
|
||||
@synchronized(self) {
|
||||
return --_activationCount;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateAudioSessionAfterEvent {
|
||||
BOOL shouldActivate = self.activationCount > 0;
|
||||
AVAudioSessionSetActiveOptions options = shouldActivate ?
|
||||
0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
|
||||
NSError *error = nil;
|
||||
if ([self.session setActive:shouldActivate
|
||||
withOptions:options
|
||||
error:&error]) {
|
||||
self.isActive = shouldActivate;
|
||||
} else {
|
||||
RTCLogError(@"Failed to set session active to %d. Error:%@",
|
||||
shouldActivate, error.localizedDescription);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyDidBeginInterruption {
|
||||
for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
|
||||
[delegate audioSessionDidBeginInterruption:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyDidEndInterruptionWithShouldResumeSession:
|
||||
(BOOL)shouldResumeSession {
|
||||
for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
|
||||
[delegate audioSessionDidEndInterruption:self
|
||||
shouldResumeSession:shouldResumeSession];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
|
||||
previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
|
||||
for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
|
||||
[delegate audioSessionDidChangeRoute:self
|
||||
reason:reason
|
||||
previousRoute:previousRoute];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyMediaServicesWereLost {
|
||||
for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
|
||||
[delegate audioSessionMediaServicesWereLost:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyMediaServicesWereReset {
|
||||
for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
|
||||
[delegate audioSessionMediaServicesWereReset:self];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Loading…
x
Reference in New Issue
Block a user