From b0c27badc28f2d35797d45acb4d1b14b536f00d7 Mon Sep 17 00:00:00 2001 From: diglesia Date: Thu, 7 Nov 2019 22:22:40 -0800 Subject: [PATCH] Add play-to-duration and await-seek-complete behavior to iOS implementation of audiofileplayer plugin --- .../ios/Classes/AudiofileplayerPlugin.m | 12 ++- .../ios/Classes/ManagedPlayer.h | 13 ++- .../ios/Classes/ManagedPlayer.m | 81 ++++++++++++++++--- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/packages/audiofileplayer/ios/Classes/AudiofileplayerPlugin.m b/packages/audiofileplayer/ios/Classes/AudiofileplayerPlugin.m index a793a0f..87107ac 100644 --- a/packages/audiofileplayer/ios/Classes/AudiofileplayerPlugin.m +++ b/packages/audiofileplayer/ios/Classes/AudiofileplayerPlugin.m @@ -11,6 +11,7 @@ static NSString *const kReleaseMethod = @"release"; static NSString *const kPlayMethod = @"play"; static NSString *const kPlayFromStart = @"playFromStart"; +static NSString *const kEndpointSeconds = @"endpointSeconds"; static NSString *const kSeekMethod = @"seek"; static NSString *const kSetVolumeMethod = @"setVolume"; static NSString *const kVolume = @"volume"; @@ -80,7 +81,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result if ([call.method isEqualToString:kPlayMethod]) { bool playFromStart = [call.arguments[kPlayFromStart] boolValue]; - [player play:playFromStart]; + NSNumber *endpointSecondsNumber = call.arguments[kEndpointSeconds]; + NSTimeInterval endpoint = + endpointSecondsNumber ? [endpointSecondsNumber doubleValue] : FLTManagedPlayerPlayToEnd; + [player play:playFromStart endpoint:endpoint]; result(nil); } else if ([call.method isEqualToString:kReleaseMethod]) { [player releasePlayer]; @@ -88,8 +92,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result result(nil); } else if ([call.method isEqualToString:kSeekMethod]) { NSTimeInterval position = [call.arguments[kPositionSeconds] doubleValue]; - [player seek:position]; - result(nil); + [player seek:position + completionHandler:^() { + result(nil); + }]; } else if ([call.method isEqualToString:kSetVolumeMethod]) { double volume = [call.arguments[kVolume] doubleValue]; [player setVolume:volume]; diff --git a/packages/audiofileplayer/ios/Classes/ManagedPlayer.h b/packages/audiofileplayer/ios/Classes/ManagedPlayer.h index b156214..ac68c93 100644 --- a/packages/audiofileplayer/ios/Classes/ManagedPlayer.h +++ b/packages/audiofileplayer/ios/Classes/ManagedPlayer.h @@ -4,6 +4,8 @@ @protocol FLTManagedPlayerDelegate +extern NSTimeInterval const FLTManagedPlayerPlayToEnd; + /** * Called by FLTManagedPlayer when a non-looping sound has finished playback, * or on calling stop(). @@ -40,10 +42,15 @@ delegate:(id)delegate isLooping:(bool)isLooping remoteLoadHandler:(void (^)(BOOL))remoteLoadHandler; - -- (void)play:(bool)playFromStart; +/** + * Plays the audio data. + * + * @param endpoint the time, as an NSTimeInterval, to play to. To play until + * the end, pass FLTManagedPlayerPlayToEnd. + */ +- (void)play:(bool)playFromStart endpoint:(NSTimeInterval)endpoint; - (void)releasePlayer; -- (void)seek:(NSTimeInterval)position; +- (void)seek:(NSTimeInterval)position completionHandler:(void (^)())completionHandler; - (void)setVolume:(double)volume; - (void)pause; diff --git a/packages/audiofileplayer/ios/Classes/ManagedPlayer.m b/packages/audiofileplayer/ios/Classes/ManagedPlayer.m index a7d4e50..e015b6f 100644 --- a/packages/audiofileplayer/ios/Classes/ManagedPlayer.m +++ b/packages/audiofileplayer/ios/Classes/ManagedPlayer.m @@ -2,6 +2,8 @@ #import +NSTimeInterval const FLTManagedPlayerPlayToEnd = -1.0; + static NSString *const kKeyPathStatus = @"status"; static float const kTimerUpdateIntervalSeconds = 0.25; @@ -17,6 +19,7 @@ @implementation FLTManagedPlayer { id _completionObserver; // Registered on NSNotificationCenter. id _timeObserver; // Registered on the AVPlayer. void (^_remoteLoadHandler)(BOOL); // Called on AVPlayer loading status change observed. + NSTimer *_endpointTimer; } // Private common initializer. [audioPlayer] or [avPlayer], but not both, must be set. @@ -41,21 +44,25 @@ - (instancetype)initWithAudioId:(NSString *)audioId _audioPlayer.numberOfLoops = isLooping ? -1 : 0; [_audioPlayer prepareToPlay]; [_delegate managedPlayerDidLoadWithDuration:_audioPlayer.duration forAudioId:_audioId]; + __weak FLTManagedPlayer *weakSelf = self; _positionTimer = [NSTimer scheduledTimerWithTimeInterval:kTimerUpdateIntervalSeconds repeats:YES block:^(NSTimer *timer) { - if (_audioPlayer.playing) { - [_delegate - managedPlayerDidUpdatePosition:_audioPlayer.currentTime - forAudioId:_audioId]; + FLTManagedPlayer *strongSelf = weakSelf; + if (strongSelf) { + if (strongSelf->_audioPlayer.playing) { + [strongSelf->_delegate + managedPlayerDidUpdatePosition:_audioPlayer.currentTime + forAudioId:strongSelf->_audioId]; + } } }]; } else { _avPlayer = avPlayer; _remoteLoadHandler = remoteLoadHandler; CMTime interval = CMTimeMakeWithSeconds(kTimerUpdateIntervalSeconds, NSEC_PER_SEC); - FLTManagedPlayer *__weak weakSelf = self; + __weak FLTManagedPlayer *weakSelf = self; _timeObserver = [_avPlayer addPeriodicTimeObserverForInterval:interval queue:nil @@ -74,8 +81,11 @@ - (instancetype)initWithAudioId:(NSString *)audioId object:_avPlayer.currentItem queue:nil usingBlock:^(NSNotification *notif) { - [_avPlayer seekToTime:kCMTimeZero]; - [_delegate managedPlayerDidFinishPlaying:_audioId]; + FLTManagedPlayer *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf->_avPlayer seekToTime:kCMTimeZero]; + [strongSelf->_delegate managedPlayerDidFinishPlaying:_audioId]; + } }]; [_avPlayer.currentItem addObserver:self forKeyPath:kKeyPathStatus @@ -134,17 +144,60 @@ - (void)dealloc { [_avPlayer removeTimeObserver:_timeObserver]; } -- (void)play:(bool)playFromStart { +- (void)play:(bool)playFromStart endpoint:(NSTimeInterval)endpoint { + // Maybe seek to start. if (_audioPlayer) { if (playFromStart) { _audioPlayer.currentTime = 0; } - [_audioPlayer play]; } else { if (playFromStart) { [_avPlayer seekToTime:kCMTimeZero]; } - [_avPlayer play]; + } + // Handle endpoint timers and start playback. + if (endpoint == FLTManagedPlayerPlayToEnd) { + // No endpoint, clear timer and start playback. + [_endpointTimer invalidate]; + _endpointTimer = nil; + if (_audioPlayer) { + [_audioPlayer play]; + } else { + [_avPlayer play]; + } + } else { + // If there is an endpoint, check that it is in the future, then start playback and schedule + // the pausing after a duration. + NSTimeInterval position; + if (_audioPlayer) { + position = _audioPlayer.currentTime; + } else { + position = (NSTimeInterval)CMTimeGetSeconds(_avPlayer.currentTime); + } + NSTimeInterval duration = endpoint - position; + NSLog(@"Called play() at position %.2f seconds, to play for duration %.2f seconds.", position, + duration); + if (duration <= 0) { + NSLog(@"Called play() at position after endpoint. No playback occurred."); + return; + } + [_endpointTimer invalidate]; + if (_audioPlayer) { + [_audioPlayer play]; + } else { + [_avPlayer play]; + } + __weak FLTManagedPlayer *weakSelf = self; + _endpointTimer = [NSTimer + scheduledTimerWithTimeInterval:duration + repeats:NO + block:^(NSTimer *timer) { + FLTManagedPlayer *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf pause]; + [strongSelf->_delegate managedPlayerDidFinishPlaying:_audioId]; + } + }]; } } @@ -161,11 +214,15 @@ - (void)releasePlayer { } } -- (void)seek:(NSTimeInterval)position { +- (void)seek:(NSTimeInterval)position completionHandler:(void (^)())completionHandler { if (_audioPlayer) { _audioPlayer.currentTime = position; + completionHandler(); } else { - [_avPlayer seekToTime:CMTimeMakeWithSeconds(position, NSEC_PER_SEC)]; + [_avPlayer seekToTime:CMTimeMakeWithSeconds(position, NSEC_PER_SEC) + completionHandler:^(BOOL completed) { + completionHandler(); + }]; } }