Notification when AVQueuePlayer rewinds to the beginning of an item - ios

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.

Related

What's the difference between playbackLikelyToKeepUp and AVPlayerItemStatusReadyToPlay?

I'm trying to understand how to properly detect when a player item can play again.
See below observer logic:
if (object == playerItem && [keyPath isEqualToString:#"playbackBufferEmpty"])
{
if (playerItem.playbackBufferEmpty)
{
// show loading indicator
}
}
if (object == playerItem && [keyPath isEqualToString:#"playbackLikelyToKeepUp"])
{
if (playerItem.playbackLikelyToKeepUp)
{
// hide loading indicator
if (playerItem.status == AVPlayerItemStatusReadyToPlay) {
// start playing
}
else if (playerItem.status == AVPlayerStatusFailed) {
// handle failed
}
else if (playerItem.status == AVPlayerStatusUnknown) {
// handle unknown
}
}
}
Is checking for AVPlayerItemStatusReadyToPlay underneath playbackLikelyToKeepUp overkill?
Or
Should I only listen to status change on the player item instead of bothering with playbackLikelyToKeepUp?
The two properties inform us of two different pieces of information in regards to the status of an AVPlayerItem. AVPlayerItemStatusReadyToPlay is a constant that will indicate readyToPlay ONLY once an AVPlayer has been given sufficient time to buffer enough of the item's data such that it is able to BEGIN playback of the item. But that is it. Just because an item is ready to play, doesn't mean that it won't stall after the first few seconds.
playBackLikelyToKeepUp returns a bool indicating that playback of the item is "likely" to keep up throughout the duration of the item. This property does NOT only pertain to the beginning of the item, like AVPlayerItemStatusReadyToPlay. It does not "care" if the item is ready for playback, all it "cares" about is wether or not it "thinks" that playback of the item will keep up without stalling. This is a PREDICTION of playability that takes into account various factors which you can read about here -> https://developer.apple.com/documentation/avfoundation/avplayeritemstatus
So in regards to your question, is it overkill to check the value of AVPlayerItemStatusReadyToPlay after you've already checked playbackLikelyToKeepUp... well it is up to you. I personally would check both. I would want to ensure first that the item is ready to play, meaning that sufficient data has been buffered by the AVPlayer in order to begin playback. AND I would then want to ensure that playbackLikeyToKeepUp == true so that I can have some degree of certainty that the user's media experience won't be interrupted. But if all you care about is knowing when an item is ready to begin playback again, then you only need check the status.

How to Stop AVAudioPlayer

I am making a noiseMaker application for iOS in xCode where there are different buttons that play different sounds. Each sound is given a tag of 0-3 along with each button in the storyboard. And the randomize button has a tag of 4, Here is the code so far:
func play (index: Int) {
if !player.isEmpty && index >= 0 && index < player.count {
player[index].play()
}
if index == 4{
let randomNumber = Int(arc4random_uniform (4) + 0)
player[randomNumber].play()
}
However, what I want is for the current song to stop playing when I press on another button or even on the same button again. I believe the stop AVAudioPlayer code would go right when the function "play" begins so that it stops the player before any other song starts playing.
All help is appreciated. Thanks in advance...
You can check that currently player is playing or not.
Check the playing property returns status of player is playing or not.
if (aPlayer.playing) {
aPlayer.stop()
//Then play again by aPlayer.play()
}
else {
aPlayer.play()
}

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.

Control sound to only play once in XNA

I use a sound each time a key is pressed to fire a missile. But it doesn't sound nice. I guess it's because the the sound is repeated so many times while the key is pressed down and that the code is within the Update method. I'm looking for a simple solution to just play the sound once when key is pressed? (I have tested to use a boolean variable to be true the first time and then false after det the sound has been played, but this didn't worked well because, when and where should I set it to true again) Help is preciated!
// Fire
if (keyboardState.IsKeyDown(Keys.Space))
{
missile.launchMissile(spaceship.spaceshipPosition, spaceship.spaceshipDirection);
soundExplosion.Play();
}
EDIT New code that isn't working!?
KeyboardState keyboardState = Keyboard.GetState();
KeyboardState prevKeyboardState;
prevKeyboardState = keyboardState;
keyboardState = Keyboard.GetState();
if (keyboardState.IsKeyDown(Keys.Space) && prevKeyboardState.IsKeyUp(Keys.Space))
{
missile.launchMissile(spaceship.spaceshipPosition, spaceship.spaceshipDirection);
soundExplosion.Play();
}
Store your previous keyboard state as well
KeyBoardState prevKeyboardState;
then in your update
prevKeyboardState = keyboardState;
keyboardState = Keyboard.GetState();
if (keyboardState.IsKeyDown(Keys.Space) && prevKeyboardState.IsKeyUp(Keys.Space))
{
//your code
}
You're basically just triggering the sound tons of times every second making it sound awful.

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