Skip to content

Commit 8e928a1

Browse files
committed
fix(audio-context): ios audio output
1 parent c6b0fdd commit 8e928a1

5 files changed

Lines changed: 206 additions & 80 deletions

File tree

packages/audio-context/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nativescript/audio-context",
3-
"version": "1.2.1",
3+
"version": "1.2.2",
44
"description": "Web Audio API for NativeScript",
55
"main": "index",
66
"types": "index.d.ts",

packages/audio-context/platforms/ios/src/NSCAudioBufferSourceNode.m

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
#pragma clang diagnostic push
77
#pragma clang diagnostic ignored "-Wimplicit-retain-self"
88

9+
@interface NSCAudioBufferSourceNode ()
10+
- (nullable NSCAudioBuffer *)processBuffer:(nullable NSCAudioBuffer *)input context:(NSCAudioContext *)context;
11+
- (void)scheduleRetryWithContext:(NSCAudioContext *)ctx;
12+
- (void)registerPendingIfNeededWithContext:(NSCAudioContext *)ctx;
13+
@end
14+
915
@implementation NSCAudioBufferSourceNode {
1016
AVAudioPlayerNode *_player;
1117
NSCAudioBuffer *_buffer;
@@ -151,28 +157,13 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
151157
AVAudioEngine *eng = ctx.engine;
152158
if (!eng)
153159
return NO;
154-
if (!eng.isRunning)
160+
if (!eng.isRunning) {
161+
NSCLogError(@"NSCAudioBSN: tryStartPlayer engine NOT running – returning NO");
155162
return NO;
156-
157-
@try {
158-
AVAudioTime *lrt = nil;
159-
@try {
160-
lrt = eng.outputNode.lastRenderTime;
161-
} @catch (NSException *e) {
162-
lrt = nil;
163-
}
164-
if (lrt) {
165-
NSCLogDebug(@"NSCAudioBufferSourceNode: tryStartPlayer initial engRunning=%d "
166-
@"lastRenderTime=%p sampleTime=%lld sampleRate=%f",
167-
(int)eng.isRunning, lrt, (long long)lrt.sampleTime, lrt.sampleRate);
168-
} else {
169-
NSCLogDebug(@"NSCAudioBufferSourceNode: tryStartPlayer initial engRunning=%d "
170-
@"lastRenderTime=NULL",
171-
(int)eng.isRunning);
172-
}
173-
} @catch (NSException *e) {
174163
}
175164

165+
NSCLogError(@"NSCAudioBSN: tryStartPlayer engine running, checking connection");
166+
176167
if (player.engine != eng) {
177168
@try {
178169
if (player.engine) {
@@ -194,8 +185,8 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
194185
MsgSendFn fn = (MsgSendFn)objc_msgSend;
195186
NSArray *pts = fn(eng, ocpSel, player, (AVAudioNodeBus)0);
196187
isConnected = pts && pts.count > 0;
197-
NSCLogDebug(@"NSCAudioBufferSourceNode: connectionPoints count=%lu",
198-
(unsigned long)(pts ? pts.count : 0));
188+
NSCLogError(@"NSCAudioBSN: connectionPoints count=%lu isConnected=%d",
189+
(unsigned long)(pts ? pts.count : 0), (int)isConnected);
199190
} @catch (NSException *e) {
200191
isConnected = NO;
201192
}
@@ -209,8 +200,8 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
209200
if (_lastDestination)
210201
targetNode = _lastDestination.avNode;
211202
if (!targetNode) {
212-
targetNode = eng.mainMixerNode;
213-
usingMainMixerFallback = YES;
203+
NSCLogDebug(@"NSCAudioBufferSourceNode: deferring start until connected to a real destination");
204+
return NO;
214205
}
215206
__block BOOL didImmediateConnect = NO;
216207
void (^immediateConnectBlock)(void) = ^{
@@ -313,13 +304,9 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
313304
safeToPlay = YES;
314305
} else {
315306
NSCLogDebug(@"NSCAudioBufferSourceNode: poll immediate now=%p "
316-
@"playerTime=nil",
307+
@"playerTime=nil, forcing play",
317308
now);
318-
#if TARGET_OS_SIMULATOR
319-
NSCLogDebug(@"NSCAudioBufferSourceNode: simulator forcing play "
320-
@"despite nil playerTime (immediate)");
321309
safeToPlay = YES;
322-
#endif
323310
}
324311
}
325312
} @catch (NSException *e) {
@@ -399,8 +386,8 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
399386
}
400387
}
401388
} @catch (NSException *e) {
402-
} @
403-
try {
389+
}
390+
@try {
404391
AVAudioEngine *e = ctx.engine;
405392
NSMutableArray *queue = [NSMutableArray
406393
arrayWithObject:(AVAudioNode *)player];
@@ -532,6 +519,7 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
532519
}
533520
}
534521
#endif
522+
NSCLogError(@"NSCAudioBSN: POLL-1 calling [player play]");
535523
[player play];
536524
if (_pendingStart) {
537525
_pendingStart = NO;
@@ -540,6 +528,21 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
540528
}
541529
_retryCount = 0;
542530
} @catch (NSException *ex) {
531+
AVAudioEngine *eng2 = ctx.engine;
532+
BOOL engRunning = eng2 ? eng2.isRunning : NO;
533+
id attachedNodes = nil;
534+
if (eng2 && [eng2 respondsToSelector:@selector(attachedNodes)]) {
535+
@try {
536+
attachedNodes = [eng2 attachedNodes];
537+
} @catch (NSException *e) {
538+
attachedNodes = nil;
539+
}
540+
}
541+
NSCLogDebug(@"NSCAudioBufferSourceNode: deferred play exception: %@; "
542+
@"ctx=%p eng=%p engRunning=%d player=%p player.engine=%p "
543+
@"attachedNodesCount=%lu",
544+
ex, ctx, eng2, engRunning, player, player.engine,
545+
attachedNodes ? (unsigned long)[attachedNodes count] : 0ul);
543546
@try {
544547
[player stop];
545548
} @catch (NSException *inner) {
@@ -549,7 +552,6 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
549552
return;
550553
}
551554
}
552-
553555
pollAttempts -= 1;
554556
if (pollAttempts > 0) {
555557
void (^nextPoll)(void) = weakPollBlock;
@@ -560,7 +562,6 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
560562
}
561563
return;
562564
}
563-
564565
[strongSelf scheduleRetryWithContext:ctx];
565566
};
566567
weakPollBlock = pollBlock;
@@ -638,12 +639,8 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
638639
(long long)playerTime.sampleTime, playerTime.sampleRate);
639640
safeToPlay = YES;
640641
} else {
641-
NSCLogDebug(@"NSCAudioBufferSourceNode: poll now=%p playerTime=nil", now);
642-
#if TARGET_OS_SIMULATOR
643-
NSCLogDebug(@"NSCAudioBufferSourceNode: simulator forcing play despite "
644-
@"nil playerTime");
642+
NSCLogDebug(@"NSCAudioBufferSourceNode: poll now=%p playerTime=nil, forcing play", now);
645643
safeToPlay = YES;
646-
#endif
647644
}
648645
}
649646
} @catch (NSException *e) {
@@ -758,6 +755,7 @@ - (BOOL)tryStartPlayer:(AVAudioPlayerNode *)player
758755
}
759756
}
760757
}
758+
NSCLogError(@"NSCAudioBSN: [player play] POLL-2 called engRunning=%d", (int)(ctx.engine.isRunning));
761759
[player play];
762760
if (_pendingStart) {
763761
_pendingStart = NO;
@@ -1139,6 +1137,7 @@ - (void)start {
11391137
_gaveUp = NO;
11401138
AVAudioPlayerNode *player = (AVAudioPlayerNode *)self.avNode;
11411139
AVAudioPCMBuffer *pcm = [_buffer getBuffer];
1140+
NSCLogError(@"NSCAudioBSN: start called buffer=%p engRunning=%d", pcm, (int)(ctx.engine.isRunning));
11421141
if (pcm != nil) {
11431142
__weak typeof(self) weakSelf = self;
11441143

@@ -1182,6 +1181,7 @@ - (void)start {
11821181
}
11831182
__weak typeof(self) weakSelf = self;
11841183

1184+
NSCLogError(@"NSCAudioBSN: start engRunning=%d before tryStart", (int)(ctx.engine.isRunning));
11851185
if (ctx.engine.isRunning) {
11861186
if ([self tryStartPlayer:player context:ctx]) {
11871187
return;

packages/audio-context/platforms/ios/src/NSCAudioContext.m

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,40 @@ static void NSCAudioContext_registerEngine(AVAudioEngine *engine, NSCAudioContex
5454

5555
static NSMapTable<AVAudioEngine *, dispatch_block_t> *gScheduledResumeMap = nil;
5656

57+
static void NSCAudioContext_prepareAudioSessionForPlayback(void) {
58+
AVAudioSession *session = [AVAudioSession sharedInstance];
59+
if (!session) return;
60+
61+
AVAudioSessionCategoryOptions options = 0;
62+
#ifdef AVAudioSessionCategoryOptionDefaultToSpeaker
63+
options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
64+
#endif
65+
#ifdef AVAudioSessionCategoryOptionAllowBluetoothA2DP
66+
options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
67+
#endif
68+
#ifdef AVAudioSessionCategoryOptionAllowAirPlay
69+
options |= AVAudioSessionCategoryOptionAllowAirPlay;
70+
#endif
71+
72+
void (^configure)(void) = ^{
73+
NSError *catErr = nil;
74+
if (![session setCategory:AVAudioSessionCategoryPlayback withOptions:options error:&catErr]) {
75+
NSCLogDebug(@"NSCAudioContext: prepare session setCategory failed: %@", catErr);
76+
}
77+
78+
NSError *actErr = nil;
79+
if (![session setActive:YES error:&actErr]) {
80+
NSCLogDebug(@"NSCAudioContext: prepare session setActive failed: %@", actErr);
81+
}
82+
};
83+
84+
if ([NSThread isMainThread]) {
85+
configure();
86+
} else {
87+
dispatch_sync(dispatch_get_main_queue(), configure);
88+
}
89+
}
90+
5791
void NSCAudioContext_cancelScheduledResume(AVAudioEngine *engine) {
5892
if (!engine) return;
5993
static dispatch_once_t onceToken;
@@ -496,6 +530,8 @@ - (nullable NSCAudioBuffer *)decodeAudioDataFromFile:(NSString *)path {
496530
- (nullable NSCAudioBuffer *)decodeAudioDataFromData:(NSData *)data {
497531
if (!data || data.length == 0) return nil;
498532

533+
@autoreleasepool {
534+
499535

500536
if (data.length > 4) {
501537
const uint8_t *bytes = (const uint8_t *)data.bytes;
@@ -521,49 +557,14 @@ - (nullable NSCAudioBuffer *)decodeAudioDataFromData:(NSData *)data {
521557
return wrapper;
522558
}
523559

524-
NSError *playerErr = nil;
525-
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:data error:&playerErr];
526-
if (player && [player prepareToPlay]) {
527-
AVAudioFormat *format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
528-
sampleRate:player.format.sampleRate
529-
channels:player.format.channelCount
530-
interleaved:NO];
531-
AVAudioEngine *engine = [[AVAudioEngine alloc] init];
532-
AVAudioPlayerNode *playerNode = [[AVAudioPlayerNode alloc] init];
533-
[engine attachNode:playerNode];
534-
[engine connect:playerNode to:engine.mainMixerNode format:format];
535-
NSError *engineErr = nil;
536-
[engine startAndReturnError:&engineErr];
537-
AVAudioPCMBuffer *pcmBuffer = nil;
538-
539-
AVAudioFile *tmpFile = nil;
540-
NSString *tmpName = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"nsc_decode_%@.caf", [[NSUUID UUID] UUIDString]]];
541-
BOOL ok = [data writeToFile:tmpName atomically:YES];
542-
if (ok) {
543-
NSURL *url = [NSURL fileURLWithPath:tmpName];
544-
tmpFile = [[AVAudioFile alloc] initForReading:url error:nil];
545-
if (tmpFile) {
546-
AVAudioFrameCount frameCount = (AVAudioFrameCount)tmpFile.length;
547-
pcmBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:tmpFile.processingFormat frameCapacity:frameCount];
548-
NSError *readErr = nil;
549-
[tmpFile readIntoBuffer:pcmBuffer error:&readErr];
550-
if (!readErr) {
551-
NSCAudioBuffer *wrapper = [[NSCAudioBuffer alloc] initWithContext:self id:[[NSUUID UUID] UUIDString] buffer:pcmBuffer];
552-
@try { [[NSFileManager defaultManager] removeItemAtPath:tmpName error:NULL]; } @catch (NSException *e) {}
553-
return wrapper;
554-
}
555-
}
556-
@try { [[NSFileManager defaultManager] removeItemAtPath:tmpName error:NULL]; } @catch (NSException *e) {}
557-
}
558-
}
559-
560560
NSString *tmpName = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"nsc_decode_%@.tmp", [[NSUUID UUID] UUIDString]]];
561561
BOOL ok = [data writeToFile:tmpName atomically:YES];
562562
if (!ok) return nil;
563563
NSURL *url = [NSURL fileURLWithPath:tmpName];
564564
NSCAudioBuffer *buf = [self _decodeAudioFileAtURL:url];
565565
@try { [[NSFileManager defaultManager] removeItemAtPath:tmpName error:NULL]; } @catch (NSException *e) {}
566566
return buf;
567+
}
567568
}
568569

569570
- (void)decodeAudioDataAsync:(NSString *)base64 :(void (^)(NSCAudioBuffer * _Nullable))completion {
@@ -589,10 +590,13 @@ - (void)decodeAudioDataFromFileAsync:(NSString *)path :(void (^)(NSCAudioBuffer
589590
- (void)decodeAudioDataFromDataAsync:(NSData *)data :(void (^)(NSCAudioBuffer * _Nullable))completion {
590591
if (!completion) return;
591592
__weak typeof(self) weakSelf = self;
593+
NSData *retainedData = [data copy];
592594
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
595+
@autoreleasepool {
593596
__strong typeof(weakSelf) s = weakSelf;
594-
NSCAudioBuffer *b = [s decodeAudioDataFromData:data];
597+
NSCAudioBuffer *b = [s decodeAudioDataFromData:retainedData];
595598
dispatch_async(dispatch_get_main_queue(), ^{ completion(b); });
599+
}
596600
});
597601
}
598602

@@ -914,6 +918,7 @@ + (BOOL)startEngineWithRetry:(AVAudioEngine *)engine
914918
__block NSError *err = nil;
915919
__block BOOL ok = NO;
916920
@try {
921+
NSCAudioContext_prepareAudioSessionForPlayback();
917922
if ([NSThread isMainThread]) {
918923
ok = [engine startAndReturnError:&err];
919924
} else {
@@ -956,6 +961,7 @@ + (BOOL)startEngineWithRetry:(AVAudioEngine *)engine
956961
__block NSError *e = nil;
957962
__block BOOL ok2 = NO;
958963
@try {
964+
NSCAudioContext_prepareAudioSessionForPlayback();
959965
if ([NSThread isMainThread]) {
960966
ok2 = [eng startAndReturnError:&e];
961967
} else {

packages/audio-context/platforms/ios/src/NSCAudioSessionManager.m

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,34 @@ - (void)_recomputeAndApply {
9898
AVAudioSession *session = [AVAudioSession sharedInstance];
9999
__block NSError *err = nil;
100100

101+
AVAudioSessionCategoryOptions categoryOptions = 0;
102+
#ifdef AVAudioSessionCategoryOptionDefaultToSpeaker
103+
categoryOptions |= AVAudioSessionCategoryOptionDefaultToSpeaker;
104+
#endif
105+
#ifdef AVAudioSessionCategoryOptionAllowBluetoothA2DP
106+
categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
107+
#endif
108+
#ifdef AVAudioSessionCategoryOptionAllowAirPlay
109+
categoryOptions |= AVAudioSessionCategoryOptionAllowAirPlay;
110+
#endif
111+
101112
if (![NSThread isMainThread]) {
102113
dispatch_sync(dispatch_get_main_queue(), ^{
103-
BOOL sessionOK = [session setCategory:AVAudioSessionCategoryPlayback error:&err];
114+
BOOL sessionOK = [session setCategory:AVAudioSessionCategoryPlayback withOptions:categoryOptions error:&err];
104115
if (!sessionOK) {
105116
NSCLogError(@"NSCAudioSessionManager: setCategory failed: %@", err);
106117
}
107118
});
108119
if (err) return;
109120
} else {
110-
BOOL sessionOK = [session setCategory:AVAudioSessionCategoryPlayback error:&err];
121+
BOOL sessionOK = [session setCategory:AVAudioSessionCategoryPlayback withOptions:categoryOptions error:&err];
111122
if (!sessionOK) {
112123
NSCLogError(@"NSCAudioSessionManager: setCategory failed: %@", err);
113124
return;
114125
}
115126
}
116127

117-
if (anyActive && needUpdate) {
128+
if (anyActive && needUpdate && self.sessionActive) {
118129
NSCLogDebug(@"NSCAudioSessionManager: deferring AVAudioSession update while a context is active");
119130
return;
120131
}

0 commit comments

Comments
 (0)