Downloaders

We developed different downloaders for handling HLS/FPS and MPEG-DASH content. They can all be used behind the common interface DownloaderAPI

DownloaderAPI

The main data type in DownloaderAPI is a Download.

let url = URL(string: "..")!
let config = PlayerConfiguration(with: url, contentType: "...")

let downloader = PRESTOplaySDK.shared.downloader()
let download = downloader.createDownload(config, headers: [:])
downloader.prepareDownload(download.uuid) { download, error in
    guard let download else { return }
    var res = false;

    if let videoRendition = request.videoTracks[0].renditions[0] {
        res = request.selectVideoRendition(videoRendition)
    }
    if let audioRendition = request.audioTracks[0].renditions[0] {
        res = request.selectAudioRendition(audioRendition)
    }

    let delegate = DownloadDelegate({})
    let error = downloader.startDownload(download, delegate: delegate)
}

To receive events related to a Download we use a delegate:

class MyDownloadDelegate: DownloadDelegate {
    public let complete: () -> Void

    public init (_ complete: @escaping () -> Void) {
        self.complete = complete
    }

    func didStateChange(_ download: MediaDownload) {
        if (download.state == .success) {
            complete()
        }
    }

    func didProgressChange(_ download: MediaDownload) {
    }
}

License

If you want to prefetch the license without forcing the user to be online during the first playback, use the method PRESTOplaySDK.shared.prefetcher(for:)

let prefetcher = PRESTOplaySDK.shared.prefetcher(for: config)
prefetcher?.prefetchKeys() { error in
    if error != nil {
        // Handle error
    }
}

HLS Streams

For downloading HLS and FPS we created a wrapper around the native HLS downloader AVAssetDownloadTask

  • enables querying and selection of stream bitrate
  • enables querying of audio media streams and subtitle media tracks
  • adds support for pre-selecting audio and subtitle tracks before download begins
  • greatly simplifies API usage and the monitoring of downloading progress.
  • supports preselection of the preferred media bitrate. If no suitable media bitrate is found, the highest bitrate will be selected. Only one media bitrate can be selected, meaning that only one video rendition will be downloaded
  • background downloads are fully supported
  • if the content is encrypted, it’s possible to prefetch the license before starting the download. It’s also possible to get the license after the download has completed (and without even starting the playback)

Background download tasks are handled by NSURLSession underneath and there are differences between the test environment and the production environment.

For background downloads an instance of HLSDownloader should be kept in AppDelegate. This is the only supported way from Apple to preserve completionHandlers of background sessions in-between Application Lifecycle changes (in our example we use static class field, but it is also possible to use object variables in AppDelegate).

When additional tracks are selected, it’s not possible to know in advance the entire download size due to an API limitation. The download progress will restart from zero for each additional track. You can however differentiate between the main download (which includes the minimum set of tracks that can be played) and the additional tracks using the flag.

When using a master playlist, the CODECS attribute of the EXT-X-STREAM-INF tag may signal CODECs not supported for playback on the downloading device, but download of such streams will be possible. Check Apple FAQ for an updated list of recommended codecs.

MPEG-DASH Streams

DASH streams are downloaded using NSURLSession with custom manifest parser library.

Additional track selection

Additional track selection can be done on Download class instance. We encourage to store all possible selection in your ViewController class.

var audioTracks = [AudioTrack]()
var audioRenditions = [AudioRendition]()
var textTracks = [TextTrack]()
var videoRenditions = [VideoRendition]()

First you need to create the download.

let download = downloader.createDownload(persistableConfiguration, headers: [:])

When prepareDownload step is finished you can get the list of all available to download tracks.

downloader.prepareDownload(download.uuid) { download, error in

    ...

    download.audioTracks.forEach { track in
        track.renditions.forEach { rendition in
            self.audioRenditions.append(rendition)
        }
    }
    self.audioTracks = download.audioTracks
    self.textTracks = download.textTracks

    download.videoTracks.forEach { track in
        track.renditions.forEach { rendition in
            self.videoRenditions.append(rendition)
        }
    }

    ...

}

Below you can find an universal method to select and unselect tracks for the download (for native HLS only the default video track is selected).

func selectTrack(_ indexPath: IndexPath , isSelected: Bool) {
    if indexPath.section == audioSectionIndex {
        let rendition = audioRenditions[indexPath.row]
        if isSelected {
            _ = download?.selectAudioRendition(rendition)
        } else {
            _ = download?.unselectAudioRendition(rendition)
        }
    } else if indexPath.section == textSectionIndex {
        let textTrack = textTracks[indexPath.row]
        if isSelected {
            _ = download?.selectTextTrack(textTrack)
        } else {
            _ = download?.unselectTextTrack(textTrack)
        }
    } else if indexPath.section == renditionSectionIndex {
        let rendition = videoRenditions[indexPath.row]
        if isSelected {
            _ = download?.selectVideoRendition(rendition)
        } else {
            _ = download?.unselectVideoRendition(rendition)
        }
    }
}