I write custom player from AVPlayer for video playback. According to Apple docs set the video layer:
self.player = [IPLPlayer new];
self.player.playerLayer = (AVPlayerLayer *)self.playerView.layer;
Where self.playerView is usual class from those docs:
#implementation PlayerView
+ (Class) layerClass {
return [AVPlayerLayer class];
}
- (AVPlayer *)player {
return [(AVPlayerLayer *)[self layer] player];
}
- (void)setPlayer:(AVPlayer *) player {
[(AVPlayerLayer *) [self layer] setPlayer:player];
}
The problem is:
When close app (Home button), or block screen, the video playback is stopped, and when resume ONLY audio playback resumed, the image on screen is still those was before block screen - it's fully static and note change frames.
How to resume VIDEO playing after screen is blocked?
Seems I must to register notifications, and after app become active resume video layer:
-(void)registerNotification
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterBackground)
name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didEnterForeground)
name:UIApplicationDidBecomeActiveNotification object:nil];
}
-(void)unregisterNotification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void)willEnterBackground
{
NSLog(#"willEnterBackground");
[self.playerView willEnterBackground];
}
-(void)didEnterForeground
{
NSLog(#"didEnterForeground");
[self.playerView didEnterForeground];
}
And one solution that binds all this information together.
Maybe player status should be handled differently, but I like the recursive way.
Note: If you do not need the exact seek time, you can use [_player seekToTime:<#(CMTime)#> completionHandler:<#^(BOOL finished)completionHandler#>] It's faster but it seeks to the nearest key frame.
- (void)viewDidLoad
{
[super viewDidLoad];
....
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appEnteredForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appEnteredBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
-(void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
....
-(void) appEnteredForeground {
AVPlayerLayer *player = (AVPlayerLayer *)[playerView layer];
[player setPlayer:NULL];
[player setPlayer:_player];
[self playAt:currentTime];
}
-(void) appEnteredBackground {
[_player pause];
currentTime = [_player currentTime];
}
-(void)playAt: (CMTime)time {
if(_player.status == AVPlayerStatusReadyToPlay && _player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
[_player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
[_player play];
}];
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self playAt:time];
});
}
}
This works for me on Swift 3
Add somewhere while setting up the view:
NotificationCenter.default.addObserver(self,
selector: #selector(appWillEnterForegroundNotification),
name: .UIApplicationWillEnterForeground, object: nil)
Grab your player and force it to play:
func appWillEnterForegroundNotification() {
myPlayer.play()
}
Don't forget to remove the observers when you don't need them:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
Use the UIApplicationWillEnterForegroundNotification as well. That way you know your app will be active and visible to the user:
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(appEnteredForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
The trick is to detach all video layers from their players when the app did enter background and reattaching them when the app did become active again.
So in your -applicationDidEnterBackground: you got to trigger a mechanism that results in
avPlayerLayer.player = nil;
and in your -applicationDidBecomeActive: you reattach the player like
avPlayerLayer.player = self.avPlayer;
Also have a look at this Tech Note (QA1668) from Apple.
After some research I've found, that the same bag is in iOS player (WebBrowser, MPMoviePlayerController). May be because distribution type of content is Progressive Download.
So solution is:
Use below notifications and save current time.
After app is resumed, recreate AVPlayer and start playing from saved
time.
In all other cases image is blocked, or view become black.
Related
I want to play play video on current viewcontroller with full screen.
so i am using MPMoviePlayerViewController to play full screen video but problem is that it does not play smooth.
it lag little bit while playing video.
Here is My code which i am using to play full screen video which is store in my documents directory of phone/ipad
-(void)PlayVideo:(NSString *)videoFilePath
{
NSURL *videoURL = [NSURL fileURLWithPath:videoFilePath];
NSLog(#"videoURL: %#", videoURL);
MPMoviePlayerViewController *playerVC = [[MPMoviePlayerViewController alloc] initWithContentURL:videoURL];
// Remove the movie player view controller from the "playback did finish" notification observers
[[NSNotificationCenter defaultCenter] removeObserver:playerVC
name:MPMoviePlayerPlaybackDidFinishNotification
object:playerVC.moviePlayer];
// Register this class as an observer instead
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:playerVC.moviePlayer];
// Set the modal transition style of your choice
playerVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
// Present the movie player view controller
[self presentViewController:playerVC animated:NO completion:nil];
// Start playback
[playerVC.moviePlayer prepareToPlay];
[playerVC.moviePlayer play];
}
- (void)movieFinishedCallback:(NSNotification*)aNotification
{
// Obtain the reason why the movie playback finished
NSNumber *finishReason = [[aNotification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
// Dismiss the view controller ONLY when the reason is not "playback ended"
if ([finishReason intValue] != MPMovieFinishReasonPlaybackEnded)
{
MPMoviePlayerController *moviePlayer = [aNotification object];
// Remove this class from the observers
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:moviePlayer];
// Dismiss the view controller
[self dismissViewControllerAnimated:YES completion:Nil];
}
}
Didn't find any issue with the code except in
[[NSNotificationCenter defaultCenter] removeObserver:playerVC
name:MPMoviePlayerPlaybackDidFinishNotification
object:playerVC.moviePlayer];
removeObserver should be self and not playerVC.
But ya, that will not cause any problem like you have mentioned.
I would suggest you try profiling your app, which might direct you to the code causing the issue. Probably there might be something that you are doing in the background when the video is playing which is causing it to lag.
I am facing problem of memory leak and other MoviePlayer new initiation as my MoviePlayer doesn't respond to function, in which I am releasing that player on my done button.
(void) playMovieAtURL
{
MPMoviePlayerViewController *mpViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:videoURL]];
mpViewController.view.backgroundColor = [UIColor blackColor];
[self presentMoviePlayerViewControllerAnimated:mpViewController];
[mpViewController.view setCenter:self.view.center];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMovieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:mpViewController];
}
// When the movie is done,release the controller. (Doesn't come in it.)
-(void)myMovieFinishedCallback:(NSNotification*)aNotification
{
MPMoviePlayerController* theMovie=[aNotification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:theMovie];
// Release the movie instance created in playMovieAtURL
[theMovie release];
}
Not sure it's your case, but this is what documentation says about MPMoviePlayerPlaybackDidFinishNotification:
This notification is not sent in cases
where the movie player is displaying
in fullscreen mode and the user taps
the Done button. In that instance, the
Done button causes movie playback to
pause while the player transitions out
of fullscreen mode. If you want to
detect this scenario in your code, you
should monitor other notifications
such as
MPMoviePlayerDidExitFullscreenNotification.
It seems that MPMoviePlayerPlaybackDidFinishNotification is called just when the movie stops by itself.
If you are using the Done button, you should use MPMoviePlayerDidExitFullscreenNotification instead.
I tried to solve it by passing nil and now it is returning me callbacks but still the movie won't releases, I will try ur suggestion also. Anyways my new code
-(void) playMovieAtURL
{
MPMoviePlayerViewController *mpViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:videoURL]];
mpViewController.view.backgroundColor = [UIColor blackColor];
[self presentMoviePlayerViewControllerAnimated:mpViewController];
[mpViewController.view setCenter:self.view.center];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMovieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
}
// When the movie is done,release the controller.
-(void)myMovieFinishedCallback:(NSNotification*)aNotification
{
MPMoviePlayerController* theMovie=[aNotification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
// Release the movie instance created in playMovieAtURL
[theMovie release];
}
I'm having lots of trouble displaying a fullscreen movie in my iPad app and then allowing the user to dismiss it with either the Done button or the "un-fullscreen" button on the player controls.
Initially I was using MPMoviePlayerViewController for the movie presentation, but I wasn't receiving the enter/exit fullscreen notifications from its MPMoviePlayerController object, so I switched to doing it myself.
I can make the movie appear fullscreen (although the transition is janky), but when either the "Done" or "un-fullscreen" buttons are pressed, no action is taken by the player. I've posted my code below:
- (void)startPlayingMovieWithURLString:(NSString *)movieURLString {
// I get all of these callbacks **EXCEPT** the "willExitFullScreen:" callback.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willEnterFullScreen:) name:MPMoviePlayerWillEnterFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willExitFullScreen:) name:MPMoviePlayerWillExitFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishPlayback:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
[self.moviePlayerController setContentURL:someExistingURL];
// "self" is a UIViewController subclass, and is presented as a "fullscreen" modal view controller from its parent
// I'm setting the movie player's view's frame to take up the full rectangle of my view controller, but really I want the movie to be completely removed when the user presses "done" (that is, removed from the view hierarchy). Not sure when/where to do this.
self.moviePlayerController.view.frame = self.view.frame;
[self.view addSubview:self.moviePlayerController.view];
[self.moviePlayerController setFullscreen:YES animated:YES];
}
And here is the code for my didFinish callback
- (void)didFinishPlayback:(NSNotification *)notification {
// This ends up recursively telling the player that playback ended, thus calling this method, thus…well you get the picture.
// What I'm trying to do here is just make the player go away and show my old UI again.
[self.moviePlayerController setFullscreen:NO animated:YES];
}
So obviously I am doing something wrong but I've been up and down the documentation and I can't figure out how to make the movie just go away. I figured it would be more intuitive than this. What am I doing wrong?
Here are how the events -> notifications work:
User presses 'Done' button
MPMoviePlayerWillExitFullscreenNotification
MPMoviePlayerDidExitFullscreenNotification
User presses 'Leave fullscreen' button on transport
MPMoviePlayerWillExitFullscreenNotification
MPMoviePlayerDidExitFullscreenNotification
Note that playback does not stop
Movie reaches end
MPMoviePlayerPlaybackDidFinishNotification with the MPMoviePlayerPlaybackDidFinishReasonUserInfoKey set to MPMovieFinishReasonPlaybackEnded
If you call setFullscreen:NO animated:YES on your MoviePlayerController instance from this notification, you'll then get the WillExit and DidExit notifications.
Note that you don't get the PlaybackDidFinish notification when the user presses the Done or Leave Fullscreen buttons.
So, typically, if you want to get rid of the MoviePlayer's view, you need to put [self.moviePlayerController.view removeFromSuperview] in the DidExitFullscreen notification handler. WillExitFullscreen is too soon.
Here's my code:
- (void)willEnterFullscreen:(NSNotification*)notification {
NSLog(#"willEnterFullscreen");
}
- (void)enteredFullscreen:(NSNotification*)notification {
NSLog(#"enteredFullscreen");
}
- (void)willExitFullscreen:(NSNotification*)notification {
NSLog(#"willExitFullscreen");
}
- (void)exitedFullscreen:(NSNotification*)notification {
NSLog(#"exitedFullscreen");
[self.movieController.view removeFromSuperview];
self.movieController = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)playbackFinished:(NSNotification*)notification {
NSNumber* reason = [[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
switch ([reason intValue]) {
case MPMovieFinishReasonPlaybackEnded:
NSLog(#"playbackFinished. Reason: Playback Ended");
break;
case MPMovieFinishReasonPlaybackError:
NSLog(#"playbackFinished. Reason: Playback Error");
break;
case MPMovieFinishReasonUserExited:
NSLog(#"playbackFinished. Reason: User Exited");
break;
default:
break;
}
[self.movieController setFullscreen:NO animated:YES];
}
- (void)showMovie {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willEnterFullscreen:) name:MPMoviePlayerWillEnterFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willExitFullscreen:) name:MPMoviePlayerWillExitFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(enteredFullscreen:) name:MPMoviePlayerDidEnterFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(exitedFullscreen:) name:MPMoviePlayerDidExitFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playbackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
NSURL* movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"tron" ofType:#"mov"]];
self.movieController = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
self.movieController.view.frame = self.view.frame;
[self.view addSubview:movieController.view];
[self.movieController setFullscreen:YES animated:YES];
[self.movieController play];
}
Yes. That's great. There are really notifications mentioned above...
However, there are no MPMoviePlayerPlaybackWillFinishNotification somewhy!!!
That's really a problem.
When you call the movie player as modal (no matter which of the following methods used presentViewController/presentModalViewController/presentVideoController), if you defined .fullScreen = YES, it's not expected to call MPMoviePlayerWillExitFullscreenNotification notification at all (obviously, because it's not cosidering we enter/exit from full screen, but only present/dismiss the controller).
But there are really no any notifications that the video is about to finish and close. That's needed (besides any other situations possible) to catch the moment when the transition of dismissing is started. (The transition, of course, starts before the MPMoviePlayerPlaybackDidFinishNotification called). And, at the same time, application:supportedInterfaceOrientationsForWindow: for previously shown controller is called before the notification, and there is no way to say the AppDelegate that our current controller must be shown already in another orientation.
So, since my video is fullscreen and also without any controls shown (this is kind of an intro, so I just until it finishes) my solution was just to have a timer which checks every short tick (0.1 seconds) what is the video current position... and it it's close to the end, then this is the moment for my own notification.
Here's the code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *musicURL = [NSURL URLWithString:#"http://live-three2.dmd2.ch/buureradio/buureradio.m3u"];
if([musicURL scheme])
{
MPMoviePlayerController *mp = [[MPMoviePlayerController alloc] initWithContentURL:musicURL];
if (mp)
{
// save the music player object
self.musicPlayer = mp;
[mp release];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(popBack:) name:#"MPMoviePlayerDidExitFullscreenNotification" object:nil];
// Play the music!
[self.musicPlayer play];
}
}
}
-(void)popBack:(NSNotification *)note
{
[self.navigationController popToRootViewControllerAnimated:YES];
}
The selector method never gets called. I just want to pop back to the root menu when the "Done" button is pressed on the movie player. I put an NSLog in the selector to check if it was even being called, nothing. The music plays fine. Any thoughts?
This should work
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(popBack:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
I went through the example from apple "MoviePlayer on iPhone"
Im trying to overlay on top of the mpmovieplayercontroller,
it works perfectly with video clip that is in bundle,
but it wont work if i stream the video from the url.
the overlay view will just get hide behind the player.
is there a way to bring the overlay view up to front?
MPMoviePlayerController creates its own window and sets that as the key window - you probably know this already from the MoviePlayer sample app.
I don't know why, but there's a delay when the player uses a stream - so the keyWindow you get right after you initialize the player is likely not the player's window, since that seems to get added later.
You can "cheat" and use a timer to get the player window a few seconds later, and add your overlay:
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(addMyOverlay:) userInfo:nil repeats:FALSE]
Or you can listen for the UIWindowDidBecomeKeyNotification event, and do the same:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyWindowChanged:) name:UIWindowDidBecomeKeyNotification object:nil];
Neither option is great (I'd love to know a cleaner way to do this), but it gets the job done.
You can overlay your view when you receive "MPMoviePlayerContentPreloadDidFinishNotification" notification.
Register for the notification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePreloadDidFinish:)
name:MPMoviePlayerContentPreloadDidFinishNotification
object:nil];
Add overlay view when receiving the notification:
// Notification called when the movie finished preloading.
- (void) moviePreloadDidFinish:(NSNotification*)notification
{
NSArray *windows = [[UIApplication sharedApplication] windows];
if ([windows count] > 1)
{
// Locate the movie player window
UIWindow *moviePlayerWindow = [[UIApplication sharedApplication] keyWindow];
if ([moviePlayerWindow viewWithTag:0x3939] == nil) {
self.videoOverlayView.tag = 0x3939;
[moviePlayerWindow addSubview:self.videoOverlayView];
}
[moviePlayerWindow bringSubviewToFront:self.videoOverlayView];
}
}
A very simple solution:
appDelegate.window.backgroundColor = [UIColor clearColor];
appDelegate.window.windowLevel = 2;
This will keep your app UI on top of the video window.
my post
Previous answer was based on timer. & fixed 5 seconds.
When Movie player begins, a new window is added to application.
Use a timer to check weather a new window is added to your application or not.
When a window ( movie player window ) is added. set notifications.
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePreloadDidFinish:)
name:MPMoviePlayerContentPreloadDidFinishNotification
object:nil];
// Register to receive a notification when the movie has finished playing.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayBackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
// Register to receive a notification when the movie scaling mode has changed.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieScalingModeDidChange:)
name:MPMoviePlayerScalingModeDidChangeNotification
object:nil];
videoListController.xmlClassVideoList=t;
// here ttttt is a timer declared in .h file
tttttt=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(startMy) userInfo:nil repeats:YES];
}
-(void)startMy{
NSArray *windows = [[UIApplication sharedApplication] windows];
NSLog(#"%i",[windows count]);
// depends on your application window
// it may be 1/2/3
if ([windows count] > 3) {
// Locate the movie player window
[tttttt invalidate];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyWindowChanged:) name:UIWindowDidBecomeKeyNotification object:nil];
}
}