I want to start, pause and stop (same as restart) my mp3 File and I'm using AVPlayer. I get the files from a server.
To start the song I do:
[self.player start];
To pause I do:
[self.player pause];
but when I want to stop the song and reload it, so that the song starts from the beginning when the User clicks on the "start button" next time, I have no idea what to do.
I tried something like this:
[self.player pause];
self.player = nil;
But then the player is nil of course and I can't restart the file again without a new initialization. Any ideas how to stop it?
Stop the video using [player pause]
Then use this code when you start the video.
[player seekToTime:kCMTimeZero];
[player play];
I like this better than the comment that solved the OP it b/c it eliminates a warning and uses a constant.
Best solution to do this in Swift < 4:
player.seek(to: kCMTimeZero)
player.play()
Swift >= 4:
player.seek(to: .zero)
player.play()
In Xamarin.ios
player.Seek(CoreMedia.CMTime.Zero);
player.Play();
seek#to has a completion handler.
p?.seek(to: .zero)
p?.rate = 1
is not really correct. You can get audio jitter.
p?.seek(to: .zero) { [weak self] _ in
self?.p?.rate = 1
}
is probably what you want.
If it is already playing, and you just do the seek and nothing else, again you could get the jitters.
You are probably best to
p?.rate = 0
p?.seek(to: .zero) { [weak self] _ in
self?.p?.rate = 1
}
If it's a really important app, you'll also have to consider the error condition in the seek completion.
In Swift 4: self.player.seek(to: CMTime.zero)
// If conti. play Video or audio apply this code
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd
:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[avPlayer currentItem]];
[avPlayer play];
- (void)playerItemDidReachEnd:(NSNotification *)notification
{
NSLog(#"Replay video in method");
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
Related
I'm really going crazy with my welcome view controller.
I have a video in background in continuos loop but every solution that I used causes a small pause/flash when the video is finished and loop.
I use two solution: MPMoviePlayerController and AVPlayer from AVFoundation but I got the same result, a small white flash when video is looped for replay.
My MPMoviePlayerController solution (I prefer a fix for this)
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:#"welcome_video" withExtension:#"mp4"];
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:videoURL];
self.moviePlayer.controlStyle = MPMovieControlStyleNone;
self.moviePlayer.scalingMode = MPMovieScalingModeAspectFill;
self.moviePlayer.view.frame = self.view.frame;
[self.view insertSubview:self.moviePlayer.view atIndex:0];
[self.moviePlayer prepareToPlay];
// Loop video
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loopVideo) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
}
- (void)loopVideo
{
[self.moviePlayer play];
}
My AVPlayer solution
(void)viewDidLoad
{
[super viewDidLoad];
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&sessionError];
[[AVAudioSession sharedInstance] setActive:YES error:&sessionError];
//Set up player
NSURL *movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"welcome_video" ofType:#"mp4"]];
AVAsset *avAsset = [AVAsset assetWithURL:movieURL];
AVPlayerItem *avPlayerItem =[[AVPlayerItem alloc]initWithAsset:avAsset];
self.avplayer = [[AVPlayer alloc]initWithPlayerItem:avPlayerItem];
AVPlayerLayer *avPlayerLayer =[AVPlayerLayer playerLayerWithPlayer:self.avplayer];
[avPlayerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[avPlayerLayer setFrame:[[UIScreen mainScreen] bounds]];
[self.movieView.layer addSublayer:avPlayerLayer];
//Config player
[self.avplayer seekToTime:kCMTimeZero];
[self.avplayer setVolume:0.0f];
[self.avplayer setActionAtItemEnd:AVPlayerActionAtItemEndNone];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.avplayer currentItem]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerStartPlaying)
name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.avplayer pause];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.avplayer play];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
- (void)playerItemDidReachEnd:(NSNotification *)notification
{
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
- (void)playerStartPlaying
{
[self.avplayer play];
}
What's wrong with these implementation? I really try different fixes found on this site but nothing seems to work.
Any suggestions?
Hm.. I might have an idea for the AVPlayer-approach.
The AVPlayer has this method:
- (void)setRate:(float)rate
time:(CMTime)itemTime
atHostTime:(CMTime)hostClockTime
This means that you can specify WHEN you want the AVPlayer to start at kCMTimeZero. I have not actually used it, but I have an idea for how it can work.
You need to know exactly the moment you start the video the first time. I see you have your own -(void)playerStartPlaying which is called by the notificationCenter on didBecomeActive. I suggest using this method for app variations of [player play];, so put
[self playerStartPlaying];
inside viewDidAppear instead of [self.avplayer play];. It might be good enough.
If you here manage to find the device's hostClockTime, and add the length of the video, you should end up with the exact time when you want it to start from scratch. I am not testing any of this, and I'm typing from head, so you need to understand what I'm doing, and fix it yourself.
- (void)playerStartPlaying
{
//The device's hostClockTime. Basically a number indicating how long the device has been powered on.
CMTime hostClockTime = CMClockGetHostTimeClock;
//A CMTime indicating when you want the video to play the next time.
CMTime nextPlay = CMTimeAdd(hostClockTime, self.avplayer.currentItem.duration);
/* I don't know if that was correct or not, but you'll find out */
//Start playing if we're not already playing. There might be an avplayer.isPlaying or something, I don't know, this is probably working as well..
if(self.avplayer.rate != 1.0)
[self.avplayer play];
//Tell the player to restart the video at the correct time.
[self.avplayer setRate:1.0 time:kCMTimeZero atHostTime:nextPlay];
}
You'll have to remove the entire AVPlayerItemDidPlayToEndTimeNotification-thing. When the video has reached the end, it's already too late. What we're doing now is telling it when to play the second turn when we start the first. We want nothing to happen when didPlayToEndTime is fired, we're handling it manually.
So, if you understand what I have done above, you'll also notice that the video only will play twice. We tell the video to play, at the same time as we tell it to replay at time = now+videoLength. When that replay is done, nothing happens. It simply reaches end. To fix this, you'll need to somehow call -(void)playerStartPlaying at the same time as the setRate:time:atHostTime is executed on the AVPlayer. I guess you could start an NSTimer or dispatch_time and let it execute he method in exactly nextPlay-amount of time, but that would kinda defeat the purpose of this thing. Maybe not. You could try different stuff out. You probably CAN do this with some success, but I suggest finding a way to register for when the player started from the start. Maybe you can observe the rate or something, I don't know.. If you want to try it with a delayed method, you can try this:
double delayInSeconds = CMTimeGetSeconds(self.avplayer.currentItem.duration); //or something
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self playerStartPlaying];
});
Just keep in mind that this is some recursive shit, so even if you pause the video, this will still keep calling after that duration. In that case I suggest making a BOOL paused; and cancel the execution of the entire playerStartPlaying if it's set to YES. ..and of course set paused = YES; whenever you want to pause, next to wherever you say [player pause];
If this actually works, but you still get flashes, I know there are several ways to improve this. For instance, the CMTimeAdd() should probably be using some kind of synchronization-tool to make sure the times add up using the correct timeScale etc.
I have now spent way too much time writing this, and it might not even work. I have no idea. Good luck and good night.
I am building an app which needs to play a track list, but between each song the music should pause to execute some code, then once complete the music should resume. This needs to work when the app is in the background as well as in the foreground.
I have tried a couple of methods but none seem to be able to do everything I want.
AVQueuePlayer - I can't seem to identify when any one song has stopped, only when the whole queue has stopped.
AVPlayer - I can identify when the track has ended with a notification, then I can run my extra code then load the next track. This works fine as long as the app is not in the background, when the app is in the background the code executes fine except the [avPlayer play] command does not work. It does not throw an error, it simply does not play. I know it has moved to the next song and loaded it into AVPlayer as I output the meta data and it has moved on.
Just to be clear the initial track does run in the background, it is only starting the next track which does not run in the background.
Code below...
Any idea what I am doing wrong?
Thanks!
+(void) playItem {
//get the play item from the song array based on intSongIndex
MPMediaItem *currentSong = [songsNowPlaying objectAtIndex:intSongIndex];
AVPlayerItem * currentItem = [AVPlayerItem playerItemWithURL:[currentSong valueForProperty:MPMediaItemPropertyAssetURL]];
[avPlayer replaceCurrentItemWithPlayerItem:currentItem];
//add notification to the currentItem
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:currentItem];
//play
[avPlayer play];
NSArray *metadataList = [[avPlayer currentItem].asset commonMetadata];
for (AVMetadataItem *metaItem in metadataList) {
NSLog(#"%#: %#",[metaItem commonKey], [metaItem value]);
}
//increment song index so next time the next song is selected
intSongIndex ++;
if (intSongIndex >= songsNowPlaying.count) {
intSongIndex = 0;
}
}
+ (void)playerItemDidReachEnd:(NSNotification *)notification {
//add code to be executed before the next song plays
//call playItem to play the next song
[self playItem];
}
Solved, this needed adding to the initial viewDidLoad
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
I have a video on a screen that must be played with an infinity loop.
So I wrote :
self.player = [AVPlayer playerWithPlayerItem:avItem];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[_player currentItem]];
[_player play];
And I fallback method is :
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[_player.currentItem seekToTime:kCMTimeZero];
[_player play];
}
It works well but sometimes it seems that my fallback is not called. The video is freezing at the end and never played again. It's happening randomly ...
Do you have an idea ?
You can just set a boundary time observer.
- (id) setupBoundaryEndWith:(NSArray*)array
{
__weak MY_AVPlayer* weakself = self;
return [self addBoundaryTimeObserverForTimes:array queue:NULL usingBlock:^{
[weakself stopAVPlayerAndLoopOrTriggerNextTrack];
}];
}
[_player currentItem] is null, since you haven't started playing yet. I would suggest either to explicitly add the AVPlayerItem (ideally) or register to notifications after starting the playback (reverse the 2 last lines).
I have not tried the 2-nd solution, so it might not work, if it takes some time to start the playback. If that is the case, I suggest setting an NSTimer to trigger a second later and then register.
I just want a continuously looping video. I set up the player like this:
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:someURL];
self.moviePlayer.controlStyle = MPMovieControlStyleNone;
self.moviePlayer.shouldAutoplay = YES;
self.moviePlayer.repeatMode = MPMovieRepeatModeOne;
self.moviePlayer.view.frame = self.container.frame;
[self.container addSubview:self.moviePlayer.view];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(moviePlayBackDidFinish:) name: MPMoviePlayerPlaybackStateDidChangeNotification
object: self.moviePlayer];
- (void) moviePlayBackDidFinish:(NSNotification*)notification {
NSLog( #"myMovieFinishedCallback: %#", notification );
MPMoviePlayerController *movieController = notification.object;
NSLog( #"player.playbackState = %d", movieController.playbackState );
}
The notification method is simply a hack that someone suggested here: Smooth video looping in iOS
I have two problems. The video looping still is not seamless. There is a very noticeable paused between loops. Second, the video stops looping after an arbitrary number of loops. Typically varies between 2-4 loops. This is obviously a huge problem for my app. Is the player really this buggy or am I doing something wrong?
I have created a complete seamless looping solution for video here seamless-video-looping-on-ios. Feel free to download the example xcode app and try it out for yourself to see my approach in action. I found that MPMoviePlayerController and AVPlayer both fail to work for this type of thing.
I was also unable to get gapless looping using MPMoviePlayerController -- there was always at least 0.5s of black, and the occasional flash of the QuickTime logo.
However, I am able to obtain gapless looping using AVPlayer -- it takes a couple of conditions to achieve, though:
Something about the encoding of my test video clip means that seeking to the beginning always causes a pause of about 0.5s at the start of each loop. Seeking to 1s into the clip with a kCMTimeZero tolerance makes it seamless. Without the explicit zero seek-tolerance, the effect is the same as seeking to the beginning of the clip.
Seeking while not playing is erratic; it causes a hang on my iPhone 4, but not my iPad 3. The two alternative fixes (shown #if'ed below), are to:
wait for the seek to finish before calling play again, or
wait until a specific time (before the end of the clip), then restart playback at the beginning.
The following code implements those two conditions:
self.player = [AVPlayer playerWithURL:url];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = self.view.bounds;
[self.view.layer addSublayer:self.playerLayer];
[self.player seekToTime:CMTimeMakeWithSeconds(1, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[self.player play];
#if 1
[[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self.player seekToTime:CMTimeMakeWithSeconds(1, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
[self.player play];
}];
}];
#endif
#if 0
NSArray *times = [NSArray arrayWithObject:[NSValue valueWithCMTime:CMTimeMake(5, 1)]];
[self.player addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
[self.player seekToTime:CMTimeMakeWithSeconds(1, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}];
#endif
I've looked around but I can't find a delegate protocol for the AVPlayer class. What gives?
I'm using its subclass, AVQueuePlayer, to play an array of AVPlayerItems, each loaded from a URL. Is there any way I can call a method when a song finishes playing? Notably at the end of the queue?
And if that's not possible, is there any way I could call a method when the song STARTS playing, after buffering? I'm trying to get a loading icon in there but it turns the icon off before the music actually begins, even though it's after the [audioPlayer play] action.
Yes, the AVPlayer class does not have a delegate protocol like the AVAudioPlayer. You need to subscribe to notifications on an AVPlayerItem. You can create an AVPlayerItem using the same URL that you would otherwise pass to -initWithURL: on AVPlayer.
-(void)startPlaybackForItemWithURL:(NSURL*)url {
// First create an AVPlayerItem
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithURL:url];
// Subscribe to the AVPlayerItem's DidPlayToEndTime notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
// Pass the AVPlayerItem to a new player
AVPlayer* player = [[[AVPlayer alloc] initWithPlayerItem:playerItem] autorelease];
// Begin playback
[player play]
}
-(void)itemDidFinishPlaying:(NSNotification *) notification {
// Will be called when AVPlayer finishes playing playerItem
}
Yes. Add a KVO observer to the player's status or rate:
- (IBAction)go {
self.player = .....
self.player.actionAtItemEnd = AVPlayerActionStop;
[self.player addObserver:self forKeyPath:#"rate" options:0 context:0];
}
- (void)stopped {
...
[self.player removeObserver:self]; //assumes we are the only observer
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == 0) {
if(player.rate==0.0) //stopped
[self stopped];
}
else
[super observeVal...];
}
So basically, that's it.
Disclaimer: I wrote that in here so I didn't check if the code was good. ALSO I never used AVPlayer before but it should be about right.
Swift 3 - I add an observer to AVPlayerItem every time I add a video to the player:
func playVideo(url: URL) {
let playerItem = AVPlayerItem(asset: AVURLAsset(url: someVideoUrl))
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEndTime), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
self.player.replaceCurrentItem(with: playerItem)
self.player.play()
}
func playerItemDidPlayToEndTime() {
// load next video or something
}
There is a lot of information in the Apple docs AVFoundation Programming Guide (look for the monitoring playback section). It appears to be mainly through KVO so you may wish to brush up on that if you are not too familiar (there is a guide for that to Key Value Observing ProgrammingGuide.
I'm using this one, and it works:
_player = [[AVPlayer alloc]initWithURL:[NSURL URLWithString:_playingAudio.url]];
CMTime endTime = CMTimeMakeWithSeconds(_playingAudio.duration, 1);
timeObserver = [_player addBoundaryTimeObserverForTimes:[NSArray arrayWithObject:[NSValue valueWithCMTime:endTime]] queue:NULL usingBlock:^(void) {
[_player removeTimeObserver:timeObserver];
timeObserver = nil;
//TODO play next sound
}];
[self play];
where _playingAudio is my custom class with some properties and timeObserver is id ivar.