AVPlayerItem fails with AVStatusFailed and error code "Cannot Decode"

21,699

Solution 1

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.

Solution 2

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."

Solution 3

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]]];
        }
    }
}

Solution 4

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.

Solution 5

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

Share:
21,699
Admin
Author by

Admin

Updated on March 22, 2021

Comments

  • Admin
    Admin about 3 years

    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.

  • mostafa tourad
    mostafa tourad over 12 years
    I fully understand that. The common way to go is to boil it down to a minimal implementation and possibly find the issue then. Often enough the minimal implementation suddenly works which assures you that the problem lies elsewhere, in a corner you would never have suspected :D ... merry xmas!
  • Piotr
    Piotr over 10 years
    How exactly did you solved this issue? Did you create player with many items inside and switch between them? Are you creating new player and item before playing each video?
  • jaredsinclair
    jaredsinclair over 8 years
    For anyone interested, I've confirmed this issue on tvOS. However, in practice I am able to reach up to around 18 pipelines before the issue appears. The max count must be platform/hardware dependent. Further, when I reach the limit and playback fails, there are no errors: no logs from AVKit, nor do any of the players emit an error (the error property is nil and the status is good).
  • Josh Bernfeld
    Josh Bernfeld almost 6 years
    Player items do emit an error but you must key value observe the player items status variable and when it changes to .error you can print the error variable associated with the player item.
  • Lance Samaria
    Lance Samaria over 3 years
    when I ran into this issue my app crashed from too many player due to memory issue stackoverflow.com/q/64485366/4833705. It turned out it wasn't too many players, it's just like @one09jason said, there were too many AVPlayerItems associated with the players stackoverflow.com/a/64485645/4833705