Using a better mechanism for picking up when the last file on the queue is finished. Dont async-stop the AudioQueue unless it's nearing the end (handled in handleAudioQueueOutput rathe than dataSourceEof)

This commit is contained in:
Thong Nguyen 2014-01-22 22:41:24 +00:00
parent 593a08a04f
commit c100cb7202
7 changed files with 111 additions and 102 deletions

View File

@ -23,6 +23,7 @@
A1115967188D6AEE00641365 /* AudioPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1115966188D6AEE00641365 /* AudioPlayerView.m */; };
A111596C188D6C8100641365 /* sample.m4a in Resources */ = {isa = PBXBuildFile; fileRef = A111596B188D6C8100641365 /* sample.m4a */; };
A111596F188D6DB100641365 /* SampleQueueId.m in Sources */ = {isa = PBXBuildFile; fileRef = A111596E188D6DB100641365 /* SampleQueueId.m */; };
A142571D189079BE005F0129 /* airplane.aac in Resources */ = {isa = PBXBuildFile; fileRef = A142571C18907861005F0129 /* airplane.aac */; };
A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */; };
/* End PBXBuildFile section */
@ -59,6 +60,7 @@
A111596B188D6C8100641365 /* sample.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; name = sample.m4a; path = Resources/sample.m4a; sourceTree = "<group>"; };
A111596D188D6DB100641365 /* SampleQueueId.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SampleQueueId.h; sourceTree = "<group>"; };
A111596E188D6DB100641365 /* SampleQueueId.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SampleQueueId.m; sourceTree = "<group>"; };
A142571C18907861005F0129 /* airplane.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = airplane.aac; sourceTree = "<group>"; };
A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
@ -168,6 +170,7 @@
A111596A188D6C4B00641365 /* Resources */ = {
isa = PBXGroup;
children = (
A142571C18907861005F0129 /* airplane.aac */,
A111596B188D6C8100641365 /* sample.m4a */,
);
name = Resources;
@ -251,6 +254,7 @@
A111593F188D686000641365 /* InfoPlist.strings in Resources */,
A111596C188D6C8100641365 /* sample.m4a in Resources */,
A1115947188D686000641365 /* Images.xcassets in Resources */,
A142571D189079BE005F0129 /* airplane.aac in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -54,18 +54,17 @@
[audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]];
}
-(void) audioPlayerViewQueueFromHTTPSelected:(AudioPlayerView*)audioPlayerView
-(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView
{
NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
NSString* path = [[NSBundle mainBundle] pathForResource:@"airplane" ofType:@"aac"];
NSURL* url = [NSURL fileURLWithPath:path];
STKAutoRecoveringHttpDataSource* dataSource = [[STKAutoRecoveringHttpDataSource alloc] initWithHttpDataSource:(STKHttpDataSource*)[audioPlayer dataSourceFromURL:url]];
[audioPlayer queueDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]];
[audioPlayer queueDataSource:[audioPlayer dataSourceFromURL:url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]];
}
-(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView
{
NSString * path = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"m4a"];
NSString* path = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"m4a"];
NSURL* url = [NSURL fileURLWithPath:path];
[audioPlayer setDataSource:[audioPlayer dataSourceFromURL:url] withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]];

View File

@ -39,7 +39,7 @@
@protocol AudioPlayerViewDelegate<NSObject>
-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView;
-(void) audioPlayerViewQueueFromHTTPSelected:(AudioPlayerView*)audioPlayerView;
-(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView;
-(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView;
@end
@ -48,9 +48,10 @@
@private
NSTimer* timer;
UISlider* slider;
UISwitch* repeatSwitch;
UIButton* playButton;
UIButton* playFromHTTPButton;
UIButton* queueFromHttpButton;
UIButton* queueShortFileButton;
UIButton* playFromLocalFileButton;
}

View File

@ -65,24 +65,29 @@
[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];
queueShortFileButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
queueShortFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 100, size.width, size.height);
[queueShortFileButton addTarget:self action:@selector(queueShortFileButtonTouched) forControlEvents:UIControlEventTouchUpInside];
[queueShortFileButton setTitle:@"Queue short file" forState:UIControlStateNormal];
playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
playButton.frame = CGRectMake((320 - size.width) / 2, 350, size.width, size.height);
playButton.frame = CGRectMake((320 - size.width) / 2, 370, size.width, size.height);
[playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside];
slider = [[UISlider alloc] initWithFrame:CGRectMake(20, 290, 280, 20)];
slider = [[UISlider alloc] initWithFrame:CGRectMake(20, 320, 280, 20)];
slider.continuous = YES;
[slider addTarget:self action:@selector(sliderChanged) forControlEvents:UIControlEventValueChanged];
size = CGSizeMake(80, 50);
repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 170, size.width, size.height)];
[self addSubview:slider];
[self addSubview:playButton];
[self addSubview:playFromHTTPButton];
[self addSubview:playFromLocalFileButton];
[self addSubview:queueFromHttpButton];
[self addSubview:queueShortFileButton];
[self addSubview:repeatSwitch];
[self setupTimer];
[self updateControls];
@ -105,7 +110,7 @@
-(void) setupTimer
{
timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(tick) userInfo:nil repeats:YES];
timer = [NSTimer timerWithTimeInterval:0.05 target:self selector:@selector(tick) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
@ -135,12 +140,11 @@
[self.delegate audioPlayerViewPlayFromLocalFileSelected:self];
}
-(void) queueFromHTTPButtonTouched
-(void) queueShortFileButtonTouched
{
[self.delegate audioPlayerViewQueueFromHTTPSelected:self];
[self.delegate audioPlayerViewQueueShortFileSelected:self];
}
-(void) playButtonPressed
{
if (!audioPlayer)
@ -162,7 +166,7 @@
{
if (audioPlayer == nil)
{
[playButton setTitle:@"Play" forState:UIControlStateNormal];
[playButton setTitle:@"" forState:UIControlStateNormal];
}
else if (audioPlayer.state == AudioPlayerStatePaused)
{
@ -174,7 +178,7 @@
}
else
{
[playButton setTitle:@"Play" forState:UIControlStateNormal];
[playButton setTitle:@"" forState:UIControlStateNormal];
}
}
@ -221,11 +225,14 @@
// This queues on the currently playing track to be buffered and played immediately after (gapless)
SampleQueueId* queueId = (SampleQueueId*)queueItemId;
if (repeatSwitch.on)
{
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
@ -237,4 +244,9 @@
NSLog(@"Finished: %@", [queueId.url description]);
}
-(void) audioPlayer:(STKAudioPlayer *)audioPlayer logInfo:(NSString *)line
{
NSLog(@"%@", line);
}
@end

View File

@ -36,4 +36,9 @@
return [((SampleQueueId*)object).url isEqual: self.url] && ((SampleQueueId*)object).count == self.count;
}
-(NSString*) description
{
return [self.url description];
}
@end

View File

@ -54,11 +54,11 @@ typedef enum
AudioPlayerInternalStateWaitingForQueueToStart = (1 << 4) | AudioPlayerInternalStateRunning,
AudioPlayerInternalStatePaused = (1 << 5) | AudioPlayerInternalStateRunning,
AudioPlayerInternalStateRebuffering = (1 << 6) | AudioPlayerInternalStateRunning,
AudioPlayerInternalStateStopping = (1 << 7),
AudioPlayerInternalStateFlushingAndStoppingButStillPlaying = (1 << 9),
AudioPlayerInternalStateStopped = (1 << 10),
AudioPlayerInternalStateDisposed = (1 << 11),
AudioPlayerInternalStateError = (1 << 12)
AudioPlayerInternalStateFlushingAndStoppingButStillPlaying = (1 << 7) | AudioPlayerInternalStateRunning,
AudioPlayerInternalStateStopping = (1 << 8),
AudioPlayerInternalStateStopped = (1 << 9),
AudioPlayerInternalStateDisposed = (1 << 10),
AudioPlayerInternalStateError = (1 << 31)
}
AudioPlayerInternalState;

View File

@ -115,7 +115,6 @@ AudioQueueBufferRefLookupEntry;
UInt64 audioDataOffset;
UInt64 audioDataByteCount;
UInt32 packetBufferSize;
volatile int bytesBuffered;
volatile int processedPacketsCount;
volatile int processedPacketsSizeTotal;
AudioStreamBasicDescription audioStreamBasicDescription;
@ -123,8 +122,10 @@ AudioQueueBufferRefLookupEntry;
@property (readwrite, retain) NSObject* queueItemId;
@property (readwrite, retain) STKDataSource* dataSource;
@property (readwrite) Float64 seekTime;
@property (readwrite) Float64 firstFrameIndex;
@property (readwrite) int bytesBuffered;
@property (readwrite) int lastByteIndex;
@property (readwrite) Float64 lastFrameIndex;
@property (readwrite) Float64 firstFrameIndex;
@property (readonly) UInt64 audioDataLengthInBytes;
-(double) duration;
@ -143,6 +144,7 @@ AudioQueueBufferRefLookupEntry;
self.dataSource = dataSourceIn;
self.queueItemId = queueItemIdIn;
self.lastFrameIndex = -1;
self.lastByteIndex = -1;
}
return self;
@ -256,7 +258,7 @@ AudioQueueBufferRefLookupEntry;
UInt8* readBuffer;
int readBufferSize;
NSOperationQueue* fastApiQueue;
NSOperationQueue* asyncApiRequestQueue;
STKQueueEntry* currentlyPlayingEntry;
STKQueueEntry* currentlyReadingEntry;
@ -319,6 +321,7 @@ AudioQueueBufferRefLookupEntry;
}
@property (readwrite) AudioPlayerInternalState internalState;
@property (readwrite) AudioPlayerInternalState stateBeforePaused;
-(void) logInfo:(NSString*)line;
-(void) processQueue:(BOOL)skipCurrent;
@ -327,7 +330,6 @@ 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;
@ -468,8 +470,8 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
{
if (self = [super init])
{
fastApiQueue = [[NSOperationQueue alloc] init];
[fastApiQueue setMaxConcurrentOperationCount:1];
asyncApiRequestQueue = [[NSOperationQueue alloc] init];
[asyncApiRequestQueue setMaxConcurrentOperationCount:1];
readBufferSize = readBufferSizeIn;
readBuffer = calloc(sizeof(UInt8), readBufferSize);
@ -651,9 +653,9 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
-(void) setDataSource:(STKDataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId
{
[fastApiQueue cancelAllOperations];
[asyncApiRequestQueue cancelAllOperations];
[fastApiQueue addOperationWithBlock:^
[asyncApiRequestQueue addOperationWithBlock:^
{
pthread_mutex_lock(&playerMutex);
{
@ -672,7 +674,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
-(void) queueDataSource:(STKDataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId
{
[fastApiQueue addOperationWithBlock:^
[asyncApiRequestQueue addOperationWithBlock:^
{
pthread_mutex_lock(&playerMutex);
{
@ -709,7 +711,6 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
UInt32 fileFormatSize = sizeof(fileFormat);
AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FileFormat, &fileFormatSize, &fileFormat);
NSLog(@"");
}
break;
case kAudioFileStreamProperty_DataFormat:
@ -971,16 +972,16 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
-(BOOL) processFinishedPlaying:(STKQueueEntry*)entry
{
NSLog(@"Finished playing");
[self logInfo:[NSString stringWithFormat:@"Finished playing: %@", entry.queueItemId]];
entry.lastFrameIndex = -1;
if (playbackThread)
{
CFRunLoopPerformBlock([playbackThreadRunLoop getCFRunLoop], NSDefaultRunLoopMode, ^
{
[self audioQueueFinishedPlaying:entry];
});
{
[self audioQueueFinishedPlaying:entry];
});
CFRunLoopWakeUp([playbackThreadRunLoop getCFRunLoop]);
@ -1008,6 +1009,11 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
if (currentlyPlayingEntry)
{
entry = currentlyPlayingEntry;
if (!audioQueueFlushing)
{
entry.bytesBuffered += bufferIn->mAudioDataByteSize;
}
}
}
OSSpinLockUnlock(&currentlyPlayingLock);
@ -1072,6 +1078,33 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
signal = YES;
}
}
else if (entry.lastByteIndex == audioPacketsPlayedCount && entry.lastByteIndex != -1)
{
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)
{
if (self.internalState != AudioPlayerInternalStateFlushingAndStoppingButStillPlaying)
{
if (audioQueue)
{
self.internalState = AudioPlayerInternalStateFlushingAndStoppingButStillPlaying;
[self logInfo:@"Stopping AudioQueue asynchronously to get notification of playback end (last item on queue)"];
AudioQueueStop(audioQueue, NO);
}
}
}
}
}
}
@ -1493,9 +1526,9 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
if (runLoop)
{
CFRunLoopPerformBlock([runLoop getCFRunLoop], NSDefaultRunLoopMode, ^
{
[self processRunloop];
});
{
[self processRunloop];
});
CFRunLoopWakeUp([runLoop getCFRunLoop]);
}
@ -1653,6 +1686,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
OSSpinLockLock(&currentlyPlayingLock);
currentlyPlayingEntry = next;
currentlyPlayingEntry.bytesBuffered = 0;
currentlyPlayingEntry.firstFrameIndex = [self currentTimeInFrames];
NSObject* playingQueueItemId = playingQueueItemId = currentlyPlayingEntry.queueItemId;
OSSpinLockUnlock(&currentlyPlayingLock);
@ -1725,6 +1759,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
else if (seekToTimeWasRequested && currentlyPlayingEntry && currentlyPlayingEntry != currentlyReadingEntry)
{
currentlyPlayingEntry.lastFrameIndex = -1;
currentlyPlayingEntry.lastByteIndex = -1;
[self setCurrentlyReadingEntry:currentlyPlayingEntry andStartPlaying:YES];
@ -1991,6 +2026,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
[self resetAudioQueueWithReason:@"from seekToTime"];
}
currentEntry.bytesBuffered = 0;
currentEntry.firstFrameIndex = [self currentTimeInFrames];
}
@ -2030,11 +2066,6 @@ 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;
@ -2052,12 +2083,8 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
audioQueueFlushing = YES;
error = AudioQueueStop(audioQueue, immediately);
if (immediately)
{
error = error | AudioQueueDispose(audioQueue, YES);
}
error = AudioQueueStop(audioQueue, YES);
error = error | AudioQueueDispose(audioQueue, YES);
audioQueue = nil;
}
@ -2238,63 +2265,23 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
[self enqueueBuffer];
}
NSLog(@"dataSourceEof");
[self logInfo:[NSString stringWithFormat:@"dataSourceEof for dataSource: %@", dataSourceIn]];
NSObject* queueItemId = currentlyReadingEntry.queueItemId;
dispatch_sync(dispatch_get_main_queue(), ^
dispatch_async(dispatch_get_main_queue(), ^
{
[self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId];
});
[fastApiQueue waitUntilAllOperationsAreFinished];
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;
currentlyReadingEntry = nil;
currentlyReadingEntry.lastByteIndex = audioPacketsReadCount;
if (self.internalState | AudioPlayerInternalStateRunning)
{
if (audioQueue)
{
if (![self audioQueueIsRunning] && !stopping)
{
[self logInfo:@"startAudioQueue from dataSourceEof"];
[self startAudioQueue];
}
}
}
currentlyReadingEntry = nil;
}
else
{
@ -2311,8 +2298,9 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
{
OSStatus error;
if (self.internalState != AudioPlayerInternalStatePaused)
if (self.internalState != AudioPlayerInternalStatePaused && (self.internalState & AudioPlayerInternalStateRunning))
{
self.stateBeforePaused = self.internalState;
self.internalState = AudioPlayerInternalStatePaused;
if (audioQueue)
@ -2343,7 +2331,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ
if (self.internalState == AudioPlayerInternalStatePaused)
{
self.internalState = AudioPlayerInternalStatePlaying;
self.internalState = self.stateBeforePaused;
if (seekToTimeWasRequested)
{