From 7608a42d298710e5f8bb1871e623aa472bb57ea9 Mon Sep 17 00:00:00 2001 From: Thong Nguyen Date: Sun, 26 Jan 2014 20:37:06 +0000 Subject: [PATCH] Fixed dispose call hanging if player or audio thread are doing sync calls to the UI thread. Better rebuffering on 2G --- ExampleApp/ExampleApp/AudioPlayerView.h | 1 + ExampleApp/ExampleApp/AudioPlayerView.m | 18 +- StreamingKit/StreamingKit/STKAudioPlayer.m | 348 +++++++++++++-------- 3 files changed, 231 insertions(+), 136 deletions(-) diff --git a/ExampleApp/ExampleApp/AudioPlayerView.h b/ExampleApp/ExampleApp/AudioPlayerView.h index 554cc25..9f50974 100644 --- a/ExampleApp/ExampleApp/AudioPlayerView.h +++ b/ExampleApp/ExampleApp/AudioPlayerView.h @@ -52,6 +52,7 @@ UISlider* slider; UISwitch* repeatSwitch; UIButton* playButton; + UIButton* disposeButton; UIButton* playFromHTTPButton; UIButton* queueShortFileButton; UIButton* playFromLocalFileButton; diff --git a/ExampleApp/ExampleApp/AudioPlayerView.m b/ExampleApp/ExampleApp/AudioPlayerView.m index 4ac7b8e..3fc7906 100644 --- a/ExampleApp/ExampleApp/AudioPlayerView.m +++ b/ExampleApp/ExampleApp/AudioPlayerView.m @@ -71,8 +71,13 @@ [queueShortFileButton setTitle:@"Queue short file" forState:UIControlStateNormal]; playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - playButton.frame = CGRectMake((320 - size.width) / 2, 380, size.width, size.height); + playButton.frame = CGRectMake(30, 380, size.width, size.height); [playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + + disposeButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + disposeButton.frame = CGRectMake((320 - size.width) - 30, 380, size.width, size.height); + [disposeButton addTarget:self action:@selector(disposeButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + [disposeButton setTitle:@"Dispose" forState:UIControlStateNormal]; slider = [[UISlider alloc] initWithFrame:CGRectMake(20, 320, 280, 20)]; slider.continuous = YES; @@ -98,6 +103,7 @@ [self addSubview:repeatSwitch]; [self addSubview:label]; [self addSubview:statusLabel]; + [self addSubview:disposeButton]; [self setupTimer]; [self updateControls]; @@ -171,12 +177,22 @@ [self.delegate audioPlayerViewQueueShortFileSelected:self]; } +-(void) disposeButtonPressed +{ + [audioPlayer dispose]; + audioPlayer = nil; +} + -(void) playButtonPressed { if (!audioPlayer) { return; } + + [audioPlayer dispose]; + + return; if (audioPlayer.state == AudioPlayerStatePaused) { diff --git a/StreamingKit/StreamingKit/STKAudioPlayer.m b/StreamingKit/StreamingKit/STKAudioPlayer.m index ff588f7..23d123e 100644 --- a/StreamingKit/StreamingKit/STKAudioPlayer.m +++ b/StreamingKit/StreamingKit/STKAudioPlayer.m @@ -43,7 +43,7 @@ #define STK_BIT_RATE_ESTIMATION_MIN_PACKETS (64) #define STK_BUFFERS_NEEDED_TO_START (32) -#define STK_BUFFERS_NEEDED_WHEN_UNDERUNNING (64) +#define STK_BUFFERS_NEEDED_WHEN_UNDERUNNING (128) #define STK_DEFAULT_READ_BUFFER_SIZE (2 * 1024) #define STK_DEFAULT_PACKET_BUFFER_SIZE (2048) #define STK_FRAMES_MISSED_BEFORE_CONSIDERED_UNDERRUN (1024) @@ -119,6 +119,7 @@ AudioQueueBufferRefLookupEntry; UInt64 audioDataOffset; UInt64 audioDataByteCount; UInt32 packetBufferSize; + volatile BOOL cancel; volatile int processedPacketsCount; volatile int processedPacketsSizeTotal; AudioStreamBasicDescription audioStreamBasicDescription; @@ -250,22 +251,24 @@ AudioQueueBufferRefLookupEntry; } } --(BOOL) isDefinitelyIncompatible:(AudioStreamBasicDescription*)basicDescription +-(BOOL) isKnownToBeIncompatible:(AudioStreamBasicDescription*)basicDescription { + if (self->audioStreamBasicDescription.mSampleRate == 0) + { + return NO; + } + return (memcmp(&(self->audioStreamBasicDescription), basicDescription, sizeof(*basicDescription)) != 0); } -(BOOL) couldBeIncompatible:(AudioStreamBasicDescription*)basicDescription { - if (self->audioStreamBasicDescription.mSampleRate != 0) + if (self->audioStreamBasicDescription.mSampleRate == 0) { - if (memcmp(&(self->audioStreamBasicDescription), basicDescription, sizeof(*basicDescription)) != 0) - { - return YES; - } + return YES; } - return NO; + return memcmp(&(self->audioStreamBasicDescription), basicDescription, sizeof(*basicDescription)) != 0; } -(NSString*) description @@ -327,6 +330,9 @@ AudioQueueBufferRefLookupEntry; pthread_mutex_t playerMutex; pthread_mutex_t queueBuffersMutex; pthread_cond_t queueBufferReadyCondition; + + pthread_mutex_t mainThreadSyncCallMutex; + pthread_cond_t mainThreadSyncCallReadyCondition; volatile BOOL waiting; volatile BOOL disposeWasRequested; @@ -400,21 +406,6 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return internalState; } --(void) syncInvokeOnMainThreadWithPlayerLock:(void(^)())block -{ - block = [block copy]; - - [self->asyncApiRequestQueue addOperationWithBlock:^ - { - pthread_mutex_lock(&playerMutex); - dispatch_sync(dispatch_get_main_queue(), ^ - { - block(); - }); - pthread_mutex_unlock(&playerMutex); - }]; -} - -(void) setInternalState:(AudioPlayerInternalState)value { if (value == internalState) @@ -426,10 +417,10 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ if ([self.delegate respondsToSelector:@selector(audioPlayer:internalStateChanged:)]) { - [self syncInvokeOnMainThreadWithPlayerLock: ^ + dispatch_async(dispatch_get_main_queue(), ^ { [self.delegate audioPlayer:self internalStateChanged:internalState]; - }]; + }); } AudioPlayerState newState; @@ -468,10 +459,10 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { self.state = newState; - [self syncInvokeOnMainThreadWithPlayerLock: ^ + dispatch_async(dispatch_get_main_queue(), ^ { [self.delegate audioPlayer:self stateChanged:self.state]; - }]; + }); } } @@ -540,6 +531,9 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ pthread_mutex_init(&playerMutex, &attr); pthread_mutex_init(&queueBuffersMutex, NULL); pthread_cond_init(&queueBufferReadyCondition, NULL); + + pthread_mutex_init(&mainThreadSyncCallMutex, NULL); + pthread_cond_init(&mainThreadSyncCallReadyCondition, NULL); threadFinishedCondLock = [[NSConditionLock alloc] initWithCondition:0]; @@ -567,6 +561,10 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ pthread_mutex_destroy(&playerMutex); pthread_mutex_destroy(&queueBuffersMutex); pthread_cond_destroy(&queueBufferReadyCondition); + + pthread_mutex_destroy(&mainThreadSyncCallMutex); + pthread_cond_destroy(&mainThreadSyncCallReadyCondition); + if (audioFileStream) { @@ -802,62 +800,69 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1); - if (currentlyReadingEntry != currentlyPlayingEntry && audioQueue && currentAudioStreamBasicDescription.mSampleRate != 0) + if (currentlyReadingEntry->cancel) { - if (memcmp(¤tAudioStreamBasicDescription, &newBasicDescription, sizeof(currentAudioStreamBasicDescription)) != 0) + cancel = YES; + } + else + { + if (currentlyReadingEntry != currentlyPlayingEntry && audioQueue && currentAudioStreamBasicDescription.mSampleRate != 0) { - [currentlyReadingEntry.dataSource unregisterForEvents]; - - if ([bufferingQueue objectAtIndex:0] == currentlyReadingEntry) + if (memcmp(¤tAudioStreamBasicDescription, &newBasicDescription, sizeof(currentAudioStreamBasicDescription)) != 0) { - [bufferingQueue removeObjectAtIndex:0]; + cancel = YES; } - - STKQueueEntry* newEntry = [[STKQueueEntry alloc] initWithDataSource:currentlyReadingEntry.dataSource andQueueItemId:currentlyReadingEntry.queueItemId]; - - entryToUpdate = newEntry; - - [upcomingQueue skipQueue:newEntry]; - - OSSpinLockLock(¤tEntryReferencesLock); - currentlyReadingEntry = nil; - OSSpinLockUnlock(¤tEntryReferencesLock); - - cancel = YES; } } - if (!cancel) + if (cancel) { - entryToUpdate->audioStreamBasicDescription = newBasicDescription; - entryToUpdate->sampleRate = entryToUpdate->audioStreamBasicDescription.mSampleRate; - entryToUpdate->packetDuration = entryToUpdate->audioStreamBasicDescription.mFramesPerPacket / entryToUpdate->sampleRate; - - UInt32 packetBufferSize = 0; - UInt32 sizeOfPacketBufferSize = sizeof(packetBufferSize); + [currentlyReadingEntry.dataSource unregisterForEvents]; - error = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfPacketBufferSize, &packetBufferSize); + if ([bufferingQueue objectAtIndex:0] == currentlyReadingEntry) + { + [bufferingQueue removeObjectAtIndex:0]; + } + + STKQueueEntry* newEntry = [[STKQueueEntry alloc] initWithDataSource:currentlyReadingEntry.dataSource andQueueItemId:currentlyReadingEntry.queueItemId]; + + entryToUpdate = newEntry; + + [upcomingQueue skipQueue:newEntry]; + + OSSpinLockLock(¤tEntryReferencesLock); + currentlyReadingEntry = nil; + OSSpinLockUnlock(¤tEntryReferencesLock); + } + + entryToUpdate->audioStreamBasicDescription = newBasicDescription; + entryToUpdate->sampleRate = entryToUpdate->audioStreamBasicDescription.mSampleRate; + entryToUpdate->packetDuration = entryToUpdate->audioStreamBasicDescription.mFramesPerPacket / entryToUpdate->sampleRate; + + UInt32 packetBufferSize = 0; + UInt32 sizeOfPacketBufferSize = sizeof(packetBufferSize); + + error = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfPacketBufferSize, &packetBufferSize); + + if (error || packetBufferSize == 0) + { + error = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfPacketBufferSize, &packetBufferSize); if (error || packetBufferSize == 0) { - error = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfPacketBufferSize, &packetBufferSize); - - if (error || packetBufferSize == 0) - { - entryToUpdate->packetBufferSize = STK_DEFAULT_PACKET_BUFFER_SIZE; - } - else - { - entryToUpdate->packetBufferSize = packetBufferSize; - } + entryToUpdate->packetBufferSize = STK_DEFAULT_PACKET_BUFFER_SIZE; } else { entryToUpdate->packetBufferSize = packetBufferSize; } - - [entryToUpdate updateAudioDataSource]; } + else + { + entryToUpdate->packetBufferSize = packetBufferSize; + } + + [entryToUpdate updateAudioDataSource]; pthread_mutex_unlock(&playerMutex); } @@ -936,7 +941,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return; } - if (seekToTimeWasRequested) + if (seekToTimeWasRequested || disposeWasRequested) { return; } @@ -1015,7 +1020,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { [self enqueueBuffer]; - if (seekToTimeWasRequested || self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed) + if (disposeWasRequested || seekToTimeWasRequested || self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed) { return; } @@ -1050,7 +1055,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { [self enqueueBuffer]; - if (seekToTimeWasRequested || self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed) + if (disposeWasRequested || seekToTimeWasRequested || self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed) { return; } @@ -1071,7 +1076,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { [self enqueueBuffer]; - if (seekToTimeWasRequested || self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed) + if (disposeWasRequested || seekToTimeWasRequested || self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed) { return; } @@ -1225,11 +1230,21 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ upcomingExistsAndIsIncompatible = [upcoming couldBeIncompatible:¤tAudioStreamBasicDescription]; } - BOOL lastInBufferingQueue; + BOOL nextBufferingExistsAndIsIncompatible = NO; + STKQueueEntry* nextBuffering = [bufferingQueue peek]; - lastInBufferingQueue = [bufferingQueue peek] == nil; + if (nextBuffering != nil) + { + if ([nextBuffering couldBeIncompatible:¤tAudioStreamBasicDescription]) + { + nextBufferingExistsAndIsIncompatible = YES; + nextBuffering->cancel = YES; + + NSAssert(bufferingQueue.count == 1, @"bufferingQueue.count == 1"); + } + } - if ((upcoming == nil || upcomingExistsAndIsIncompatible) && lastInBufferingQueue) + if ((upcoming == nil || upcomingExistsAndIsIncompatible) && (nextBuffering == nil || nextBufferingExistsAndIsIncompatible)) { if (self.internalState != AudioPlayerInternalStateFlushingAndStoppingButStillPlaying) { @@ -1253,10 +1268,9 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { if (numberOfBuffersUsed == 0 && !seekToTimeWasRequested + && !disposeWasRequested && self.internalState != AudioPlayerInternalStateWaitingForData) { - LOGINFO(([NSString stringWithFormat:@"UNDERRUN WITH TIME: %f", [self currentTimeInFrames]])); - if (self->rebufferingStartFrames == 0) { [self invokeOnPlaybackThread:^ @@ -1264,11 +1278,16 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ pthread_mutex_lock(&playerMutex); if (self->numberOfBuffersUsed == 0 + && !disposeWasRequested && !seekToTimeWasRequested && self->rebufferingStartFrames == 0 && self.internalState != AudioPlayerInternalStateWaitingForData && [self moreFramesAreDefinitelyAvailableToPlay]) { + if ([self moreFramesAreDefinitelyAvailableToPlay]) + { + } + AudioQueuePause(audioQueue); self->rebufferingStartFrames = [self currentTimeInFrames]; @@ -1280,7 +1299,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ }]; } - if (self.internalState != AudioPlayerStateBuffering && self.internalState != AudioPlayerInternalStatePaused) + if (self.internalState != AudioPlayerInternalStateRebuffering && self.internalState != AudioPlayerInternalStatePaused) { Float64 interval = STK_FRAMES_MISSED_BEFORE_CONSIDERED_UNDERRUN / currentAudioStreamBasicDescription.mSampleRate; @@ -1294,6 +1313,8 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ } } + signal = signal || disposeWasRequested; + if (self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed @@ -1368,11 +1389,6 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ return NO; } - if (upcomingQueue.count > 0 && [(STKQueueEntry*)[upcomingQueue peek] isDefinitelyIncompatible:¤tAudioStreamBasicDescription]) - { - return YES; - } - return NO; } @@ -1573,7 +1589,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ waiting = YES; - while (bufferUsed[fillBufferIndex] && !(seekToTimeWasRequested || self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed)) + while (bufferUsed[fillBufferIndex] && !(disposeWasRequested || seekToTimeWasRequested || self.internalState == AudioPlayerInternalStateStopped || self.internalState == AudioPlayerInternalStateStopping || self.internalState == AudioPlayerInternalStateDisposed)) { if (numberOfBuffersUsed == 0) { @@ -1616,12 +1632,15 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { LOGINFO(@"AudioQueueStop/1"); + pthread_mutex_unlock(&queueBuffersMutex); AudioQueueStop(audioQueue, YES); AudioQueueDispose(audioQueue, YES); memset(¤tAudioStreamBasicDescription, 0, sizeof(currentAudioStreamBasicDescription)); audioQueue = nil; + + pthread_mutex_lock(&queueBuffersMutex); } if (currentlyPlayingEntry == nil) @@ -2068,19 +2087,56 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ } } +-(void) dispatchSyncOnMainThread:(void(^)())block +{ + __block BOOL finished = NO; + + if (disposeWasRequested) + { + return; + } + + dispatch_async(dispatch_get_main_queue(), ^ + { + block(); + + pthread_mutex_lock(&mainThreadSyncCallMutex); + finished = YES; + pthread_cond_signal(&mainThreadSyncCallReadyCondition); + pthread_mutex_unlock(&mainThreadSyncCallMutex); + }); + + while (true) + { + if (disposeWasRequested) + { + break; + } + + if (finished) + { + break; + } + + pthread_mutex_lock(&mainThreadSyncCallMutex); + pthread_cond_wait(&mainThreadSyncCallReadyCondition, &mainThreadSyncCallMutex); + pthread_mutex_unlock(&mainThreadSyncCallMutex); + } +} + -(void) playbackThreadQueueMainThreadSyncBlock:(void(^)())block { - if ([NSThread currentThread] == playbackThread) + block = [block copy]; + + [self invokeOnPlaybackThread:^ { - dispatch_sync(dispatch_get_main_queue(), block); - } - else - { - [self invokeOnPlaybackThread:^ + if (disposeWasRequested) { - dispatch_sync(dispatch_get_main_queue(), block); - }]; - } + return; + } + + [self dispatchSyncOnMainThread:block]; + }]; } -(BOOL) processRunloop @@ -2175,7 +2231,7 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ next = [upcomingQueue peek]; - if ([next couldBeIncompatible:¤tAudioStreamBasicDescription] && currentlyPlayingEntry != nil) + if ([next isKnownToBeIncompatible:¤tAudioStreamBasicDescription] && currentlyPlayingEntry != nil) { goto endOfIfElse; } @@ -2618,58 +2674,68 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ NSObject* queueItemId = currentlyReadingEntry.queueItemId; - dispatch_sync(dispatch_get_main_queue(), ^ + if (disposeWasRequested) + { + return; + } + + [self dispatchSyncOnMainThread:^ { [self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId]; - }); - - pthread_mutex_lock(&playerMutex); + }]; + + if (disposeWasRequested) { - if (audioQueue) + return; + } + + pthread_mutex_lock(&playerMutex); + + if (audioQueue) + { + currentlyReadingEntry.lastFrameIndex = self->framesQueued; + currentlyReadingEntry.lastByteIndex = audioPacketsReadCount; + + if (audioPacketsReadCount == 0 && audioPacketsPlayedCount == audioPacketsReadCount && currentlyReadingEntry == currentlyPlayingEntry) { - currentlyReadingEntry.lastFrameIndex = self->framesQueued; - currentlyReadingEntry.lastByteIndex = audioPacketsReadCount; + LOGINFO(@"Seeked to end"); - if (audioPacketsReadCount == 0 && audioPacketsPlayedCount == audioPacketsReadCount && currentlyReadingEntry == currentlyPlayingEntry) + seekToTimeWasRequested = NO; + + if (audioQueue) { - LOGINFO(@"Seeked to end"); - - seekToTimeWasRequested = NO; - - if (audioQueue) + if ([self audioQueueIsRunning]) { - if ([self audioQueueIsRunning]) - { - self.internalState = AudioPlayerInternalStateFlushingAndStoppingButStillPlaying; + self.internalState = AudioPlayerInternalStateFlushingAndStoppingButStillPlaying; - LOGINFO(@"Stopping AudioQueue asynchronously"); - - if (AudioQueueStop(audioQueue, NO) != 0) - { - LOGINFO(@"Stopping AudioQueue asynchronously failed"); - - [self processFinishedPlayingViaAudioQueueStop]; - } - } - else + LOGINFO(@"Stopping AudioQueue asynchronously"); + + if (AudioQueueStop(audioQueue, NO) != 0) { - LOGINFO(@"AudioQueue already stopped"); + LOGINFO(@"Stopping AudioQueue asynchronously failed"); [self processFinishedPlayingViaAudioQueueStop]; } } + else + { + LOGINFO(@"AudioQueue already stopped"); + + [self processFinishedPlayingViaAudioQueueStop]; + } } + } - OSSpinLockLock(¤tEntryReferencesLock); - currentlyReadingEntry = nil; - OSSpinLockUnlock(¤tEntryReferencesLock); - } - else - { - stopReason = AudioPlayerStopReasonEof; - self.internalState = AudioPlayerInternalStateStopped; - } + OSSpinLockLock(¤tEntryReferencesLock); + currentlyReadingEntry = nil; + OSSpinLockUnlock(¤tEntryReferencesLock); } + else + { + stopReason = AudioPlayerStopReasonEof; + self.internalState = AudioPlayerInternalStateStopped; + } + pthread_mutex_unlock(&playerMutex); } @@ -2786,18 +2852,30 @@ static void AudioQueueIsRunningCallbackProc(void* userData, AudioQueueRef audioQ { BOOL wait = NO; - pthread_mutex_lock(&playerMutex); + NSRunLoop* runLoop = playbackThreadRunLoop; + + if (runLoop != nil) { - disposeWasRequested = YES; + wait = YES; - if (playbackThread && playbackThreadRunLoop) + [self invokeOnPlaybackThread:^ { - wait = YES; - - CFRunLoopStop([playbackThreadRunLoop getCFRunLoop]); - } + pthread_mutex_lock(&playerMutex); + disposeWasRequested = YES; + pthread_mutex_unlock(&queueBuffersMutex); + }]; + + pthread_mutex_lock(&queueBuffersMutex); + pthread_cond_signal(&queueBufferReadyCondition); + pthread_mutex_unlock(&queueBuffersMutex); + + pthread_mutex_lock(&mainThreadSyncCallMutex); + pthread_cond_signal(&mainThreadSyncCallReadyCondition); + pthread_mutex_unlock(&mainThreadSyncCallMutex); + + + CFRunLoopStop([runLoop getCFRunLoop]); } - pthread_mutex_unlock(&playerMutex); if (wait) {