HLS m3u8 Download takes a lot of time - ios

I am trying to download a HLS stream onto my iPhone using apple's documentation mentioned here - https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/HTTPLiveStreaming/HTTPLiveStreaming.html
Even small 2 minutes videos are taking about 3-4 mins to download, i am having a fast internet connection so that is not an issue.
What i want to know is there any specific stream i need to download or specify while downloading? The streams in my m3u8 are 320p, 720p, 1080p. Is it downloading all of them so it's taking that much of time?
This is how i am setting up code to download hls -
let urlConfiguration = URLSessionConfiguration.background(withIdentifier: downloadIdentifier)
// Create a new AVAssetDownloadURLSession with background configuration, delegate, and queue
let assetDownloadSession = AVAssetDownloadURLSession(configuration: urlConfiguration,
assetDownloadDelegate: self,
delegateQueue: OperationQueue.main)
let asset = AVURLAsset(url: URL(string: assetUrl)!)
// Create new AVAssetDownloadTask for the desired asset
let avAssetDownloadTask = assetDownloadSession?.makeAssetDownloadTask(asset: asset,
assetTitle: title,
assetArtworkData: nil,
options: nil)
// Start task and begin download
avAssetDownloadTask?.resume()

You might consider using a tool like Charles Proxy or Wireshark to determine the throughput of underlying network requests. You may have a slow server.

Related

An online mp3 AVPlayerItem that failed to play will never play again

I am having trouble playing online mp3 files as AVPlayerItems which have previously failed to play due to data connection issues.
For example:
I try play online mp3 file "FILE_A" with poor data connection and the associated AVPlayerItem fails to play. The AVPlayerItem will then not load and my AVPlayer will stay paused (which is fine for now).
Let's say that after a period of time I gain better data connection and play a different online mp3 file "FILE_B" as an AVPlayerItem. The AVPlayer plays this AVPlayerItem with no issues.
I then try play the failed file "FILE_A" on good data connection. The AVPlayerItem for "FILE_A" will always refuse to play. I can even close/kill the app and try again and "FILE_A" will not play. The only work around is to do a fresh re-install of the app.
I have 3 questions:
Why does a failed AVPlayerItem never become playable, is there a AVPlayerItem cache in the AVPlayer that always tries to play the failed item?
Is there a way to refresh an unplayable AVPlayerItem to make it play again?
What is the best way to deinitialize an AVPLayer? Currently I am using self.player = nil to deinitialize the player in the deint of my view controller or when a user logs out of their account in the app.
This is how I set up the AVPlayer:
self.player = AVPlayer()
self.player?.actionAtItemEnd = .pause
self.player?.automaticallyWaitsToMinimizeStalling = false
self.player?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
This is how I set up an AVPlayerItem and set it as the AVPlayer's currentItem:
let url = URL(string: onlineAudioFileUrl)
currentPlayerItem = AVPlayerItem(url: url)
player?.replaceCurrentItem(with: currentPlayerItem)
The onlineAudioFileUrl above is String url representing the url of an online mp3 file stored on a server. It has the form:
https://ourserver.com/audio_file_name.mp3?Expires=NUMERIC_EXPIRE_TIME&Key-Pair-Id=KEY_PAIR_ID_STRING&Signature=VERY_LONG_SIGNATURE_STRING

Free hosting video platform compatible with AVPlayer support

I am trying to play a video from my iOS app when tapping a button, but I was wondering how and where I can host my video (for free) so that I can play the video in an AVPlayer using the URL. I tried hosting the video on youtube, and setting the AVPlayer URL to the youtube video URL, but the player loads forever. I included my code, it's functional, I just need to know where I can upload my video to get a proper URL.
#IBAction func didTapPlayButton(_ sender: UIButton) {
guard let url = URL(string: "") else {
return
}
// Create an AVPlayer, passing it the HTTP Live Streaming URL.
let player = AVPlayer(url: url)
// Create a new AVPlayerViewController and pass it a reference to the player.
let controller = AVPlayerViewController()
controller.player = player
// Modally present the player and call the player's play() method when complete.
present(controller, animated: true) {
player.play()
}
}
You need to host and serve your video as a binary file.
You can use an object storage (e.g. Firebase Storage or AWS S3) or serve the file through a web API.
Just set the correct Content-Type (e.g. "video/mp4" to help the player recognise the binary file type)
You can then initiate your AVPlayer with the URL of the video.
Please note that Firebase Storage has a very generous free tier, it might be all you need.
For example:
You can take a look at this video file:
https://file-examples.com/wp-content/uploads/2017/04/file_example_MP4_480_1_5MG.mp4
This file is hosted as a binary and the Content-Type is set to video/mp4.
Try to initiate your AVPlayer with the above URL and see that it works.

AVAssetDownloadTask iOS13

