Update iOS to new state model.
This commit is contained in:
parent
e4789d9cd2
commit
2c3d38f1ad
|
@ -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");
|
||||||
|
_playResult(nil);
|
||||||
|
_playResult = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)stop {
|
|
||||||
_playing = NO;
|
|
||||||
[_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];
|
||||||
|
|
|
@ -53,9 +53,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (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 {
|
||||||
return CMTimeSubtract(CMTIME_IS_INVALID(_end) ? self.playerItem.duration : _end, _start);
|
return CMTimeSubtract(CMTIME_IS_INVALID(_end) ? self.playerItem.duration : _end, _start);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -49,8 +49,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (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 {
|
||||||
return _playerItem.duration;
|
return _playerItem.duration;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -6,12 +6,11 @@
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
enum PlaybackState {
|
enum ProcessingState {
|
||||||
none,
|
none,
|
||||||
stopped,
|
loading,
|
||||||
paused,
|
buffering,
|
||||||
playing,
|
ready,
|
||||||
connecting,
|
|
||||||
completed
|
completed
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,11 @@
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
enum PlaybackState {
|
enum ProcessingState {
|
||||||
none,
|
none,
|
||||||
stopped,
|
loading,
|
||||||
paused,
|
buffering,
|
||||||
playing,
|
ready,
|
||||||
connecting,
|
|
||||||
completed
|
completed
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue