Should I stop current AVPlayer instance when playing music from another URL? - iphone

I just created AVPlayer and it plays music well. I have two questions
How to play another music from another URL (should I stop current player?)
How to show current time of the song in UISlider (actually is it a method that called when the song is playing?)

Use -[AVPlayer replaceCurrentItemWithPlayerItem] to replace the current playing item reusing the player instance. You can create an item with an URL or with an asset.
In order to know when a given item finishes playing use the notification AVPlayerItemDidPlayToEndTimeNotification.
Use -[AVPlayer addPeriodicTimeObserverForInterval] to perform some action periodically while the player is playing. See this example:
[self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil
usingBlock:^(CMTime time) {
<# your code will be called each 1/10th second #>
}];

1) If you used - (id)initWithURL:(NSURL *)URL then you should stop player with pause, dealloc it and create new instance.
AVPlayer *player = [AVPlayer alloc] initWithURL:[NSURL URLWithString:#"http:/someurl.com"]];
[player play];
[player pause];
[player release];
player = [AVPlayer alloc] initWithURL:[NSURL URLWithString:#"http:/someurl2.com"]];
[player pause];
[player release];
If you used playerWithURL, then just call the same line again.
2). The easiest is the get duration of the current item https://stackoverflow.com/a/3999238/619434 and then update the UISlider with that value. You can use NSTimer to periodically check the duration.
self.player.currentItem.asset.duration

Related

Asynchronous MPMoviePlayerController load has no duration value

I assume that the initWithContentURL: method is not asynchronous. I added the following methods to do the load in the background and then assign the temporary player to my variable.
-(void)createPlayer {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
MPMoviePlayerController *temp = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:#"URLString"]];
[self performSelectorOnMainThread:#selector(passPlayer:) withObject:temp waitUntilDone:YES];
[temp release];
[pool release];
}
-(void)passPlayer:(MPMoviePlayerController*)temp {
player = [temp retain];
player.movieSourceType = MPMovieSourceTypeStreaming;
//player.view.hidden = YES;
//[self.view addSubview:player.view];
[player prepareToPlay];
}
The problem is when I print out the duration of the player I get this:
double duration = player.duration;
NSLog(#"duration: %f, duration);
console output: duration: nan
I have added the MPMoviePlayerControllerNotifications to be notified when there is a duration value available, but even then the value is still 'nan'. If I do the load on the main thread the duration has a value, but I don't want to use the main thread so my UI doesn't lock up.
Any ideas?
First of all, don't create UI objects on a non-main thread. All UI operations must be done on the main thread, or odd things will occur. Such as, perhaps, the movie never loading and NAN being returned for the duration.
Second, initWithContentURL: probably is asynchronous or there wouldn't be notifications to let you know when the movie was actually loaded. Just create the controller as you would normally, subscribe to the appropriate notifications to know when the status is updated, and then check the status immediately just in case the movie happened to be a local file or already cached so the load was able to happen synchronously.
This happens because the movie player has not been able to calculate the duration. If you subscribe to the MPMovieDurationAvailableNotification notification, you'll be notified if and when a value has been determined.
MPMovieDurationAvailableNotification
This notification is posted when the
duration of a movie object is
determined. The object of the
notification is the
MPMoviePlayerController object itself.
There is no userInfo dictionary. The
duration value is reflected in the
duration property of the movie player
controller.
Source: Apple documentation
From the official doc :
Discussion
If the duration of the movie is not known, the value in this property is 0.0. If the duration is subsequently determined, this property is updated and a MPMovieDurationAvailableNotification notification is posted.
So use NSNotificationCenter to register the MPMovieDurationAvailableNotification notification.

Why does MPMoviePlayerController prevent resumption of audio after the movie ends?

I have a class that plays a repeating background music loop with an AVAudioPlayer, and on specific occasion, plays a full-screen video with its own sound track using MPMoviePlayerController. In order to to have only one track at a time, I stop the background music before launching the video:
-(void)startVideo{
[backgroundMusic stop];
MPMoviePlayerViewController *mp = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"videofile" ofType:#"m4v"]]];
[self presentMoviePlayerViewControllerAnimated:mp];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(videoOver) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
[mp.moviePlayer play];
[mp release];
}
-(void)videoOver{
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
if(![backgroundMusic play]){
NSLog(#"bad: can't resume bg music!");
[backgroundMusic release];
backgroundMusic = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"videofile" ofType:#"m4v"]] error:NULL];
backgroundMusic.delegate = self;
backgroundMusic.numberOfLoops = -1;
[backgroundMusic play];
}
}
The resumption worked fine without recreating the AVAudioPlayer object (i.e. the play method returned YES) on analogous code on os versions up to and including 3.2. But on iOS4, the play method always returns NO, and has to recreate the object. Why is that, and can I get to resume the background track properly (I have cases where the solution used above is unacceptable.)?
Figured this out. It turns out that in iOS 3.2 and above, when a video finishes playing, it goes into MPMoviePlaybackStatePaused state rather than MPMoviePlaybackStateStopped, and in order to make it release the hardware, you have to explicitly call the stop method on MPMoviePlayerController after it finishes playing before trying to resume AVAudioPlayer.

Loading Multiple Movies in iPhone MPMoviePlayerController

-(void)initAndPlayMovie:(NSURL *)movieURL
{
// Initialize a movie player object with the specified URL
MPMoviePlayerController *mp = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
if (mp)
{
self.moviePlayer = mp;
[mp release];
[self.moviePlayer play];
}
}
Here, in above code, We can pass just one movie URL. Isn't it possible to pass multiple urls to it?
So, Movie Player will load second url after playing first one.
Is it possible? How can we do that?
Right now, when I try to pass other url, after finishing first one.
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
[self initAndPlayMovie:secondURL];
}
The Device First change its orientation while loading and after loading Device again come back to landscape mode.
How to resolve this problem?
You might want to change the orientation by changing the statusBar orientation before you start playing videos and change it back after you are done with all.
[[UIApplication sharedApplication] setStatusBarOrientation: UIInterfaceOrientationLandscapeRight animated:YES];
You should be able to call setContentURL just as the first movie is about to close to change to another movie. Check endPlaybackTime and fire off your method to invoke setContentURL one second prior to the movie ending.

AVAudioPlayer is not rentrant correct?

It appears that AVAudioPlayer is not reentent. So in the following would soundfx play to completion, delay 1 second, then play again, rather then the 1 second delay - and resultant overlapping playback I desire:
// ...
AVAudioPlayer* soundfx = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:nil];
(void) makeSomeNoise {
[soundfx play];
sleep(1);
[soundfx play];
}
Must I then resort to NSOperation - threading - to achieve my goal of overlapping playback?
Note: I am using IMA4/ADPCM format which is apparently the correct format for layered sound playback.
Cheers,
Doug
It's not that AVAudioPlayer is not reentrant. It is that AVAudioPlayer starts to play your sound after the runloop has ended, and not in your makeSomeNoise function. If you want to play your sound with a one second delay, you can do this:
(void) makeSomeNoise {
[soundfx play];
[soundfx performSelector:play withObject: nil afterDelay:1.0];
}

MPMoviePlayerController and overlay window

Iam trying to play a video from a remote location and trying to overlay a window over the entire screen which is more or less transparent with few images along the edges . As soon as the movie is preloaded I play the video. This happens good the first time. But if I try to replay the video it does not play the video and neither am i able to click on the Play button from the controls of the player. I guess the overlay window is over the controls. How do i get the controls over the overlay window.
//- (void)moviePlayerContentPreloadDidFinish:(NSNotification *)notification{
NSLog(#"content preloaded");
NSDictionary *userInfo = [notification userInfo];
if ([userInfo valueForKey:#"error"]) {
NSLog(#"*** An error occurred preloading the movie");
return;
}
[self.spinner stopAnimating];
// Add the overlay view to the movie, so we can catch the clicks
OverlayViewController *ovc = [[OverlayViewController alloc] initWithNibName:#"OverlayView" bundle:nil];
self.overlayController = ovc;
[ovc release];
[self.overlayController setNextResponder:self];
MPMoviePlayerController *moviePlayer = [notification object];
[moviePlayer play];
// Locate the movie player window
NSArray *windows = [[UIApplication sharedApplication] windows];
NSLog(#"window count= %d",[windows count]);
if ([windows count] < 2) {
NSLog(#"*** Window for movie player is missing");
return;
}
UIWindow *moviePlayerWindow = [windows objectAtIndex:1];
[moviePlayerWindow addSubview:self.overlayController.view];
}
The code that I use is
Am I doing something wrong. Is it possible to get the controls over the overlay or auto play it.
Two things.
First, according to your code, in the moviePlayBackDidFinish method you must first release the moviePlayer and then instantiate it again with an alloc. You must do that every time you want to play a video.
MPMoviePlayerController *moviePlayer = [notification object];
[moviePlayer release]
...
MPMoviePlayerController *newMoviePlayer = [[MPMoviePlayerController alloc] initWithContentURL: anURL];
The structure of your code works, but I think you should better have a look at the example provided by apple:
http://developer.apple.com/iphone/library/samplecode/MoviePlayer_iPhone/index.html
Second, about the overlay, you are right, the overlay gets the events but you cannot put it under the movie player controls. What you have to do is to use the events you receive in your overlay to control the player. You could use touchesBegan event to pass the initial touch event and use it with the player (play, pause, stop).