I have a simple app with an AVPlayer playing M3U8 content. Within the app, I do the following to allow background audio:
NSError *audioError;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&audioError];
if (audioError == nil)
{
[[AVAudioSession sharedInstance] setActive:YES error:nil];
}
When I hit the lock button on my phone, the audio pauses. If I hit the lock button again (to look at the lock screen) I see media controls and I can un-pause the content to hear the audio.
My question is, how do I prevent this auto-pause so it keeps playing when the lock button is first pressed? I tried something like the following:
- (id) init
{
// ...
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(handleEnteredBackground) name: UIApplicationDidEnterBackgroundNotification object: nil];
// ...
}
- (void) handleEnteredBackground
{
// [player play];
[player performSelector:#selector(play) withObject:nil afterDelay:1];
}
But this didn't seem to work.
After some searching I figured out my issue:
https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/creating_a_basic_video_player_ios_and_tvos/playing_audio_from_a_video_asset_in_the_background
If you’re playing audio-only assets, such as MP3 or M4A files, your setup is complete and your app can play background audio. If you need to play the audio portion of a video asset, an additional step is required. If the player’s current item is displaying video on the device, playback of the AVPlayer instance is automatically paused when the app is sent to the background. If you want to continue playing audio, you disconnect the AVPlayer instance from the presentation when entering the background and reconnect it when returning to the foreground.
Their (Swift) example code looks like so:
func applicationDidEnterBackground(_ application: UIApplication) {
// Disconnect the AVPlayer from the presentation when entering background
// If presenting video with AVPlayerViewController
playerViewController.player = nil
// If presenting video with AVPlayerLayer
playerLayer.player = nil
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Reconnect the AVPlayer to the presentation when returning to foreground
// If presenting video with AVPlayerViewController
playerViewController.player = player
// If presenting video with AVPlayerLayer
playerLayer.player = player
}
An Objective-C equivalent would be:
- (void) applicationDidEnterBackground:(UIApplication*)application
{
playerViewController.player = nil;
}
- (void) applicationWillEnterForeground:(UIApplication*)application
{
playerViewController.player = player;
}
Or in my case the player was embedded into a View and was not controlled directly by the app delegate or the view controller, so I used the following:
- (id) init
{
// ...
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(handleEnteredBackground) name: UIApplicationDidEnterBackgroundNotification object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(handleEnteredForeground) name: UIApplicationDidBecomeActiveNotification object: nil];
// ...
}
- (void) handleEnteredBackground
{
controller.player = nil;
}
- (void) handleEnteredForeground
{
controller.player = player;
}
Related
Hi, I replaced MPMoviePlayerController with AVPlayerViewController since MPMoviePlayerController is deprecated.
I'm nearly there, but have one question. My movie start as a view within a view. When playing fullscreen I want it to jump back to NO fullscreen when finished playing. But I don't know how. Here is my code:
- (void)viewDidLoad {
// grab a local URL to our video
NSURL *videoURL = [[NSBundle mainBundle]URLForResource:#"movie" withExtension:#"m4v"];
// create an AVPlayer
AVPlayer *player = [AVPlayer playerWithURL:videoURL];
// create a player view controller
self.controller = [[AVPlayerViewController alloc]init];
controller.player = player;
[player play];
// show the view controller
[self addChildViewController:controller];
[self.view addSubview:controller.view];
controller.view.frame = CGRectMake(0,25, 750, 422);
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:player];
}
With MPMoviePlayer it used to work with this code:
- (void) playerPlaybackDidFinish:(NSNotification*)notification{
// movie finished playing
[moviePlayerController setFullscreen:NO];
}
With what code do I need to replace it??
-(void)itemDidFinishPlaying:(NSNotification *) notification {
// Will be called when AVPlayer finishes playing playerItem
???????????}
Thanks, Meg
#iOS 10 and higher and Swift 4.2 this code are working.
Write this code in your player init methods
if #available(iOS 11.0, *) {
self.playerVC?.exitsFullScreenWhenPlaybackEnds = true
}
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd(notification:)), name: .AVPlayerItemDidPlayToEndTime, object:self.playerVC?.player!.currentItem)
This is youy notification delegate
func playerItemDidReachEnd(note:NSNotification){
print("finished")
dismissViewControllerAnimated(true, completion: nil)
}
In my application I need to play a movie from vimeo, this goes fine but my hole application is made for portrait only. I want to play the movie in a specific viewController and force this controller to be able to rotate to portrait or landscape.
When someone clicks the thumbnail of the movie, I segue to the VideoViewController, I need to know how to determine when this movie clips is finished so I can segue them back directly after the video is finished.
Code;
#implementation VideoViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self vimeoVideo];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)vimeoVideo
{
[YTVimeoExtractor fetchVideoURLFromURL:#"http://vimeo.com/1071123395"
quality:YTVimeoVideoQualityMedium
referer:#"http://xxxxx.com"
completionHandler:^(NSURL *videoURL, NSError *error, YTVimeoVideoQuality quality) {
if (error) {
// handle error
NSLog(#"Video URL: %#", [videoURL absoluteString]);
} else {
// run player
self.playerView = [[MPMoviePlayerViewController alloc] initWithContentURL:videoURL];
[self.playerView.moviePlayer prepareToPlay];
[self presentViewController:self.playerView animated:YES completion:^(void) {
self.playerView = nil;
}];
}
}];
}
You need to add an observer on viewDidLoad so when your Video will stop playing, let it execute your method.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:playerView];
This is the receiver method when the video stops playing
- (void)moviePlayerDidFinish:(NSNotification *)notification
{
//Do your stuff
}
In the MPMoviePlayerController, there are list of Notifications available for you. The one you need is MPMoviePlayerPlaybackDidFinishNotification
Posted when a movie has finished playing. The userInfo dictionary of this notification contains the MPMoviePlayerPlaybackDidFinishReasonUserInfoKey key, which indicates the reason that playback finished. This notification is also sent when playback fails because of an error.
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.
I would like to play an intro video before my game starts on OSX. What is the easiest way to setup an AVPlayer and play the video? I would like the video to play completely before the game can proceed any further.
I have gone through the AVPlayer documentation on the mac developer library but ran into the following problem on my OSX app: AVPlayer does not show video
Please help!
So after the splash screen, Your first view controller can have this:
-(void) playMovieAtURL: (NSURL*) theURL {
MPMoviePlayerController* theMovie =
[[MPMoviePlayerController alloc] initWithContentURL: theURL];
theMovie.scalingMode = MPMovieScalingModeAspectFill;
theMovie.movieControlMode = MPMovieControlModeHidden;
// Register for the playback finished notification
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: #selector(myMovieFinishedCallback:)
name: MPMoviePlayerPlaybackDidFinishNotification
object: theMovie];
// Movie playback is asynchronous, so this method returns immediately.
[theMovie play];
}
// When the movie is done, release the controller.
-(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];
}
More info: https://developer.apple.com/library/ios/documentation/audiovideo/conceptual/multimediapg/UsingVideo/UsingVideo.html
In myMovieFinishedCallback, you can then show the menu of the game.
I hope I helped :)
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.