iOS managing AVPlayer buffer prompt - ios

I use AVPlayer to play a video that is on the internet. So my question is how can I give proper prompt to the user when he needs to wait? (e.g. an animating UIActivityIndicatorView whenever he needs to wait for the player to load some more data or seek to a specific time)
I spinning the UIActivityIndicatorView whenever I set a new playerItem to my AVPlayer, then I observe status of the playerItem, when it becomes AVPlayerItemStatusReadyToPlay, I stop the spinning and start play. So whenever a new playerItem is set on my AVPlayer, a spinning will be there until it is ready to play.
I also want to give my user the spinning when he needs to wait for the playerItem to load more buffer when network connection is bad. I tried observing playbackBufferEmpty and playbackLikelyToKeepUp but did not work as I expected. I also messed with the rate property of my AVPlayer, still not work quite well.

Related

Best way to play silence using AVAudioPlayer on iOS

I found myself in a situation where I need to simulate audio playback to trick OS controls and MPNowPlayingInfoCenter into thinking that an audio is being played. This is because I am building a player that plays multiple audio tracks, with pauses in-between creating one, continuous "audio" track. I have already everything setup inside the app itself, and the lock screen controls are working correctly but the only problem I am facing is while the actual audio stops and a pause is being "played", the lock screen info center stops the timer, and it only continues with showing correct time and overall state once another audio track starts playing.
Here is the example of my audio track built from audio files and pause items:
let items: [AudioItem] = [
.audio("part-1.mp3"),
.pause(duration: 5), // value of type: TimeInterval
.audio("part-2.mp3"),
.pause(duration: 3),
... // the list goes on
]
then in my custom player, once AVAudioPlayer finishes its job with current item, I get the next one from the array and play either a .pause with a scheduled Timer or another .audio with AVAudioPlayer.
extension Player: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
playNextItem()
}
}
And here lies the problem, once the AVAudioPlayer stops, the Now Playing info center automatically stops too, even tho I keep feeding it fresh nowPlayingInfo. Then when it hits another .audio item, it resumes correctly and shows current time, etc.
And here lies the question
how do I trick the MPNowPlayingInfoCenter into thinking that audio is being played while I "play" my .pause item?
I realise that it may still not be clear, what I am trying to achieve but I am happy to share more insight if needed. Thanks!
Some solutions I am currently thinking about:
A. Keeping 1s long empty audio track that would play on loop for as long as the pause is needed to play.
B. Creating programatically empty audio track with appropriate lenght and playing it instead of using Timer for keeping track of pause duration/progress and relying completely on AVAudioPlayer for both .audio and .pause items. Not sure this is possible though.
C. Maybe there is a way to tell the MPNowPlayingInfoCenter that the audio keeps playing without the need of using AVAudioPlayer but some API I am not familiar with?
AVAudioPlayer is probably the wrong tool here. You want AVAudioPlayerNode, which is slightly lower-level. Create an AVAudioEngine, and attach an AVAudioPlayerNode. You can then call scheduleFile(_:at:completionHandler:) to play the audio at the times you want.
Much of the Apple documentation on AVAudioEngine appears broken right this moment, but the links hopefully will be available again shortly in the links for Audio Engine Building Blocks. (If it stays down and you have trouble finding docs, leave a comment and I'll hunt down the WWDC videos and other tutorials on using AVAudioEngine. It's not particularly difficult for simple problems.)
If you know in advance how you want to compose these items (and it looks like you may), see also AVMutableComposition, which lets you glue together assets very efficiently, including adding empty segments of silence. See Media Composition and Editing for the various tools in that space.

AVPlayerItem doesn't play video even its status is `readyToPlay` and `isPlaybackLikelyToKeepUp` is true

