Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Playing AES encrypted hls stream using avplayer - ios swift

i'm trying to play an AES encrypted stream in AVPlayer.. typically a link of the key is delivered to the player inside the M3U8 playlist.. in my scenario the key is divided in half.. the first half is delivered by the server and i should append the other half inside the app to decrypt when playing

i've already done this on Android, is there a way to do it also on iOS?

This is the playlist:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=200000,RESOLUTION=284x160
chunklist_w670540365_b200000.m3u8?t=57b5b16d3824d
#EXT-X-STREAM-INF:BANDWIDTH=850000,RESOLUTION=640x360
chunklist_w670540365_b850000.m3u8?t=57b5b16d3824d

And this is the chunk list:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:13544
#EXT-X-KEY:METHOD=AES-128,URI="http://example.com/api/getEncryptionKey?t=57b5b16d3824d"
#EXTINF:9.6,
media_w670540365_b200000_13544.ts?t=57b5b16d3824d
#EXTINF:9.6,
media_w670540365_b200000_13545.ts?t=57b5b16d3824d
#EXTINF:10.56,
media_w670540365_b200000_13546.ts?t=57b5b16d3824d

This is what AVPlayer does:

1- the playlist gets downloaded and a chunk list is selected 2- the player downloads the chunk list 3- the decryption key to decrypt the chunks is downloaded 4- the player begins downloading the chunks sequentially to play them 5- every chunk is decrypted and played

What i need to do is: after the 3rd step when the player calls the api to get the encryption key using this link: 'http://example.com/api/getEncryptionKey?t=57b5b16d3824d', i want to intercept the response and append the other half of the key

Is it possible?

like image 999
Hadi Najem Avatar asked Aug 24 '16 13:08

Hadi Najem


2 Answers

yes, it is very much possible! I recently did it in one of my projects.

Whenever AVPlayer loads the encrypted video, it tries to load decryption key from the URL mentioned in prog_index.m3u8. If AVPlayer is not able to play video with the fetched key or if it didn't get the key at all on the specified url, it calls a delegate method from AVAssetResourceLoaderDelegate that is

 public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForRenewalOfRequestedResource renewalRequest: AVAssetResourceRenewalRequest) -> Bool {
    return shouldLoadOrRenewRequestedResource(resourceLoadingRequest: renewalRequest)
}

and,

public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
    return shouldLoadOrRenewRequestedResource(resourceLoadingRequest: loadingRequest)
}

which of course differ in the cases they are being called. Prior one is called when the player should wait for loading resource and later one is called when the player needs to renew the resource.

func shouldLoadOrRenewRequestedResource(resourceLoadingRequest: AVAssetResourceLoadingRequest) -> Bool {

    guard var url = resourceLoadingRequest.request.url else {
        return false
    }

   //FETCH THE KEY FROM NETWORK CALL/KEYSTORE, CONVERT IT TO DATA AND FINISH LOADING OF RESOURCE WITH THAT DATA, IN YOUR CASE JOIN THE OTHER HALF OF THE KEY TO ACTUAL KEY (you can get the first half from the url above)
   resourceLoadingRequest.dataRequest?.respond(with: keyData)
   resourceLoadingRequest.finishLoading()

    return true;
}}

Once you'll return true with the actual key, the video will be played instantly.

like image 57
abhinavroy23 Avatar answered Nov 16 '22 23:11

abhinavroy23


While trying with Azure Media services the following sample works fine. Here we are adding the token as part of AVURLAsset options.

    var options = [String: [String: String]]()
    if (!token.isEmpty) {
        let headers = ["Authorization": "Bearer " + token!]
        options = ["AVURLAssetHTTPHeaderFieldsKey": headers]
    }
    let avAsset = AVURLAsset(url: videoUrl, options: options)
    let avItem = AVPlayerItem(asset: avAsset)
    let player = AVPlayer(playerItem: avItem)
    
    let playerFrame = view.viewWithTag(1)?.frame
    controller.player = player
    if (autoPlay.isOn) {
        player.rate = 1
    }
    controller.view.frame = playerFrame ?? CGRect(x: 0, y: 0, width: view.frame.width , height: 250)
    addChild(controller)
    view.viewWithTag(1)?.addSubview(controller.view)
    controller.didMove(toParent: self)
    

The complete working sample can be found from

https://github.com/Azure-Samples/media-services-3rdparty-player-samples/tree/master/src/avplayer

like image 1
arango_86 Avatar answered Nov 17 '22 00:11

arango_86