Detroy AVPlayer Completely - ios

I used AVPlayer to play youtube video in iOS, i created VideoViewController to manage play video.
When VideoViewController pop out NavigationController, i implement
- (void)viewDidDisappear:(BOOL)animated {
[self.player removeObserver:self forKeyPath:kCurrentItemKey context:MyStreamingMovieViewControllerCurrentItemObservationContext];
[self.player removeObserver:self forKeyPath:kRateKey context:MyStreamingMovieViewControllerRateObservationContext];
[self removePlayerTimeObserver];
self.playerLayerView = nil;
self.playerItem = nil;
self.player = nil;
}
but NetworkAcitivtyIndicator still loading in Status Bar, i want stop it. How to fix it?

Did you manage activity indicator by yourself? If so, insert this line in your pop method: [UIApplication sharedApplication].networkActivityIndicatorVisible = NO
Or try to stop player before you set it's reference to nil.

With ARC, objects with a nil value are released at the end of the block or scope in which they are nulled. Although AVPlayer is an object generated by a class cluster (i.e., many different objects), you don't have to release all of them individually,
just the player.
So, this single line will release the player and everything else underneath, including the asset and/or player item:
[self.player replaceCurrentItemWithPlayerItem:nil];
If, for some strange reason, you cannot wait until the end of the method to release the player (perhaps it is preceded by a long-running iterator or is retained by a C-inline block variable), encompass the line in an #autorelease pool:
#autorelease {
[self.player replaceCurrentItemWithPlayerItem:nil];
}

Related

Pause other instances of AVPlayer when playButton is pushed

I have Table View with cells loading video objects from Parse.
When the video is loaded a playButton appears over a still image representation of the video. The play button is an outlet to a storyboard UIButton.
In my FeedCell.m:
- (IBAction)playButtonTapped:(id)sender {
[self.player play];
if (self.player.rate != 0 && (self.player.error == nil)) {
// player is playing
self.playButton.hidden = YES;
}
self.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector: #selector (playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.player currentItem]];
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
self.playButton.hidden = NO;
[self.playerItem seekToTime:kCMTimeZero];
[self.player pause];
}
I would like to add to the functionality of this button so that when the playButton is tapped the AVPlayerItem will play and all other instances of AVPlayers will be paused.
Am I able to do this by setting up another NSNotification?
NSNotification is not the answer because it sends a broadcast message to all the receiver added as observer,in this case each cell containing the AVPlayers.
But there's a problem: as soon as you scroll the UITableView down, some UITableViewCells go out of screen and they won't receive any NSNotification even if they are added as observers.
So you could add all your AVPlayers to an NSMutableArray and when you want to stop all videos you could cycle the array and manually call the stop method on them.
Then remove them from the array.
Keep in mind that adding AVPlayers to an array cause their reference count to never reach 0 so they are not deallocated while the array is alive.
So put the NSMutableArray on the same controller which contains the UITableView so when it's deallocated,the array is also deallocated and then all AVPlayers are deallocated

AVPlayer playerWithPlayerItem deadlock

