AVPlayer play for exact amount of time - ios

Using an AVPlayer I would like to play a mov file for exactly 1 second then pause it.
Currently I'm playing the mov then setting a timer to pause it after 1 second as below. Unfortunately, this does not appear to be exactly accurate and the mov is sometimes playing for slightly shorter or longer than 1 second. Is there a more accurate way of doing this please?
[self.player4 play];
[self performSelector:#selector(pausePlayer4:) withObject:nil afterDelay:1.0];
- (void)pausePlayer4:(NSTimer *)timer
{
[self.player4 pause];
}

Even if you can get an event to fire precisely enough, media playback on iOS devices happens in an entirely different process (a daemon) and there's always latency when doing IPC.
Depending on your needs it might be best to build an AVMutableComposition that plays exactly one second of content from your AVURLAsset, and then assign the composition to your player.

The best way wold be to add a boundary observer to trigger after a second
NSValue *endBoundary = [NSValue valueWithCMTime:CMTimeMakeWithSeconds(1.0, 300)];
[self.player4 addBoundaryTimeObserverForTimes:#[endBoundary]
queue:NULL
usingBlock:^{
[self.player4 stop];
}];
[self.player4 play];

Related

How to set timeout for AVPlayer [duplicate]

I want to play a specified duration within a sound file on IOS. I found a method in AVAudioPlayer that seeks to the begining of the playing (playAtTime:) but i cannot find a direct way to specify an end time before the end of the sound file.
Is there is a way to achieve this?
If you don't need much precision and you want to stick with AVAudioPlayer, this is one option:
- (void)playAtTime:(NSTimeInterval)time withDuration:(NSTimeInterval)duration {
NSTimeInterval shortStartDelay = 0.01;
NSTimeInterval now = player.deviceCurrentTime;
[self.audioPlayer playAtTime:now + shortStartDelay];
self.stopTimer = [NSTimer scheduledTimerWithTimeInterval:shortStartDelay + duration
target:self
selector:#selector(stopPlaying:)
userInfo:nil
repeats:NO];
}
- (void)stopPlaying:(NSTimer *)theTimer {
[self.audioPlayer pause];
}
Bear in mind that stopTimer will fire on the thread's run loop, so there will be some variability in how long the audio plays, depending on what else the app is doing at the time. If you need a higher level of precision, consider using AVPlayer instead of AVAudioPlayer. AVPlayer plays AVPlayerItem objects, which let you specify a forwardPlaybackEndTime.

AVPlayer seekToTime -- finished is NO -- Can't get player to play again

I'm using the AVPlayer method:
- (void)seekToTime:(CMTime)time
toleranceBefore:(CMTime)toleranceBefore
toleranceAfter:(CMTime)toleranceAfter
completionHandler:(void (^)(BOOL finished))completionHandler
for scrubbing a video. toleranceBefore and toleranceAfter are both 0.
However, when the user scrubs to within about 5 seconds of the end, on certain videos, finished is always equal to NO.
The player will then be in an inconsistent state where pause and play methods do nothing. The video is frozen at the time passed to seekToTime.
This happens consistently on some videos, and not at all on others.
I have a simple if/else but I don't know what to put in the else to get the player to play again:
[_avPlayer seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)
toleranceBefore:toleranceBefore
toleranceAfter:toleranceAfter
completionHandler:^(BOOL finished) {
if(finished){
}
else {
// How to get the player to play again??
}
}];
Like I said above, I've tried putting _avPlayer play in there but it doesn't play.

How to end a sound right away?

