MediaPlayer or AVFoundation stop working eventually - iphone

I have a strange problem with MediaPlayer or AVFoundation. At some random point in the app, it stops working. With MPMoviePlayerViewController for instance it is automatically dismissed without playing the movie; with [AVURLAsset URLAssetWithURL:movieURL options:nil];
it returns nil.
The thing is that when I close the app, also from the OS background, and restart it, it will start working again.
My question is if why does it happen, or if it is a way of reset something so it could start working again. Thanks.

Hard to tell without seeing any code, but, if [AVURLAsset URLAssetWithURL:movieURL options:nil]; is returning nil, then my guess would be that movieURL is no longer valid.
How are you creating movieURL? How are you releasing it?
My suggestion is to set a breakpoint where you call [AVURLAsset URLAssetWithURL:movieURL options:nil]; and check the value for movieURL. I am willing to be that it will be nil when the movie fails to load.

Related

AVPlayerItem fails with AVStatusFailed and error code "Cannot Decode"

I'm running into a strange issue, I hope someone can help.
In my iOS app I create a video with a custom soundtrack using MutableComposition by combining a video from the user's photo library and an audio file from the app bundle. I then use an AVPlayer and AVPlayerItem to play the video back to the user using a custom video player I made.
Each time a new composition is created, the assets, the player and the composition are cleared, released and it basically starts from a clean, init state.
All works fine, until after exactly 4 successful videos created this way every other attempt to create the player fails with error Cannot Decode. It does not matter if its the same video I'm recreating, has no relation to the size/length of the video or the audio file it simply always fails exactly on the fifth attempt, like clockwork. Once it fails, it will then always fail!
This is weird, because it just decoded the same video four times with no problem, so all of a sudden it fails? So, if anyone has a clue, please let me know.
Ok everyone, I have the answer to this straight from Apple. I used one of my developer TSI lifelines to ask the question, and I'll summarize the response.
There is a limit on the number of concurrent video players that AVFoundation will allow. It is due to the limitations of iOS hardware. The limit for current devices is 4 players. If you create a 5th player, you will get the "cannot decode" error. It is not a limit on the number of instances of AVPlayer, or AVPlayerItem. Rather,it is the association of AVPlayerItem with an AVPlayer which creates a "render pipeline", and you are limited to 4 of these. For example, this causes a new render pipeline:
AVPlayer *player = [AVPlayer playerWithPlayerItem:somePlayerItem];
// assuming the AVPlayerItem is ready to go with an AVAsset that has been loaded
I was also warned that you cannot assume that you will have 4 pipelines available to you. Another App may be using one or more. Indeed, I have seen this happen on an iPad, but it was not clear which app was using a pipeline.
So, there you go, it was totally undocumented, but that is the story.
I ran into the same error message after creating 4 AVPlayer instances, the fix in my case wasn't exactly the same though. Perhaps this will help anyone else who comes across this problem.
What I eventually found is that the AVPlayers were not being released when I had thought they were. In my case I was pushing my AVPlayer View Controller onto a Navigation Controller. Even though I was only creating one AVPlayer instance at a time, when the View Controllers are popped off a nav controller they were not being released immediately. It was then very easy for me to reach 4 AVPlayer instances before the old View Controllers were cleaned up.
It wasn't until I made sure that the previous players were released that this problem went away. To be complete I released the AVPlayerItem, AVPlayer and set the player on the AVPlayerLayer to nil before releasing.
I have to wonder if there is some limit on AVPlayer instances, unintentional or not. A related bit of info from the docs:
https://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html
"Multiple player layers: You can create arbitrarily many AVPlayerLayer objects from a single AVPlayer instance, but only the most-recently-created such layer will display any video content on-screen."
This one was absolutely killing me until I figured it out, picking up clues from this thread and a few others. The biggest single problem in my code was that I was instantiating my video player controller every time I wanted to play a video. Now, it gets instantiated once in the primary controller (in this case, my DetailViewContoller):
#interface DetailViewController () {
VideoPlayerViewController *videoPlayerViewController;
}
- (void) viewDidLoad
{
[super viewDidLoad];
videoPlayerViewController = [[VideoPlayerViewController alloc] initWithNibName: nil bundle: nil];
}
When I want to show a video, I call my DetailViewController's startVideoPlayback method:
- (void) startVideoPlayback: (NSString *)videoUID
{
videoPlayerViewController.videoUID = videoUID;
[self presentModalViewController: videoPlayerViewController animated: YES];
}
(NOTE: I'm passing it 'videoUID' -- a unique identified that was used to create the video in another part of the app.)
In the VideoPlayerViewController (which is largely cribbed from Apple's AVPlayerDemo sample), the one-time screen setup (initializing the AVPlayer, setting up the toolbar, etc.) is done in viewDidLoad -- which now only get's called once, and all per-video setup gets done within viewWillAppear, which then calls prepareToPlay:
- (void) prepareToPlay
{
[self initScrubberTimer];
[self syncPlayPauseButtons];
[self syncScrubber];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//*** Retrieve and play video at associated with this videoUID
NSString *destinationPath = [documentsDirectory stringByAppendingFormat: #"/%#.mov", videoUID];
if ([self fileExists: destinationPath]) {
//*** Show the activity indicator spinny thing
[pleaseWait startAnimating];
[self setURL: [NSURL fileURLWithPath: destinationPath]];
//*** Get things going with the first video in this session
if (isFirst) {
isFirst = NO;
//*** Subseqeunt videos replace the first one
} else {
[self.mPlayer replaceCurrentItemWithPlayerItem: [AVPlayerItem playerItemWithURL: [NSURL fileURLWithPath: destinationPath]]];
}
}
}
OK, I figured out a solution, I hope this is helpful to anyone who may stumble on something similar to this problem.
The solution in my case was to initialize the asset for the AVPlayer and the AVPlayerItem on the main thread and make sure I don't create the actual AVPlayerLayer before the playerItem and the player objects return with status "ReadyToPlay".
This proved to be tricky to isolate and I still don't know why it worked the first 4 times and then failed consistently on the 5th time.
Till, I couldn't really include the code, it wasn't a matter of one line or even a few functions. It was a complex problem that I couldn't isolate to begin with. Thanks for the comments though.
It seems like that issue can be caused by any decoding tasks, not only actual players.
I randomly had this problem when I implemented a background task to extract frames from currently playing videos with generateCGImagesAsynchronously
I need to display 4 videos on screen and a race condition would sometime cause the frame extraction to start before the video started playing and I would wait for isReadyForDisplay forever.
Not sure what a good recover strategy is if you can't avoid the condition in the first place, I would probably try to replaceCurrentItem

Audio will play in iPhone simulator, but not on device

Basically, when a button on my app is touched, audio is played. Simple enough, and yet it fails to work on my actual device, but works well in the simulator. When the button is touched, this function is executed:
- (IBAction)playButtonSound
{
SystemSoundID buttonSound;
NSURL *tapSound = [[NSBundle mainBundle] URLForResource: #"Button_Sound" withExtension: #"caf"];
CFURLRef buttonSoundURLRef = (CFURLRef) [tapSound retain];
AudioServicesCreateSystemSoundID (buttonSoundURLRef, &buttonSound);
AudioServicesPlaySystemSound(buttonSound);
[tapSound release];
}
I'm still an iPhone newbie, so I'm not entirely sure what the cause of this is.
Works on iPhone Simulator but not on device
Make sure you write the correct file names, iOS is case sensitive, simulator is not.
Do you have system sounds disabled on your device? As its name implies, AudioServicesPlaySystemSound will normally not play if the user has system sounds disabled. There is a property kAudioServicesPropertyIsUISound that is supposed to be able to be set to NO to disable this behavior.

iPhone SDK: AVAudioRecorder will not record after calling [AVPlayer play]

I have a view controller that uses AVPlayer. this view controller can load a modal view controler where the user can record audio using AVAudioRecorder.
this is what happens:
if the user plays the composition in the fist controller with [AVPLayer play] the AVAudioRecorder will not record in the modal view controller. there are no errors but the current time returned by AVAudioRecorder is 0.0;
if the user dismisses the modal dialog and reloads it. AVAudioRecorder works fine.
this can be repeated over and over
AVAudioRecorder does not work the first time it is invoked after a [AVPlayer play] call
I have been fighting with this for days and just have reorganized my code related to both AVPlayer and AVAudioRecorder and it's still acting weird.
Any help or pointer is much appreciated
Thanks in advance
Jean-Pierre
I've been having the same problem too, and this is the solution I found.
When recording, write this line code after AVAudioRecord is initialized:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:NULL];
Then invoke record method.
How are you setting the AVAudioSession's category, i.e., play or record? Try something like
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
I've been having the same problem. I use AVPlayer to play compositions (previous recordings I've used AVAudioRecord for). However, I found that once I've used AVPlayer I could no longer use AVAudioRecorder. After some searching, I discovered that so long as AVPlayer is instantiated in memory and has been played at least once (which is usually what you do immediately after instantiating it) AVAudioRecorder will not record. However, once AVPlayer is dealloc'd, AVAudioRecorder is then free to record again. It appears that AVPlayer holds on to some kind of connection that AVAudioRecorder needs, and it's greedy...it won't let it go until you pry it from it's cold dead hands.
This is the solution I've found. Some people claim that instantiating AVPlayer takes too much time to keep breaking down and setting back up. However, this is not true. Instantiating AVPlayer is actually quite trivial. So also is instantiating AVPlayerItem. What isn't trivial is loading up AVAsset (or any of it's subclasses). You really only want to do that once. They key is to use this sequence:
Load up AVAsset (for example, if you're loading from a file, use AVURLAsset directly or add it to a AVMutableComposition and use that) and keep a reference to it. Don't let it go until you're done with it. Loading it is what takes all the time.
Once you're ready to play: instantiate AVPlayerItem with your asset, then AVPlayer with the AVPlayerItem and play it. Don't keep a reference to AVPlayerItem, AVPlayer will keep a reference to it and you can't reuse it with another player anyway.
Once it's done playing, immediately destroy AVPlayer...release it, set its var to nil, whatever you need to do. **
Now you can record. AVPlayer doesn't exist, so AVAudioRecorder is free to do its thing.
When you're ready to play again, re-instantiate AVPlayerItem with the asset you've already loaded & AVPlayer. Again, this is trivial. The asset has already been loaded so there shouldn't be a delay.
** Note that destroying AVPlayer may take more than just releasing it and setting its var to nil. Most likely, you've also added a periodic time observer to keep track of the play progress. When you do this, you receive back an opaque object you're supposed to hold on to. If you don't remove this item from the player AND release it/set it to nil, AVPlayer will not dealloc. It appears that Apple creates an intentional retain cycle you must break manually. So before you destroy AVPlayer you need to (example):
[_player removeTimeObserver:_playerObserver];
[_playerObserver release]; //Only if you're not using ARC
_playerObserver = nil;
As a side note, you may also have set up NSNotifications (I use one to determine when the player has completed playing)...don't forget to remove those as well.
If you need use AVPlayer and AVAudioRecorder at the same time do following:
set audio category to "play and record" (as described above or with C-based Audio Session Function)
"record" method must be invoked after invocation of "play" method with some delay. (I set 0.5 sec)
If you don't provide this, playback will start, but recording will not start.
shouldn't it be [AVAudioRecorder record]; instead of play?
I've had the same issue: AVAudioRecorder did not start recording.
My example is a bit different: I have a tabbar based app with several AVAudioPlayers in the different views. For the sake of the example, lets say that the first loaded view has 3 AVAudioPlayers, while the second view has one AVAudioPlayer and one AVAudioRecorder. They are all initiated in the viewDidLoad method.
The recorder was not working in second view. When I hit my record button, the view slightly shaked and the recording did not begin.
Strangely, the simulator worked fine and did not show the same symptoms... (anyone has an idea why?)
After some research and reading this thread I have realized that I have not set the players in the first view to nil, so I guess they were still in the memory preventing the recorder to start. Once I have set them to nil in the viewDidDisappear method, it was working alright again.
here is what helped me:
-(void)viewDidDisappear:(BOOL)animated {
firstPlayer = nil;
secondPlayer = nil;
thirdPlayer = nil;
}
ViewDidUnload did not help because the View, and therefore the variablbes in the class itself were not unloaded when switching views.
Hope this may be useful for others as well.
#Aaron Hayman explanation is accurate but the solution for me was very simple. Just implement the didFinishPlaying method of the player delegate and release the player there.
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
self.playerWord = nil;
}

MPMoviePlayer crashes in device but works fine in simulator

In my app i am playing a video using the following code.
NSURL *myURL = [[NSURL alloc] initWithString:downloadURL];
mMoviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:myURL];
if (mMoviePlayer) {
[self initMoviePlayer];
[mMoviePlayer play];
}
This code is working fine in simulator, but when i test it in device it is giving "BAD_ACCESS" error. Device is not at all sending the request to video.
Can some one help me with this..
Thanks...
There's nothing wrong with this code. The bug is somewhere else.
BAD_ACCESS generally points to memory management issues: you're trying to access an object that has been deallocated. If you're sure that the crash is happening on the above line, double check that safeURL exists at that point. If it does, you're going to have to post more code for us to give you any pointers.

AVAudioPlayer not responding

I initialize an AVAudioPlayer instance:
NSString* path = [[NSBundle mainBundle] pathForResource:#"foo" ofType:#"wav"];
AVAudioPlayer* player =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil]; //Returned object not nil, indicating that the file has loaded successfully
BOOL b = [player prepareToPlay]; //returns TRUE
BOOL b2 = [player play];
//returns TRUE, goes immediately to the next line since asynchronous
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]; //wait 1 sec
printf("%10.4f\n",player.duration); //duration is 2s
printf("%10.4f\n",player.currentTime); //position remains 0
/* There is no sound output, player is also stuck since position==0 */
Does anyone know why the AVAudioPlayer is not responding? Is there something that I am overlooking while initializing, playing the audioplayer? Boilerplate code maybe?
I am running this on the iPhone simulator. Also, AudioServicesPlaySystemSound() works for the same file, which is puzzling, but this indicates that sound playback might work with AVAudioPlayer as well.
I have 2 quick suggestions:
Check to see if that path actually gives you data. Alloc a NSData object using [[NSData alloc] initWithContentsOfFile:path] and inspect it in the debugger to see if you're actually loading that wav file.
I wrote a sample project that uses AVAudioPlayer here: AVAudioPlayer Example. As the code is pretty much the same as yours, the only thing I can imagine is that there is a problem with your data.
Check those out and see if it gets you anywhere!
I have just had a similar problem. I am not sure if it is the same solution, but my application records audio through the iphone and then plays it back to the user.
I thought I was doing the correct thing by telling my audio session that it was going to record data, so I did the following:
[audioSession setCategory:AVAudioSessionCategoryRecord error:nil];
Using this setting playback worked on the Simulator but not on the device.... the didFinishPlaying event never got raised.
I removed this line after realising I didn't need it an audio playback started working correctly. Now I just need to work out why it is so quiet :)
Paul