avplayer pause or stop notification based on the rate - ios

I need to send notifications when the AVPlayer is Play/Paused and Stopped.
For play and pause below is the code
if (self.player.rate > 0.0f) {
NSLog(#" Playing ..")
}
if (self.player.rate == 0.0f) {
NSLog(#" Paused ..")
}
But for stopped also the rate = 0.0 then is there any other property or way to identify the difference between paused and stopped.
For both, paused and stopped the rate = 0.0 and hence need another way for it.
Thanks

There is no stop command for an AVPlayer. So there is no such thing as stopped as distinct from paused. Either the rate is zero (not playing) or it is more than zero (playing).
You can distinguish where the player is within its item (currentTime) so you can tell whether we are at the start, end, or middle; and you can arrange to be notified periodically during play or when the end is reached.
Apart from that, there are no distinctions to be drawn.

Related

AVAudioPlayerNode playAt() not synchronized

I have one main AVAudioPlayerNode that plays constantly. And there are other players that are put in the queue and play when the main player reaches a certain point.
These AVAudioPlayerNode`s should be synchronized to a millisecond. Sometimes 4-10 pieces can be started at a time.
How its works - I store lastRenderTime of the main player on point when need start all scheduled players and then start all needed player with
player.start(at: lastRenderTime)
Usually, it's working well and without any latency between sounds.
But, I got some latency on this case:
Running on OLD devices(iPad 2, iPhone 5 etc)
ONLY when MANY players the first time after application startup (For example on iPhone 7 I run 8 players in same time after the application is running and I hear desynchronization between sounds), but all players are running with same player.start(at: lastRenderTime) time.
All player actions I run async with custom concurrent queue.
Spend 3 days on trying fix this. I would like for any pieces of advice.
Some code:
Here is how I starting main player -
self.scheduleBuffer(audioFileBuffer, at: nil, options: [], completionHandler: {
lastRenderTime = lastRenderTime + audioBufferLength //Schematic showing logic that after each loop I calculate current render time value.
if self.isStopPressed == false { //Run fileEndCompletion only when player seek to end.
fileEndCompletionHandler() //here is callback that running all scheduled players.
if loop { // Recursive play this file again.
self.playAudioBuffer(audioFileBuffer: audioFileBuffer, loop: loop, fileEndCompletionHandler: fileEndCompletionHandler)
}
}
})
Example of code that running all scheduled player.
AudioEnginePlayer.sharedInstance.audioQueue.async {
if !self.isPlaying {
self.volume = 1.0
self.audioFile.framePosition = frameCount.startingFrame
self.prepare(withFrameCount: frameCount.frameCount)
self.play(at: AudioEnginePlayer.sharedInstance.loopMsLastStartRenderTime) //Calculated lastRenderTime
}
self.scheduleSegment(self.audioFile, startingFrame: frameCount.startingFrame, frameCount: frameCount.frameCount, at: nil) {
//WARNING: COMPLETION HANDLER RUNNING EARLY THEN FILE IS COMPLETED
if self.isStopPressed == false { //Run fileEndCompletion only when player seek to end.
fileEndCompletionHandler()
if loop {
self.playAudioFile(loop: loop, startTime: 0, fileEndCompletionHandler: fileEndCompletionHandler)
}
}
}
}

Strange behaviour with setCurrentPlaybackTime

I use: MPMoviePlayerController to show video.
Below I put list of thumbs from the video.
When pressing a thumb I want to jump to a specific place in the video using: setCurrentPlaybackTime.
I also have a timer updating the selected thumb according to the location of the video using: currentPlaybackTime.
My problem: when calling: setCurrentPlaybackTime, the player keeps giving the seconds before seeking to the specific second. It take few seconds to the player to reflect the new seconds. In the mean time the experience of the user is bad: Pressing a thumb shows it selected for a show time, then the timer updates to the previous thumb, then it jumps back to the thumb I selected.
I tried using (in the timer):
if (moviePlayer.playbackState != MPMoviePlaybackStatePlaying && !(moviePlayer.loadState & MPMovieLoadStatePlaythroughOK)) return;
In order to prevent from the timer to update the selected thumb as long the player is in a transition phase between showing the previous thumb and the new thumb, but it doesn't seem to work. The "playbackState" and "loadState" seems to be totally inconstant and unpredictable.
For solving this issue, this how I have implemented this nasty state coverage in one of my projects. This is nasty and fragile but worked good enough for me.
I used two flags and two time intervals;
BOOL seekInProgress_;
BOOL seekRecoveryInProgress_;
NSTimeInterval seekingTowards_;
NSTimeInterval seekingRecoverySince_;
All of the above should be defaulted to NO and 0.0.
When initiating the seek:
//are we supposed to seek?
if (movieController_.currentPlaybackTime != seekToTime)
{ //yes->
movieController_.currentPlaybackTime = seekToTime;
seekingTowards_ = seekToTime;
seekInProgress_ = YES;
}
Within the timer callback:
//are we currently seeking?
if (seekInProgress_)
{ //yes->did the playback-time change since the seeking has been triggered?
if (seekingTowards_ != movieController_.currentPlaybackTime)
{ //yes->we are now in seek-recovery state
seekingRecoverySince_ = movieController_.currentPlaybackTime;
seekRecoveryInProgress_ = YES;
seekInProgress_ = NO;
seekingTowards_ = 0.0;
}
}
//are we currently recovering from seeking?
else if (seekRecoveryInProgress_)
{ //yes->did the playback-time change since the seeking-recovery has been triggered?
if (seekingRecoverySince_ != movieController_.currentPlaybackTime)
{ //yes->seek recovery done!
seekRecoveryInProgress_ = NO;
seekingRecoverySince_ = 0.0;
}
}
In the end, MPMoviePlayerController simply is not really meant for such "micro-management". I had to throw in at least half a dozen flags for state coverage in all kinds of situations and I would never recommend to repeat this within other projects. Once you reach this level, it might be a great idea to think about using AVPlayer instead.

MPMoviePlayerViewController repeat count

I am playing a video using MPMoviePlayerViewController.
I have repeat mode as MPMovieRepeatModeOne so that the video may play in a loop.
Is there any method that I can use to get the repeat count of the played video.
You can add a Notification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(introMovieFinished:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:self.moviePlayerController];
And then use this notification to detect when video finished a loop:
- (void)introMovieFinished:(NSNotification *)notification
{
if (notification.object == self.moviePlayerController) {
NSInteger reason = [[notification.userInfo objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] integerValue];
if (reason == MPMovieFinishReasonPlaybackEnded)
{
NSLog(#"Video has looped!");
}
}
}
To my knowledge, you cannot use the MPMoviePlayerPlaybackDidFinishNotification with the repeat mode because it will be triggered only if you get out of the video not if you repeat it.
If you use the repeat mode, you will only have a change of state [=> PLAY (end of the former video)... PAUSED (former video)... PLAY (beginning of the new video)] detected thanks to the MPMoviePlayerPlaybackStateDidChangeNotification but the didFinishNotification won't be in the loop.
At first sight, you should store previous states of the player to follow this pattern for instance:
[stateCurrentTime (PLAY)] > (playingDuration - 1 sec) ==> end of your video
[stateCurrentTime (PAUSED)] < 0.5 sec ==> transitional state between former and new video
[stateCurrentTime (PLAY)] < 1 sec ==> beginning of your new video.
... And if someone succeeds in finding a way using MPMoviePlayerPlaybackDidFinishNotification, I'll be definitely interested in it because the behavior of this notification doesn't go together with repeat mode.

Notification when AVQueuePlayer rewinds to the beginning of an item

I have 5 AVPlayerItems in my AVQueuePlayer, which is set to AVPlayerActionAtItemEndAdvance. I hit play on my UI, and play the first, the second and then start playing the third. Then I hit my rewind button. What I want to happen is that the third video rewinds to its start, and I then get a notification that allows me to stop. What I'm seeing is that I get a status of ready to play for the 4th item, followed by a current item changed to the 4th item - then the 4th item plays.
Why does the 4th item become the current item after the 3rd item has rewound to its start
Is the only way I can stop this to set the player to not auto advance (AVPlayerActionAtItemEndPause), observe the end of each item, and hope I get an "end of play" notification for the rewinding of the 3rd item as well as when it plays to its end naturally. Then in my end observer code, I can check the rate of the player, and if rewinding, not advance to the next item.
The way I handled this was in the begin seeking code, set the actionAtItemEnd to AVPlayerActionAtItemEndNone, and then reset it back to AVPlayerActionAtItemEndAdvance when the seeking ends. On iOS6, it appears that one can seek past the start of the track. In the "end seeking" code, I reset the rate and current time before beginning normal playback.
The following method is called by a long press gesture recognizer.
- (IBAction)fastRewind:(id)sender
{
UIGestureRecognizer *recog = (UIGestureRecognizer*)sender;
if (recog.state == UIGestureRecognizerStateBegan) {
if (_player.rate == 1) {
NSLog(#"fastRewind begin\n%#", sender);
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
_player.rate = -2;
}
} else if (recog.state != UIGestureRecognizerStateChanged) {
// Ended/Canceled
NSLog(#"fastRewind end\n%#", sender);
if (_player.rate < 0) {
_player.rate = 0;
if (CMTimeGetSeconds(_player.currentTime) < 0) {
[_player seekToTime:CMTimeMake(0, 1)];
}
_player.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
_player.rate = 1;
}
}
}
One may wish to speed up the seek if it is held for a longer period of time.

MPMoviePlayerController seek forward in fullscreen mode until end is stuck

There seems to be a problem with the MPMoviePlayerController where once you're in fullscreen mode and you hold down the fast forward button, letting it seek forward (playing at fast speed) all the way to the end of the video.
Thereafter the you just get a black screen and it's stuck. In other words it does not respond to any taps gestures and you can not get out of this situation. Has anyone else encountered this problem?
Is there anyway to work around it in code?
It seems it's an iOS bug since fast backward to the very beginning won't cause the black screen but fast forward to the end will, and after that the 'play'/'pause' call to the video player never works. I temporarily fix this by adding protected logic into the scrubber refresh callback:
let's assume that monitorPlaybackTime will be called in 'PLAY_BACK_TIME_MONITOR_INTERVAL' seconds period to refresh the scrubber, and in it I add a check logic:
NSTimeInterval duration = self.moviePlayer.duration;
NSTimeInterval current = self.moviePlayer.currentPlaybackTime;
if (isnan(current) || current > duration) {
current = duration;
} else if (self.moviePlayer.playbackState == MPMoviePlaybackStateSeekingForward) {
if (current + self.moviePlayer.currentPlaybackRate*PLAY_BACK_TIME_MONITOR_INTERVAL > duration) {
[self.moviePlayer endSeeking];
}
}
A workaround to solve the black screen, not perfect, hope it can help.
I'm guessing you are not handling the MPMoviePlayerPlaybackDidFinishNotification. You really should if you're not.
Still its unexpected for me that the movie player would go into a "stuck" state like you describe. I would more readily expect it to stop playback automatically and reset when it reaches the end. Anyway, I think your problem will go away if you observe the MPMoviePlayerPlaybackDidFinishNotification and handle the movie controller appropriately.
Ran into the same issue on iOS6. Managed to fix it by registering for the MPMoviePlayerPlaybackDidFinishNotification (as suggested by Leuguimerius) with the following implementation:
- (void)playbackDidFisnish:(NSNotification *)notif {
if (self.player.currentPlaybackTime <= 0.1) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.player stop];
[self.player play];
[self.player pause];
});
}
}
Where self.player is the associated MPMoviePlayerController instance. The check against currentPlaybackTime serves to distinguish the more standard invocations of playbackDidFinish (where the movie is allowed to play at normal speed until its end) from those scenarios where the user fast forwards until the end. Stopping then playing and pausing results in a usable, visually consistent interface even when fast-forwarding to the end.
None of the aforementioned solutions worked for me, so this is what I ended up doing:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("moviePlayerLoadStateDidChange"), name: MPMoviePlayerLoadStateDidChangeNotification, object: nil)
func moviePlayerLoadStateDidChange() {
let loadState = moviePlayerController?.loadState
if loadState == MPMovieLoadState.Unknown {
moviePlayerController?.contentURL = currentmovieURL
moviePlayerController?.prepareToPlay()
}
}
I think the issue is that when the seek foraward button is single pressed, it wants to skip to the next video, that's why a loading indicator appears. Listening for the load state change event, you can specify what the next video should be, and if you don't have any, you can just give it the same url.

Resources