I'm using Apple's AVPlayerDemo in order to playback video files (the videos are .mp4 coming from my Amazon S3 server).
The player is a subview of a custom table view cell, and the user can scroll the table to switch between videos (an players). There is no simultaneous playback, the player is deallocated on the 'didEndDisplayingCell' method.
The problem is that there is a deadlock after several scrolls (the number of scrolls before the deadlock varies, but eventually it always happens).
the deadlock is on this line (running on the main thread):
[self setPlayer:[AVPlayer playerWithPlayerItem:self.playerItem]];
It seems that playerWithPlayerItem is on mutexwait, waiting for a background thread.
Logging shows that the dealloc of the former AVPlayerDemoPlaybackViewController is called before the deadlock. here is the dealloc code:
if (self.player) {
[self.player removeObserver:self forKeyPath:#"currentItem"];
[self.player.currentItem removeObserver:self forKeyPath:#"status"];
[self.player pause];
[self.playbackView.layer removeFromSuperlayer];
[self.playbackView removeFromSuperview];
self.player = nil;
}
The view controller's player property is nil before it is assigned by the dead-locked line.
The dead-locked line is called from this block:
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:mURL options:nil];
NSArray *requestedKeys = #[#"playable"];
/* Tells the asset to load the values of any of the specified keys that are not already loaded. */
[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
^{
dispatch_async( dispatch_get_main_queue(),
^{
[self prepareToPlayAsset:asset withKeys:requestedKeys];
});
}];
Here is the threads state at the time of the deadlock:
Please advise - thanks!!

How to call method/code when app is paused in iOS

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!

iOS pause between songs to exec code then resume, when app is in background

I am building an app which needs to play a track list, but between each song the music should pause to execute some code, then once complete the music should resume. This needs to work when the app is in the background as well as in the foreground.
I have tried a couple of methods but none seem to be able to do everything I want.
AVQueuePlayer - I can't seem to identify when any one song has stopped, only when the whole queue has stopped.
AVPlayer - I can identify when the track has ended with a notification, then I can run my extra code then load the next track. This works fine as long as the app is not in the background, when the app is in the background the code executes fine except the [avPlayer play] command does not work. It does not throw an error, it simply does not play. I know it has moved to the next song and loaded it into AVPlayer as I output the meta data and it has moved on.
Just to be clear the initial track does run in the background, it is only starting the next track which does not run in the background.
Code below...
Any idea what I am doing wrong?
Thanks!
+(void) playItem {
//get the play item from the song array based on intSongIndex
MPMediaItem *currentSong = [songsNowPlaying objectAtIndex:intSongIndex];
AVPlayerItem * currentItem = [AVPlayerItem playerItemWithURL:[currentSong valueForProperty:MPMediaItemPropertyAssetURL]];
[avPlayer replaceCurrentItemWithPlayerItem:currentItem];
//add notification to the currentItem
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:currentItem];
//play
[avPlayer play];
NSArray *metadataList = [[avPlayer currentItem].asset commonMetadata];
for (AVMetadataItem *metaItem in metadataList) {
NSLog(#"%#: %#",[metaItem commonKey], [metaItem value]);
}
//increment song index so next time the next song is selected
intSongIndex ++;
if (intSongIndex >= songsNowPlaying.count) {
intSongIndex = 0;
}
}
+ (void)playerItemDidReachEnd:(NSNotification *)notification {
//add code to be executed before the next song plays
//call playItem to play the next song
[self playItem];
}
Solved, this needed adding to the initial viewDidLoad
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

How do you release an MPMoviePlayerViewController after playback finishes?

I am using an MPMoviePlayerViewController to play a video in iOS. When the video stops or crashes, I would like to close this view controller and release it. To simulate the crash, I've passed in a nonexistent file path:
MPMoviePlayerViewController* p = [
[MPMoviePlayerViewController alloc] init
];
self.player = p;
[p release];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(moviePlayBackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:self.player.moviePlayer
];
self.player.moviePlayer.contentURL = #"purposelyFake.mp4";
This will immediately trigger the following callback.
- (void) moviePlayBackDidFinish
{
if (self.player != nil) {
[self.player.moviePlayer stop];
self.player = nil
}
}
self.player is a nonatomic retained property, so setting it to nil should release it. This clears the memory, but my log shows this warning:
An instance 0x127b20 of class AVPlayerItem was deallocated while key
value observers were still registered with it. Observation info was
leaked, and may even become mistakenly attached to some other object.
Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger.
The reason is because moviePlayBackDidFinish is called twice when the video crashes. Once to say MPMovieFinishReasonPlaybackEnded and again to say MPMovieFinishReasonPlaybackError. The MPMoviePlayerViewController becomes deallocated while the second notification is still being sent. My question is: how do I get rid of this warning? Keep in mind that in normal playback finishing, only MPMovieFinishReasonPlaybackEnded is issued, so I can't just hardcode it to only release whenever I receive two notifications in a row.
In your callback method try removing yourself as an observer of the notification before you release it.
[[NSNotificationCenter defaultCenter] removeObserver: self name: MPMoviePlayerPlaybackDidFinishNotification object: myMPMoviePlayerController];

Resources