Note, for the below question: All assets are local on the device -- no network streaming is taking place. The videos contain audio tracks.
I'm working on an iOS application that requires playing video files with minimum delay to start the video clip in question. Unfortunately we do not know what specific video clip is next until we actually need to start it up. Specifically: When one video clip is playing, we will know what the next set of (roughly) 10 video clips are, but we don't know which one exactly, until it comes time to 'immediately' play the next clip.
What I've done to look at actual start delays is to call addBoundaryTimeObserverForTimes
on the video player, with a time period of one millisecond to see when the video actually started to play, and I take the difference of that time stamp with the first place in the code that indicates which asset to start playing.
From what I've seen thus-far, I have found that using the combination of AVAsset
loading, and then creating an AVPlayerItem
from that once it's ready, and then waiting for AVPlayerStatusReadyToPlay
before I call play, tends to take between 1 and 3 seconds to start the clip.
I've since switched to what I think is roughly equivalent: calling [AVPlayerItem playerItemWithURL:]
and waiting for AVPlayerItemStatusReadyToPlay
to play. Roughly same performance.
One thing I'm observing is that the first AVPlayer item load is slower than the rest. Seems one idea is to pre-flight the AVPlayer with a short / empty asset before trying to play the first video might be of good general practice. [Slow start for AVAudioPlayer the first time a sound is played
I'd love to get the video start times down as much as possible, and have some ideas of things to experiment with, but would like some guidance from anyone that might be able to help.
Update: idea 7, below, as-implemented yields switching times of around 500 ms. This is an improvement, but it it'd be nice to get this even faster.
Idea 1: Use N AVPlayers (won't work)
Using ~ 10 AVPPlayer
objects and start-and-pause all ~ 10 clips, and once we know which one we really need, switch to, and un-pause the correct AVPlayer
, and start all over again for the next cycle.
I don't think this works, because I've read there is roughly a limit of 4 active AVPlayer's
in iOS. There was someone asking about this on StackOverflow here, and found out about the 4 AVPlayer limit: fast-switching-between-videos-using-avfoundation
Idea 2: Use AVQueuePlayer (won't work)
I don't believe that shoving 10 AVPlayerItems
into an AVQueuePlayer
would pre-load them all for seamless start. AVQueuePlayer
is a queue, and I think it really only makes the next video in the queue ready for immediate playback. I don't know which one out of ~10 videos we do want to play back, until it's time to start that one. ios-avplayer-video-preloading
Idea 3: Load, Play, and retain AVPlayerItems
in background (not 100% sure yet -- but not looking good)
I'm looking at if there is any benefit to load and play the first second of each video clip in the background (suppress video and audio output), and keep a reference to each AVPlayerItem
, and when we know which item needs to be played for real, swap that one in, and swap the background AVPlayer with the active one. Rinse and Repeat.
The theory would be that recently played AVPlayer/AVPlayerItem
's may still hold some prepared resources which would make subsequent playback faster. So far, I have not seen benefits from this, but I might not have the AVPlayerLayer
setup correctly for the background. I doubt this will really improve things from what I've seen.
Idea 4: Use a different file format -- maybe one that is faster to load?
I'm currently using .m4v's (video-MPEG4) H.264 format. H.264 has a lot of different codec options, so it's possible that some options are faster to seek than others. I have found that using more advanced settings that make the file size smaller increase the seek time, but have not found any options that go the other way.
Idea 5: Combination of lossless video format + AVQueuePlayer
If there is a video format that is fast to load, but maybe where the file size is insane, one idea might be to pre-prepare the first 10 seconds of each video clip with a version that is bloated but faster to load, but back that up with an asset that is encoded in H.264. Use an AVQueuePlayer, and add the first 10 seconds in the uncompressed file format, and follow that up with one that is in H.264 which gets up to 10 seconds of prepare/preload time. So I'd get 'the best' of both worlds: fast start times, but also benefits from a more compact format.
Idea 6: Use a non-standard AVPlayer / write my own / use someone else's
Given my needs, maybe I can't use AVPlayer, but have to resort to AVAssetReader, and decode the first few seconds (possibly write raw file to disk), and when it comes to playback, make use of the raw format to play it back fast. Seems like a huge project to me, and if I go about it in a naive way, it's unclear / unlikely to even work better. Each decoded and uncompressed video frame is 2.25 MB. Naively speaking -- if we go with ~ 30 fps for the video, I'd end up with ~60 MB/s read-from-disk requirement, which is probably impossible / pushing it. Obviously we'd have to do some level of image compression (perhaps native openGL/es compression formats via PVRTC)... but that's kind crazy. Maybe there is a library out there that I can use?
Idea 7: Combine everything into a single movie asset, and seekToTime
One idea that might be easier than some of the above, is to combine everything into a single movie, and use seekToTime. The thing is that we'd be jumping all around the place. Essentially random access into the movie. I think this may actually work out okay: avplayer-movie-playing-lag-in-ios5
Which approach do you think would be best? So far, I've not made that much progress in terms of reducing the lag.
For iOS 10.x and greater to reduce AVPlayer start delay I set:
avplayer.automaticallyWaitsToMinimizeStalling = false;
and that seemed to fix it for me. This could have other consequences, but I haven't hit those yet.
I got the idea for it from: https://stackoverflow.com/a/50598525/9620547