I'm playing live video with AVPlayer and AVPlayerItem. I'm observing AVPlayerItem's status, isPlaybackLikelyToKeepUp, and AVPlayerItemPlaybackStalled. I'm also observing AVPlayer's rate.
While I'm testing I found really weird thing happened.
While AVPlayer plays video normally(AVPlayerItem.status == readyToPlay and AVPlayerItem.isPlaybackLikelyToKeepUp == true), if I press home button, the AVPlayer.rate becomes 0.0. It means player stopped playing.
When app got back to foreground and I press play button, it is still AVPlayerItem.status == readyToPlay and AVPlayerItem.isPlaybackLikelyToKeepUp == true but paused and after a while it starts to play.
What I want is to know the player is paused or playing. In this case, even player is not playing video, the status indicates it is playing. (AVPlayerItem.status == readyToPlay, AVPlayerItem.isPlaybackLikelyToKeepUp == true, AVPlayer.rate == 1.0)
I checked AVPlayerItem's loadedTimeRanges and item.loadedTimeRanges.first is not nil(loadedTimeRanges has only one item) and its start and duration are not 0.
What should I check?
I'm adding little more.
This symptom only happens with streaming(m3u8) not with vod(mp4).
So I guess it is a problem of buffered data. While player is playing, buffer has data to play and the data is still valid. After app goes background and back to foreground, player's buffer still has data so isPlaybackLikelyToKeepUp == true, AVPlayerItem.status == readyToPlay. However when I try to play, AVPlayer flushes buffer because it is old.
It looks like the only way so far is create new AVPlayerItem and set when app is back to foreground.
What is the exact problem I'm encountering and want to know how to avoid it.
This is the repo I'm working on.
https://github.com/trick14/SLPlayer
The upper one is live and bottom one is VOD. After loading is done, play and go background and foreground couple times. And if you try to play, all status looks normal but player is stalled.
I have a similar experience long time ago.
At that time, I've stored the status of playing and the playback location when user press home button.
When return to the foreground, I used the stored information to restore or play the location.
I hope you were helpful.

How to stop downloading a streaming HLS video after navigating away from AVPlayer view?

I am trying to play a HLS stream using AVPlayer. The player plays the stream fine, however, after navigating away from the player view, it does not seem to stop downloading data for the HLS stream. I see a network data spinner on the status bar for a few minutes after navigating away from the AVPlayer view.
I have tried cancelPendingSeeks, cancelLoading, and tried removing the AVPlayerLayer from its superlayer using removeFromSuperLayer, however, none of these seems to solve the issue — there is still a spinner on the status bar.
This spinner is seen on actual device, but there is no spinner on the simulator. I am certain the video is being downloaded; I can see the data usage in network monitoring apps. How can I fix this?
Call pause on your AVPlayer object, then replace the item with a nil item, and set your video player to nil.
[anAVPlayerObject pause];
[anAVPlayerObject replaceCurrentItemWithPlayerItem:nil];
anAVPlayerObject = nil;

AVPlayer seekToTime never completes

I am using AVPlayer to stream video remotely. Whenever I scrub and use seekToTime it works, and the video plays from the location, however if I do this enough times sequentially then the completion handler of seekToTime never returns with "finished == YES" and is stuck.
Also, when profiling the connections it shows that the buffer never loads again even if seekToTime is called again after that. I can drag the scrubber to the beginning and playback will resume however it is not buffering anymore so it will get stuck in a "buffering" state once the buffer runs out but no buffer will be downloading.
Any ideas what might be wrong?
I had this problem for a long time, but also noticed that even if the seek never completed, I would quickly get a current playback position update (via the block passed to addPeriodicTimeObserverForInterval:queue:usingBlock:) that matched the target seek.
It appears, though, that I was doing some things in the wrong order when initializing the player with an item. I would set an item, and when the player's status became AVPlayerStatusReadyToPlay I would begin the seek, and it would never complete. But if I don't begin the seek until the current player item status is AVPlayerItemStatusReadyToPlay, everything works great.
The problem was with the content distribution network. The streaming url was expiring and then would no longer stream to the player

iOS: AVQueuePlayer does not indicate that it has stopped playback

I have an AVQueuePlayer which loads URLs to play audio files and it works well for the most part. However, I have run into a problem where after the player finishes playing a file (with another file in the queue), it will simply stop playing. Normally, the app would be able to use the player's rate, status, and items. In this case, I have gone through with the debugger and everything looks normal.
Everything appears to be playing, except for the player itself. After forcing the player to play, the player will skip to the next track, indicating that the AVPlayerItem it had was not loaded (I can confirm the audio urls are valid).
Does anyone have any ideas how I catch this programatically?
You need to have the delegate method:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
Once this method is finished, start the new audio.

Resources