Tried iOS13.0 and iOS13.1 and still not working, I tried both AVAggregateAssetDownloadTask and AVAssetDownloadURLSession but none of them working. Not any delegate was called to tell me error of finish, and I found downloaded cache was only 25Kb what was not the right size.
The error is:
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedDescription=The operation could not be completed, _NSURLErrorFailingURLSessionTaskErrorKey=BackgroundAVAssetDownloadTask <AFDCA3CC-FA49-488B-AB16-C74425345EE4>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundAVAssetDownloadTask <AFDCA3CC-FA49-488B-AB16-C74425345EE4>.<1>"
), NSLocalizedFailureReason=An unknown error occurred (-16654)}
Found out AVAssetDownloadURLSession can only download HLS with master playlist structure which contains codec attribute into EXT-X-STREAM-INF m3u8 meta on iOS 13+.
I have no idea if this is a bug or function restriction.
(m3u8 meta have no CODECS attribute can be played with AVFoundation, but can't be downloaded with AVAssetDownloadURLSession)
Anyway, the solution is:
If you have HLS master playlist:
add CODECS attribute into your #EXT-X-STREAM-INF in m3u8 meta.
e.g.
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=63701,CODECS="mp4a.40.34"
playlist.m3u8
If you haven't HLS master playlist yet:
You have to make a master playlist even if you're not supporting adaptive streaming.
The master playlist is the only m3u8 which can contain #EXT-X-STREAM-INF hence CODECS attribute.
So, I found out that the 'AVAssetDownloadTask' had some error in calling delegates in iOS 13 (13.1,13.2.13.3). Finally, in iOS 13.4.1, Apple has fixed this error and now delegates have called after setting delegate and starting the task. Below is what I used to start downloading the m3u8 file from the server and saving it as an Asset to play later offline.
func downloadVideo(_ url: URL) {
let configuration = URLSessionConfiguration.background(withIdentifier: currentFileName)
let downloadSession = AVAssetDownloadURLSession(configuration: configuration,
assetDownloadDelegate: self,
delegateQueue: OperationQueue.main)
// HLS Asset URL
let asset = AVURLAsset(url: url)
// Create new AVAssetDownloadTask for the desired asset
let downloadTask = downloadSession.makeAssetDownloadTask(asset: asset,
assetTitle: currentFileName,
assetArtworkData: nil,
options: nil)
// Start task and begin download
downloadTask?.resume()
}
I tried this on iOS 12 and iOS 13.4.1 and it is working as expected. Also, it was already on the Apple Developer Forums here. Hope this helps someone.

Cache videos in tableview

I want to cache videos that are displayed in a tableview. However, I am not sure what to cache. I'm using AVFoundation, in particular I'm using AVPlayer and creating AVPlayerItem's.
My question is: what do I cache? Is it the AVPlayer, AVPlayerItem, or the underlying asset of AVPlayerItem called the AVAsset?
Please give a code sample (or library) with answer. Thanks!
You need to have private var mediaCache = NSMutableDictionary() for caching videos.
How to cache:
let assetForCache = AVAsset(url: URL(string: cell.videoRef)!)
self.mediaCache.setObject(assetForCache, forKey: cacheKey as NSCopying)
How to use: Just check if object exists in cache for this row. If yes - use it, no - download it
Cache means download the video of each cell and try to use downloaded video for the second time . Either you can Temp location or Document directory.
Execute a download call to download the video from URL Video Link.
In your cellForRowat indexpath check whether video available in your local if its there play.or else stream for the first time , simultaneously place a download
call for that video in background .So that for second time you can use local video and play without internet .

Looping an AVPlayer video stream and refreshing the bitrate on each loop

I'm looping my streamed videos (not live stream) via .m3u8 playlist and each time the video restarts, it plays the video with the same bitrate adapting that occurs the first time you watch the video (bad quality -> good quality). Is there a way to refresh the stream quality each time the video loops so that the beginning gets replaced with the higher-rate bitrate seamlessly? Instead of just re-playing what was initially loaded?
Apple's AVPlayer attempts to load the first stream listed in the HLS playlist. So if you want the highest quality stream to be loaded first by default, you need to specify it as the first stream in the playlist file.
With that in mind, one way of achieving what you need to achieve is to have a different m3u8 file for each of your streams.
For example, if you have a three variant stream playlist, you would have three .m3u8 playlists.
Then in your view controller where you are using your AVPlayer, you need to keep a reference to the last observed bitrate and most recent bit rate:
var lastObservedBitrate: double = 0
var mostRecentBitrate: double = 0
You would then need to register a notification observer on your player with notification name: AVPlayerItemNewAccessLogEntryNotification
NSNotificationCenter.defaultCenter().addObserver(self, selector:#selector(MyViewController.accessEventLog(_:)), name: AVPlayerItemNewAccessLogEntryNotification, object: nil)
Whenever the access log is updated, you can then inspect the bitrate and stream used using the following code:
func accessLogEvent(notification: NSNotification) {
guard let item = notification.object as? AVPlayerItem,
accessLog = item.accessLog() else {
return
}
accessLog.events.forEach { lastEvent in
let bitrate = lastEvent.indicatedBitrate
lastObservedBitrate = lastEvent.observedBitrate
if let mostRecentBitrate = self.mostRecentBitrate where bitrate != mostRecentBitrate {
self.mostRecentBitrate = bitrate
}
}
}
Whenever your player loops, you can load the appropriate m3u8 file based on your lastObservedBitrate. So if your lastObservedBitrate is 2500 kbps, you would load your m3u8 file that has the 2500kbps stream at the top of the file.
Shameless plug: We've designed something similar in our video api. All you need to do is request the m3u8 file with your connection type: wifi or cellular and lastObservedBitrate and our API will vend you the best possible stream for that bitrate, but still have the ability to downgrade/upgrade the stream if network conditions change.
If you are interested in checking it out visit: https://api.storie.com or https://github.com/Storie/StorieCloudSDK

Resources