xref: /aosp_15_r20/external/webrtc/sdk/objc/components/audio/RTCAudioSession.mm (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1*d9f75844SAndroid Build Coastguard Worker/*
2*d9f75844SAndroid Build Coastguard Worker *  Copyright 2016 The WebRTC Project Authors. All rights reserved.
3*d9f75844SAndroid Build Coastguard Worker *
4*d9f75844SAndroid Build Coastguard Worker *  Use of this source code is governed by a BSD-style license
5*d9f75844SAndroid Build Coastguard Worker *  that can be found in the LICENSE file in the root of the source
6*d9f75844SAndroid Build Coastguard Worker *  tree. An additional intellectual property rights grant can be found
7*d9f75844SAndroid Build Coastguard Worker *  in the file PATENTS.  All contributing project authors may
8*d9f75844SAndroid Build Coastguard Worker *  be found in the AUTHORS file in the root of the source tree.
9*d9f75844SAndroid Build Coastguard Worker */
10*d9f75844SAndroid Build Coastguard Worker
11*d9f75844SAndroid Build Coastguard Worker#import "RTCAudioSession+Private.h"
12*d9f75844SAndroid Build Coastguard Worker
13*d9f75844SAndroid Build Coastguard Worker#import <UIKit/UIKit.h>
14*d9f75844SAndroid Build Coastguard Worker
15*d9f75844SAndroid Build Coastguard Worker#include <atomic>
16*d9f75844SAndroid Build Coastguard Worker#include <vector>
17*d9f75844SAndroid Build Coastguard Worker
18*d9f75844SAndroid Build Coastguard Worker#include "absl/base/attributes.h"
19*d9f75844SAndroid Build Coastguard Worker#include "rtc_base/checks.h"
20*d9f75844SAndroid Build Coastguard Worker#include "rtc_base/synchronization/mutex.h"
21*d9f75844SAndroid Build Coastguard Worker
22*d9f75844SAndroid Build Coastguard Worker#import "RTCAudioSessionConfiguration.h"
23*d9f75844SAndroid Build Coastguard Worker#import "base/RTCLogging.h"
24*d9f75844SAndroid Build Coastguard Worker
25*d9f75844SAndroid Build Coastguard Worker#if !defined(ABSL_HAVE_THREAD_LOCAL)
26*d9f75844SAndroid Build Coastguard Worker#error ABSL_HAVE_THREAD_LOCAL should be defined for MacOS / iOS Targets.
27*d9f75844SAndroid Build Coastguard Worker#endif
28*d9f75844SAndroid Build Coastguard Worker
29*d9f75844SAndroid Build Coastguard WorkerNSString *const kRTCAudioSessionErrorDomain = @"org.webrtc.RTC_OBJC_TYPE(RTCAudioSession)";
30*d9f75844SAndroid Build Coastguard WorkerNSInteger const kRTCAudioSessionErrorLockRequired = -1;
31*d9f75844SAndroid Build Coastguard WorkerNSInteger const kRTCAudioSessionErrorConfiguration = -2;
32*d9f75844SAndroid Build Coastguard WorkerNSString * const kRTCAudioSessionOutputVolumeSelector = @"outputVolume";
33*d9f75844SAndroid Build Coastguard Worker
34*d9f75844SAndroid Build Coastguard Workernamespace {
35*d9f75844SAndroid Build Coastguard Worker// Since webrtc::Mutex is not a reentrant lock and cannot check if the mutex is locked,
36*d9f75844SAndroid Build Coastguard Worker// we need a separate variable to check that the mutex is locked in the RTCAudioSession.
37*d9f75844SAndroid Build Coastguard WorkerABSL_CONST_INIT thread_local bool mutex_locked = false;
38*d9f75844SAndroid Build Coastguard Worker}  // namespace
39*d9f75844SAndroid Build Coastguard Worker
40*d9f75844SAndroid Build Coastguard Worker@interface RTC_OBJC_TYPE (RTCAudioSession)
41*d9f75844SAndroid Build Coastguard Worker() @property(nonatomic,
42*d9f75844SAndroid Build Coastguard Worker             readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates;
43*d9f75844SAndroid Build Coastguard Worker@end
44*d9f75844SAndroid Build Coastguard Worker
45*d9f75844SAndroid Build Coastguard Worker// This class needs to be thread-safe because it is accessed from many threads.
46*d9f75844SAndroid Build Coastguard Worker// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
47*d9f75844SAndroid Build Coastguard Worker// lock contention so coarse locks should be fine for now.
48*d9f75844SAndroid Build Coastguard Worker@implementation RTC_OBJC_TYPE (RTCAudioSession) {
49*d9f75844SAndroid Build Coastguard Worker  webrtc::Mutex _mutex;
50*d9f75844SAndroid Build Coastguard Worker  AVAudioSession *_session;
51*d9f75844SAndroid Build Coastguard Worker  std::atomic<int> _activationCount;
52*d9f75844SAndroid Build Coastguard Worker  std::atomic<int> _webRTCSessionCount;
53*d9f75844SAndroid Build Coastguard Worker  BOOL _isActive;
54*d9f75844SAndroid Build Coastguard Worker  BOOL _useManualAudio;
55*d9f75844SAndroid Build Coastguard Worker  BOOL _isAudioEnabled;
56*d9f75844SAndroid Build Coastguard Worker  BOOL _canPlayOrRecord;
57*d9f75844SAndroid Build Coastguard Worker  BOOL _isInterrupted;
58*d9f75844SAndroid Build Coastguard Worker}
59*d9f75844SAndroid Build Coastguard Worker
60*d9f75844SAndroid Build Coastguard Worker@synthesize session = _session;
61*d9f75844SAndroid Build Coastguard Worker@synthesize delegates = _delegates;
62*d9f75844SAndroid Build Coastguard Worker@synthesize ignoresPreferredAttributeConfigurationErrors =
63*d9f75844SAndroid Build Coastguard Worker    _ignoresPreferredAttributeConfigurationErrors;
64*d9f75844SAndroid Build Coastguard Worker
65*d9f75844SAndroid Build Coastguard Worker+ (instancetype)sharedInstance {
66*d9f75844SAndroid Build Coastguard Worker  static dispatch_once_t onceToken;
67*d9f75844SAndroid Build Coastguard Worker  static RTC_OBJC_TYPE(RTCAudioSession) *sharedInstance = nil;
68*d9f75844SAndroid Build Coastguard Worker  dispatch_once(&onceToken, ^{
69*d9f75844SAndroid Build Coastguard Worker    sharedInstance = [[self alloc] init];
70*d9f75844SAndroid Build Coastguard Worker  });
71*d9f75844SAndroid Build Coastguard Worker  return sharedInstance;
72*d9f75844SAndroid Build Coastguard Worker}
73*d9f75844SAndroid Build Coastguard Worker
74*d9f75844SAndroid Build Coastguard Worker- (instancetype)init {
75*d9f75844SAndroid Build Coastguard Worker  return [self initWithAudioSession:[AVAudioSession sharedInstance]];
76*d9f75844SAndroid Build Coastguard Worker}
77*d9f75844SAndroid Build Coastguard Worker
78*d9f75844SAndroid Build Coastguard Worker/** This initializer provides a way for unit tests to inject a fake/mock audio session. */
79*d9f75844SAndroid Build Coastguard Worker- (instancetype)initWithAudioSession:(id)audioSession {
80*d9f75844SAndroid Build Coastguard Worker  if (self = [super init]) {
81*d9f75844SAndroid Build Coastguard Worker    _session = audioSession;
82*d9f75844SAndroid Build Coastguard Worker
83*d9f75844SAndroid Build Coastguard Worker    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
84*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
85*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleInterruptionNotification:)
86*d9f75844SAndroid Build Coastguard Worker                   name:AVAudioSessionInterruptionNotification
87*d9f75844SAndroid Build Coastguard Worker                 object:nil];
88*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
89*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleRouteChangeNotification:)
90*d9f75844SAndroid Build Coastguard Worker                   name:AVAudioSessionRouteChangeNotification
91*d9f75844SAndroid Build Coastguard Worker                 object:nil];
92*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
93*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleMediaServicesWereLost:)
94*d9f75844SAndroid Build Coastguard Worker                   name:AVAudioSessionMediaServicesWereLostNotification
95*d9f75844SAndroid Build Coastguard Worker                 object:nil];
96*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
97*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleMediaServicesWereReset:)
98*d9f75844SAndroid Build Coastguard Worker                   name:AVAudioSessionMediaServicesWereResetNotification
99*d9f75844SAndroid Build Coastguard Worker                 object:nil];
100*d9f75844SAndroid Build Coastguard Worker    // Posted on the main thread when the primary audio from other applications
101*d9f75844SAndroid Build Coastguard Worker    // starts and stops. Foreground applications may use this notification as a
102*d9f75844SAndroid Build Coastguard Worker    // hint to enable or disable audio that is secondary.
103*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
104*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleSilenceSecondaryAudioHintNotification:)
105*d9f75844SAndroid Build Coastguard Worker                   name:AVAudioSessionSilenceSecondaryAudioHintNotification
106*d9f75844SAndroid Build Coastguard Worker                 object:nil];
107*d9f75844SAndroid Build Coastguard Worker    // Also track foreground event in order to deal with interruption ended situation.
108*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
109*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleApplicationDidBecomeActive:)
110*d9f75844SAndroid Build Coastguard Worker                   name:UIApplicationDidBecomeActiveNotification
111*d9f75844SAndroid Build Coastguard Worker                 object:nil];
112*d9f75844SAndroid Build Coastguard Worker    [_session addObserver:self
113*d9f75844SAndroid Build Coastguard Worker               forKeyPath:kRTCAudioSessionOutputVolumeSelector
114*d9f75844SAndroid Build Coastguard Worker                  options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
115*d9f75844SAndroid Build Coastguard Worker                  context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
116*d9f75844SAndroid Build Coastguard Worker
117*d9f75844SAndroid Build Coastguard Worker    RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): init.", self);
118*d9f75844SAndroid Build Coastguard Worker  }
119*d9f75844SAndroid Build Coastguard Worker  return self;
120*d9f75844SAndroid Build Coastguard Worker}
121*d9f75844SAndroid Build Coastguard Worker
122*d9f75844SAndroid Build Coastguard Worker- (void)dealloc {
123*d9f75844SAndroid Build Coastguard Worker  [[NSNotificationCenter defaultCenter] removeObserver:self];
124*d9f75844SAndroid Build Coastguard Worker  [_session removeObserver:self
125*d9f75844SAndroid Build Coastguard Worker                forKeyPath:kRTCAudioSessionOutputVolumeSelector
126*d9f75844SAndroid Build Coastguard Worker                   context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
127*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): dealloc.", self);
128*d9f75844SAndroid Build Coastguard Worker}
129*d9f75844SAndroid Build Coastguard Worker
130*d9f75844SAndroid Build Coastguard Worker- (NSString *)description {
131*d9f75844SAndroid Build Coastguard Worker  NSString *format = @"RTC_OBJC_TYPE(RTCAudioSession): {\n"
132*d9f75844SAndroid Build Coastguard Worker                      "  category: %@\n"
133*d9f75844SAndroid Build Coastguard Worker                      "  categoryOptions: %ld\n"
134*d9f75844SAndroid Build Coastguard Worker                      "  mode: %@\n"
135*d9f75844SAndroid Build Coastguard Worker                      "  isActive: %d\n"
136*d9f75844SAndroid Build Coastguard Worker                      "  sampleRate: %.2f\n"
137*d9f75844SAndroid Build Coastguard Worker                      "  IOBufferDuration: %f\n"
138*d9f75844SAndroid Build Coastguard Worker                      "  outputNumberOfChannels: %ld\n"
139*d9f75844SAndroid Build Coastguard Worker                      "  inputNumberOfChannels: %ld\n"
140*d9f75844SAndroid Build Coastguard Worker                      "  outputLatency: %f\n"
141*d9f75844SAndroid Build Coastguard Worker                      "  inputLatency: %f\n"
142*d9f75844SAndroid Build Coastguard Worker                      "  outputVolume: %f\n"
143*d9f75844SAndroid Build Coastguard Worker                      "}";
144*d9f75844SAndroid Build Coastguard Worker  NSString *description = [NSString stringWithFormat:format,
145*d9f75844SAndroid Build Coastguard Worker      self.category, (long)self.categoryOptions, self.mode,
146*d9f75844SAndroid Build Coastguard Worker      self.isActive, self.sampleRate, self.IOBufferDuration,
147*d9f75844SAndroid Build Coastguard Worker      self.outputNumberOfChannels, self.inputNumberOfChannels,
148*d9f75844SAndroid Build Coastguard Worker      self.outputLatency, self.inputLatency, self.outputVolume];
149*d9f75844SAndroid Build Coastguard Worker  return description;
150*d9f75844SAndroid Build Coastguard Worker}
151*d9f75844SAndroid Build Coastguard Worker
152*d9f75844SAndroid Build Coastguard Worker- (void)setIsActive:(BOOL)isActive {
153*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
154*d9f75844SAndroid Build Coastguard Worker    _isActive = isActive;
155*d9f75844SAndroid Build Coastguard Worker  }
156*d9f75844SAndroid Build Coastguard Worker}
157*d9f75844SAndroid Build Coastguard Worker
158*d9f75844SAndroid Build Coastguard Worker- (BOOL)isActive {
159*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
160*d9f75844SAndroid Build Coastguard Worker    return _isActive;
161*d9f75844SAndroid Build Coastguard Worker  }
162*d9f75844SAndroid Build Coastguard Worker}
163*d9f75844SAndroid Build Coastguard Worker
164*d9f75844SAndroid Build Coastguard Worker- (void)setUseManualAudio:(BOOL)useManualAudio {
165*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
166*d9f75844SAndroid Build Coastguard Worker    if (_useManualAudio == useManualAudio) {
167*d9f75844SAndroid Build Coastguard Worker      return;
168*d9f75844SAndroid Build Coastguard Worker    }
169*d9f75844SAndroid Build Coastguard Worker    _useManualAudio = useManualAudio;
170*d9f75844SAndroid Build Coastguard Worker  }
171*d9f75844SAndroid Build Coastguard Worker  [self updateCanPlayOrRecord];
172*d9f75844SAndroid Build Coastguard Worker}
173*d9f75844SAndroid Build Coastguard Worker
174*d9f75844SAndroid Build Coastguard Worker- (BOOL)useManualAudio {
175*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
176*d9f75844SAndroid Build Coastguard Worker    return _useManualAudio;
177*d9f75844SAndroid Build Coastguard Worker  }
178*d9f75844SAndroid Build Coastguard Worker}
179*d9f75844SAndroid Build Coastguard Worker
180*d9f75844SAndroid Build Coastguard Worker- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
181*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
182*d9f75844SAndroid Build Coastguard Worker    if (_isAudioEnabled == isAudioEnabled) {
183*d9f75844SAndroid Build Coastguard Worker      return;
184*d9f75844SAndroid Build Coastguard Worker    }
185*d9f75844SAndroid Build Coastguard Worker    _isAudioEnabled = isAudioEnabled;
186*d9f75844SAndroid Build Coastguard Worker  }
187*d9f75844SAndroid Build Coastguard Worker  [self updateCanPlayOrRecord];
188*d9f75844SAndroid Build Coastguard Worker}
189*d9f75844SAndroid Build Coastguard Worker
190*d9f75844SAndroid Build Coastguard Worker- (BOOL)isAudioEnabled {
191*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
192*d9f75844SAndroid Build Coastguard Worker    return _isAudioEnabled;
193*d9f75844SAndroid Build Coastguard Worker  }
194*d9f75844SAndroid Build Coastguard Worker}
195*d9f75844SAndroid Build Coastguard Worker
196*d9f75844SAndroid Build Coastguard Worker- (void)setIgnoresPreferredAttributeConfigurationErrors:
197*d9f75844SAndroid Build Coastguard Worker    (BOOL)ignoresPreferredAttributeConfigurationErrors {
198*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
199*d9f75844SAndroid Build Coastguard Worker    if (_ignoresPreferredAttributeConfigurationErrors ==
200*d9f75844SAndroid Build Coastguard Worker        ignoresPreferredAttributeConfigurationErrors) {
201*d9f75844SAndroid Build Coastguard Worker      return;
202*d9f75844SAndroid Build Coastguard Worker    }
203*d9f75844SAndroid Build Coastguard Worker    _ignoresPreferredAttributeConfigurationErrors = ignoresPreferredAttributeConfigurationErrors;
204*d9f75844SAndroid Build Coastguard Worker  }
205*d9f75844SAndroid Build Coastguard Worker}
206*d9f75844SAndroid Build Coastguard Worker
207*d9f75844SAndroid Build Coastguard Worker- (BOOL)ignoresPreferredAttributeConfigurationErrors {
208*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
209*d9f75844SAndroid Build Coastguard Worker    return _ignoresPreferredAttributeConfigurationErrors;
210*d9f75844SAndroid Build Coastguard Worker  }
211*d9f75844SAndroid Build Coastguard Worker}
212*d9f75844SAndroid Build Coastguard Worker
213*d9f75844SAndroid Build Coastguard Worker// TODO(tkchin): Check for duplicates.
214*d9f75844SAndroid Build Coastguard Worker- (void)addDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
215*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Adding delegate: (%p)", delegate);
216*d9f75844SAndroid Build Coastguard Worker  if (!delegate) {
217*d9f75844SAndroid Build Coastguard Worker    return;
218*d9f75844SAndroid Build Coastguard Worker  }
219*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
220*d9f75844SAndroid Build Coastguard Worker    _delegates.push_back(delegate);
221*d9f75844SAndroid Build Coastguard Worker    [self removeZeroedDelegates];
222*d9f75844SAndroid Build Coastguard Worker  }
223*d9f75844SAndroid Build Coastguard Worker}
224*d9f75844SAndroid Build Coastguard Worker
225*d9f75844SAndroid Build Coastguard Worker- (void)removeDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
226*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Removing delegate: (%p)", delegate);
227*d9f75844SAndroid Build Coastguard Worker  if (!delegate) {
228*d9f75844SAndroid Build Coastguard Worker    return;
229*d9f75844SAndroid Build Coastguard Worker  }
230*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
231*d9f75844SAndroid Build Coastguard Worker    _delegates.erase(std::remove(_delegates.begin(),
232*d9f75844SAndroid Build Coastguard Worker                                 _delegates.end(),
233*d9f75844SAndroid Build Coastguard Worker                                 delegate),
234*d9f75844SAndroid Build Coastguard Worker                     _delegates.end());
235*d9f75844SAndroid Build Coastguard Worker    [self removeZeroedDelegates];
236*d9f75844SAndroid Build Coastguard Worker  }
237*d9f75844SAndroid Build Coastguard Worker}
238*d9f75844SAndroid Build Coastguard Worker
239*d9f75844SAndroid Build Coastguard Worker#pragma clang diagnostic push
240*d9f75844SAndroid Build Coastguard Worker#pragma clang diagnostic ignored "-Wthread-safety-analysis"
241*d9f75844SAndroid Build Coastguard Worker
242*d9f75844SAndroid Build Coastguard Worker- (void)lockForConfiguration {
243*d9f75844SAndroid Build Coastguard Worker  RTC_CHECK(!mutex_locked);
244*d9f75844SAndroid Build Coastguard Worker  _mutex.Lock();
245*d9f75844SAndroid Build Coastguard Worker  mutex_locked = true;
246*d9f75844SAndroid Build Coastguard Worker}
247*d9f75844SAndroid Build Coastguard Worker
248*d9f75844SAndroid Build Coastguard Worker- (void)unlockForConfiguration {
249*d9f75844SAndroid Build Coastguard Worker  mutex_locked = false;
250*d9f75844SAndroid Build Coastguard Worker  _mutex.Unlock();
251*d9f75844SAndroid Build Coastguard Worker}
252*d9f75844SAndroid Build Coastguard Worker
253*d9f75844SAndroid Build Coastguard Worker#pragma clang diagnostic pop
254*d9f75844SAndroid Build Coastguard Worker
255*d9f75844SAndroid Build Coastguard Worker#pragma mark - AVAudioSession proxy methods
256*d9f75844SAndroid Build Coastguard Worker
257*d9f75844SAndroid Build Coastguard Worker- (NSString *)category {
258*d9f75844SAndroid Build Coastguard Worker  return self.session.category;
259*d9f75844SAndroid Build Coastguard Worker}
260*d9f75844SAndroid Build Coastguard Worker
261*d9f75844SAndroid Build Coastguard Worker- (AVAudioSessionCategoryOptions)categoryOptions {
262*d9f75844SAndroid Build Coastguard Worker  return self.session.categoryOptions;
263*d9f75844SAndroid Build Coastguard Worker}
264*d9f75844SAndroid Build Coastguard Worker
265*d9f75844SAndroid Build Coastguard Worker- (NSString *)mode {
266*d9f75844SAndroid Build Coastguard Worker  return self.session.mode;
267*d9f75844SAndroid Build Coastguard Worker}
268*d9f75844SAndroid Build Coastguard Worker
269*d9f75844SAndroid Build Coastguard Worker- (BOOL)secondaryAudioShouldBeSilencedHint {
270*d9f75844SAndroid Build Coastguard Worker  return self.session.secondaryAudioShouldBeSilencedHint;
271*d9f75844SAndroid Build Coastguard Worker}
272*d9f75844SAndroid Build Coastguard Worker
273*d9f75844SAndroid Build Coastguard Worker- (AVAudioSessionRouteDescription *)currentRoute {
274*d9f75844SAndroid Build Coastguard Worker  return self.session.currentRoute;
275*d9f75844SAndroid Build Coastguard Worker}
276*d9f75844SAndroid Build Coastguard Worker
277*d9f75844SAndroid Build Coastguard Worker- (NSInteger)maximumInputNumberOfChannels {
278*d9f75844SAndroid Build Coastguard Worker  return self.session.maximumInputNumberOfChannels;
279*d9f75844SAndroid Build Coastguard Worker}
280*d9f75844SAndroid Build Coastguard Worker
281*d9f75844SAndroid Build Coastguard Worker- (NSInteger)maximumOutputNumberOfChannels {
282*d9f75844SAndroid Build Coastguard Worker  return self.session.maximumOutputNumberOfChannels;
283*d9f75844SAndroid Build Coastguard Worker}
284*d9f75844SAndroid Build Coastguard Worker
285*d9f75844SAndroid Build Coastguard Worker- (float)inputGain {
286*d9f75844SAndroid Build Coastguard Worker  return self.session.inputGain;
287*d9f75844SAndroid Build Coastguard Worker}
288*d9f75844SAndroid Build Coastguard Worker
289*d9f75844SAndroid Build Coastguard Worker- (BOOL)inputGainSettable {
290*d9f75844SAndroid Build Coastguard Worker  return self.session.inputGainSettable;
291*d9f75844SAndroid Build Coastguard Worker}
292*d9f75844SAndroid Build Coastguard Worker
293*d9f75844SAndroid Build Coastguard Worker- (BOOL)inputAvailable {
294*d9f75844SAndroid Build Coastguard Worker  return self.session.inputAvailable;
295*d9f75844SAndroid Build Coastguard Worker}
296*d9f75844SAndroid Build Coastguard Worker
297*d9f75844SAndroid Build Coastguard Worker- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
298*d9f75844SAndroid Build Coastguard Worker  return self.session.inputDataSources;
299*d9f75844SAndroid Build Coastguard Worker}
300*d9f75844SAndroid Build Coastguard Worker
301*d9f75844SAndroid Build Coastguard Worker- (AVAudioSessionDataSourceDescription *)inputDataSource {
302*d9f75844SAndroid Build Coastguard Worker  return self.session.inputDataSource;
303*d9f75844SAndroid Build Coastguard Worker}
304*d9f75844SAndroid Build Coastguard Worker
305*d9f75844SAndroid Build Coastguard Worker- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
306*d9f75844SAndroid Build Coastguard Worker  return self.session.outputDataSources;
307*d9f75844SAndroid Build Coastguard Worker}
308*d9f75844SAndroid Build Coastguard Worker
309*d9f75844SAndroid Build Coastguard Worker- (AVAudioSessionDataSourceDescription *)outputDataSource {
310*d9f75844SAndroid Build Coastguard Worker  return self.session.outputDataSource;
311*d9f75844SAndroid Build Coastguard Worker}
312*d9f75844SAndroid Build Coastguard Worker
313*d9f75844SAndroid Build Coastguard Worker- (double)sampleRate {
314*d9f75844SAndroid Build Coastguard Worker  return self.session.sampleRate;
315*d9f75844SAndroid Build Coastguard Worker}
316*d9f75844SAndroid Build Coastguard Worker
317*d9f75844SAndroid Build Coastguard Worker- (double)preferredSampleRate {
318*d9f75844SAndroid Build Coastguard Worker  return self.session.preferredSampleRate;
319*d9f75844SAndroid Build Coastguard Worker}
320*d9f75844SAndroid Build Coastguard Worker
321*d9f75844SAndroid Build Coastguard Worker- (NSInteger)inputNumberOfChannels {
322*d9f75844SAndroid Build Coastguard Worker  return self.session.inputNumberOfChannels;
323*d9f75844SAndroid Build Coastguard Worker}
324*d9f75844SAndroid Build Coastguard Worker
325*d9f75844SAndroid Build Coastguard Worker- (NSInteger)outputNumberOfChannels {
326*d9f75844SAndroid Build Coastguard Worker  return self.session.outputNumberOfChannels;
327*d9f75844SAndroid Build Coastguard Worker}
328*d9f75844SAndroid Build Coastguard Worker
329*d9f75844SAndroid Build Coastguard Worker- (float)outputVolume {
330*d9f75844SAndroid Build Coastguard Worker  return self.session.outputVolume;
331*d9f75844SAndroid Build Coastguard Worker}
332*d9f75844SAndroid Build Coastguard Worker
333*d9f75844SAndroid Build Coastguard Worker- (NSTimeInterval)inputLatency {
334*d9f75844SAndroid Build Coastguard Worker  return self.session.inputLatency;
335*d9f75844SAndroid Build Coastguard Worker}
336*d9f75844SAndroid Build Coastguard Worker
337*d9f75844SAndroid Build Coastguard Worker- (NSTimeInterval)outputLatency {
338*d9f75844SAndroid Build Coastguard Worker  return self.session.outputLatency;
339*d9f75844SAndroid Build Coastguard Worker}
340*d9f75844SAndroid Build Coastguard Worker
341*d9f75844SAndroid Build Coastguard Worker- (NSTimeInterval)IOBufferDuration {
342*d9f75844SAndroid Build Coastguard Worker  return self.session.IOBufferDuration;
343*d9f75844SAndroid Build Coastguard Worker}
344*d9f75844SAndroid Build Coastguard Worker
345*d9f75844SAndroid Build Coastguard Worker- (NSTimeInterval)preferredIOBufferDuration {
346*d9f75844SAndroid Build Coastguard Worker  return self.session.preferredIOBufferDuration;
347*d9f75844SAndroid Build Coastguard Worker}
348*d9f75844SAndroid Build Coastguard Worker
349*d9f75844SAndroid Build Coastguard Worker- (BOOL)setActive:(BOOL)active
350*d9f75844SAndroid Build Coastguard Worker            error:(NSError **)outError {
351*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
352*d9f75844SAndroid Build Coastguard Worker    return NO;
353*d9f75844SAndroid Build Coastguard Worker  }
354*d9f75844SAndroid Build Coastguard Worker  int activationCount = _activationCount.load();
355*d9f75844SAndroid Build Coastguard Worker  if (!active && activationCount == 0) {
356*d9f75844SAndroid Build Coastguard Worker    RTCLogWarning(@"Attempting to deactivate without prior activation.");
357*d9f75844SAndroid Build Coastguard Worker  }
358*d9f75844SAndroid Build Coastguard Worker  [self notifyWillSetActive:active];
359*d9f75844SAndroid Build Coastguard Worker  BOOL success = YES;
360*d9f75844SAndroid Build Coastguard Worker  BOOL isActive = self.isActive;
361*d9f75844SAndroid Build Coastguard Worker  // Keep a local error so we can log it.
362*d9f75844SAndroid Build Coastguard Worker  NSError *error = nil;
363*d9f75844SAndroid Build Coastguard Worker  BOOL shouldSetActive =
364*d9f75844SAndroid Build Coastguard Worker      (active && !isActive) || (!active && isActive && activationCount == 1);
365*d9f75844SAndroid Build Coastguard Worker  // Attempt to activate if we're not active.
366*d9f75844SAndroid Build Coastguard Worker  // Attempt to deactivate if we're active and it's the last unbalanced call.
367*d9f75844SAndroid Build Coastguard Worker  if (shouldSetActive) {
368*d9f75844SAndroid Build Coastguard Worker    AVAudioSession *session = self.session;
369*d9f75844SAndroid Build Coastguard Worker    // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
370*d9f75844SAndroid Build Coastguard Worker    // that other audio sessions that were interrupted by our session can return
371*d9f75844SAndroid Build Coastguard Worker    // to their active state. It is recommended for VoIP apps to use this
372*d9f75844SAndroid Build Coastguard Worker    // option.
373*d9f75844SAndroid Build Coastguard Worker    AVAudioSessionSetActiveOptions options =
374*d9f75844SAndroid Build Coastguard Worker        active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
375*d9f75844SAndroid Build Coastguard Worker    success = [session setActive:active
376*d9f75844SAndroid Build Coastguard Worker                     withOptions:options
377*d9f75844SAndroid Build Coastguard Worker                           error:&error];
378*d9f75844SAndroid Build Coastguard Worker    if (outError) {
379*d9f75844SAndroid Build Coastguard Worker      *outError = error;
380*d9f75844SAndroid Build Coastguard Worker    }
381*d9f75844SAndroid Build Coastguard Worker  }
382*d9f75844SAndroid Build Coastguard Worker  if (success) {
383*d9f75844SAndroid Build Coastguard Worker    if (active) {
384*d9f75844SAndroid Build Coastguard Worker      if (shouldSetActive) {
385*d9f75844SAndroid Build Coastguard Worker        self.isActive = active;
386*d9f75844SAndroid Build Coastguard Worker        if (self.isInterrupted) {
387*d9f75844SAndroid Build Coastguard Worker          self.isInterrupted = NO;
388*d9f75844SAndroid Build Coastguard Worker          [self notifyDidEndInterruptionWithShouldResumeSession:YES];
389*d9f75844SAndroid Build Coastguard Worker        }
390*d9f75844SAndroid Build Coastguard Worker      }
391*d9f75844SAndroid Build Coastguard Worker      [self incrementActivationCount];
392*d9f75844SAndroid Build Coastguard Worker      [self notifyDidSetActive:active];
393*d9f75844SAndroid Build Coastguard Worker    }
394*d9f75844SAndroid Build Coastguard Worker  } else {
395*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"Failed to setActive:%d. Error: %@",
396*d9f75844SAndroid Build Coastguard Worker                active, error.localizedDescription);
397*d9f75844SAndroid Build Coastguard Worker    [self notifyFailedToSetActive:active error:error];
398*d9f75844SAndroid Build Coastguard Worker  }
399*d9f75844SAndroid Build Coastguard Worker  // Set isActive and decrement activation count on deactivation
400*d9f75844SAndroid Build Coastguard Worker  // whether or not it succeeded.
401*d9f75844SAndroid Build Coastguard Worker  if (!active) {
402*d9f75844SAndroid Build Coastguard Worker    self.isActive = active;
403*d9f75844SAndroid Build Coastguard Worker    [self notifyDidSetActive:active];
404*d9f75844SAndroid Build Coastguard Worker    [self decrementActivationCount];
405*d9f75844SAndroid Build Coastguard Worker  }
406*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Number of current activations: %d", _activationCount.load());
407*d9f75844SAndroid Build Coastguard Worker  return success;
408*d9f75844SAndroid Build Coastguard Worker}
409*d9f75844SAndroid Build Coastguard Worker
410*d9f75844SAndroid Build Coastguard Worker- (BOOL)setCategory:(NSString *)category
411*d9f75844SAndroid Build Coastguard Worker        withOptions:(AVAudioSessionCategoryOptions)options
412*d9f75844SAndroid Build Coastguard Worker              error:(NSError **)outError {
413*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
414*d9f75844SAndroid Build Coastguard Worker    return NO;
415*d9f75844SAndroid Build Coastguard Worker  }
416*d9f75844SAndroid Build Coastguard Worker  return [self.session setCategory:category withOptions:options error:outError];
417*d9f75844SAndroid Build Coastguard Worker}
418*d9f75844SAndroid Build Coastguard Worker
419*d9f75844SAndroid Build Coastguard Worker- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
420*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
421*d9f75844SAndroid Build Coastguard Worker    return NO;
422*d9f75844SAndroid Build Coastguard Worker  }
423*d9f75844SAndroid Build Coastguard Worker  return [self.session setMode:mode error:outError];
424*d9f75844SAndroid Build Coastguard Worker}
425*d9f75844SAndroid Build Coastguard Worker
426*d9f75844SAndroid Build Coastguard Worker- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
427*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
428*d9f75844SAndroid Build Coastguard Worker    return NO;
429*d9f75844SAndroid Build Coastguard Worker  }
430*d9f75844SAndroid Build Coastguard Worker  return [self.session setInputGain:gain error:outError];
431*d9f75844SAndroid Build Coastguard Worker}
432*d9f75844SAndroid Build Coastguard Worker
433*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
434*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
435*d9f75844SAndroid Build Coastguard Worker    return NO;
436*d9f75844SAndroid Build Coastguard Worker  }
437*d9f75844SAndroid Build Coastguard Worker  return [self.session setPreferredSampleRate:sampleRate error:outError];
438*d9f75844SAndroid Build Coastguard Worker}
439*d9f75844SAndroid Build Coastguard Worker
440*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
441*d9f75844SAndroid Build Coastguard Worker                               error:(NSError **)outError {
442*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
443*d9f75844SAndroid Build Coastguard Worker    return NO;
444*d9f75844SAndroid Build Coastguard Worker  }
445*d9f75844SAndroid Build Coastguard Worker  return [self.session setPreferredIOBufferDuration:duration error:outError];
446*d9f75844SAndroid Build Coastguard Worker}
447*d9f75844SAndroid Build Coastguard Worker
448*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
449*d9f75844SAndroid Build Coastguard Worker                                    error:(NSError **)outError {
450*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
451*d9f75844SAndroid Build Coastguard Worker    return NO;
452*d9f75844SAndroid Build Coastguard Worker  }
453*d9f75844SAndroid Build Coastguard Worker  return [self.session setPreferredInputNumberOfChannels:count error:outError];
454*d9f75844SAndroid Build Coastguard Worker}
455*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
456*d9f75844SAndroid Build Coastguard Worker                                     error:(NSError **)outError {
457*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
458*d9f75844SAndroid Build Coastguard Worker    return NO;
459*d9f75844SAndroid Build Coastguard Worker  }
460*d9f75844SAndroid Build Coastguard Worker  return [self.session setPreferredOutputNumberOfChannels:count error:outError];
461*d9f75844SAndroid Build Coastguard Worker}
462*d9f75844SAndroid Build Coastguard Worker
463*d9f75844SAndroid Build Coastguard Worker- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
464*d9f75844SAndroid Build Coastguard Worker                          error:(NSError **)outError {
465*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
466*d9f75844SAndroid Build Coastguard Worker    return NO;
467*d9f75844SAndroid Build Coastguard Worker  }
468*d9f75844SAndroid Build Coastguard Worker  return [self.session overrideOutputAudioPort:portOverride error:outError];
469*d9f75844SAndroid Build Coastguard Worker}
470*d9f75844SAndroid Build Coastguard Worker
471*d9f75844SAndroid Build Coastguard Worker- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
472*d9f75844SAndroid Build Coastguard Worker                    error:(NSError **)outError {
473*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
474*d9f75844SAndroid Build Coastguard Worker    return NO;
475*d9f75844SAndroid Build Coastguard Worker  }
476*d9f75844SAndroid Build Coastguard Worker  return [self.session setPreferredInput:inPort error:outError];
477*d9f75844SAndroid Build Coastguard Worker}
478*d9f75844SAndroid Build Coastguard Worker
479*d9f75844SAndroid Build Coastguard Worker- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
480*d9f75844SAndroid Build Coastguard Worker                     error:(NSError **)outError {
481*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
482*d9f75844SAndroid Build Coastguard Worker    return NO;
483*d9f75844SAndroid Build Coastguard Worker  }
484*d9f75844SAndroid Build Coastguard Worker  return [self.session setInputDataSource:dataSource error:outError];
485*d9f75844SAndroid Build Coastguard Worker}
486*d9f75844SAndroid Build Coastguard Worker
487*d9f75844SAndroid Build Coastguard Worker- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
488*d9f75844SAndroid Build Coastguard Worker                      error:(NSError **)outError {
489*d9f75844SAndroid Build Coastguard Worker  if (![self checkLock:outError]) {
490*d9f75844SAndroid Build Coastguard Worker    return NO;
491*d9f75844SAndroid Build Coastguard Worker  }
492*d9f75844SAndroid Build Coastguard Worker  return [self.session setOutputDataSource:dataSource error:outError];
493*d9f75844SAndroid Build Coastguard Worker}
494*d9f75844SAndroid Build Coastguard Worker
495*d9f75844SAndroid Build Coastguard Worker#pragma mark - Notifications
496*d9f75844SAndroid Build Coastguard Worker
497*d9f75844SAndroid Build Coastguard Worker- (void)handleInterruptionNotification:(NSNotification *)notification {
498*d9f75844SAndroid Build Coastguard Worker  NSNumber* typeNumber =
499*d9f75844SAndroid Build Coastguard Worker      notification.userInfo[AVAudioSessionInterruptionTypeKey];
500*d9f75844SAndroid Build Coastguard Worker  AVAudioSessionInterruptionType type =
501*d9f75844SAndroid Build Coastguard Worker      (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
502*d9f75844SAndroid Build Coastguard Worker  switch (type) {
503*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionInterruptionTypeBegan:
504*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio session interruption began.");
505*d9f75844SAndroid Build Coastguard Worker      self.isActive = NO;
506*d9f75844SAndroid Build Coastguard Worker      self.isInterrupted = YES;
507*d9f75844SAndroid Build Coastguard Worker      [self notifyDidBeginInterruption];
508*d9f75844SAndroid Build Coastguard Worker      break;
509*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionInterruptionTypeEnded: {
510*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio session interruption ended.");
511*d9f75844SAndroid Build Coastguard Worker      self.isInterrupted = NO;
512*d9f75844SAndroid Build Coastguard Worker      [self updateAudioSessionAfterEvent];
513*d9f75844SAndroid Build Coastguard Worker      NSNumber *optionsNumber =
514*d9f75844SAndroid Build Coastguard Worker          notification.userInfo[AVAudioSessionInterruptionOptionKey];
515*d9f75844SAndroid Build Coastguard Worker      AVAudioSessionInterruptionOptions options =
516*d9f75844SAndroid Build Coastguard Worker          optionsNumber.unsignedIntegerValue;
517*d9f75844SAndroid Build Coastguard Worker      BOOL shouldResume =
518*d9f75844SAndroid Build Coastguard Worker          options & AVAudioSessionInterruptionOptionShouldResume;
519*d9f75844SAndroid Build Coastguard Worker      [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
520*d9f75844SAndroid Build Coastguard Worker      break;
521*d9f75844SAndroid Build Coastguard Worker    }
522*d9f75844SAndroid Build Coastguard Worker  }
523*d9f75844SAndroid Build Coastguard Worker}
524*d9f75844SAndroid Build Coastguard Worker
525*d9f75844SAndroid Build Coastguard Worker- (void)handleRouteChangeNotification:(NSNotification *)notification {
526*d9f75844SAndroid Build Coastguard Worker  // Get reason for current route change.
527*d9f75844SAndroid Build Coastguard Worker  NSNumber* reasonNumber =
528*d9f75844SAndroid Build Coastguard Worker      notification.userInfo[AVAudioSessionRouteChangeReasonKey];
529*d9f75844SAndroid Build Coastguard Worker  AVAudioSessionRouteChangeReason reason =
530*d9f75844SAndroid Build Coastguard Worker      (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
531*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Audio route changed:");
532*d9f75844SAndroid Build Coastguard Worker  switch (reason) {
533*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionRouteChangeReasonUnknown:
534*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio route changed: ReasonUnknown");
535*d9f75844SAndroid Build Coastguard Worker      break;
536*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
537*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio route changed: NewDeviceAvailable");
538*d9f75844SAndroid Build Coastguard Worker      break;
539*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
540*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio route changed: OldDeviceUnavailable");
541*d9f75844SAndroid Build Coastguard Worker      break;
542*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionRouteChangeReasonCategoryChange:
543*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio route changed: CategoryChange to :%@",
544*d9f75844SAndroid Build Coastguard Worker             self.session.category);
545*d9f75844SAndroid Build Coastguard Worker      break;
546*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionRouteChangeReasonOverride:
547*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio route changed: Override");
548*d9f75844SAndroid Build Coastguard Worker      break;
549*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionRouteChangeReasonWakeFromSleep:
550*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio route changed: WakeFromSleep");
551*d9f75844SAndroid Build Coastguard Worker      break;
552*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
553*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
554*d9f75844SAndroid Build Coastguard Worker      break;
555*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
556*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Audio route changed: RouteConfigurationChange");
557*d9f75844SAndroid Build Coastguard Worker      break;
558*d9f75844SAndroid Build Coastguard Worker  }
559*d9f75844SAndroid Build Coastguard Worker  AVAudioSessionRouteDescription* previousRoute =
560*d9f75844SAndroid Build Coastguard Worker      notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
561*d9f75844SAndroid Build Coastguard Worker  // Log previous route configuration.
562*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Previous route: %@\nCurrent route:%@",
563*d9f75844SAndroid Build Coastguard Worker         previousRoute, self.session.currentRoute);
564*d9f75844SAndroid Build Coastguard Worker  [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
565*d9f75844SAndroid Build Coastguard Worker}
566*d9f75844SAndroid Build Coastguard Worker
567*d9f75844SAndroid Build Coastguard Worker- (void)handleMediaServicesWereLost:(NSNotification *)notification {
568*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Media services were lost.");
569*d9f75844SAndroid Build Coastguard Worker  [self updateAudioSessionAfterEvent];
570*d9f75844SAndroid Build Coastguard Worker  [self notifyMediaServicesWereLost];
571*d9f75844SAndroid Build Coastguard Worker}
572*d9f75844SAndroid Build Coastguard Worker
573*d9f75844SAndroid Build Coastguard Worker- (void)handleMediaServicesWereReset:(NSNotification *)notification {
574*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Media services were reset.");
575*d9f75844SAndroid Build Coastguard Worker  [self updateAudioSessionAfterEvent];
576*d9f75844SAndroid Build Coastguard Worker  [self notifyMediaServicesWereReset];
577*d9f75844SAndroid Build Coastguard Worker}
578*d9f75844SAndroid Build Coastguard Worker
579*d9f75844SAndroid Build Coastguard Worker- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
580*d9f75844SAndroid Build Coastguard Worker  // TODO(henrika): just adding logs here for now until we know if we are ever
581*d9f75844SAndroid Build Coastguard Worker  // see this notification and might be affected by it or if further actions
582*d9f75844SAndroid Build Coastguard Worker  // are required.
583*d9f75844SAndroid Build Coastguard Worker  NSNumber *typeNumber =
584*d9f75844SAndroid Build Coastguard Worker      notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
585*d9f75844SAndroid Build Coastguard Worker  AVAudioSessionSilenceSecondaryAudioHintType type =
586*d9f75844SAndroid Build Coastguard Worker      (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
587*d9f75844SAndroid Build Coastguard Worker  switch (type) {
588*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
589*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Another application's primary audio has started.");
590*d9f75844SAndroid Build Coastguard Worker      break;
591*d9f75844SAndroid Build Coastguard Worker    case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
592*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"Another application's primary audio has stopped.");
593*d9f75844SAndroid Build Coastguard Worker      break;
594*d9f75844SAndroid Build Coastguard Worker  }
595*d9f75844SAndroid Build Coastguard Worker}
596*d9f75844SAndroid Build Coastguard Worker
597*d9f75844SAndroid Build Coastguard Worker- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
598*d9f75844SAndroid Build Coastguard Worker  BOOL isInterrupted = self.isInterrupted;
599*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Application became active after an interruption. Treating as interruption "
600*d9f75844SAndroid Build Coastguard Worker          "end. isInterrupted changed from %d to 0.",
601*d9f75844SAndroid Build Coastguard Worker         isInterrupted);
602*d9f75844SAndroid Build Coastguard Worker  if (isInterrupted) {
603*d9f75844SAndroid Build Coastguard Worker    self.isInterrupted = NO;
604*d9f75844SAndroid Build Coastguard Worker    [self updateAudioSessionAfterEvent];
605*d9f75844SAndroid Build Coastguard Worker  }
606*d9f75844SAndroid Build Coastguard Worker  // Always treat application becoming active as an interruption end event.
607*d9f75844SAndroid Build Coastguard Worker  [self notifyDidEndInterruptionWithShouldResumeSession:YES];
608*d9f75844SAndroid Build Coastguard Worker}
609*d9f75844SAndroid Build Coastguard Worker
610*d9f75844SAndroid Build Coastguard Worker#pragma mark - Private
611*d9f75844SAndroid Build Coastguard Worker
612*d9f75844SAndroid Build Coastguard Worker+ (NSError *)lockError {
613*d9f75844SAndroid Build Coastguard Worker  NSDictionary *userInfo =
614*d9f75844SAndroid Build Coastguard Worker      @{NSLocalizedDescriptionKey : @"Must call lockForConfiguration before calling this method."};
615*d9f75844SAndroid Build Coastguard Worker  NSError *error = [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
616*d9f75844SAndroid Build Coastguard Worker                                              code:kRTCAudioSessionErrorLockRequired
617*d9f75844SAndroid Build Coastguard Worker                                          userInfo:userInfo];
618*d9f75844SAndroid Build Coastguard Worker  return error;
619*d9f75844SAndroid Build Coastguard Worker}
620*d9f75844SAndroid Build Coastguard Worker
621*d9f75844SAndroid Build Coastguard Worker- (std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> >)delegates {
622*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
623*d9f75844SAndroid Build Coastguard Worker    // Note: this returns a copy.
624*d9f75844SAndroid Build Coastguard Worker    return _delegates;
625*d9f75844SAndroid Build Coastguard Worker  }
626*d9f75844SAndroid Build Coastguard Worker}
627*d9f75844SAndroid Build Coastguard Worker
628*d9f75844SAndroid Build Coastguard Worker// TODO(tkchin): check for duplicates.
629*d9f75844SAndroid Build Coastguard Worker- (void)pushDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
630*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
631*d9f75844SAndroid Build Coastguard Worker    _delegates.insert(_delegates.begin(), delegate);
632*d9f75844SAndroid Build Coastguard Worker  }
633*d9f75844SAndroid Build Coastguard Worker}
634*d9f75844SAndroid Build Coastguard Worker
635*d9f75844SAndroid Build Coastguard Worker- (void)removeZeroedDelegates {
636*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
637*d9f75844SAndroid Build Coastguard Worker    _delegates.erase(
638*d9f75844SAndroid Build Coastguard Worker        std::remove_if(_delegates.begin(),
639*d9f75844SAndroid Build Coastguard Worker                       _delegates.end(),
640*d9f75844SAndroid Build Coastguard Worker                       [](id delegate) -> bool { return delegate == nil; }),
641*d9f75844SAndroid Build Coastguard Worker        _delegates.end());
642*d9f75844SAndroid Build Coastguard Worker  }
643*d9f75844SAndroid Build Coastguard Worker}
644*d9f75844SAndroid Build Coastguard Worker
645*d9f75844SAndroid Build Coastguard Worker- (int)activationCount {
646*d9f75844SAndroid Build Coastguard Worker  return _activationCount.load();
647*d9f75844SAndroid Build Coastguard Worker}
648*d9f75844SAndroid Build Coastguard Worker
649*d9f75844SAndroid Build Coastguard Worker- (int)incrementActivationCount {
650*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Incrementing activation count.");
651*d9f75844SAndroid Build Coastguard Worker  return _activationCount.fetch_add(1) + 1;
652*d9f75844SAndroid Build Coastguard Worker}
653*d9f75844SAndroid Build Coastguard Worker
654*d9f75844SAndroid Build Coastguard Worker- (NSInteger)decrementActivationCount {
655*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Decrementing activation count.");
656*d9f75844SAndroid Build Coastguard Worker  return _activationCount.fetch_sub(1) - 1;
657*d9f75844SAndroid Build Coastguard Worker}
658*d9f75844SAndroid Build Coastguard Worker
659*d9f75844SAndroid Build Coastguard Worker- (int)webRTCSessionCount {
660*d9f75844SAndroid Build Coastguard Worker  return _webRTCSessionCount.load();
661*d9f75844SAndroid Build Coastguard Worker}
662*d9f75844SAndroid Build Coastguard Worker
663*d9f75844SAndroid Build Coastguard Worker- (BOOL)canPlayOrRecord {
664*d9f75844SAndroid Build Coastguard Worker  return !self.useManualAudio || self.isAudioEnabled;
665*d9f75844SAndroid Build Coastguard Worker}
666*d9f75844SAndroid Build Coastguard Worker
667*d9f75844SAndroid Build Coastguard Worker- (BOOL)isInterrupted {
668*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
669*d9f75844SAndroid Build Coastguard Worker    return _isInterrupted;
670*d9f75844SAndroid Build Coastguard Worker  }
671*d9f75844SAndroid Build Coastguard Worker}
672*d9f75844SAndroid Build Coastguard Worker
673*d9f75844SAndroid Build Coastguard Worker- (void)setIsInterrupted:(BOOL)isInterrupted {
674*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
675*d9f75844SAndroid Build Coastguard Worker    if (_isInterrupted == isInterrupted) {
676*d9f75844SAndroid Build Coastguard Worker      return;
677*d9f75844SAndroid Build Coastguard Worker   }
678*d9f75844SAndroid Build Coastguard Worker   _isInterrupted = isInterrupted;
679*d9f75844SAndroid Build Coastguard Worker  }
680*d9f75844SAndroid Build Coastguard Worker}
681*d9f75844SAndroid Build Coastguard Worker
682*d9f75844SAndroid Build Coastguard Worker- (BOOL)checkLock:(NSError **)outError {
683*d9f75844SAndroid Build Coastguard Worker  if (!mutex_locked) {
684*d9f75844SAndroid Build Coastguard Worker    if (outError) {
685*d9f75844SAndroid Build Coastguard Worker      *outError = [RTC_OBJC_TYPE(RTCAudioSession) lockError];
686*d9f75844SAndroid Build Coastguard Worker    }
687*d9f75844SAndroid Build Coastguard Worker    return NO;
688*d9f75844SAndroid Build Coastguard Worker  }
689*d9f75844SAndroid Build Coastguard Worker  return YES;
690*d9f75844SAndroid Build Coastguard Worker}
691*d9f75844SAndroid Build Coastguard Worker
692*d9f75844SAndroid Build Coastguard Worker- (BOOL)beginWebRTCSession:(NSError **)outError {
693*d9f75844SAndroid Build Coastguard Worker  if (outError) {
694*d9f75844SAndroid Build Coastguard Worker    *outError = nil;
695*d9f75844SAndroid Build Coastguard Worker  }
696*d9f75844SAndroid Build Coastguard Worker  _webRTCSessionCount.fetch_add(1);
697*d9f75844SAndroid Build Coastguard Worker  [self notifyDidStartPlayOrRecord];
698*d9f75844SAndroid Build Coastguard Worker  return YES;
699*d9f75844SAndroid Build Coastguard Worker}
700*d9f75844SAndroid Build Coastguard Worker
701*d9f75844SAndroid Build Coastguard Worker- (BOOL)endWebRTCSession:(NSError **)outError {
702*d9f75844SAndroid Build Coastguard Worker  if (outError) {
703*d9f75844SAndroid Build Coastguard Worker    *outError = nil;
704*d9f75844SAndroid Build Coastguard Worker  }
705*d9f75844SAndroid Build Coastguard Worker  _webRTCSessionCount.fetch_sub(1);
706*d9f75844SAndroid Build Coastguard Worker  [self notifyDidStopPlayOrRecord];
707*d9f75844SAndroid Build Coastguard Worker  return YES;
708*d9f75844SAndroid Build Coastguard Worker}
709*d9f75844SAndroid Build Coastguard Worker
710*d9f75844SAndroid Build Coastguard Worker- (BOOL)configureWebRTCSession:(NSError **)outError {
711*d9f75844SAndroid Build Coastguard Worker  if (outError) {
712*d9f75844SAndroid Build Coastguard Worker    *outError = nil;
713*d9f75844SAndroid Build Coastguard Worker  }
714*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Configuring audio session for WebRTC.");
715*d9f75844SAndroid Build Coastguard Worker
716*d9f75844SAndroid Build Coastguard Worker  // Configure the AVAudioSession and activate it.
717*d9f75844SAndroid Build Coastguard Worker  // Provide an error even if there isn't one so we can log it.
718*d9f75844SAndroid Build Coastguard Worker  NSError *error = nil;
719*d9f75844SAndroid Build Coastguard Worker  RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *webRTCConfig =
720*d9f75844SAndroid Build Coastguard Worker      [RTC_OBJC_TYPE(RTCAudioSessionConfiguration) webRTCConfiguration];
721*d9f75844SAndroid Build Coastguard Worker  if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
722*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"Failed to set WebRTC audio configuration: %@",
723*d9f75844SAndroid Build Coastguard Worker                error.localizedDescription);
724*d9f75844SAndroid Build Coastguard Worker    // Do not call setActive:NO if setActive:YES failed.
725*d9f75844SAndroid Build Coastguard Worker    if (outError) {
726*d9f75844SAndroid Build Coastguard Worker      *outError = error;
727*d9f75844SAndroid Build Coastguard Worker    }
728*d9f75844SAndroid Build Coastguard Worker    return NO;
729*d9f75844SAndroid Build Coastguard Worker  }
730*d9f75844SAndroid Build Coastguard Worker
731*d9f75844SAndroid Build Coastguard Worker  // Ensure that the device currently supports audio input.
732*d9f75844SAndroid Build Coastguard Worker  // TODO(tkchin): Figure out if this is really necessary.
733*d9f75844SAndroid Build Coastguard Worker  if (!self.inputAvailable) {
734*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"No audio input path is available!");
735*d9f75844SAndroid Build Coastguard Worker    [self unconfigureWebRTCSession:nil];
736*d9f75844SAndroid Build Coastguard Worker    if (outError) {
737*d9f75844SAndroid Build Coastguard Worker      *outError = [self configurationErrorWithDescription:@"No input path."];
738*d9f75844SAndroid Build Coastguard Worker    }
739*d9f75844SAndroid Build Coastguard Worker    return NO;
740*d9f75844SAndroid Build Coastguard Worker  }
741*d9f75844SAndroid Build Coastguard Worker
742*d9f75844SAndroid Build Coastguard Worker  // It can happen (e.g. in combination with BT devices) that the attempt to set
743*d9f75844SAndroid Build Coastguard Worker  // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
744*d9f75844SAndroid Build Coastguard Worker  // configuration attempt using the sample rate that worked using the active
745*d9f75844SAndroid Build Coastguard Worker  // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
746*d9f75844SAndroid Build Coastguard Worker  // combination with BT headsets. Using this "trick" seems to avoid a state
747*d9f75844SAndroid Build Coastguard Worker  // where Core Audio asks for a different number of audio frames than what the
748*d9f75844SAndroid Build Coastguard Worker  // session's I/O buffer duration corresponds to.
749*d9f75844SAndroid Build Coastguard Worker  // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
750*d9f75844SAndroid Build Coastguard Worker  // tested on a limited set of iOS devices and BT devices.
751*d9f75844SAndroid Build Coastguard Worker  double sessionSampleRate = self.sampleRate;
752*d9f75844SAndroid Build Coastguard Worker  double preferredSampleRate = webRTCConfig.sampleRate;
753*d9f75844SAndroid Build Coastguard Worker  if (sessionSampleRate != preferredSampleRate) {
754*d9f75844SAndroid Build Coastguard Worker    RTCLogWarning(
755*d9f75844SAndroid Build Coastguard Worker        @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
756*d9f75844SAndroid Build Coastguard Worker        sessionSampleRate, preferredSampleRate);
757*d9f75844SAndroid Build Coastguard Worker    if (![self setPreferredSampleRate:sessionSampleRate
758*d9f75844SAndroid Build Coastguard Worker                                error:&error]) {
759*d9f75844SAndroid Build Coastguard Worker      RTCLogError(@"Failed to set preferred sample rate: %@",
760*d9f75844SAndroid Build Coastguard Worker                  error.localizedDescription);
761*d9f75844SAndroid Build Coastguard Worker      if (outError) {
762*d9f75844SAndroid Build Coastguard Worker        *outError = error;
763*d9f75844SAndroid Build Coastguard Worker      }
764*d9f75844SAndroid Build Coastguard Worker    }
765*d9f75844SAndroid Build Coastguard Worker  }
766*d9f75844SAndroid Build Coastguard Worker
767*d9f75844SAndroid Build Coastguard Worker  return YES;
768*d9f75844SAndroid Build Coastguard Worker}
769*d9f75844SAndroid Build Coastguard Worker
770*d9f75844SAndroid Build Coastguard Worker- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
771*d9f75844SAndroid Build Coastguard Worker  if (outError) {
772*d9f75844SAndroid Build Coastguard Worker    *outError = nil;
773*d9f75844SAndroid Build Coastguard Worker  }
774*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Unconfiguring audio session for WebRTC.");
775*d9f75844SAndroid Build Coastguard Worker  [self setActive:NO error:outError];
776*d9f75844SAndroid Build Coastguard Worker
777*d9f75844SAndroid Build Coastguard Worker  return YES;
778*d9f75844SAndroid Build Coastguard Worker}
779*d9f75844SAndroid Build Coastguard Worker
780*d9f75844SAndroid Build Coastguard Worker- (NSError *)configurationErrorWithDescription:(NSString *)description {
781*d9f75844SAndroid Build Coastguard Worker  NSDictionary* userInfo = @{
782*d9f75844SAndroid Build Coastguard Worker    NSLocalizedDescriptionKey: description,
783*d9f75844SAndroid Build Coastguard Worker  };
784*d9f75844SAndroid Build Coastguard Worker  return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
785*d9f75844SAndroid Build Coastguard Worker                                    code:kRTCAudioSessionErrorConfiguration
786*d9f75844SAndroid Build Coastguard Worker                                userInfo:userInfo];
787*d9f75844SAndroid Build Coastguard Worker}
788*d9f75844SAndroid Build Coastguard Worker
789*d9f75844SAndroid Build Coastguard Worker- (void)updateAudioSessionAfterEvent {
790*d9f75844SAndroid Build Coastguard Worker  BOOL shouldActivate = self.activationCount > 0;
791*d9f75844SAndroid Build Coastguard Worker  AVAudioSessionSetActiveOptions options = shouldActivate ?
792*d9f75844SAndroid Build Coastguard Worker      0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
793*d9f75844SAndroid Build Coastguard Worker  NSError *error = nil;
794*d9f75844SAndroid Build Coastguard Worker  if ([self.session setActive:shouldActivate
795*d9f75844SAndroid Build Coastguard Worker                  withOptions:options
796*d9f75844SAndroid Build Coastguard Worker                        error:&error]) {
797*d9f75844SAndroid Build Coastguard Worker    self.isActive = shouldActivate;
798*d9f75844SAndroid Build Coastguard Worker  } else {
799*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"Failed to set session active to %d. Error:%@",
800*d9f75844SAndroid Build Coastguard Worker                shouldActivate, error.localizedDescription);
801*d9f75844SAndroid Build Coastguard Worker  }
802*d9f75844SAndroid Build Coastguard Worker}
803*d9f75844SAndroid Build Coastguard Worker
804*d9f75844SAndroid Build Coastguard Worker- (void)updateCanPlayOrRecord {
805*d9f75844SAndroid Build Coastguard Worker  BOOL canPlayOrRecord = NO;
806*d9f75844SAndroid Build Coastguard Worker  BOOL shouldNotify = NO;
807*d9f75844SAndroid Build Coastguard Worker  @synchronized(self) {
808*d9f75844SAndroid Build Coastguard Worker    canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
809*d9f75844SAndroid Build Coastguard Worker    if (_canPlayOrRecord == canPlayOrRecord) {
810*d9f75844SAndroid Build Coastguard Worker      return;
811*d9f75844SAndroid Build Coastguard Worker    }
812*d9f75844SAndroid Build Coastguard Worker    _canPlayOrRecord = canPlayOrRecord;
813*d9f75844SAndroid Build Coastguard Worker    shouldNotify = YES;
814*d9f75844SAndroid Build Coastguard Worker  }
815*d9f75844SAndroid Build Coastguard Worker  if (shouldNotify) {
816*d9f75844SAndroid Build Coastguard Worker    [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
817*d9f75844SAndroid Build Coastguard Worker  }
818*d9f75844SAndroid Build Coastguard Worker}
819*d9f75844SAndroid Build Coastguard Worker
820*d9f75844SAndroid Build Coastguard Worker- (void)audioSessionDidActivate:(AVAudioSession *)session {
821*d9f75844SAndroid Build Coastguard Worker  if (_session != session) {
822*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"audioSessionDidActivate called on different AVAudioSession");
823*d9f75844SAndroid Build Coastguard Worker  }
824*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Audio session was externally activated.");
825*d9f75844SAndroid Build Coastguard Worker  [self incrementActivationCount];
826*d9f75844SAndroid Build Coastguard Worker  self.isActive = YES;
827*d9f75844SAndroid Build Coastguard Worker  // When a CallKit call begins, it's possible that we receive an interruption
828*d9f75844SAndroid Build Coastguard Worker  // begin without a corresponding end. Since we know that we have an activated
829*d9f75844SAndroid Build Coastguard Worker  // audio session at this point, just clear any saved interruption flag since
830*d9f75844SAndroid Build Coastguard Worker  // the app may never be foregrounded during the duration of the call.
831*d9f75844SAndroid Build Coastguard Worker  if (self.isInterrupted) {
832*d9f75844SAndroid Build Coastguard Worker    RTCLog(@"Clearing interrupted state due to external activation.");
833*d9f75844SAndroid Build Coastguard Worker    self.isInterrupted = NO;
834*d9f75844SAndroid Build Coastguard Worker  }
835*d9f75844SAndroid Build Coastguard Worker  // Treat external audio session activation as an end interruption event.
836*d9f75844SAndroid Build Coastguard Worker  [self notifyDidEndInterruptionWithShouldResumeSession:YES];
837*d9f75844SAndroid Build Coastguard Worker}
838*d9f75844SAndroid Build Coastguard Worker
839*d9f75844SAndroid Build Coastguard Worker- (void)audioSessionDidDeactivate:(AVAudioSession *)session {
840*d9f75844SAndroid Build Coastguard Worker  if (_session != session) {
841*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession");
842*d9f75844SAndroid Build Coastguard Worker  }
843*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Audio session was externally deactivated.");
844*d9f75844SAndroid Build Coastguard Worker  self.isActive = NO;
845*d9f75844SAndroid Build Coastguard Worker  [self decrementActivationCount];
846*d9f75844SAndroid Build Coastguard Worker}
847*d9f75844SAndroid Build Coastguard Worker
848*d9f75844SAndroid Build Coastguard Worker- (void)observeValueForKeyPath:(NSString *)keyPath
849*d9f75844SAndroid Build Coastguard Worker                      ofObject:(id)object
850*d9f75844SAndroid Build Coastguard Worker                        change:(NSDictionary *)change
851*d9f75844SAndroid Build Coastguard Worker                       context:(void *)context {
852*d9f75844SAndroid Build Coastguard Worker  if (context == (__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class) {
853*d9f75844SAndroid Build Coastguard Worker    if (object == _session) {
854*d9f75844SAndroid Build Coastguard Worker      NSNumber *newVolume = change[NSKeyValueChangeNewKey];
855*d9f75844SAndroid Build Coastguard Worker      RTCLog(@"OutputVolumeDidChange to %f", newVolume.floatValue);
856*d9f75844SAndroid Build Coastguard Worker      [self notifyDidChangeOutputVolume:newVolume.floatValue];
857*d9f75844SAndroid Build Coastguard Worker    }
858*d9f75844SAndroid Build Coastguard Worker  } else {
859*d9f75844SAndroid Build Coastguard Worker    [super observeValueForKeyPath:keyPath
860*d9f75844SAndroid Build Coastguard Worker                         ofObject:object
861*d9f75844SAndroid Build Coastguard Worker                           change:change
862*d9f75844SAndroid Build Coastguard Worker                          context:context];
863*d9f75844SAndroid Build Coastguard Worker  }
864*d9f75844SAndroid Build Coastguard Worker}
865*d9f75844SAndroid Build Coastguard Worker
866*d9f75844SAndroid Build Coastguard Worker- (void)notifyAudioUnitStartFailedWithError:(OSStatus)error {
867*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
868*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSession:audioUnitStartFailedWithError:);
869*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
870*d9f75844SAndroid Build Coastguard Worker      [delegate audioSession:self
871*d9f75844SAndroid Build Coastguard Worker          audioUnitStartFailedWithError:[NSError errorWithDomain:kRTCAudioSessionErrorDomain
872*d9f75844SAndroid Build Coastguard Worker                                                            code:error
873*d9f75844SAndroid Build Coastguard Worker                                                        userInfo:nil]];
874*d9f75844SAndroid Build Coastguard Worker    }
875*d9f75844SAndroid Build Coastguard Worker  }
876*d9f75844SAndroid Build Coastguard Worker}
877*d9f75844SAndroid Build Coastguard Worker
878*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidBeginInterruption {
879*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
880*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSessionDidBeginInterruption:);
881*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
882*d9f75844SAndroid Build Coastguard Worker      [delegate audioSessionDidBeginInterruption:self];
883*d9f75844SAndroid Build Coastguard Worker    }
884*d9f75844SAndroid Build Coastguard Worker  }
885*d9f75844SAndroid Build Coastguard Worker}
886*d9f75844SAndroid Build Coastguard Worker
887*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidEndInterruptionWithShouldResumeSession:
888*d9f75844SAndroid Build Coastguard Worker    (BOOL)shouldResumeSession {
889*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
890*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
891*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
892*d9f75844SAndroid Build Coastguard Worker      [delegate audioSessionDidEndInterruption:self
893*d9f75844SAndroid Build Coastguard Worker                           shouldResumeSession:shouldResumeSession];
894*d9f75844SAndroid Build Coastguard Worker    }
895*d9f75844SAndroid Build Coastguard Worker  }
896*d9f75844SAndroid Build Coastguard Worker}
897*d9f75844SAndroid Build Coastguard Worker
898*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
899*d9f75844SAndroid Build Coastguard Worker    previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
900*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
901*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
902*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
903*d9f75844SAndroid Build Coastguard Worker      [delegate audioSessionDidChangeRoute:self
904*d9f75844SAndroid Build Coastguard Worker                                    reason:reason
905*d9f75844SAndroid Build Coastguard Worker                             previousRoute:previousRoute];
906*d9f75844SAndroid Build Coastguard Worker    }
907*d9f75844SAndroid Build Coastguard Worker  }
908*d9f75844SAndroid Build Coastguard Worker}
909*d9f75844SAndroid Build Coastguard Worker
910*d9f75844SAndroid Build Coastguard Worker- (void)notifyMediaServicesWereLost {
911*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
912*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSessionMediaServerTerminated:);
913*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
914*d9f75844SAndroid Build Coastguard Worker      [delegate audioSessionMediaServerTerminated:self];
915*d9f75844SAndroid Build Coastguard Worker    }
916*d9f75844SAndroid Build Coastguard Worker  }
917*d9f75844SAndroid Build Coastguard Worker}
918*d9f75844SAndroid Build Coastguard Worker
919*d9f75844SAndroid Build Coastguard Worker- (void)notifyMediaServicesWereReset {
920*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
921*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSessionMediaServerReset:);
922*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
923*d9f75844SAndroid Build Coastguard Worker      [delegate audioSessionMediaServerReset:self];
924*d9f75844SAndroid Build Coastguard Worker    }
925*d9f75844SAndroid Build Coastguard Worker  }
926*d9f75844SAndroid Build Coastguard Worker}
927*d9f75844SAndroid Build Coastguard Worker
928*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
929*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
930*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
931*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
932*d9f75844SAndroid Build Coastguard Worker      [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
933*d9f75844SAndroid Build Coastguard Worker    }
934*d9f75844SAndroid Build Coastguard Worker  }
935*d9f75844SAndroid Build Coastguard Worker}
936*d9f75844SAndroid Build Coastguard Worker
937*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidStartPlayOrRecord {
938*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
939*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
940*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
941*d9f75844SAndroid Build Coastguard Worker      [delegate audioSessionDidStartPlayOrRecord:self];
942*d9f75844SAndroid Build Coastguard Worker    }
943*d9f75844SAndroid Build Coastguard Worker  }
944*d9f75844SAndroid Build Coastguard Worker}
945*d9f75844SAndroid Build Coastguard Worker
946*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidStopPlayOrRecord {
947*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
948*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
949*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
950*d9f75844SAndroid Build Coastguard Worker      [delegate audioSessionDidStopPlayOrRecord:self];
951*d9f75844SAndroid Build Coastguard Worker    }
952*d9f75844SAndroid Build Coastguard Worker  }
953*d9f75844SAndroid Build Coastguard Worker}
954*d9f75844SAndroid Build Coastguard Worker
955*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidChangeOutputVolume:(float)volume {
956*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
957*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSession:didChangeOutputVolume:);
958*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
959*d9f75844SAndroid Build Coastguard Worker      [delegate audioSession:self didChangeOutputVolume:volume];
960*d9f75844SAndroid Build Coastguard Worker    }
961*d9f75844SAndroid Build Coastguard Worker  }
962*d9f75844SAndroid Build Coastguard Worker}
963*d9f75844SAndroid Build Coastguard Worker
964*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches {
965*d9f75844SAndroid Build Coastguard Worker  for (auto delegate : self.delegates) {
966*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSession:didDetectPlayoutGlitch:);
967*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
968*d9f75844SAndroid Build Coastguard Worker      [delegate audioSession:self didDetectPlayoutGlitch:totalNumberOfGlitches];
969*d9f75844SAndroid Build Coastguard Worker    }
970*d9f75844SAndroid Build Coastguard Worker  }
971*d9f75844SAndroid Build Coastguard Worker}
972*d9f75844SAndroid Build Coastguard Worker
973*d9f75844SAndroid Build Coastguard Worker- (void)notifyWillSetActive:(BOOL)active {
974*d9f75844SAndroid Build Coastguard Worker  for (id delegate : self.delegates) {
975*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSession:willSetActive:);
976*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
977*d9f75844SAndroid Build Coastguard Worker      [delegate audioSession:self willSetActive:active];
978*d9f75844SAndroid Build Coastguard Worker    }
979*d9f75844SAndroid Build Coastguard Worker  }
980*d9f75844SAndroid Build Coastguard Worker}
981*d9f75844SAndroid Build Coastguard Worker
982*d9f75844SAndroid Build Coastguard Worker- (void)notifyDidSetActive:(BOOL)active {
983*d9f75844SAndroid Build Coastguard Worker  for (id delegate : self.delegates) {
984*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSession:didSetActive:);
985*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
986*d9f75844SAndroid Build Coastguard Worker      [delegate audioSession:self didSetActive:active];
987*d9f75844SAndroid Build Coastguard Worker    }
988*d9f75844SAndroid Build Coastguard Worker  }
989*d9f75844SAndroid Build Coastguard Worker}
990*d9f75844SAndroid Build Coastguard Worker
991*d9f75844SAndroid Build Coastguard Worker- (void)notifyFailedToSetActive:(BOOL)active error:(NSError *)error {
992*d9f75844SAndroid Build Coastguard Worker  for (id delegate : self.delegates) {
993*d9f75844SAndroid Build Coastguard Worker    SEL sel = @selector(audioSession:failedToSetActive:error:);
994*d9f75844SAndroid Build Coastguard Worker    if ([delegate respondsToSelector:sel]) {
995*d9f75844SAndroid Build Coastguard Worker      [delegate audioSession:self failedToSetActive:active error:error];
996*d9f75844SAndroid Build Coastguard Worker    }
997*d9f75844SAndroid Build Coastguard Worker  }
998*d9f75844SAndroid Build Coastguard Worker}
999*d9f75844SAndroid Build Coastguard Worker
1000*d9f75844SAndroid Build Coastguard Worker@end
1001