shaka-project / shaka-player-embedded

Shaka Player in a C++ Framework
Apache License 2.0
239 stars 62 forks source link

Widevine license download for offline playback #148

Closed OmarPedraza closed 3 years ago

OmarPedraza commented 4 years ago

Hi there, We're experiencing a couple of problems while downloading assets in our app.

We've already implemented the following network filter:

func onPlayer(_ player: ShakaPlayer, networkRequest request: ShakaPlayerRequest, of type: ShakaPlayerRequestType, with block: @escaping ShakaPlayerAsyncBlock) {
    switch type {
    case .license:
        if var uri = request.uris.firstObject as? String {
            if let body = request.body {
                getMPXToken {
                    if $0 == nil, let mpx = self.mpxToken {
                        let base64String = body.base64EncodedString(options: .endLineWithLineFeed)
                        if let escapedString = CFURLCreateStringByReplacingPercentEscapes(kCFAllocatorDefault, base64String as CFString, Constants.Downloads.widevineChallengeLeaveEscapedCharacters as CFString) as String? {
                            request.method = "GET"

                            uri.add(parameters: ["_releasePid": "kJeMdyumNGP2", "_widevineChallenge": escapedString, "account": self.playbackConfiguration.mpxAccountID, "form": "json", "schema": "1.0", "token": mpx.token])
                            request.uris = [uri]

                            block(nil)
                        } else {
                            block(ShakaPlayerError.incorrectRequestBodyError)
                        }
                    } else {
                        block(ShakaPlayerError.missingMPXTokenError)
                    }
                }
            } else {
                block(ShakaPlayerError.missingRequestBodyError)
            }
        } else {
            block(ShakaPlayerError.missingLicenseServerURL)
        }
    default:
        block(nil)
    }
}

Nevertheless, it doesn't work since we're receiving the following error:

▿ Optional<ShakaPlayerError>
  - some : Error Domain=ShakaPlayerErrorDomain Code=6007 "Shaka Error DRM.LICENSE_REQUEST_FAILED (shaka.util.Error {
  "severity": 2,
  "category": 1,
  "code": 1002,
  "data": [
    "https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getWidevineLicense?_widevineChallenge=CAQ=&schema=1.0&token=iRjdiFw-Hr12SnAIU8f2oRDgQHDqILBu&form=json&account=http://access.auth.theplatform.com/data/Account/2669535363&_releasePid=kJeMdyumNGP2",
    {},
    2
  ],
  "handled": false,
  "message": "Shaka Error NETWORK.HTTP_ERROR (https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getWidevineLicense?_widevineChallenge=CAQ=&schema=1.0&token=iRjdiFw-Hr12SnAIU8f2oRDgQHDqILBu&form=json&account=http://access.auth.theplatform.com/data/Account/2669535363&_releasePid=kJeMdyumNGP2,[object Event],2)",
  "stack": "Error@/var/containers/Bundle/Application/5E1D4FE0-894B-4B17-BA8A-E9A1BBD343B7/hayu.app/Frameworks/ShakaPlayerEmbedded.framework/shaka-player.compiled.js:104:742\nonerror@/var/containers/Bundle/Application/5E1D4FE0-894B-4B17-BA8A-E9A1BBD343B7/hayu.app/Frameworks/ShakaPlayerEmbedded.framework/shaka-player.compiled.js:860:415"
})" UserInfo={NSLocalizedDescription=Shaka Error DRM.LICENSE_REQUEST_FAILED (shaka.util.Error {
  "severity": 2,
  "category": 1,
  "code": 1002,
  "data": [
    "https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getWidevineLicense?_widevineChallenge=CAQ=&schema=1.0&token=iRjdiFw-Hr12SnAIU8f2oRDgQHDqILBu&form=json&account=http://access.auth.theplatform.com/data/Account/2669535363&_releasePid=kJeMdyumNGP2",
    {},
    2
  ],
  "handled": false,
  "message": "Shaka Error NETWORK.HTTP_ERROR (https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getWidevineLicense?_widevineChallenge=CAQ=&schema=1.0&token=iRjdiFw-Hr12SnAIU8f2oRDgQHDqILBu&form=json&account=http://access.auth.theplatform.com/data/Account/2669535363&_releasePid=kJeMdyumNGP2,[object Event],2)",
  "stack": "Error@/var/containers/Bundle/Application/5E1D4FE0-894B-4B17-BA8A-E9A1BBD343B7/hayu.app/Frameworks/ShakaPlayerEmbedded.framework/shaka-player.compiled.js:104:742\nonerror@/var/containers/Bundle/Application/5E1D4FE0-894B-4B17-BA8A-E9A1BBD343B7/hayu.app/Frameworks/ShakaPlayerEmbedded.framework/shaka-player.compiled.js:860:415"
}), ShakaPlayerErrorSeverityKey=2, ShakaPlayerErrorCategoryKey=6}

