From dde738dffd568101a79594247edb7880979c089a Mon Sep 17 00:00:00 2001 From: Thong Nguyen Date: Wed, 22 Jan 2014 19:34:17 +0000 Subject: [PATCH] Fixed finished event not raised if item is only item in queue. New timeline method of checking whether item has finished playing won't work if item is the only or last item in the queue as handleAudioQueueOutput isn't called once last packet is played (it's gets called if other items are queued). If no items are queued, the queue is stopped and the queue property event is used to figure out if its finished playing --- ExampleApp/ExampleApp/AppDelegate.m | 14 ++- ExampleApp/ExampleApp/AudioPlayerView.h | 2 + ExampleApp/ExampleApp/AudioPlayerView.m | 18 ++- StreamingKit/StreamingKit/STKAudioPlayer.h | 7 +- StreamingKit/StreamingKit/STKAudioPlayer.m | 135 +++++++++++++++++---- 5 files changed, 147 insertions(+), 29 deletions(-) diff --git a/ExampleApp/ExampleApp/AppDelegate.m b/ExampleApp/ExampleApp/AppDelegate.m index 4e1e985..1ec9fea 100644 --- a/ExampleApp/ExampleApp/AppDelegate.m +++ b/ExampleApp/ExampleApp/AppDelegate.m @@ -47,13 +47,24 @@ -(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView { - NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; +// NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; + NSURL* url = [NSURL URLWithString:@"file:///Users/tum/testaudio.aac"]; STKAutoRecoveringHttpDataSource* dataSource = [[STKAutoRecoveringHttpDataSource alloc] initWithHttpDataSource:(STKHttpDataSource*)[audioPlayer dataSourceFromURL:url]]; [audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; } +-(void) audioPlayerViewQueueFromHTTPSelected:(AudioPlayerView*)audioPlayerView +{ +// NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; + NSURL* url = [NSURL URLWithString:@"file:///Users/tum/testaudio.aac"]; + + STKAutoRecoveringHttpDataSource* dataSource = [[STKAutoRecoveringHttpDataSource alloc] initWithHttpDataSource:(STKHttpDataSource*)[audioPlayer dataSourceFromURL:url]]; + + [audioPlayer queueDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; +} + -(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView { NSString * path = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"m4a"]; @@ -62,5 +73,4 @@ [audioPlayer setDataSource:[audioPlayer dataSourceFromURL:url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; } - @end diff --git a/ExampleApp/ExampleApp/AudioPlayerView.h b/ExampleApp/ExampleApp/AudioPlayerView.h index 972da83..f2a65ad 100644 --- a/ExampleApp/ExampleApp/AudioPlayerView.h +++ b/ExampleApp/ExampleApp/AudioPlayerView.h @@ -39,6 +39,7 @@ @protocol AudioPlayerViewDelegate -(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView; +-(void) audioPlayerViewQueueFromHTTPSelected:(AudioPlayerView*)audioPlayerView; -(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView; @end @@ -49,6 +50,7 @@ UISlider* slider; UIButton* playButton; UIButton* playFromHTTPButton; + UIButton* queueFromHttpButton; UIButton* playFromLocalFileButton; } diff --git a/ExampleApp/ExampleApp/AudioPlayerView.m b/ExampleApp/ExampleApp/AudioPlayerView.m index 9c4f903..df648d0 100644 --- a/ExampleApp/ExampleApp/AudioPlayerView.m +++ b/ExampleApp/ExampleApp/AudioPlayerView.m @@ -64,7 +64,12 @@ playFromLocalFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 50, size.width, size.height); [playFromLocalFileButton addTarget:self action:@selector(playFromLocalFileButtonTouched) forControlEvents:UIControlEventTouchUpInside]; [playFromLocalFileButton setTitle:@"Play from Local File" forState:UIControlStateNormal]; - + + queueFromHttpButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + queueFromHttpButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 100, size.width, size.height); + [queueFromHttpButton addTarget:self action:@selector(queueFromHTTPButtonTouched) forControlEvents:UIControlEventTouchUpInside]; + [queueFromHttpButton setTitle:@"Queue from HTTP" forState:UIControlStateNormal]; + playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; playButton.frame = CGRectMake((320 - size.width) / 2, 350, size.width, size.height); [playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside]; @@ -77,6 +82,7 @@ [self addSubview:playButton]; [self addSubview:playFromHTTPButton]; [self addSubview:playFromLocalFileButton]; + [self addSubview:queueFromHttpButton]; [self setupTimer]; [self updateControls]; @@ -129,6 +135,12 @@ [self.delegate audioPlayerViewPlayFromLocalFileSelected:self]; } +-(void) queueFromHTTPButtonTouched +{ + [self.delegate audioPlayerViewQueueFromHTTPSelected:self]; +} + + -(void) playButtonPressed { if (!audioPlayer) @@ -211,9 +223,9 @@ SampleQueueId* queueId = (SampleQueueId*)queueItemId; - NSLog(@"Requeuing: %@", [queueId.url description]); +// NSLog(@"Requeuing: %@", [queueId.url description]); - [self->audioPlayer queueDataSource:[self->audioPlayer dataSourceFromURL:queueId.url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:queueId.url andCount:queueId.count + 1]]; +// [self->audioPlayer queueDataSource:[self->audioPlayer dataSourceFromURL:queueId.url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:queueId.url andCount:queueId.count + 1]]; } -(void) audioPlayer:(STKAudioPlayer*)audioPlayer didFinishPlayingQueueItemId:(NSObject*)queueItemId withReason:(AudioPlayerStopReason)stopReason andProgress:(double)progress andDuration:(double)duration diff --git a/StreamingKit/StreamingKit/STKAudioPlayer.h b/StreamingKit/StreamingKit/STKAudioPlayer.h index f5f07ec..6626120 100644 --- a/StreamingKit/StreamingKit/STKAudioPlayer.h +++ b/StreamingKit/StreamingKit/STKAudioPlayer.h @@ -55,9 +55,10 @@ typedef enum AudioPlayerInternalStatePaused = (1 << 5) | AudioPlayerInternalStateRunning, AudioPlayerInternalStateRebuffering = (1 << 6) | AudioPlayerInternalStateRunning, AudioPlayerInternalStateStopping = (1 << 7), - AudioPlayerInternalStateStopped = (1 << 8), - AudioPlayerInternalStateDisposed = (1 << 9), - AudioPlayerInternalStateError = (1 << 10) + AudioPlayerInternalStateFlushingAndStoppingButStillPlaying = (1 << 9), + AudioPlayerInternalStateStopped = (1 << 10), + AudioPlayerInternalStateDisposed = (1 << 11), + AudioPlayerInternalStateError = (1 << 12) } AudioPlayerInternalState; diff --git a/StreamingKit/StreamingKit/STKAudioPlayer.m b/StreamingKit/StreamingKit/STKAudioPlayer.m index d7458b0..7a23f1d 100644 --- a/StreamingKit/StreamingKit/STKAudioPlayer.m +++ b/StreamingKit/StreamingKit/STKAudioPlayer.m @@ -327,6 +327,7 @@ AudioQueueBufferRefLookupEntry; -(void) resetAudioQueueWithReason:(NSString*)reason; -(BOOL) startAudioQueue; -(void) stopAudioQueueWithReason:(NSString*)reason; +-(void) stopAudioQueueWithReason:(NSString*)reason immediately:(BOOL)immediately; -(BOOL) processRunloop; -(void) wakeupPlaybackThread; -(void) audioQueueFinishedPlaying:(STKQueueEntry*)entry; @@ -406,6 +407,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ case AudioPlayerInternalStateWaitingForQueueToStart: case AudioPlayerInternalStatePlaying: case AudioPlayerInternalStateRebuffering: + case AudioPlayerInternalStateFlushingAndStoppingButStillPlaying: newState = AudioPlayerStatePlaying; break; case AudioPlayerInternalStateStopping: @@ -701,6 +703,15 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ [currentlyReadingEntry updateAudioDataSource]; } break; + case kAudioFileStreamProperty_FileFormat: + { + char fileFormat[4]; + UInt32 fileFormatSize = sizeof(fileFormat); + + AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FileFormat, &fileFormatSize, &fileFormat); + NSLog(@""); + } + break; case kAudioFileStreamProperty_DataFormat: { if (currentlyReadingEntry->audioStreamBasicDescription.mSampleRate == 0) { @@ -958,6 +969,27 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ } } +-(BOOL) processFinishedPlaying:(STKQueueEntry*)entry +{ + NSLog(@"Finished playing"); + + entry.lastFrameIndex = -1; + + if (playbackThread) + { + CFRunLoopPerformBlock([playbackThreadRunLoop getCFRunLoop], NSDefaultRunLoopMode, ^ + { + [self audioQueueFinishedPlaying:entry]; + }); + + CFRunLoopWakeUp([playbackThreadRunLoop getCFRunLoop]); + + return YES; + } + + return NO; +} + -(void) handleAudioQueueOutput:(AudioQueueRef)audioQueueIn buffer:(AudioQueueBufferRef)bufferIn { int bufferIndex = -1; @@ -1024,7 +1056,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ signal = YES; } - if (!audioQueueFlushing && [self progress] > 4.0 && numberOfBuffersUsed == 0 ) + if (!audioQueueFlushing && [self progress] > 4.0 && numberOfBuffersUsed == 0 && self.internalState == AudioPlayerInternalStatePlaying) { self.internalState = AudioPlayerInternalStateRebuffering; } @@ -1035,19 +1067,8 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { if ([self currentTimeInFrames] > entry.lastFrameIndex && entry.lastFrameIndex != -1) { - NSLog(@"Finished playing"); - - entry.lastFrameIndex = -1; - - if (playbackThread) + if ([self processFinishedPlaying:entry]) { - CFRunLoopPerformBlock([playbackThreadRunLoop getCFRunLoop], NSDefaultRunLoopMode, ^ - { - [self audioQueueFinishedPlaying:entry]; - }); - - CFRunLoopWakeUp([playbackThreadRunLoop getCFRunLoop]); - signal = YES; } } @@ -1117,10 +1138,33 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { self.internalState = AudioPlayerInternalStateStopped; } + else if (![self audioQueueIsRunning] && self.internalState == AudioPlayerInternalStateFlushingAndStoppingButStillPlaying) + { + NSRunLoop* runLoop = playbackThreadRunLoop; + + if (runLoop) + { + CFRunLoopPerformBlock([runLoop getCFRunLoop], NSDefaultRunLoopMode, ^ + { + if (audioQueue) + { + [self stopAudioQueueWithReason:@"handlePropertyChangeForQueue`2"]; + } + + if (currentlyPlayingEntry) + { + self->stopReason = AudioPlayerStopReasonEof; + self.internalState = AudioPlayerInternalStateStopped; + + [self processFinishedPlaying:currentlyPlayingEntry]; + } + }); + + CFRunLoopWakeUp([runLoop getCFRunLoop]); + } + } else if (self.internalState == AudioPlayerInternalStateWaitingForQueueToStart) { - [NSRunLoop currentRunLoop]; - self.internalState = AudioPlayerInternalStatePlaying; } } @@ -1245,6 +1289,8 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { OSStatus error; + NSLog(@"CreateAudioQueue"); + [self startSystemBackgroundTask]; if (audioQueue) @@ -1520,6 +1566,11 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ [self resetAudioQueueWithReason:@"from setCurrentlyReadingEntry"]; pthread_mutex_lock(&queueBuffersMutex); + + if (![self audioQueueIsRunning]) + { + [self startAudioQueue]; + } } } @@ -1572,6 +1623,8 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ [self processDidFinishPlaying:entry withNext:next]; } pthread_mutex_unlock(&queueBuffersMutex); + + [self processRunloop]; } pthread_mutex_unlock(&playerMutex); } @@ -1647,8 +1700,12 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ -(BOOL) processRunloop { + BOOL dontPlayNew = NO; + pthread_mutex_lock(&playerMutex); { + dontPlayNew = self.internalState == AudioPlayerInternalStateFlushingAndStoppingButStillPlaying; + if (self.internalState == AudioPlayerInternalStatePaused) { pthread_mutex_unlock(&playerMutex); @@ -1730,7 +1787,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ [self resetAudioQueueWithReason:@"from processRunLoop"]; } - else if (currentlyReadingEntry == nil) + else if (currentlyReadingEntry == nil && !dontPlayNew) { BOOL nextIsIncompatible = NO; @@ -1792,7 +1849,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ } pthread_mutex_unlock(&playerMutex); - if (currentlyReadingEntry && currentlyReadingEntry->parsedHeader && currentlyReadingEntry != currentlyPlayingEntry) + if (currentlyReadingEntry && currentlyReadingEntry->parsedHeader && currentlyReadingEntry != currentlyPlayingEntry && !dontPlayNew) { if (currentAudioStreamBasicDescription.mSampleRate != 0) { @@ -1973,6 +2030,11 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ } -(void) stopAudioQueueWithReason:(NSString*)reason +{ + [self stopAudioQueueWithReason:reason immediately:YES]; +} + +-(void) stopAudioQueueWithReason:(NSString*)reason immediately:(BOOL)immediately { OSStatus error; @@ -1990,8 +2052,12 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ audioQueueFlushing = YES; - error = AudioQueueStop(audioQueue, true); - error = error | AudioQueueDispose(audioQueue, true); + error = AudioQueueStop(audioQueue, immediately); + + if (immediately) + { + error = error | AudioQueueDispose(audioQueue, YES); + } audioQueue = nil; } @@ -2172,17 +2238,44 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ [self enqueueBuffer]; } + NSLog(@"dataSourceEof"); + [self logInfo:[NSString stringWithFormat:@"dataSourceEof for dataSource: %@", dataSourceIn]]; NSObject* queueItemId = currentlyReadingEntry.queueItemId; - dispatch_async(dispatch_get_main_queue(), ^ + dispatch_sync(dispatch_get_main_queue(), ^ { [self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId]; }); pthread_mutex_lock(&playerMutex); { + BOOL stopping = NO; + BOOL everythingInBufferingQueueBuffered = YES; + + for (int i = 0; i < bufferingQueue.count; i++) + { + if ([[bufferingQueue objectAtIndex:i] lastFrameIndex] == -1 && [bufferingQueue objectAtIndex:i] != currentlyReadingEntry) + { + everythingInBufferingQueueBuffered = NO; + } + } + + if ([upcomingQueue peek] == nil && everythingInBufferingQueueBuffered) + { + self.internalState = AudioPlayerInternalStateFlushingAndStoppingButStillPlaying; + + if (audioQueue) + { + NSLog(@"stopping audio queue asynchronously in dataSourceEof"); + + AudioQueueStop(audioQueue, NO); + + stopping = YES; + } + } + if (audioQueue) { currentlyReadingEntry.lastFrameIndex = self->framesQueued; @@ -2192,7 +2285,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { if (audioQueue) { - if (![self audioQueueIsRunning]) + if (![self audioQueueIsRunning] && !stopping) { [self logInfo:@"startAudioQueue from dataSourceEof"];