I am trying to play video in background.I follow so many tutorial but i did not get appropriate results.For that i am using AVPlayer.I am able to play my video whenever application state is active.But i want to play music in background for that i need to detach AVPlayerLayer from AVPlayer,If you have any alternate solution so that i can play my video in background.Please help me.
This is my code:
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
[[AVAudioSession sharedInstance] setDelegate: self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"sample"
withExtension:#"m4v" subdirectory:nil];
avPlayerItem = [AVPlayerItem playerItemWithURL:url];
self.songPlayer = [AVPlayer playerWithPlayerItem:avPlayerItem];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer: self.songPlayer];
self.avPlayerLayer.frame = self.view.layer.bounds;
UIView *newView = [[UIView alloc] initWithFrame:self.view.bounds];
[newView.layer addSublayer:avPlayerLayer];
[self.view addSubview:newView];
[self.songPlayer play];
}
AppDelegate.m
- (void)applicationWillResignActive:(UIApplication *)application
{
ViewController *vc=[[ViewController alloc]init];
vc.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
ViewController *vc=[[ViewController alloc]init];
vc.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:vc.songPlayer];
}
You are creating a new view controller in both your application delegate methods, with a new player etc.. That will not change a thing. You must use the player and layer you created in the view controller's viewDidLoad method, and modify that one later in the delegate methods. For this, your application delegate could store the main view controller in it's properties, or you could implement the NSNotifications for application delegate in your view controller class, like so: (could be added to the view controller's viewDidLoad method)
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
To detach the player layer from the player, as it's said in the documentation, is slightly incorrect, at least confusing. In fact you have to detach the player from the player layer.
Add this code to the applicationDidEnterBackground (not applicationWillResignActive) method:
// Detach player from playerlayer to avoid pause while in background
if (self.playerLayer && self.player)
{
[self.playerLayer setPlayer: nil];
}
In your applicationWillEnterForeground, store a flag to remember that when the application becomes active, it is due to entering the foreground, and not one of the other numerous reasons for which applicationDidBecomeActive may be called:
self.enteringForeground = true;
And then, in your applicationDidBecomeActive: method,
if (self.enteringForeground)
{
// Re-attach player to playerlayer
if (self.playerLayer && self.player)
{
if (self.playerLayer.player != self.player)
[self.playerLayer setPlayer: self.player];
}
self.enteringForeground = false;
}
For this to work, you will need a new property in your application delegate .h file or view controller .h file, depending which implementation you chose:
#property (readwrite) BOOL enteringForeground;
Of course, you also need to add the entry UIBackgroundModes with value 'audio' to your application's info.plist file (or in the Info tab of your project, add the entry 'Required background modes' and then the value 'App plays audio or streams audio/video using AirPlay').
I hope this is helpful. I too struggled at first to implement background playback, and this method seems to work quite well.
Related
I'm attempting to use MPMusicPlayerController to play apple music songs, but I can't get the lock screen controls to work. It seems as though MPMusicPlayerController overridess the remoteControlReceivedWithEvent listener.
Here is how I set up my controller:
self.player = [MPMusicPlayerController applicationMusicPlayer];
self.player.repeatMode = MPMusicRepeatModeNone;
self.player.shuffleMode = MPMusicShuffleModeOff;
[self.player beginGeneratingPlaybackNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handlePlaybackStateChanged:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.player ];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleItemChanged:) name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:self.player ];
Then I play apple music songs:
NSMutableArray *storeIDS = [NSMutableArray arrayWithObjects:anthem.song.apple_id, #"1", nil];
[self.player setQueueWithStoreIDs:storeIDS];
[self.player play];
[self.player setCurrentPlaybackRate:1.0];
For reference, here is how I setup the remote control listener in didFinishLaunchingWithOptions:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
At this point, the player plays the song as requested, but I can no longer receive any remote control notifications. Hitting next/prev simply stop the song since it has reached the end of the list. I've tried with the applicationMusicPlayer as well as the systemMusicPlayer. I can't use AVPlayer or AVAudioPlayer because it's Apple Music and I cannot get the URL to stream.
Any ideas!?
To play from Apple Music use MPMusicPlayerController.systemMusicPlayer()
I want to play a video in seprate view controller, where I can Play/Pause or dismiss that view controller. In my case, I recorded the video in my app and calling this delegate method in order to save this video in assets, but I want to play it first before using the ALAssetsLibrary.
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error
And the TEMO URL im getting is :
file:///private/var/mobile/Containers/Data/Application/EA6D31AC-6CC3-4BDF-B874-BC6F30BA5677/tmp/output.mov
How can I play this video in next view controller or in same view controller??
My try:
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentURL:outputFileURL];
player.view.frame = CGRectMake(184, 200, 400, 300);
[self.view addSubview:player.view];
[player play];
PS: This is showing a black screen area but not actually playing this video.
I had the same problem a few days ago. It ends up that I was calling play method to soon, before the video become ready to play. So, add this before call [player play]. This will notify whenever your video become read to play in MPMoviePlayerController.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(videoPlayBackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:player];
Then setup the observer:
- (void)videoPlayBackDidFinish:(NSNotification *)notification {
[[NSNotificationCenter defaultCenter]removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
}
This worked for me.
Hope it helps.
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.
What is the appropriate point to send the play message to a MPMoviePlayerController instance instantiated in a splitView detail View Controller?
My app is receiving the above console message (with a !) but not crashing...
The app is utilizing MPMoviePlayerController to play a movie from an asset URL
and the responsilble method is called as follows:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startPlayingVideo:self];
}
It plays the video just fine but the console message is looming...
If I move the method call to viewWillAppear:animate:, the console message does not show up. The issue is now I can only hear audio and do not see the video.
For completeness, here is the called method...
- (void) startPlayingVideo:(id)sender
NSURL *movieURL = [NSURL URLWithString:self.movieURLString];
if (self.moviePlayer != nil){
[self stopPlayingVideo:nil];
}
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:movieURL ];
if (self.moviePlayer != nil){
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(videoHasFinishedPlaying:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:self.moviePlayer];
self.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
[self.moviePlayer prepareToPlay];
[self.moviePlayer play];
[self.view addSubview:self.moviePlayer.view];
[self.moviePlayer setFullscreen:YES animated:YES];
} else {
NSLog(#"Failed to instantiate the movie player.");
}
}
My original issue stemmed from having the MoviePlayerController as an entirely different viewController (embedded in a detail navigation controller). I redesigned the parent view to include a moviePlayer child view. This solved the issue.
I have a single view app with 5 buttons and when one of the buttons is pressed, the player slides up over the original view and begins playing the video in fullscreen (as it should).
All works great with the exception of when pressing the Fullscreen/Minimize icon (the two diagonal arrows pointing to each other next to the play back controls). When pressing this, the original view with the five buttons slides up over the video player. The problem is the video is still playing underneath the original view. I would really like to eliminate the Fullscreen/Minimize icon but from I can tell, that does not seem possible. So... I am thinking, I might be able to use an observer to listen to when the Fullscreen/Minimize icon is pressed and I can do what I need to. I just can not find anything solid on how to do this. Any help/direction would be greatly appreciated.
Here is my current code...
-(IBAction)playvideo {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Megamind" ofType:#"mov"]];
MPMoviePlayerViewController * playerController = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
[self presentMoviePlayerViewControllerAnimated:(MPMoviePlayerViewController *)playerController];
playerController.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
[playerController.moviePlayer play];
[playerController release];
playerController=nil;
}
- (void)moviePlayerWillExitFullscreen:(NSNotification *)theNotification {
MPMoviePlayerController *playerController = [theNotification object];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerWillExitFullscreen:)
name:MPMoviePlayerWillExitFullscreenNotification
object:nil];
[playerController stop];
[self dismissMoviePlayerViewControllerAnimated];
}
This line is causing you that behaviour.
[self presentMoviePlayerViewControllerAnimated:(MPMoviePlayerViewController *)playerController];
It is pretty much similar to your regular presentModalViewController method.
It presents the Movieplayer and its view controller Modally. So the default settings here are
movieplayer.controlStyle = MPMovieControlStyleFullScreen
which are set up by default.
So when you press those diagonal arrows, it exits that mode, and gives a notification for that. But you have to setup an observer first to listen to that notifcation as you did for movie finished.
You did
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(movieFinishedPlayback:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
This adds a notification to observe for movie completion notifications.
For exiting full screen mode add one more observer that is this..
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(movieExitFullScreen:) name:MPMoviePlayerDidExitFullscreenNotification object:nil];
And you should be good to go after adding the -(void) movieExitFullScreen:(NSNotification *) selector for the same. Hope it helps. :)
Put this line just after the init of your MPMoviePlayer :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerWillExitFullscreen:)
name:MPMoviePlayerWillExitFullscreenNotification
object:nil];
I think you're adding the observer in the method where you want to be REMOVING it.
You want this
MPMoviePlayerController *playerController = [theNotification object];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerWillExitFullscreen:)
name:MPMoviePlayerWillExitFullscreenNotification
object:nil];
in the playVideo()
and THIS
[[NSNotificationCenter defaultCenter] removeObserver:self
name:name:MPMoviePlayerWillExitFullscreenNotificationn
object:nil];
in the moviePlayerWillExitFullscreen method.
I did find a solution and my lack of knowledge put me in a situation where I do not fully understand why it works this way. My apologies for not having a thorough reasoning. In my original code... the MPMoviePlayerWillExitFullscreenNotification was not answering to taps. This is true for MPMoviePlayerDidExitFullscreenNotification as well. What was answering was MPMoviePlayerPlaybackDidFinishNotification. Here is teh working code in knowing that the MPMoviePlayerPlaybackDidFinishNotification was working and also applied to the Fullscreen/Embed presses.
-(IBAction)playvideo {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Megamind" ofType:#"mov"]];
MPMoviePlayerViewController * playerController = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(movieFinishedPlayback:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
[self presentMoviePlayerViewControllerAnimated:(MPMoviePlayerViewController *)playerController];
playerController.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
[playerController.moviePlayer play];
[playerController release];
playerController=nil;
NSLog(#"playvideo");
}
- (void)movieFinishedPlayback:(NSNotification*)notification {
MPMoviePlayerController *playerController = [notification object];
[playerController pause];
[self dismissMoviePlayerViewControllerAnimated];
}