As the title reads; I am currently playing a sound file on loop using this code:
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"smoothjazz" ofType:#".mp3"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
NSFileManager *filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath:soundFilePath])
{
// Your code to play the sound file
[player setVolume:1.0];
player.numberOfLoops = 5; //infinite
[player play];
}
However, the music keeps playing when you pause the app (hit the home button). How can I call [player setVolume:0.0] when the app is paused and [player setVolume:1.0] when it is resumed?
All help appreciated.
You can use NSNotificationCenter to listen to the UIApplicationWillResignActiveNotification and UIApplicationDidBecomeActiveNotification notifications in your view controller (or whatever object your above code is in) and the pause/resume playing you sounds there.
You should probably not set the volume on your player. It would probably be better to call
[player pause] and [player play]
There is a protocol that handles state change: UIApplicationDelegate. The ones you are interested in right now are willResignActive and didBecomeActive.
Note that these are not didEnterBackground and willEnterForeground. The difference is the former will get hit when apps takeover, such as Siri, and the latter will not.
You can implement this protocol in your audio manager class and set the volume at that point, like so:
- (void)applicationWillResignActive:(UIApplication *)application {
self.currentVolume = self.player.volume;
self.player.volume = 0;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
self.player.volume = self.currentVolume;
}
There may be no need to store the current volume, but designing on it to be used like that now will allow you to implement that in the future.
You should consider whether or not you want to mute the player or pause it. I'm sure you have, but a general better practice here would be to pause it rather that mute it, but that's not something that applies to every situation.
Additionally, you should know that some programmers are of the school of thought that only the AppDelegate should implement the UIApplicationDelegate protocol. There's some good arguments for it, and personally, I'm not really decided on what's best practice on that, but if you want to follow that, then you can either set up a protocol to delegate these in your AppDelegate and have your audio manager implement that delegate or you can use NSNotificationCenter to broadcast the event to any listeners - so, your audio manager in this case. Of the two, I would say using notifications is a cleaner way to handle it (delegating to delegates is a bit silly to me), but they also can get messy if you're not careful.
Here's the code I added to the viewDidLoad to call the method on the app pausing:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(muteMusic)
name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(activateMusic)
name:UIApplicationDidBecomeActiveNotification object:nil];
Then, of course, my two methods:
- (void) muteMusic {
[player pause];
}
- (void) activateMusic {
[player play];
}
Enjoy!
Related
I do not have a problem a stream, but I do not know when it is buffering or when the stream has ended. Is there anyway to determine this in Objective-C. I have found solutions for audio and I even tried the AVPlayerItemDidPlayToEndTimeNotification but it does not work. Any suggestions?
NSString *url = liveStream.stream[#"cdn"];
dispatch_async(dispatch_get_main_queue(), ^{
AVPlayerItem *playerItem = [[AVPlayerItem alloc]
initWithURL:[NSURL URLWithString:url]];
[_player replaceCurrentItemWithPlayerItem:playerItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
[_player play];
});
}
-(void)itemDidFinishPlaying:(NSNotification *) notification {
}
In addition to the notification you're using, you should use KVO to observe the rate of the avPlayer, as well as the status of the avplayer's current AVPlayer item. From observing those properties, you can make a state machine of sorts where your player view controller knows what's going on, and can recover from various changes instate. There are various player states that you have to prepare for and recover from based on the properties that you observe.
Here's an answer on how to check for buffering
Here's an example of a complete AVPLayer
And of course, here's apple's documentation on AVPlayer
Here's the documentation on AVPlayerItem
Lastly, here's the link on KVOController. You'll thank me for this later.
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 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'm playing an audio file in the background when the app is started. The function is called in viewDidLoad.
-(void)playsong
{
NSString *songUrl = [[NSBundle mainBundle] pathForResource:#"chant" ofType:#"m4a"];
NSData *songFile = [NSData dataWithContentsOfFile:songUrl];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithData:songFile error:&error ];
[audioPlayer play];
}
When the user presses the home button, and the song's current position is 10 secs, the song will stop playing.
But when the user opens the app again, the song starts from the same position.
I'd like it to be started from the beginning every time the app is opened.
Plus for memory reasons wouldn't it be better to deallocate the memory?
I tried to call the function from
- (void)viewWillAppear:(BOOL)animated
and then set it to nil in
- (void)viewWillDisappear:(BOOL)animated
but the method - (void)viewWillDisappear:(BOOL)animated isn't getting called.
If viewWillDisappear isn't working for you, try adding an observer in NSNotification center, which calls a method when the application didEnterBackground. Like so:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(enteredBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
Then add your code into this method:
-(void)enteredBackground:(NSNotification *)notification {
}
Hope this helps!
UPDATE
Okay, so from where you want to acces the audio object in the about us view, try this:
HomeViewController *HomeVC = [HomeViewController alloc] init];
HomeVC.audioPlayer...//then do what you want with it
As long as you have declared the audio object in the home VC then this should work.
Make sure you have imported the homeview controller in one of the about us view controller, files as well.
I'm uisng this code to display a movie:
MPMoviePlayerViewController *mp = [[MPMoviePlayerViewController alloc]
initWithContentURL:movieURL];
mp.moviePlayer.movieSourceType = MPMovieSourceTypeUnknown;
[self presentMoviePlayerViewControllerAnimated:mp]; [mp.moviePlayer play];
The code is working fine. However when the application goes to the background while playing a movie, when the app comes back in the foreground the movieplayer is not displayed. (I see the view of the controller that called presentMoviePlayerViewControllerAnimated:mp
Is it possible when entering the foregound to resume the movie that was playing before the app went to the background?
Have you set the UIBackgroundmode to audio and also there has been problem with playing the video after app enters foreground .Refer this Tutorial on MPMoviePlayerViewController Also you can try using MPMoviePlayerViewController which has options for implementing various notifications .
you can implement notification techniques to handle it. Add a notification in the class where movie player is playing and associate with it a selector. When app goes to background then in the delegate method
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask = 0;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
}
write this code.Actually when app goes background it pauses the MPMoviePlayerController so when it is coming to foreground you post the notification which call the method in class where movie controller is implemented and play it again in this method.
-(void)playIntroAnimationAgain
{
[[NSNotificationCenter defaultCenter]removeObserver:self name:NOTIFICATION_PlayAgain_Player object:nil];
[self.moviePlayerController play];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playIntroAnimationAgain)name:NOTIFICATION_PlayAgain_Player object:nil];
}
It solved my problem.