I've tried that URL in Postman and in a browser and it returns a license properly so, for checking if the problem is in response's parsing, I've also added the following network filter for responses but it never gets called:

func onPlayer(_ player: ShakaPlayer, networkResponse response: ShakaPlayerResponse, of type: ShakaPlayerRequestType, with block: @escaping ShakaPlayerAsyncBlock) {
    switch type {
    case .license:
        block(nil)
    default:
        block(nil)
    }
}

What can be failing here?

Also, as you can see, _releasePid is being hardcoded for testing purposes. Given that that value is fetched while parsing asset's SMIL for getting the MPEG-DASH URL and network filter methods don't give information about what asset it's being processed in that moment, how can we add that value to the request? Do we have to manage a parallel queue with those values? If so, for taking it into account during development, is it possible to download more than one asset simultaneously?

Thanks!

TheModMaker commented 4 years ago

What other logging is there? This is a network error that isn't from the server, so you should see some logging about what happened.

I don't think this version supports parallel downloads; so you'd need different ShakaPlayerStorage instances to download. There isn't any way to report multiple downloads, but they should be supported.

OmarPedraza commented 4 years ago

Thanks @TheModMaker.

I've done several changes for including releasePid into the request without being hardcoded so this is the current status of the code:

element.getAsset(forOffline: true) { (asset, error) in
    if error == nil, let asset = asset as? DRMAsset, case .widevine(let releasePID) = asset.security {
        var server = self.playbackConfiguration.widevineLicenseServer
        server.add(parameters: ["_releasePid": releasePID])
        storage.configure("drm.servers.\(Constants.Downloads.widevineServerName)", with: server)

        storage.store(asset.url.absoluteString) { (content, failure) in
            if let failure = failure {
                print(failure)
            } else {
                print(content)
            }
        }
    } else {
        completion?(error)
    }
}
func onPlayer(_ player: ShakaPlayer, networkRequest request: ShakaPlayerRequest, of type: ShakaPlayerRequestType, with block: @escaping ShakaPlayerAsyncBlock) {
    switch type {
    case .license:
        if var uri = request.uris.firstObject as? String {
            if let body = request.body {
                getMPXToken {
                    if $0 == nil, let mpx = self.mpxToken {
                        let base64String = body.base64EncodedString(options: .endLineWithLineFeed)
                        if let escapedString = base64String.unescape(charactersToLeaveEscaped: Constants.Downloads.widevineChallengeLeaveEscapedCharacters) {
                            request.body = nil
                            request.method = "GET"

                            uri.add(parameters: ["_widevineChallenge": escapedString, "account": self.playbackConfiguration.mpxAccountID, "form": "json", "schema": "1.0", "token": mpx.token])
                            request.uris = [uri]

                            block(nil)
                        } else {
                            block(ShakaPlayerError.incorrectRequestBodyError)
                        }
                    } else {
                        block(ShakaPlayerError.missingMPXTokenError)
                    }
                }
            } else {
                block(ShakaPlayerError.missingRequestBodyError)
            }
        } else {
            block(ShakaPlayerError.missingLicenseServerURL)
        }
    default:
        block(nil)
    }
}
func onPlayer(_ player: ShakaPlayer, networkResponse response: ShakaPlayerResponse, of type: ShakaPlayerRequestType, with block: @escaping ShakaPlayerAsyncBlock) {
    switch type {
    case .license:
        if let data = response.data {
            if let license = getWidevineLicense(from: data) {
                response.data = license

                block(nil)
            } else {
                block(ShakaPlayerError.incorrectResponseDataError)
            }
        } else {
            block(ShakaPlayerError.missingResponseDataError)
        }
    default:
        block(nil)
    }
}

