I'm working on an iOS app that plays FairPlay-encrypted audio via HLS, and supports both downloading and streaming. And I'm unable to play downloaded content when in airplane mode. If I create an AVURLAsset
from the local URL when the download completes, asset.assetCache.isPlayableOffline
returns NO
, and sure enough when I try to play in airplane mode it still tries to request one of the .m3u8 playlist files.
My master playlist looks like this:
#EXTM3U
# Created with Bento4 mp4-hls.py version 1.1.0r623
#EXT-X-VERSION:5
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,URI="skd://url/to/key?KID=foobar",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"
# Media Playlists
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=133781,BANDWIDTH=134685,CODECS="mp4a.40.2" media-1/stream.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=67526,BANDWIDTH=67854,CODECS="mp4a.40.2" media-2/stream.m3u8
The stream playlists look like this:
#EXTM3U
#EXT-X-VERSION:5
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:30
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://url/to/key?KID=foobar",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"
#EXTINF:30.000181,
#EXT-X-BYTERANGE:470290@0
media.aac
# more segments...
#EXT-X-ENDLIST
Downloading an asset:
AVURLAsset *asset = [AVURLAsset assetWithURL:myM3u8Url];
[asset.resourceLoader setDelegate:[FairPlayKeyManager instance] queue:[FairPlayKeyManager queue]];
asset.resourceLoader.preloadsEligibleContentKeys = YES;
AVAssetDownloadTask *task = [self.session assetDownloadTaskWithURLAsset:asset assetTitle:@"Track" assetArtworkData:imgData options:nil];
[task resume];
In the delegate's URLSession:assetDownloadTask:didFinishDownloadingToURL:
:
self.downloadedPath = location.relativePath;
In the delegate's URLSession:task:didCompleteWithError:
:
if (!error)
{
NSString *strUrl = [NSHomeDirectory() stringByAppendingPathComponent:self.downloadedPath];
NSURL *url = [NSURL fileURLWithPath:strUrl];
AVURLAsset *localAsset = [AVURLAsset assetWithURL:url];
if (!localAsset.assetCache.playableOffline)
NSLog(@"Oh no!"); //not playable offline
}
The download doesn't give an error besides the asset cache reporting not playable offline. But if you switch to airplane mode and try to play the downloaded asset, it'll properly ask the resource loader delegate for a key (and I'm using persistent keys, so that works fine offline), then try to make a request for media-1/stream.m3u8
.
Are there any gotchas that I'm not handling here? Should the playlist file be different in some way? Is there some property on the task or asset that I'm missing?
As it turned out, this was because the URL I was downloading the audio from (ex.
https://mywebsite.com/path/to/master.m3u8
was redirecting to a CDN url (https://my.cdn/other/path/to/master.m3u8
). Something was going wrong in theAVAssetDownloadTask
bookkeeping such that when I tried to play the resulting downloaded files offline, it thought it needed more files from the network. I've filed this as radar 43285278. I solved this by manually doing aHEAD
request to the same URL, then givingAVAssetDownloadTask
the resulting redirect URL.