Why can't I play certain .m3u8 items with AVPlayer? - ios

I'm working on a project where I have an instance of AVPlayer capable of playing different audio content that I retrieve from a backend, from podcast to music and streamings. Every content has two types of urls: one with mp3 and another with a m3u8 file. All the mp3 files work good. However some m3u8 files work fine and others don't. In particular, those who don't work cause the AVPlayer to crash with the error:
Error Domain=AVFoundationErrorDomain Code=-11819 "Cannot Complete Action"
UserInfo={NSLocalizedRecoverySuggestion=Try again later.,
NSLocalizedDescription=Cannot Complete Action.}
I don't understand what the problem is. According to this answer it is a wrong Manifest file, which in my case is - for example - the following:
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8",GROUP-ID="2#48000-64000",NAME="AAC 64",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_80/index.m3u8",GROUP-ID="2#48000-80000",NAME="AAC 80",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_96/index.m3u8",GROUP-ID="2#48000-96000",NAME="AAC 96",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-STREAM-INF:BANDWIDTH=133336,CODECS="mp4a.40.2",AUDIO="2#48000-96000"
_96/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=100641,CODECS="mp4a.40.2",AUDIO="2#48000-64000"
_64/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=116989,CODECS="mp4a.40.2",AUDIO="2#48000-80000"
_80/index.m3u8
On the Apple forum, I found this answer which says iOS 14+ is on fault. Unfortunately I cannot test with an iOS 13 physical device.
Do you have any suggestion?
Tested on Xcode 13.1 with iPhone 7plus with iOS 15.0.2.

Finally I found a solution for this issue. What worked for me was this. I believe the problem was that my manifest files were structured like the following:
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8", GROUP-ID="1#48000-64000",NAME="Audio 64",DEFAULT=NO,AUTOSELECT=NO
In particular they had DEFAULT=NO,AUTOSELECT=NO. Therefore before calling replaceCurrentItem I now do the following:
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
for characteristic in asset.availableMediaCharacteristicsWithMediaSelectionOptions {
if let group = asset.mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristic.audible) {
if let option = group.options.first {
playerItem.select(option, in: group)
}
}
}
This makes all my HLS audio playable by the AVPlayer.

I dont see version in your .m3u8. Try adding #EXT-X-VERSION:03 into your playlist. AVPlayer does need to have version included in playlist (Android EXO player does not need it). Here is example of playlist that might work:
#EXTM3U
#EXT-X-VERSION:03
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8",GROUP-ID="2#48000-64000",NAME="AAC 64",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_80/index.m3u8",GROUP-ID="2#48000-80000",NAME="AAC 80",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_96/index.m3u8",GROUP-ID="2#48000-96000",NAME="AAC 96",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-STREAM-INF:BANDWIDTH=133336,CODECS="mp4a.40.2",AUDIO="2#48000-96000"
_96/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=100641,CODECS="mp4a.40.2",AUDIO="2#48000-64000"
_64/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=116989,CODECS="mp4a.40.2",AUDIO="2#48000-80000"
_80/index.m3u8

Related

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.

How can you obtain a current manifest from the playhead in AV player?

I am currently trying to capture the currently playing HLS/DASH manifest in the AV player.
Would anyone have any code snippets/documentation that could help with it?
To access the current url that the avplayer is streaming from:
if let currAsset = player.currentItem.asset as? AVURLAsset {
var url = currAsset.url
... use the url ...
}
I didn't find any official documentation about this either. I suppose then at the time of writing that AVPlayer does not provide the manifest.
Failing that, consider downloading the playlist and parse the manifest by your own.
I found M3U8Kit useful to parse m3u8 manifest.

Streaming audio with avplayer long buffering - playImmediatelyAtRate doesn't work

I'm streaming audio in my iOS swift app.
The main issue is that avplayer has to load all the file to start the playback.
Using playImmediatelyAtRate doesn't work because playbackBufferEmpty is always true until the file is completely downloaded which can be an issue on long audio files.
Any ideas?
Not really AVPlayer related answer but you could use VLCKit to handle the stream.
Here is a basic sample in Swift:
let mediaPlayer = VLCMediaPlayer()
// replace streamURL by the url of the stream
mediaPlayer.media = VLCMedia(url: streamURL)
// outputView is the view where you want to display the stream
mediaPlayer.drawable = outputView
mediaPlayer.play()
If you have any issue with VLCKit, feel free to ping me!
For iOS >10 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:
AVPlayer stops playing video after buffering

URLs of Media Bought on iTunes

I have created an application that gets all of the songs on a device, puts some of their information in a UITableView, then plays a song of a selected cell using an AVAudioPlayer. For some reason however, it seems as though three of the songs on my device do not have urls. These songs were more recently bought, perhaps in the last two months. See the code below.
//get all the songs
var songsList: [MPMediaItem] = MPMediaQuery.songsQuery().items!
if let url = song.valueForProperty(MPMediaItemPropertyAssetURL) as! NSURL? {
self.player = try! AVAudioPlayer(contentsOfURL: url)
let playing: Bool = player.play()
print("The audio play status: " + String(playing))
}
else {
print("Failed to cast to URL")
}
For some reason, these three songs fail the type cast from AnyObject? to NSURL? meaning that the url must not exist on the device? I do not understand. My only guess is that I bought them from iTunes on another Macintosh device and they were stored on there via some cloud transfer through my iTunes account. Any explanation would be a great help.
You can't get an asset URL if the track is
A) Not on your device -- at least not completely -- but available to download/stream on demand. This can arise by not being completely played; being purchased on other devices/via Family Sharing; being an Apple Match track; yadayadayada.
B) DRM-protected. That's getting pretty rare these days I thought, but it was a big thing back a few years. If the file has an .m4p extension, that is definitely the problem. There's reports that it arises with .m4a files as well, and showing up in iTunes as "Purchased AAC audio file" indicates that it figures you're not allowed to get at said audio.

Swift 2 - m4a file won't play on iOS

I'm trying to play an m4a file on an iOS device programatically. The file is downloaded from a server and stored. When the user presses a button, it is played. The file, when copied from the device to OSX does play fine. I am using the code
var player:AVAudioPlayer = AVAudioPlayer()
func playAudio(sender: UIButton!) {
let audioPath = FileUtils.getPath(audioPaths[sender.tag])
print("PATH " + audioPath)//The printed path IS DEFINETLY correct
do{
player = try! AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
player.prepareToPlay()
player.play()
}
}
The path is correct so I'm not sure why it won't work. Could it be the format (even though it plays on OSX)?
An example file: https://www.dropbox.com/s/csewwg6n9vzan5z/131015-08%3A13%3A30.m4a?dl=0
That file won't play on iOS because it's encoded in AMR NarrowBand, which is no longer supported.
I would try re-encoding the m4a files as AAC, or switch to mp3.
It's likely just a feature of the way this m4a file is compressed. The simple fact is that not every m4a file, even if it is a perfectly valid file, will play on your device. (For example, perhaps this file is very compressed.) And to make things more complicated, it differs from device to device! And to make things even more complicated, the file might play in the Music app but not in an AVAudioPlayer!!
To test this theory, recode the file as a more middle-of-the-road m4a and see if that plays.

Resources