Nevertheless, nothing has changed. Network filter for response is not being called and error is still the same one. With GLOG_v=1 activated, this is the full log output:

[Info]: "Starting attach..."
[Warn]: "Unexpected number of arguments for .offline.progressCallback"
WARNING: Logging before InitGoogleLogging() is written to STDERR
I0702 17:11:07.122421 1870786560 js_engine.cc:33] Begin GC run
I0702 17:11:07.125115 1870786560 object_tracker.cc:143] Deleted 3 object(s).
I0702 17:11:07.125622 1870786560 js_engine.cc:44] End GC run
I0702 17:11:37.122097 1870786560 js_engine.cc:33] Begin GC run
I0702 17:11:37.123657 1870786560 object_tracker.cc:143] Deleted 0 object(s).
I0702 17:11:37.124017 1870786560 js_engine.cc:44] End GC run
I0702 17:12:07.121335 1870786560 js_engine.cc:33] Begin GC run
I0702 17:12:07.122170 1870786560 object_tracker.cc:143] Deleted 0 object(s).
I0702 17:12:07.122562 1870786560 js_engine.cc:44] End GC run
[Info]: "No Period ID given for Period with start time 0,  Assigning a default"
[Info]: "Created MediaKeys object for key system"   "com.widevine.alpha"
[Log]: "codecs" "avc1-mp4a" "avg bandwidth" 830112.5714285715
[Log]: "Mounting v3 idb storage cell"
[Log]: "Mounting session ID idb storage cell"
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/cdm_engine.cpp(120):OpenSession] CdmEngine::OpenSession
[DEBUG:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/crypto_session.cpp(644):Open] CryptoSession::Open: requested_security_level: Default
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/usage_table_header.cpp(38):Init] UsageTableHeader::Init: security level: 3
[WARN:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/device_files.cpp(1223):RetrieveHashedFile] DeviceFiles::RetrieveHashedFile: usgtable.bin does not exist
[WARN:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/device_files.cpp(1103):RetrieveUsageTableInfo] DeviceFiles::RetrieveUsageTableInfo: unable to retrieve file
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/device_files.cpp(155):ExtractDeviceInfo] ExtractDeviceInfo Entry
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/cdm_engine.cpp(156):OpenSession] CdmEngine::OpenSession: ksid015A019E
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/cdm_engine.cpp(120):OpenSession] CdmEngine::OpenSession
[DEBUG:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/crypto_session.cpp(644):Open] CryptoSession::Open: requested_security_level: Default
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/device_files.cpp(155):ExtractDeviceInfo] ExtractDeviceInfo Entry
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/cdm_engine.cpp(156):OpenSession] CdmEngine::OpenSession: ksid0BBD5098
E0702 17:12:19.913257 1871360000 xml_http_request.cc:509] Error returned by curl: 56
E0702 17:12:20.799569 1871360000 xml_http_request.cc:509] Error returned by curl: 56
I0702 17:12:37.120648 1870786560 js_engine.cc:33] Begin GC run
I0702 17:12:37.157539 1870786560 object_tracker.cc:143] Deleted 1420 object(s).
I0702 17:12:37.161263 1870786560 js_engine.cc:44] End GC run
I0702 17:13:07.168892 1870786560 js_engine.cc:33] Begin GC run
I0702 17:13:07.211004 1870786560 object_tracker.cc:143] Deleted 1383 object(s).
I0702 17:13:07.214826 1870786560 js_engine.cc:44] End GC run
I0702 17:13:37.168053 1870786560 js_engine.cc:33] Begin GC run
I0702 17:13:37.308118 1870786560 object_tracker.cc:143] Deleted 1341 object(s).
I0702 17:13:37.312077 1870786560 js_engine.cc:44] End GC run
I0702 17:14:07.167502 1870786560 js_engine.cc:33] Begin GC run
I0702 17:14:07.406848 1870786560 object_tracker.cc:143] Deleted 1377 object(s).
I0702 17:14:07.412370 1870786560 js_engine.cc:44] End GC run
I0702 17:14:37.229406 1870786560 js_engine.cc:33] Begin GC run
I0702 17:14:37.351403 1870786560 object_tracker.cc:143] Deleted 1355 object(s).
I0702 17:14:37.357255 1870786560 js_engine.cc:44] End GC run
I0702 17:15:07.229346 1870786560 js_engine.cc:33] Begin GC run
I0702 17:15:07.365311 1870786560 object_tracker.cc:143] Deleted 1424 object(s).
I0702 17:15:07.370318 1870786560 js_engine.cc:44] End GC run
I0702 17:15:37.228466 1870786560 js_engine.cc:33] Begin GC run
I0702 17:15:37.331032 1870786560 object_tracker.cc:143] Deleted 1164 object(s).
I0702 17:15:37.335767 1870786560 js_engine.cc:44] End GC run
I0702 17:16:07.227344 1870786560 js_engine.cc:33] Begin GC run
I0702 17:16:07.330134 1870786560 object_tracker.cc:143] Deleted 685 object(s).
I0702 17:16:07.333099 1870786560 js_engine.cc:44] End GC run
I0702 17:16:37.227419 1870786560 js_engine.cc:33] Begin GC run
I0702 17:16:37.415120 1870786560 object_tracker.cc:143] Deleted 718 object(s).
I0702 17:16:37.418228 1870786560 js_engine.cc:44] End GC run
I0702 17:17:07.227813 1870786560 js_engine.cc:33] Begin GC run
I0702 17:17:07.357771 1870786560 object_tracker.cc:143] Deleted 668 object(s).
I0702 17:17:07.360716 1870786560 js_engine.cc:44] End GC run
I0702 17:17:37.227115 1870786560 js_engine.cc:33] Begin GC run
I0702 17:17:37.444553 1870786560 object_tracker.cc:143] Deleted 719 object(s).
I0702 17:17:37.447964 1870786560 js_engine.cc:44] End GC run
I0702 17:18:07.227159 1870786560 js_engine.cc:33] Begin GC run
I0702 17:18:07.372339 1870786560 object_tracker.cc:143] Deleted 819 object(s).
I0702 17:18:07.376473 1870786560 js_engine.cc:44] End GC run
I0702 17:18:37.226505 1870786560 js_engine.cc:33] Begin GC run
I0702 17:18:37.372854 1870786560 object_tracker.cc:143] Deleted 682 object(s).
I0702 17:18:37.376375 1870786560 js_engine.cc:44] End GC run
I0702 17:19:07.292429 1870786560 js_engine.cc:33] Begin GC run
I0702 17:19:07.445088 1870786560 object_tracker.cc:143] Deleted 688 object(s).
I0702 17:19:07.448861 1870786560 js_engine.cc:44] End GC run
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/cdm_engine.cpp(198):CloseSession] CdmEngine::CloseSession: ksid015A019E
[INFO:/Users/modmaker/Code/ios-sdk/oemcrypto-arxan/third_party/cdm/core/src/cdm_engine.cpp(198):CloseSession] CdmEngine::CloseSession: ksid0BBD5098
Error Domain=ShakaPlayerErrorDomain Code=6007 "Shaka Error DRM.LICENSE_REQUEST_FAILED (shaka.util.Error {
  "severity": 2,
  "category": 1,
  "code": 1002,
  "data": [
    "https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getWidevineLicense?_releasePid=RghGdJhEB8Ya&schema=1.0&token=lD9HBfjscU_6AEE-o0cEMcA-wHAOIKBQ&account=http://access.auth.theplatform.com/data/Account/2669535363&_widevineChallenge=CAQ=&form=json",
    {},
    2
  ],
  "handled": false,
  "message": "Shaka Error NETWORK.HTTP_ERROR (https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getWidevineLicense?_releasePid=RghGdJhEB8Ya&schema=1.0&token=lD9HBfjscU_6AEE-o0cEMcA-wHAOIKBQ&account=http://access.auth.theplatform.com/data/Account/2669535363&_widevineChallenge=CAQ=&form=json,[object Event],2)",
  "stack": "Error@/var/containers/Bundle/Application/553BF0F5-8A8D-4F45-8802-02846AFFD566/hayu.app/Frameworks/ShakaPlayerEmbedded.framework/shaka-player.compiled.js:104:742\nonerror@/var/containers/Bundle/Application/553BF0F5-8A8D-4F45-8802-02846AFFD566/hayu.app/Frameworks/ShakaPlayerEmbedded.framework/shaka-player.compiled.js:860:415"
})" UserInfo={NSLocalizedDescription=Shaka Error DRM.LICENSE_REQUEST_FAILED (shaka.util.Error {
  "severity": 2,
  "category": 1,
  "code": 1002,
  "data": [
    "https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getWidevineLicense?_releasePid=RghGdJhEB8Ya&schema=1.0&token=lD9HBfjscU_6AEE-o0cEMcA-wHAOIKBQ&account=http://access.auth.theplatform.com/data/Account/2669535363&_widevineChallenge=CAQ=&form=json",
    {},
    2
  ],
  "handled": false,
  "message": "Shaka Error NETWORK.HTTP_ERROR (https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getWidevineLicense?_releasePid=RghGdJhEB8Ya&schema=1.0&token=lD9HBfjscU_6AEE-o0cEMcA-wHAOIKBQ&account=http://access.auth.theplatform.com/data/Account/2669535363&_widevineChallenge=CAQ=&form=json,[object Event],2)",
  "stack": "Error@/var/containers/Bundle/Application/553BF0F5-8A8D-4F45-8802-02846AFFD566/hayu.app/Frameworks/ShakaPlayerEmbedded.framework/shaka-player.compiled.js:104:742\nonerror@/var/containers/Bundle/Application/553BF0F5-8A8D-4F45-8802-02846AFFD566/hayu.app/Frameworks/ShakaPlayerEmbedded.framework/shaka-player.compiled.js:860:415"
}), ShakaPlayerErrorSeverityKey=2, ShakaPlayerErrorCategoryKey=6}
I0702 17:19:38.181527 1870786560 js_engine.cc:33] Begin GC run
I0702 17:19:38.580801 1870786560 object_tracker.cc:143] Deleted 1154 object(s).
I0702 17:19:38.587257 1870786560 js_engine.cc:44] End GC run

Do you see something strange there?

TheModMaker commented 3 years ago

The logs show Error returned by curl: 56, which is CURLE_RECV_ERROR. This is usually an error from the server that isn't caused by an HTTP error code; some problem downloading the response. I can reproduce with just that URL, where the server returns a status 200 but JSON data with some error text.

With verbose curl logs, it reports an errSSLClosedAbort error from the OS. I'm not sure if this is a bug in curl, or how we are handling the request, if your server is closing the connection early, or something else. This doesn't happen when running curl from the command line.

OmarPedraza commented 3 years ago

Working now!

Thanks @TheModMaker!