Update iOS to new state model.

This commit is contained in:
Ryan Heise 2020-08-05 03:24:52 +10:00
parent e4789d9cd2
commit 2c3d38f1ad
9 changed files with 153 additions and 129 deletions

View File

@ -22,7 +22,7 @@
NSMutableArray<NSNumber *> *_order; NSMutableArray<NSNumber *> *_order;
NSMutableArray<NSNumber *> *_orderInv; NSMutableArray<NSNumber *> *_orderInv;
int _index; int _index;
enum PlaybackState _state; enum ProcessingState _processingState;
enum LoopMode _loopMode; enum LoopMode _loopMode;
BOOL _shuffleModeEnabled; BOOL _shuffleModeEnabled;
long long _updateTime; long long _updateTime;
@ -32,8 +32,8 @@
// Set when the current item hasn't been played yet so we aren't sure whether sufficient audio has been buffered. // Set when the current item hasn't been played yet so we aren't sure whether sufficient audio has been buffered.
BOOL _bufferUnconfirmed; BOOL _bufferUnconfirmed;
CMTime _seekPos; CMTime _seekPos;
FlutterResult _connectionResult; FlutterResult _loadResult;
BOOL _buffering; FlutterResult _playResult;
id _timeObserver; id _timeObserver;
BOOL _automaticallyWaitsToMinimizeStalling; BOOL _automaticallyWaitsToMinimizeStalling;
BOOL _configuredSession; BOOL _configuredSession;
@ -54,7 +54,7 @@
binaryMessenger:[registrar messenger]]; binaryMessenger:[registrar messenger]];
[_eventChannel setStreamHandler:self]; [_eventChannel setStreamHandler:self];
_index = 0; _index = 0;
_state = none; _processingState = none;
_loopMode = loopOff; _loopMode = loopOff;
_shuffleModeEnabled = NO; _shuffleModeEnabled = NO;
_player = nil; _player = nil;
@ -63,7 +63,6 @@
_order = nil; _order = nil;
_orderInv = nil; _orderInv = nil;
_seekPos = kCMTimeInvalid; _seekPos = kCMTimeInvalid;
_buffering = NO;
_timeObserver = 0; _timeObserver = 0;
_updatePosition = 0; _updatePosition = 0;
_updateTime = 0; _updateTime = 0;
@ -71,6 +70,8 @@
_bufferedPosition = 0; _bufferedPosition = 0;
_bufferUnconfirmed = NO; _bufferUnconfirmed = NO;
_playing = NO; _playing = NO;
_loadResult = nil;
_playResult = nil;
_automaticallyWaitsToMinimizeStalling = YES; _automaticallyWaitsToMinimizeStalling = YES;
__weak __typeof__(self) weakSelf = self; __weak __typeof__(self) weakSelf = self;
[_methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { [_methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
@ -84,14 +85,10 @@
if ([@"load" isEqualToString:call.method]) { if ([@"load" isEqualToString:call.method]) {
[self load:args[0] result:result]; [self load:args[0] result:result];
} else if ([@"play" isEqualToString:call.method]) { } else if ([@"play" isEqualToString:call.method]) {
[self play]; [self play:result];
result(nil);
} else if ([@"pause" isEqualToString:call.method]) { } else if ([@"pause" isEqualToString:call.method]) {
[self pause]; [self pause];
result(nil); result(nil);
} else if ([@"stop" isEqualToString:call.method]) {
[self stop];
result(nil);
} else if ([@"setVolume" isEqualToString:call.method]) { } else if ([@"setVolume" isEqualToString:call.method]) {
[self setVolume:(float)[args[0] doubleValue]]; [self setVolume:(float)[args[0] doubleValue]];
result(nil); result(nil);
@ -191,7 +188,7 @@
} else { } else {
_index = 0; _index = 0;
} }
[self enqueueFrom:_index skipMode:NO]; [self enqueueFrom:_index];
// Notify each new IndexedAudioSource that it's been attached to the player. // Notify each new IndexedAudioSource that it's been attached to the player.
for (int i = 0; i < [_indexedAudioSources count]; i++) { for (int i = 0; i < [_indexedAudioSources count]; i++) {
if (!_indexedAudioSources[i].isAttached) { if (!_indexedAudioSources[i].isAttached) {
@ -236,7 +233,7 @@
[self updateOrder]; [self updateOrder];
if (_index >= _indexedAudioSources.count) _index = _indexedAudioSources.count - 1; if (_index >= _indexedAudioSources.count) _index = _indexedAudioSources.count - 1;
if (_index < 0) _index = 0; if (_index < 0) _index = 0;
[self enqueueFrom:_index skipMode:NO]; [self enqueueFrom:_index];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} }
@ -274,12 +271,11 @@
- (void)checkForDiscontinuity { - (void)checkForDiscontinuity {
if (!_eventSink) return; if (!_eventSink) return;
if (!_playing || CMTIME_IS_VALID(_seekPos)) return; if (!_playing || CMTIME_IS_VALID(_seekPos) || _processingState == completed) return;
int position = [self getCurrentPosition]; int position = [self getCurrentPosition];
if (_buffering) { if (_processingState == buffering) {
if (position > _lastPosition) { if (position > _lastPosition) {
NSLog(@"stall ended"); [self leaveBuffering:@"stall ended"];
_buffering = NO;
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} }
@ -293,8 +289,8 @@
if (_updateTime == 0L) { if (_updateTime == 0L) {
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} else if (drift < -100) { } else if (drift < -100) {
NSLog(@"stall detected: %lld", drift); [self enterBuffering:@"stalling"];
_buffering = YES; NSLog(@"Drift: %lld", drift);
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} }
@ -302,11 +298,20 @@
_lastPosition = position; _lastPosition = position;
} }
- (void)enterBuffering:(NSString *)reason {
NSLog(@"ENTER BUFFERING: %@", reason);
_processingState = buffering;
}
- (void)leaveBuffering:(NSString *)reason {
NSLog(@"LEAVE BUFFERING: %@", reason);
_processingState = ready;
}
- (void)broadcastPlaybackEvent { - (void)broadcastPlaybackEvent {
if (!_eventSink) return; if (!_eventSink) return;
_eventSink(@{ _eventSink(@{
@"state": @(_state), @"processingState": @(_processingState),
@"buffering": @(_buffering),
@"updatePosition": @(_updatePosition), @"updatePosition": @(_updatePosition),
@"updateTime": @(_updateTime), @"updateTime": @(_updateTime),
// TODO: buffer position // TODO: buffer position
@ -319,7 +324,7 @@
} }
- (int)getCurrentPosition { - (int)getCurrentPosition {
if (_state == none || _state == connecting) { if (_processingState == none || _processingState == loading) {
return 0; return 0;
} else if (CMTIME_IS_VALID(_seekPos)) { } else if (CMTIME_IS_VALID(_seekPos)) {
return (int)(1000 * CMTimeGetSeconds(_seekPos)); return (int)(1000 * CMTimeGetSeconds(_seekPos));
@ -333,7 +338,7 @@
} }
- (int)getBufferedPosition { - (int)getBufferedPosition {
if (_state == none || _state == connecting) { if (_processingState == none || _processingState == loading) {
return 0; return 0;
} else if (_indexedAudioSources) { } else if (_indexedAudioSources) {
int ms = (int)(1000 * CMTimeGetSeconds(_indexedAudioSources[_index].bufferedPosition)); int ms = (int)(1000 * CMTimeGetSeconds(_indexedAudioSources[_index].bufferedPosition));
@ -345,7 +350,7 @@
} }
- (int)getDuration { - (int)getDuration {
if (_state == none) { if (_processingState == none) {
return -1; return -1;
} else if (_indexedAudioSources) { } else if (_indexedAudioSources) {
int v = (int)(1000 * CMTimeGetSeconds(_indexedAudioSources[_index].duration)); int v = (int)(1000 * CMTimeGetSeconds(_indexedAudioSources[_index].duration));
@ -355,17 +360,6 @@
} }
} }
- (void)setPlaybackState:(enum PlaybackState)state {
_state = state;
[self broadcastPlaybackEvent];
}
- (void)setPlaybackBufferingState:(enum PlaybackState)state buffering:(BOOL)buffering {
_buffering = buffering;
[self updatePosition];
[self setPlaybackState:state];
}
- (void)removeItemObservers:(AVPlayerItem *)playerItem { - (void)removeItemObservers:(AVPlayerItem *)playerItem {
[playerItem removeObserver:self forKeyPath:@"status"]; [playerItem removeObserver:self forKeyPath:@"status"];
[playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"]; [playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
@ -429,8 +423,7 @@
} }
} }
// TODO: remove the skipMode parameter after testing - (void)enqueueFrom:(int)index {
- (void)enqueueFrom:(int)index skipMode:(BOOL)skipMode {
_index = index; _index = index;
// Update the queue while keeping the currently playing item untouched. // Update the queue while keeping the currently playing item untouched.
@ -439,6 +432,7 @@
/* [self dumpQueue]; */ /* [self dumpQueue]; */
// First, remove all _player items except for the currently playing one (if any). // First, remove all _player items except for the currently playing one (if any).
IndexedPlayerItem *oldItem = _player.currentItem;
IndexedPlayerItem *existingItem = nil; IndexedPlayerItem *existingItem = nil;
NSArray *oldPlayerItems = [NSArray arrayWithArray:_player.items]; NSArray *oldPlayerItems = [NSArray arrayWithArray:_player.items];
for (int i = 0; i < oldPlayerItems.count; i++) { for (int i = 0; i < oldPlayerItems.count; i++) {
@ -458,9 +452,6 @@
int si = [_order[i] intValue]; int si = [_order[i] intValue];
if (si == _index) include = YES; if (si == _index) include = YES;
if (include && _indexedAudioSources[si].playerItem != existingItem) { if (include && _indexedAudioSources[si].playerItem != existingItem) {
if (!skipMode) {
[_indexedAudioSources[si] seek:kCMTimeZero];
}
[_player insertItem:_indexedAudioSources[si].playerItem afterItem:nil]; [_player insertItem:_indexedAudioSources[si].playerItem afterItem:nil];
} }
} }
@ -468,8 +459,13 @@
/* NSLog(@"after reorder: _player.items.count: ", _player.items.count); */ /* NSLog(@"after reorder: _player.items.count: ", _player.items.count); */
/* [self dumpQueue]; */ /* [self dumpQueue]; */
if (skipMode) { if (_processingState != loading && oldItem != _indexedAudioSources[_index].playerItem) {
_buffering = _player.currentItem.playbackBufferEmpty; // || !_player.currentItem.playbackLikelyToKeepUp; // || !_player.currentItem.playbackLikelyToKeepUp;
if (_player.currentItem.playbackBufferEmpty) {
[self enterBuffering:@"enqueueFrom playbackBufferEmpty"];
} else {
[self leaveBuffering:@"enqueueFrom !playbackBufferEmpty"];
}
[self updatePosition]; [self updatePosition];
} }
} }
@ -480,18 +476,17 @@
} }
- (void)load:(NSDictionary *)source result:(FlutterResult)result { - (void)load:(NSDictionary *)source result:(FlutterResult)result {
if (_state == connecting) { if (!_playing) {
[self abortExistingConnection];
_playing = NO;
[_player pause];
} else if (_state == playing) {
_playing = NO;
[_player pause]; [_player pause];
} }
_connectionResult = result; if (_processingState == loading) {
[self abortExistingConnection];
}
_loadResult = result;
_index = 0; _index = 0;
[self updatePosition]; [self updatePosition];
[self setPlaybackState:connecting]; _processingState = loading;
[self broadcastPlaybackEvent];
// Remove previous observers // Remove previous observers
if (_indexedAudioSources) { if (_indexedAudioSources) {
for (int i = 0; i < [_indexedAudioSources count]; i++) { for (int i = 0; i < [_indexedAudioSources count]; i++) {
@ -556,18 +551,19 @@
} }
} }
// Initialise the AVQueuePlayer with items. // Initialise the AVQueuePlayer with items.
[self enqueueFrom:0 skipMode:YES]; [self enqueueFrom:0];
// Notify each IndexedAudioSource that it's been attached to the player. // Notify each IndexedAudioSource that it's been attached to the player.
for (int i = 0; i < [_indexedAudioSources count]; i++) { for (int i = 0; i < [_indexedAudioSources count]; i++) {
[_indexedAudioSources[i] attach:_player]; [_indexedAudioSources[i] attach:_player];
} }
if (_player.currentItem.status == AVPlayerItemStatusReadyToPlay) { if (_player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
_connectionResult(@([self getDuration])); _loadResult(@([self getDuration]));
_connectionResult = nil; _loadResult = nil;
} else { } else {
// We send result after the playerItem is ready in observeValueForKeyPath. // We send result after the playerItem is ready in observeValueForKeyPath.
} }
[self broadcastPlaybackEvent];
} }
- (void)updateOrder { - (void)updateOrder {
@ -633,10 +629,12 @@
// For now we just do a seek back to the start. // For now we just do a seek back to the start.
if ([_order count] == 1) { if ([_order count] == 1) {
[self seek:kCMTimeZero index:[NSNull null] completionHandler:^(BOOL finished) { [self seek:kCMTimeZero index:[NSNull null] completionHandler:^(BOOL finished) {
// XXX: Necessary?
[self play]; [self play];
}]; }];
} else { } else {
[self seek:kCMTimeZero index:_order[0] completionHandler:^(BOOL finished) { [self seek:kCMTimeZero index:_order[0] completionHandler:^(BOOL finished) {
// XXX: Necessary?
[self play]; [self play];
}]; }];
} }
@ -665,30 +663,40 @@
// Detect buffering in different ways depending on whether we're playing // Detect buffering in different ways depending on whether we're playing
if (_playing) { if (_playing) {
if (@available(macOS 10.12, iOS 10.0, *)) { if (@available(macOS 10.12, iOS 10.0, *)) {
_buffering = _player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate; if (_player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) {
[self enterBuffering:@"ready to play: playing, waitingToPlay"];
} else {
[self leaveBuffering:@"ready to play: playing, !waitingToPlay"];
}
[self updatePosition]; [self updatePosition];
} else { } else {
// If this happens when we're playing, check whether buffer is confirmed // If this happens when we're playing, check whether buffer is confirmed
if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) { if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) {
// Stay in bufering // Stay in bufering - XXX Test
[self enterBuffering:@"ready to play: playing, bufferUnconfirmed && !playbackBufferFull"];
} else { } else {
_buffering = _player.currentItem.playbackBufferEmpty;// || !_player.currentItem.playbackLikelyToKeepUp; if (_player.currentItem.playbackBufferEmpty) {
// !_player.currentItem.playbackLikelyToKeepUp;
[self enterBuffering:@"ready to play: playing, playbackBufferEmpty"];
} else {
[self leaveBuffering:@"ready to play: playing, !playbackBufferEmpty"];
}
[self updatePosition]; [self updatePosition];
} }
} }
} else { } else {
_buffering = _player.currentItem.playbackBufferEmpty;// || !_player.currentItem.playbackLikelyToKeepUp; if (_player.currentItem.playbackBufferEmpty) {
[self updatePosition]; [self enterBuffering:@"ready to play: !playing, playbackBufferEmpty"];
// || !_player.currentItem.playbackLikelyToKeepUp;
} else {
[self leaveBuffering:@"ready to play: !playing, !playbackBufferEmpty"];
} }
// XXX: Maybe if _state == connecting? [self updatePosition];
// Although then make sure connecting is only used for this purpose.
if (!_playing) {
_state = stopped;
} }
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
if (_connectionResult) { if (_loadResult) {
_connectionResult(@([self getDuration])); _loadResult(@([self getDuration]));
_connectionResult = nil; _loadResult = nil;
} }
break; break;
} }
@ -713,7 +721,7 @@
} else { } else {
if (_bufferUnconfirmed && playerItem.playbackBufferFull) { if (_bufferUnconfirmed && playerItem.playbackBufferFull) {
_bufferUnconfirmed = NO; _bufferUnconfirmed = NO;
_buffering = NO; [self leaveBuffering:@"playing, _bufferUnconfirmed && playbackBufferFull"];
[self updatePosition]; [self updatePosition];
NSLog(@"Buffering confirmed! leaving buffering"); NSLog(@"Buffering confirmed! leaving buffering");
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
@ -721,14 +729,13 @@
} }
} else { } else {
if (playerItem.playbackBufferEmpty) { if (playerItem.playbackBufferEmpty) {
_buffering = YES; [self enterBuffering:@"!playing, playbackBufferEmpty"];
[self updatePosition]; [self updatePosition];
NSLog(@"[%d] BUFFERING YES: playbackBufferEmpty = %d, playbackBufferFull = %d", [self indexForItem:playerItem], playerItem.playbackBufferEmpty, playerItem.playbackBufferFull);
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} else if (!playerItem.playbackBufferEmpty || playerItem.playbackBufferFull) { } else if (!playerItem.playbackBufferEmpty || playerItem.playbackBufferFull) {
_buffering = NO; _processingState = ready;
[self leaveBuffering:@"!playing, !playbackBufferEmpty || playbackBufferFull"];
[self updatePosition]; [self updatePosition];
NSLog(@"[%d] BUFFERING NO: playbackBufferEmpty = %d, playbackBufferFull = %d", [self indexForItem:playerItem], playerItem.playbackBufferEmpty, playerItem.playbackBufferFull);
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} }
} }
@ -746,13 +753,16 @@
break; break;
case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate: case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate:
//NSLog(@"AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate"); //NSLog(@"AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate");
_buffering = YES; if (_processingState != completed) {
[self enterBuffering:@"timeControlStatus"];
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} else {
NSLog(@"Ignoring wait signal because we reached the end");
}
break; break;
case AVPlayerTimeControlStatusPlaying: case AVPlayerTimeControlStatusPlaying:
//NSLog(@"AVPlayerTimeControlStatusPlaying"); [self leaveBuffering:@"timeControlStatus"];
_buffering = NO;
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
break; break;
@ -779,11 +789,11 @@
_player.actionAtItemEnd = AVPlayerActionAtItemEndPause; _player.actionAtItemEnd = AVPlayerActionAtItemEndPause;
[_player pause]; [_player pause];
} }
_buffering = YES; [self enterBuffering:@"currentItem changed, seeking"];
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
[source seek:kCMTimeZero completionHandler:^(BOOL finished) { [source seek:kCMTimeZero completionHandler:^(BOOL finished) {
_buffering = NO; [self leaveBuffering:@"currentItem changed, finished seek"];
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
if (shouldResumePlayback) { if (shouldResumePlayback) {
@ -815,9 +825,9 @@
} }
- (void)sendError:(FlutterError *)flutterError playerItem:(IndexedPlayerItem *)playerItem { - (void)sendError:(FlutterError *)flutterError playerItem:(IndexedPlayerItem *)playerItem {
if (_connectionResult && playerItem == _player.currentItem) { if (_loadResult && playerItem == _player.currentItem) {
_connectionResult(flutterError); _loadResult(flutterError);
_connectionResult = nil; _loadResult = nil;
} }
if (_eventSink) { if (_eventSink) {
// Broadcast all errors even if they aren't on the current item. // Broadcast all errors even if they aren't on the current item.
@ -842,46 +852,52 @@
} }
- (void)play { - (void)play {
[self play:nil];
}
- (void)play:(FlutterResult)result {
if (result) {
if (_playResult) {
NSLog(@"INTERRUPTING PLAY");
_playResult(nil);
}
_playResult = result;
}
_playing = YES; _playing = YES;
if (_configuredSession) { if (_configuredSession) {
[[AVAudioSession sharedInstance] setActive:YES error:nil]; [[AVAudioSession sharedInstance] setActive:YES error:nil];
} }
[_player play]; [_player play];
[self updatePosition];
if (!@available(macOS 10.12, iOS 10.0, *)) { if (!@available(macOS 10.12, iOS 10.0, *)) {
if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) { if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) {
_buffering = YES; [self enterBuffering:@"play, _bufferUnconfirmed && !playbackBufferFull"];
[self broadcastPlaybackEvent];
} }
} }
[self updatePosition];
[self setPlaybackState:playing];
} }
- (void)pause { - (void)pause {
_playing = NO; _playing = NO;
[_player pause]; [_player pause];
[self updatePosition]; [self updatePosition];
[self setPlaybackState:paused]; [self broadcastPlaybackEvent];
} if (_playResult) {
NSLog(@"PLAY FINISHED DUE TO PAUSE");
- (void)stop { _playResult(nil);
_playing = NO; _playResult = nil;
[_player pause]; }
[self updatePosition];
[_indexedAudioSources[_index] seek:kCMTimeZero
completionHandler:^(BOOL finished) {
[self updatePosition];
[self setPlaybackBufferingState:stopped buffering:NO];
}];
} }
- (void)complete { - (void)complete {
_playing = NO; [self updatePosition];
[_player pause]; _processingState = completed;
[self enqueueFrom:_index skipMode:YES]; [self broadcastPlaybackEvent];
[_indexedAudioSources[_index] seek:kCMTimeZero if (_playResult) {
completionHandler:^(BOOL finished) { NSLog(@"PLAY FINISHED DUE TO COMPLETE");
[self setPlaybackBufferingState:completed buffering:NO]; _playResult(nil);
}]; _playResult = nil;
}
} }
- (void)setVolume:(float)volume { - (void)setVolume:(float)volume {
@ -894,6 +910,7 @@
|| (speed > 1.0 && _player.currentItem.canPlayFastForward)) { || (speed > 1.0 && _player.currentItem.canPlayFastForward)) {
_player.rate = speed; _player.rate = speed;
} }
[self updatePosition];
} }
- (void)setLoopMode:(int)loopMode { - (void)setLoopMode:(int)loopMode {
@ -916,7 +933,7 @@
[self updateOrder]; [self updateOrder];
[self enqueueFrom:_index skipMode:NO]; [self enqueueFrom:_index];
} }
- (void)dumpQueue { - (void)dumpQueue {
@ -966,10 +983,10 @@
// The "currentItem" key observer will respect that a seek is already in progress // The "currentItem" key observer will respect that a seek is already in progress
_seekPos = position; _seekPos = position;
[self updatePosition]; [self updatePosition];
[self enqueueFrom:index skipMode:YES]; [self enqueueFrom:index];
IndexedAudioSource *source = _indexedAudioSources[_index]; IndexedAudioSource *source = _indexedAudioSources[_index];
if (abs((int)(1000 * CMTimeGetSeconds(CMTimeSubtract(source.position, position)))) > 100) { if (abs((int)(1000 * CMTimeGetSeconds(CMTimeSubtract(source.position, position)))) > 100) {
_buffering = YES; [self enterBuffering:@"seek to index"];
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
[source seek:position completionHandler:^(BOOL finished) { [source seek:position completionHandler:^(BOOL finished) {
@ -980,7 +997,7 @@
if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) { if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) {
// Stay in buffering // Stay in buffering
} else if (source.playerItem.status == AVPlayerItemStatusReadyToPlay) { } else if (source.playerItem.status == AVPlayerItemStatusReadyToPlay) {
_buffering = NO; [self leaveBuffering:@"seek to index finished, (!bufferUnconfirmed || playbackBufferFull) && ready to play"];
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} }
@ -989,7 +1006,7 @@
if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) { if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) {
// Stay in buffering // Stay in buffering
} else if (source.playerItem.status == AVPlayerItemStatusReadyToPlay) { } else if (source.playerItem.status == AVPlayerItemStatusReadyToPlay) {
_buffering = NO; [self leaveBuffering:@"seek to index finished, (!bufferUnconfirmed || playbackBufferFull) && ready to play"];
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} }
@ -1019,7 +1036,7 @@
//NSLog(@"seek. enter buffering. pos = %d", (int)(1000*CMTimeGetSeconds(_indexedAudioSources[_index].position))); //NSLog(@"seek. enter buffering. pos = %d", (int)(1000*CMTimeGetSeconds(_indexedAudioSources[_index].position)));
// TODO: Move this into a separate method so it can also // TODO: Move this into a separate method so it can also
// be used in skip. // be used in skip.
_buffering = YES; [self enterBuffering:@"seek"];
[self updatePosition]; [self updatePosition];
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
[_indexedAudioSources[_index] seek:position completionHandler:^(BOOL finished) { [_indexedAudioSources[_index] seek:position completionHandler:^(BOOL finished) {
@ -1034,9 +1051,13 @@
// when buffering has completed, so we use // when buffering has completed, so we use
// !playbackBufferEmpty. Although this always seems to // !playbackBufferEmpty. Although this always seems to
// be full even right after a seek. // be full even right after a seek.
_buffering = _player.currentItem.playbackBufferEmpty; if (_player.currentItem.playbackBufferEmpty) {
[self enterBuffering:@"seek finished, playbackBufferEmpty"];
} else {
[self leaveBuffering:@"seek finished, !playbackBufferEmpty"];
}
[self updatePosition]; [self updatePosition];
if (!_buffering) { if (_processingState != buffering) {
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} }
} }
@ -1050,9 +1071,10 @@
} }
- (void)dispose { - (void)dispose {
if (_state != none) { if (_processingState != none) {
[self stop]; [_player pause];
[self setPlaybackBufferingState:none buffering:NO]; _processingState = none;
[self broadcastPlaybackEvent];
} }
if (_timeObserver) { if (_timeObserver) {
[_player removeTimeObserver:_timeObserver]; [_player removeTimeObserver:_timeObserver];

View File

@ -53,8 +53,10 @@
} }
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler { - (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler {
if (!completionHandler || (self.playerItem.status == AVPlayerItemStatusReadyToPlay)) {
CMTime absPosition = CMTimeAdd(_start, position); CMTime absPosition = CMTimeAdd(_start, position);
[_audioSource.playerItem seekToTime:absPosition toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler]; [_audioSource.playerItem seekToTime:absPosition toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
}
} }
- (CMTime)duration { - (CMTime)duration {

View File

@ -26,7 +26,7 @@
if ([@"init" isEqualToString:call.method]) { if ([@"init" isEqualToString:call.method]) {
NSArray* args = (NSArray*)call.arguments; NSArray* args = (NSArray*)call.arguments;
NSString* playerId = args[0]; NSString* playerId = args[0];
AudioPlayer* player = [[AudioPlayer alloc] initWithRegistrar:_registrar playerId:playerId configuredSession:_configuredSession]; /*AudioPlayer* player =*/ [[AudioPlayer alloc] initWithRegistrar:_registrar playerId:playerId configuredSession:_configuredSession];
result(nil); result(nil);
} else if ([@"setIosCategory" isEqualToString:call.method]) { } else if ([@"setIosCategory" isEqualToString:call.method]) {
NSNumber* categoryIndex = (NSNumber*)call.arguments; NSNumber* categoryIndex = (NSNumber*)call.arguments;

View File

@ -30,7 +30,7 @@
- (NSArray *)getShuffleOrder { - (NSArray *)getShuffleOrder {
NSMutableArray *order = [NSMutableArray new]; NSMutableArray *order = [NSMutableArray new];
int offset = [order count]; int offset = (int)[order count];
for (int i = 0; i < [_audioSources count]; i++) { for (int i = 0; i < [_audioSources count]; i++) {
AudioSource *audioSource = _audioSources[i]; AudioSource *audioSource = _audioSources[i];
NSArray *childShuffleOrder = [audioSource getShuffleOrder]; NSArray *childShuffleOrder = [audioSource getShuffleOrder];

View File

@ -49,7 +49,9 @@
} }
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler { - (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler {
if (!completionHandler || (_playerItem.status == AVPlayerItemStatusReadyToPlay)) {
[_playerItem seekToTime:position toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler]; [_playerItem seekToTime:position toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
}
} }
- (CMTime)duration { - (CMTime)duration {

View File

@ -110,7 +110,8 @@ class _MyAppState extends State<MyApp> {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (processingState == ProcessingState.buffering) if (processingState == ProcessingState.loading ||
processingState == ProcessingState.buffering)
Container( Container(
margin: EdgeInsets.all(8.0), margin: EdgeInsets.all(8.0),
width: 64.0, width: 64.0,

View File

@ -6,12 +6,11 @@
@end @end
enum PlaybackState { enum ProcessingState {
none, none,
stopped, loading,
paused, buffering,
playing, ready,
connecting,
completed completed
}; };

View File

@ -432,7 +432,6 @@ class AudioPlayer {
Future<void> play() async { Future<void> play() async {
if (playing) return; if (playing) return;
_playingSubject.add(true); _playingSubject.add(true);
// TODO: Make platform side wait for playback to stop on iOS.
await _invokeMethod('play'); await _invokeMethod('play');
} }

View File

@ -6,12 +6,11 @@
@end @end
enum PlaybackState { enum ProcessingState {
none, none,
stopped, loading,
paused, buffering,
playing, ready,
connecting,
completed completed
}; };