I'm working on an IOS game, and the background music clip is 7 seconds long, so when the scene changes from one scene to the next, I want it to stop instantly, but it continues and finishes the 7 second loop then stops.
This is the code:
[self runAction:[SKAction repeatActionForever:[SKAction playSoundFileNamed:#"sound 1.m4a" waitForCompletion:YES]]];
if (_dead == YES) {
[self removeAllActions];
}
where _dead is when the player dies, and the new scene is triggered.
How can I get the music to stop that instant, as opposed to finishing its loop?
Instead of using SKAction to play your background sound file, I would suggest using AVFoundation which allows you to simply issue a stop command.
Update
I can't think of any food tutorials that focus primarily on the sound side. Most deal with video and sound. Try google with something like 'AVFoundation tutorial'.
To use AVFoundation you can do this for your purposes...
Add the AVFoundation.framework to your project.
In your .h file add the delegate <AVAudioPlayerDelegate>
In your .m file #import <AVFoundation/AVFoundation.h>
Create a property AVAudioPlayer *_backgroundMusicPlayer;
Create this method:
- (void)playBackgroundMusic:(NSString *)filename {
NSError *error;
NSURL *backgroundMusicURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
_backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
_backgroundMusicPlayer.numberOfLoops = -1;
_backgroundMusicPlayer.volume = 0.2;
_backgroundMusicPlayer.delegate = self;
[_backgroundMusicPlayer prepareToPlay];
[_backgroundMusicPlayer play];
}
To start playing [self playBackgroundMusic:[NSString stringWithFormat:#"bgMusic.mp3"]];
To stop playing [_backgroundMusicPlayer stop];
That should do the trick. I suggest you read up on the AVAudioPlayer Class Reference so you understand its properties. For example, numberOfLoops set to -1 will loop the sound indefinitely until you call the stop method. Another issue to keep in mind is the audio file sound format. AVFoundation is somewhat picky about what sound files it will play. I always stick to mp3.
Fuzzygoat also makes an excellent point. I have not tried his approach in regards to sound so I do not know if it will solve your issue. It is similar to what you already have but with a slight change.
To start the sound:
SKAction *mySound = [SKAction repeatActionForever:[SKAction playSoundFileNamed:#"astroKitty 1.m4a" waitForCompletion:YES]];
[self runAction:mySound withKey:#"boogieDown"];
To stop the sound:
[self removeActionForKey:#"boogieDown"];

How can I register for when AVPlayer actually starts playing (from external source)

I'm having some trouble with registering WHEN the player is starting to play external videos (over internet) using AVPlayer. Please read the question before suggesting solutions.
I initialize the player like this:
player = [[AVPlayer alloc] initWithURL:[[NSURL alloc] initWithString:#"http://example.com/video.mp4"]];
playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[playerLayer setFrame:[videoView bounds]];
[videoView.layer addSublayer:playerLayer];
This adds the player to the view correctly. I have added the following two lines of code to keep track of when the player is ready, and what the status/rate is;
[player addObserver:self forKeyPath:#"rate" options:0 context:nil];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
These two line will call the method - (void)observeValueForKeyPath:.... when something changes with the status or the rate of the AVPlayer.
So far it looks like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//To print out if it is 'rate' or 'status' that has changed:
NSLog(#"Changed: %#", keyPath);
if ([keyPath isEqualToString:#"rate"]) //If rate has changed:
{
if ([player rate] != 0) //If it started playing
{
NSLog(#"Total time: %f", CMTimeGetSeconds([[player currentItem] duration]));
// This NSLog is supposed to print out the duration of the video.
[self setControls];
// This method (setControls) is supposed to set play/pause-buttons
// as well as labels for the current and total time of the current video.
}
}
else if ([keyPath isEqualToString:#"status"]) // If the status changed
{
if(player.status == AVPlayerStatusReadyToPlay) //If "ReadyToPlay"
{
NSLog(#"ReadyToPlay");
[player play]; //Start the video
}
}
}
The state of the AVPlayer changes to readyToPlay almost immediately after initializing it, and I then call [player play]. When this happens, the rate changes to 1.00000, meaning it's actually playing at that rate, but the video is now just starting to buffer, not playing. The screen is black, and it takes a couple of seconds, and then it starts playing. The rate, however, indicates it starts playing before it does. The rate stays at 1.00000, not going down to 0 when start-buffering, which makes it very difficult for me to know when the player has enough information to start setting the controls (I.E time stamps etc).
The NSLog() printing out the duration of the video above prints out nan (Not A Number), which leads me to think that the item isn't ready to be played, however, the rate stays at 1.0000 until it has buffered a while, then it will actually play, still with rate at 1.0000.
It does, however, get called twice. The rate "changes" to 1.0000 twice without being anything else in between. In neither calls, the duration of the video is an available variable.
My goal is to fetch the current and total timestamp of the video as fast as possible (I.E 0:00/3:52). This will also be used to register the scrubbing of a slider (for fast-forward etc.).
These values are not ready when the player notifies me it's playing at a rate of 1.0000, twice. If I manually click "play" after a second or so (and call [player play]), then it's working. How can I register to know when the video is ready, not just 'ready to get ready'?
See addBoundaryTimeObserverForTimes:queue:usingBlock: on AVPlayer and this example from Apple.
AVPlayer *player = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"]];
[player play];
// Assumes a property: #property (strong) id playerObserver;
// Cannot use kCMTimeZero so instead use a very small period of time
self.playerObserver = [player addBoundaryTimeObserverForTimes:#[[NSValue valueWithCMTime:CMTimeMake(1, 1000)]] queue:NULL usingBlock:^{
//Playback started
[player removeTimeObserver:self.playerObserver];
}];
I think the nearest you'll get to is to observe player.currentItem.playbackLikelyToKeepUp

Find out whether AVPlayer is loading or playing mp3 from internet

I would like to use AVPlayer to stream a .mp3 from internet
[[AVPlayer alloc] initWithURL:url];
It all works apart from the fact that I am unable to tell whether player is Loading (buffering) or playing ?
I have tried to use KVO on many properties, but none seem to provide me with a simple way of determining whether the AVPlayer is currently actually playing audio or whether it is loading data. It is driving me nuts.
Thank you for your suggestions in advance
You can detect when buffering is done and playback has started by creating a periodic time observation (using addPeriodicTimeObserverForInterval:queue:usingBlock:) to detect when the play time has started to move. For example:
AVPlayer *player;
// allocate AVPlayer and start playing
__weak PlayerController *blockSelf = self;
id observerToken = [player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1.0, NSEC_PER_SEC)
queue:nil
usingBlock:^ (CMTime time)
{
if (CMTimeGetSeconds(time) > 0.0)
{
// done buffering, should be playing now
[player removeTimeObserver:[blockSelf playerTimeObserverToken]];
[blockSelf setPlayerTimeObserverToken:observerToken:nil];
}
}];
[self setPlayerTimeObserverToken:observerToken]; // Keep a reference to the token for later use in block.

Resources