blob: 805f601bddeda7208f383e3bb35c46cb2e348d43 [file] [log] [blame]
tkchin0ce3bf92016-03-12 16:52:04 -08001/*
2 * Copyright 2016 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#import <Foundation/Foundation.h>
jttehf84c1d62017-04-21 13:56:39 -070012#import <OCMock/OCMock.h>
tkchin0ce3bf92016-03-12 16:52:04 -080013
Joe Chen0b3a6e32019-12-26 23:01:42 -080014#include <vector>
15
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020016#include "rtc_base/gunit.h"
tkchin0ce3bf92016-03-12 16:52:04 -080017
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020018#import "components/audio/RTCAudioSession+Private.h"
denicija59ee91b2017-06-05 05:48:47 -070019
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020020#import "components/audio/RTCAudioSession.h"
21#import "components/audio/RTCAudioSessionConfiguration.h"
tkchine54467f2016-03-15 16:54:03 -070022
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020023@interface RTC_OBJC_TYPE (RTCAudioSession)
24(UnitTesting)
Peter Hanspers47217362017-10-05 11:39:15 +020025
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020026 @property(nonatomic,
27 readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates;
Joe Chen0b3a6e32019-12-26 23:01:42 -080028
Peter Hanspers47217362017-10-05 11:39:15 +020029- (instancetype)initWithAudioSession:(id)audioSession;
30
31@end
32
33@interface MockAVAudioSession : NSObject
34
35@property (nonatomic, readwrite, assign) float outputVolume;
36
37@end
38
39@implementation MockAVAudioSession
40@synthesize outputVolume = _outputVolume;
41@end
42
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020043@interface RTCAudioSessionTestDelegate : NSObject <RTC_OBJC_TYPE (RTCAudioSessionDelegate)>
jtteh13ae11a2017-05-25 17:52:20 -070044
45@property (nonatomic, readonly) float outputVolume;
46
tkchine54467f2016-03-15 16:54:03 -070047@end
48
49@implementation RTCAudioSessionTestDelegate
50
jtteh13ae11a2017-05-25 17:52:20 -070051@synthesize outputVolume = _outputVolume;
52
53- (instancetype)init {
54 if (self = [super init]) {
55 _outputVolume = -1;
56 }
57 return self;
58}
59
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020060- (void)audioSessionDidBeginInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070061}
62
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020063- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session
tkchine54467f2016-03-15 16:54:03 -070064 shouldResumeSession:(BOOL)shouldResumeSession {
65}
66
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020067- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session
68 reason:(AVAudioSessionRouteChangeReason)reason
69 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -070070}
71
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020072- (void)audioSessionMediaServerTerminated:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070073}
74
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020075- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070076}
77
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020078- (void)audioSessionShouldConfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070079}
80
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020081- (void)audioSessionShouldUnconfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070082}
83
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020084- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
jtteh13ae11a2017-05-25 17:52:20 -070085 didChangeOutputVolume:(float)outputVolume {
86 _outputVolume = outputVolume;
87}
88
tkchine54467f2016-03-15 16:54:03 -070089@end
90
tkchinefdd9302016-04-11 12:00:59 -070091// A delegate that adds itself to the audio session on init and removes itself
92// in its dealloc.
93@interface RTCTestRemoveOnDeallocDelegate : RTCAudioSessionTestDelegate
94@end
95
96@implementation RTCTestRemoveOnDeallocDelegate
97
98- (instancetype)init {
99 if (self = [super init]) {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200100 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700101 [session addDelegate:self];
102 }
103 return self;
104}
105
106- (void)dealloc {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200107 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700108 [session removeDelegate:self];
109}
110
111@end
112
tkchin0ce3bf92016-03-12 16:52:04 -0800113
114@interface RTCAudioSessionTest : NSObject
115
tkchin0ce3bf92016-03-12 16:52:04 -0800116@end
117
118@implementation RTCAudioSessionTest
119
tkchine54467f2016-03-15 16:54:03 -0700120- (void)testAddAndRemoveDelegates {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200121 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700122 NSMutableArray *delegates = [NSMutableArray array];
123 const size_t count = 5;
124 for (size_t i = 0; i < count; ++i) {
125 RTCAudioSessionTestDelegate *delegate =
126 [[RTCAudioSessionTestDelegate alloc] init];
127 [session addDelegate:delegate];
128 [delegates addObject:delegate];
129 EXPECT_EQ(i + 1, session.delegates.size());
130 }
131 [delegates enumerateObjectsUsingBlock:^(RTCAudioSessionTestDelegate *obj,
132 NSUInteger idx,
133 BOOL *stop) {
134 [session removeDelegate:obj];
135 }];
136 EXPECT_EQ(0u, session.delegates.size());
137}
138
139- (void)testPushDelegate {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200140 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700141 NSMutableArray *delegates = [NSMutableArray array];
142 const size_t count = 2;
143 for (size_t i = 0; i < count; ++i) {
144 RTCAudioSessionTestDelegate *delegate =
145 [[RTCAudioSessionTestDelegate alloc] init];
146 [session addDelegate:delegate];
147 [delegates addObject:delegate];
148 }
149 // Test that it gets added to the front of the list.
150 RTCAudioSessionTestDelegate *pushedDelegate =
151 [[RTCAudioSessionTestDelegate alloc] init];
152 [session pushDelegate:pushedDelegate];
153 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
154
155 // Test that it stays at the front of the list.
156 for (size_t i = 0; i < count; ++i) {
157 RTCAudioSessionTestDelegate *delegate =
158 [[RTCAudioSessionTestDelegate alloc] init];
159 [session addDelegate:delegate];
160 [delegates addObject:delegate];
161 }
162 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
163
164 // Test that the next one goes to the front too.
165 pushedDelegate = [[RTCAudioSessionTestDelegate alloc] init];
166 [session pushDelegate:pushedDelegate];
167 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
168}
169
170// Tests that delegates added to the audio session properly zero out. This is
171// checking an implementation detail (that vectors of __weak work as expected).
172- (void)testZeroingWeakDelegate {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200173 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700174 @autoreleasepool {
175 // Add a delegate to the session. There should be one delegate at this
176 // point.
177 RTCAudioSessionTestDelegate *delegate =
178 [[RTCAudioSessionTestDelegate alloc] init];
179 [session addDelegate:delegate];
180 EXPECT_EQ(1u, session.delegates.size());
181 EXPECT_TRUE(session.delegates[0]);
182 }
183 // The previously created delegate should've de-alloced, leaving a nil ptr.
184 EXPECT_FALSE(session.delegates[0]);
185 RTCAudioSessionTestDelegate *delegate =
186 [[RTCAudioSessionTestDelegate alloc] init];
187 [session addDelegate:delegate];
188 // On adding a new delegate, nil ptrs should've been cleared.
189 EXPECT_EQ(1u, session.delegates.size());
190 EXPECT_TRUE(session.delegates[0]);
191}
192
tkchinefdd9302016-04-11 12:00:59 -0700193// Tests that we don't crash when removing delegates in dealloc.
194// Added as a regression test.
195- (void)testRemoveDelegateOnDealloc {
196 @autoreleasepool {
197 RTCTestRemoveOnDeallocDelegate *delegate =
198 [[RTCTestRemoveOnDeallocDelegate alloc] init];
199 EXPECT_TRUE(delegate);
200 }
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200201 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700202 EXPECT_EQ(0u, session.delegates.size());
203}
204
jtteh3c9a6c02017-04-18 09:09:35 -0700205- (void)testAudioSessionActivation {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200206 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
jtteh3c9a6c02017-04-18 09:09:35 -0700207 EXPECT_EQ(0, audioSession.activationCount);
208 [audioSession audioSessionDidActivate:[AVAudioSession sharedInstance]];
209 EXPECT_EQ(1, audioSession.activationCount);
210 [audioSession audioSessionDidDeactivate:[AVAudioSession sharedInstance]];
211 EXPECT_EQ(0, audioSession.activationCount);
212}
213
jttehf84c1d62017-04-21 13:56:39 -0700214// Hack - fixes OCMVerify link error
215// Link error is: Undefined symbols for architecture i386:
216// "OCMMakeLocation(objc_object*, char const*, int)", referenced from:
217// -[RTCAudioSessionTest testConfigureWebRTCSession] in RTCAudioSessionTest.o
218// ld: symbol(s) not found for architecture i386
219// REASON: https://github.com/erikdoe/ocmock/issues/238
220OCMLocation *OCMMakeLocation(id testCase, const char *fileCString, int line){
221 return [OCMLocation locationWithTestCase:testCase
222 file:[NSString stringWithUTF8String:fileCString]
223 line:line];
224}
225
226- (void)testConfigureWebRTCSession {
227 NSError *error = nil;
228
229 void (^setActiveBlock)(NSInvocation *invocation) = ^(NSInvocation *invocation) {
230 __autoreleasing NSError **retError;
231 [invocation getArgument:&retError atIndex:4];
232 *retError = [NSError errorWithDomain:@"AVAudioSession"
233 code:AVAudioSessionErrorInsufficientPriority
234 userInfo:nil];
235 BOOL failure = NO;
236 [invocation setReturnValue:&failure];
237 };
238
239 id mockAVAudioSession = OCMPartialMock([AVAudioSession sharedInstance]);
Mirko Bonadeiad8a00d2021-02-10 09:23:03 +0100240 OCMStub([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES
241 withOptions:0
242 error:([OCMArg anyObjectRef])])
243 .andDo(setActiveBlock);
jttehf84c1d62017-04-21 13:56:39 -0700244
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200245 id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]);
jttehf84c1d62017-04-21 13:56:39 -0700246 OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession);
247
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200248 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession;
jttehf84c1d62017-04-21 13:56:39 -0700249 EXPECT_EQ(0, audioSession.activationCount);
250 [audioSession lockForConfiguration];
jttehf84c1d62017-04-21 13:56:39 -0700251 // configureWebRTCSession is forced to fail in the above mock interface,
252 // so activationCount should remain 0
Mirko Bonadeiad8a00d2021-02-10 09:23:03 +0100253 OCMExpect([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES
254 withOptions:0
255 error:([OCMArg anyObjectRef])])
256 .andDo(setActiveBlock);
jttehf84c1d62017-04-21 13:56:39 -0700257 OCMExpect([mockAudioSession session]).andReturn(mockAVAudioSession);
258 EXPECT_FALSE([audioSession configureWebRTCSession:&error]);
259 EXPECT_EQ(0, audioSession.activationCount);
260
261 id session = audioSession.session;
262 EXPECT_EQ(session, mockAVAudioSession);
263 EXPECT_EQ(NO, [mockAVAudioSession setActive:YES withOptions:0 error:&error]);
264 [audioSession unlockForConfiguration];
265
266 OCMVerify([mockAudioSession session]);
267 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES withOptions:0 error:&error]);
268 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:NO withOptions:0 error:&error]);
269
270 [mockAVAudioSession stopMocking];
271 [mockAudioSession stopMocking];
272}
273
jtteh13ae11a2017-05-25 17:52:20 -0700274- (void)testAudioVolumeDidNotify {
Peter Hanspers47217362017-10-05 11:39:15 +0200275 MockAVAudioSession *mockAVAudioSession = [[MockAVAudioSession alloc] init];
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200276 RTC_OBJC_TYPE(RTCAudioSession) *session =
277 [[RTC_OBJC_TYPE(RTCAudioSession) alloc] initWithAudioSession:mockAVAudioSession];
jtteh13ae11a2017-05-25 17:52:20 -0700278 RTCAudioSessionTestDelegate *delegate =
279 [[RTCAudioSessionTestDelegate alloc] init];
280 [session addDelegate:delegate];
281
Peter Hanspers47217362017-10-05 11:39:15 +0200282 float expectedVolume = 0.75;
283 mockAVAudioSession.outputVolume = expectedVolume;
jtteh13ae11a2017-05-25 17:52:20 -0700284
Peter Hanspers47217362017-10-05 11:39:15 +0200285 EXPECT_EQ(expectedVolume, delegate.outputVolume);
jtteh13ae11a2017-05-25 17:52:20 -0700286}
287
tkchin0ce3bf92016-03-12 16:52:04 -0800288@end
289
tkchine54467f2016-03-15 16:54:03 -0700290namespace webrtc {
291
292class AudioSessionTest : public ::testing::Test {
293 protected:
Mirko Bonadei17aff352018-07-26 12:20:40 +0200294 void TearDown() override {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200295 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
296 for (id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> delegate : session.delegates) {
tkchine54467f2016-03-15 16:54:03 -0700297 [session removeDelegate:delegate];
298 }
299 }
300};
301
tkchine54467f2016-03-15 16:54:03 -0700302TEST_F(AudioSessionTest, AddAndRemoveDelegates) {
303 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
304 [test testAddAndRemoveDelegates];
305}
306
307TEST_F(AudioSessionTest, PushDelegate) {
308 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
309 [test testPushDelegate];
310}
311
312TEST_F(AudioSessionTest, ZeroingWeakDelegate) {
313 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
314 [test testZeroingWeakDelegate];
315}
316
tkchinefdd9302016-04-11 12:00:59 -0700317TEST_F(AudioSessionTest, RemoveDelegateOnDealloc) {
318 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
319 [test testRemoveDelegateOnDealloc];
320}
321
jtteh3c9a6c02017-04-18 09:09:35 -0700322TEST_F(AudioSessionTest, AudioSessionActivation) {
323 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
324 [test testAudioSessionActivation];
325}
326
jttehf84c1d62017-04-21 13:56:39 -0700327TEST_F(AudioSessionTest, ConfigureWebRTCSession) {
328 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
329 [test testConfigureWebRTCSession];
330}
jtteh3c9a6c02017-04-18 09:09:35 -0700331
jtteh13ae11a2017-05-25 17:52:20 -0700332TEST_F(AudioSessionTest, AudioVolumeDidNotify) {
333 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
334 [test testAudioVolumeDidNotify];
335}
336
tkchine54467f2016-03-15 16:54:03 -0700337} // namespace webrtc