From aafacc3cf9a21fcc6dc68f5aea559cf6a5c9c8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=CC=81lvaro=20Velad=20Galva=CC=81n?= Date: Thu, 6 Mar 2025 10:28:14 +0100 Subject: [PATCH 1/6] feat: Move shaka.util.FairPlayUtils to shaka.drm.FairPlay --- build/types/core | 1 + docs/tutorials/upgrade.md | 1 + lib/drm/fairplay.js | 428 +++++++++++++++++++++++++++++++++++++ lib/util/fairplay_utils.js | 423 +----------------------------------- shaka-player.uncompiled.js | 1 + 5 files changed, 437 insertions(+), 417 deletions(-) create mode 100644 lib/drm/fairplay.js diff --git a/build/types/core b/build/types/core index 2326b0e5f9..9e196b0f8b 100644 --- a/build/types/core +++ b/build/types/core @@ -19,6 +19,7 @@ +../../lib/drm/drm_engine.js +../../lib/drm/drm_utils.js ++../../lib/drm/fairplay.js +../../lib/drm/playready.js +../../lib/media/adaptation_set.js diff --git a/docs/tutorials/upgrade.md b/docs/tutorials/upgrade.md index 8e121c60ff..b4e38465d7 100644 --- a/docs/tutorials/upgrade.md +++ b/docs/tutorials/upgrade.md @@ -127,3 +127,4 @@ application: - The constructor no longer takes `mediaElement` as a parameter; use the `attach` method to attach to a media element instead. (Deprecated in v4.6) - The `TimelineRegionInfo.eventElement` has been replaced with `TimelineRegionInfo.eventNode` property, the new property type is `shaka.externs.xml.Node` instead of `Element` - New API for audio: `getAudioTracks` and `selectAudioTrack`, we also deprecated in v4.14 `getAudioLanguages`, `getAudioLanguagesAndRoles` and `selectAudioLanguage`. + - `shaka.util.FairPlayUtils` is moved to `shaka.drm.FairPlay` (Deprecated in v4.14) diff --git a/lib/drm/fairplay.js b/lib/drm/fairplay.js new file mode 100644 index 0000000000..8aa9a30be9 --- /dev/null +++ b/lib/drm/fairplay.js @@ -0,0 +1,428 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.provide('shaka.drm.FairPlay'); + +goog.require('goog.Uri'); +goog.require('goog.asserts'); +goog.require('shaka.drm.DrmUtils'); +goog.require('shaka.net.NetworkingEngine'); +goog.require('shaka.util.BufferUtils'); +goog.require('shaka.util.Error'); +goog.require('shaka.util.StringUtils'); +goog.require('shaka.util.Uint8ArrayUtils'); + + +/** + * @summary A set of FairPlay utility functions. + * @export + */ +shaka.drm.FairPlay = class { + /** + * Check if FairPlay is supported. + * + * @return {!Promise} + * @export + */ + static async isFairPlaySupported() { + const config = { + initDataTypes: ['cenc', 'sinf', 'skd'], + videoCapabilities: [ + { + contentType: 'video/mp4; codecs="avc1.42E01E"', + }, + ], + }; + try { + await navigator.requestMediaKeySystemAccess('com.apple.fps', [config]); + return true; + } catch (err) { + return false; + } + } + + /** + * Using the default method, extract a content ID from the init data. This is + * based on the FairPlay example documentation. + * + * @param {!BufferSource} initData + * @return {string} + * @export + */ + static defaultGetContentId(initData) { + const uriString = shaka.util.StringUtils.fromBytesAutoDetect(initData); + + // The domain of that URI is the content ID according to Apple's FPS + // sample. + const uri = new goog.Uri(uriString); + return uri.getDomain(); + } + + /** + * Transforms the init data buffer using the given data. The format is: + * + *
+   * [4 bytes] initDataSize
+   * [initDataSize bytes] initData
+   * [4 bytes] contentIdSize
+   * [contentIdSize bytes] contentId
+   * [4 bytes] certSize
+   * [certSize bytes] cert
+   * 
+ * + * @param {!BufferSource} initData + * @param {!BufferSource|string} contentId + * @param {?BufferSource} cert The server certificate; this will throw if not + * provided. + * @return {!Uint8Array} + * @export + */ + static initDataTransform(initData, contentId, cert) { + if (!cert || !cert.byteLength) { + throw new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, + shaka.util.Error.Category.DRM, + shaka.util.Error.Code.SERVER_CERTIFICATE_REQUIRED); + } + + // From that, we build a new init data to use in the session. This is + // composed of several parts. First, the init data as a UTF-16 sdk:// URL. + // Second, a 4-byte LE length followed by the content ID in UTF-16-LE. + // Third, a 4-byte LE length followed by the certificate. + /** @type {BufferSource} */ + let contentIdArray; + if (typeof contentId == 'string') { + contentIdArray = + shaka.util.StringUtils.toUTF16(contentId, /* littleEndian= */ true); + } else { + contentIdArray = contentId; + } + + // The init data we get is a UTF-8 string; convert that to a UTF-16 string. + const sdkUri = shaka.util.StringUtils.fromBytesAutoDetect(initData); + const utf16 = + shaka.util.StringUtils.toUTF16(sdkUri, /* littleEndian= */ true); + + const rebuiltInitData = new Uint8Array( + 12 + utf16.byteLength + contentIdArray.byteLength + cert.byteLength); + + let offset = 0; + /** @param {BufferSource} array */ + const append = (array) => { + rebuiltInitData.set(shaka.util.BufferUtils.toUint8(array), offset); + offset += array.byteLength; + }; + /** @param {BufferSource} array */ + const appendWithLength = (array) => { + const view = shaka.util.BufferUtils.toDataView(rebuiltInitData); + const value = array.byteLength; + view.setUint32(offset, value, /* littleEndian= */ true); + offset += 4; + append(array); + }; + + appendWithLength(utf16); + appendWithLength(contentIdArray); + appendWithLength(cert); + + goog.asserts.assert( + offset == rebuiltInitData.length, 'Inconsistent init data length'); + return rebuiltInitData; + } + + /** + * Basic initDataTransform configuration. + * + * @param {!Uint8Array} initData + * @param {string} initDataType + * @param {?shaka.extern.DrmInfo} drmInfo + * @return {!Uint8Array} + * @private + */ + static basicInitDataTransform_(initData, initDataType, drmInfo) { + if (initDataType !== 'skd') { + return initData; + } + const StringUtils = shaka.util.StringUtils; + const cert = drmInfo.serverCertificate; + const initDataAsString = StringUtils.fromBytesAutoDetect(initData); + const contentId = initDataAsString.split('skd://').pop(); + return shaka.drm.FairPlay.initDataTransform(initData, contentId, cert); + } + + /** + * Verimatrix initDataTransform configuration. + * + * @param {!Uint8Array} initData + * @param {string} initDataType + * @param {?shaka.extern.DrmInfo} drmInfo + * @return {!Uint8Array} + * @export + */ + static verimatrixInitDataTransform(initData, initDataType, drmInfo) { + return shaka.drm.FairPlay.basicInitDataTransform_( + initData, initDataType, drmInfo); + } + + /** + * EZDRM initDataTransform configuration. + * + * @param {!Uint8Array} initData + * @param {string} initDataType + * @param {?shaka.extern.DrmInfo} drmInfo + * @return {!Uint8Array} + * @export + */ + static ezdrmInitDataTransform(initData, initDataType, drmInfo) { + if (initDataType !== 'skd') { + return initData; + } + const StringUtils = shaka.util.StringUtils; + const cert = drmInfo.serverCertificate; + const initDataAsString = StringUtils.fromBytesAutoDetect(initData); + const contentId = initDataAsString.split(';').pop(); + return shaka.drm.FairPlay.initDataTransform(initData, contentId, cert); + } + + /** + * Conax initDataTransform configuration. + * + * @param {!Uint8Array} initData + * @param {string} initDataType + * @param {?shaka.extern.DrmInfo} drmInfo + * @return {!Uint8Array} + * @export + */ + static conaxInitDataTransform(initData, initDataType, drmInfo) { + if (initDataType !== 'skd') { + return initData; + } + const StringUtils = shaka.util.StringUtils; + const cert = drmInfo.serverCertificate; + const initDataAsString = StringUtils.fromBytesAutoDetect(initData); + const skdValue = initDataAsString.split('skd://').pop().split('?').shift(); + const stringToArray = (string) => { + // 2 bytes for each char + const buffer = new ArrayBuffer(string.length * 2); + const array = shaka.util.BufferUtils.toUint16(buffer); + for (let i = 0, strLen = string.length; i < strLen; i++) { + array[i] = string.charCodeAt(i); + } + return array; + }; + const contentId = stringToArray(window.atob(skdValue)); + return shaka.drm.FairPlay.initDataTransform(initData, contentId, cert); + } + + /** + * ExpressPlay initDataTransform configuration. + * + * @param {!Uint8Array} initData + * @param {string} initDataType + * @param {?shaka.extern.DrmInfo} drmInfo + * @return {!Uint8Array} + * @export + */ + static expressplayInitDataTransform(initData, initDataType, drmInfo) { + return shaka.drm.FairPlay.basicInitDataTransform_( + initData, initDataType, drmInfo); + } + + /** + * Mux initDataTransform configuration. + * + * @param {!Uint8Array} initData + * @param {string} initDataType + * @param {?shaka.extern.DrmInfo} drmInfo + * @return {!Uint8Array} + * @export + */ + static muxInitDataTransform(initData, initDataType, drmInfo) { + return shaka.drm.FairPlay.basicInitDataTransform_( + initData, initDataType, drmInfo); + } + + /** + * Verimatrix FairPlay request. + * + * @param {shaka.net.NetworkingEngine.RequestType} type + * @param {shaka.extern.Request} request + * @param {shaka.extern.RequestContext=} context + * @export + */ + static verimatrixFairPlayRequest(type, request, context) { + if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { + return; + } + + const drmInfo = request.drmInfo; + if (!drmInfo || + !shaka.drm.DrmUtils.isFairPlayKeySystem(drmInfo.keySystem)) { + return; + } + + const body = /** @type {!(ArrayBuffer|ArrayBufferView)} */(request.body); + const originalPayload = shaka.util.BufferUtils.toUint8(body); + const base64Payload = shaka.util.Uint8ArrayUtils.toBase64(originalPayload); + request.headers['Content-Type'] = 'application/x-www-form-urlencoded'; + request.body = shaka.util.StringUtils.toUTF8('spc=' + base64Payload); + } + + /** + * Set content-type to application/octet-stream in a FairPlay request. + * + * @param {shaka.net.NetworkingEngine.RequestType} type + * @param {shaka.extern.Request} request + * @param {shaka.extern.RequestContext=} context + * @private + */ + static octetStreamFairPlayRequest_(type, request, context) { + if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { + return; + } + + const drmInfo = request.drmInfo; + if (!drmInfo || + !shaka.drm.DrmUtils.isFairPlayKeySystem(drmInfo.keySystem)) { + return; + } + + request.headers['Content-Type'] = 'application/octet-stream'; + } + + /** + * EZDRM FairPlay request. + * + * @param {shaka.net.NetworkingEngine.RequestType} type + * @param {shaka.extern.Request} request + * @param {shaka.extern.RequestContext=} context + * @export + */ + static ezdrmFairPlayRequest(type, request, context) { + shaka.drm.FairPlay.octetStreamFairPlayRequest_(type, request); + } + + /** + * Conax FairPlay request. + * + * @param {shaka.net.NetworkingEngine.RequestType} type + * @param {shaka.extern.Request} request + * @param {shaka.extern.RequestContext=} context + * @export + */ + static conaxFairPlayRequest(type, request, context) { + shaka.drm.FairPlay.octetStreamFairPlayRequest_(type, request); + } + + /** + * ExpressPlay FairPlay request. + * + * @param {shaka.net.NetworkingEngine.RequestType} type + * @param {shaka.extern.Request} request + * @param {shaka.extern.RequestContext=} context + * @export + */ + static expressplayFairPlayRequest(type, request, context) { + if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { + return; + } + + const drmInfo = request.drmInfo; + if (!drmInfo || + !shaka.drm.DrmUtils.isFairPlayKeySystem(drmInfo.keySystem)) { + return; + } + + shaka.drm.FairPlay.octetStreamFairPlayRequest_(type, request); + } + + /** + * Mux FairPlay request. + * + * @param {shaka.net.NetworkingEngine.RequestType} type + * @param {shaka.extern.Request} request + * @param {shaka.extern.RequestContext=} context + * @export + */ + static muxFairPlayRequest(type, request, context) { + shaka.drm.FairPlay.octetStreamFairPlayRequest_(type, request); + } + + /** + * Common FairPlay response transform for some DRMs providers. + * + * @param {shaka.net.NetworkingEngine.RequestType} type + * @param {shaka.extern.Response} response + * @param {shaka.extern.RequestContext=} context + * @export + */ + static commonFairPlayResponse(type, response, context) { + if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { + return; + } + + const drmInfo = response.originalRequest.drmInfo; + if (!drmInfo || + !shaka.drm.DrmUtils.isFairPlayKeySystem(drmInfo.keySystem)) { + return; + } + + // In Apple's docs, responses can be of the form: + // '\nbase64encoded\n' or 'base64encoded' + // We have also seen responses in JSON format from some of our partners. + // In all of these text-based formats, the CKC data is base64-encoded. + + let responseText; + try { + // Convert it to text for further processing. + responseText = shaka.util.StringUtils.fromUTF8(response.data); + } catch (error) { + // Assume it's not a text format of any kind and leave it alone. + return; + } + + let licenseProcessing = false; + + // Trim whitespace. + responseText = responseText.trim(); + + // Look for wrapper and remove it. + if (responseText.substr(0, 5) === '' && + responseText.substr(-6) === '') { + responseText = responseText.slice(5, -6); + licenseProcessing = true; + } + + if (!licenseProcessing) { + // Look for a JSON wrapper and remove it. + try { + const responseObject = /** @type {!Object} */(JSON.parse(responseText)); + if (responseObject['ckc']) { + responseText = responseObject['ckc']; + licenseProcessing = true; + } + if (responseObject['CkcMessage']) { + responseText = responseObject['CkcMessage']; + licenseProcessing = true; + } + if (responseObject['License']) { + responseText = responseObject['License']; + licenseProcessing = true; + } + } catch (err) { + // It wasn't JSON. Fall through with other transformations. + } + } + + if (licenseProcessing) { + // Decode the base64-encoded data into the format the browser expects. + // It's not clear why FairPlay license servers don't just serve this + // directly. + response.data = shaka.util.BufferUtils.toArrayBuffer( + shaka.util.Uint8ArrayUtils.fromBase64(responseText)); + } + } +}; diff --git a/lib/util/fairplay_utils.js b/lib/util/fairplay_utils.js index 794de18622..43aeea525a 100644 --- a/lib/util/fairplay_utils.js +++ b/lib/util/fairplay_utils.js @@ -6,426 +6,15 @@ goog.provide('shaka.util.FairPlayUtils'); -goog.require('goog.Uri'); -goog.require('goog.asserts'); -goog.require('shaka.drm.DrmUtils'); -goog.require('shaka.net.NetworkingEngine'); -goog.require('shaka.util.BufferUtils'); -goog.require('shaka.util.Error'); -goog.require('shaka.util.StringUtils'); -goog.require('shaka.util.Uint8ArrayUtils'); +goog.require('shaka.drm.FairPlay'); /** - * @summary A set of FairPlay utility functions. + * @summary A set of FairPlay utility functions. DEPRECATED: Please use + * shaka.drm.FairPlay instead. + * @deprecated * @export */ -shaka.util.FairPlayUtils = class { - /** - * Check if FairPlay is supported. - * - * @return {!Promise} - * @export - */ - static async isFairPlaySupported() { - const config = { - initDataTypes: ['cenc', 'sinf', 'skd'], - videoCapabilities: [ - { - contentType: 'video/mp4; codecs="avc1.42E01E"', - }, - ], - }; - try { - await navigator.requestMediaKeySystemAccess('com.apple.fps', [config]); - return true; - } catch (err) { - return false; - } - } - - /** - * Using the default method, extract a content ID from the init data. This is - * based on the FairPlay example documentation. - * - * @param {!BufferSource} initData - * @return {string} - * @export - */ - static defaultGetContentId(initData) { - const uriString = shaka.util.StringUtils.fromBytesAutoDetect(initData); - - // The domain of that URI is the content ID according to Apple's FPS - // sample. - const uri = new goog.Uri(uriString); - return uri.getDomain(); - } - - /** - * Transforms the init data buffer using the given data. The format is: - * - *
-   * [4 bytes] initDataSize
-   * [initDataSize bytes] initData
-   * [4 bytes] contentIdSize
-   * [contentIdSize bytes] contentId
-   * [4 bytes] certSize
-   * [certSize bytes] cert
-   * 
- * - * @param {!BufferSource} initData - * @param {!BufferSource|string} contentId - * @param {?BufferSource} cert The server certificate; this will throw if not - * provided. - * @return {!Uint8Array} - * @export - */ - static initDataTransform(initData, contentId, cert) { - if (!cert || !cert.byteLength) { - throw new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, - shaka.util.Error.Category.DRM, - shaka.util.Error.Code.SERVER_CERTIFICATE_REQUIRED); - } - - // From that, we build a new init data to use in the session. This is - // composed of several parts. First, the init data as a UTF-16 sdk:// URL. - // Second, a 4-byte LE length followed by the content ID in UTF-16-LE. - // Third, a 4-byte LE length followed by the certificate. - /** @type {BufferSource} */ - let contentIdArray; - if (typeof contentId == 'string') { - contentIdArray = - shaka.util.StringUtils.toUTF16(contentId, /* littleEndian= */ true); - } else { - contentIdArray = contentId; - } - - // The init data we get is a UTF-8 string; convert that to a UTF-16 string. - const sdkUri = shaka.util.StringUtils.fromBytesAutoDetect(initData); - const utf16 = - shaka.util.StringUtils.toUTF16(sdkUri, /* littleEndian= */ true); - - const rebuiltInitData = new Uint8Array( - 12 + utf16.byteLength + contentIdArray.byteLength + cert.byteLength); - - let offset = 0; - /** @param {BufferSource} array */ - const append = (array) => { - rebuiltInitData.set(shaka.util.BufferUtils.toUint8(array), offset); - offset += array.byteLength; - }; - /** @param {BufferSource} array */ - const appendWithLength = (array) => { - const view = shaka.util.BufferUtils.toDataView(rebuiltInitData); - const value = array.byteLength; - view.setUint32(offset, value, /* littleEndian= */ true); - offset += 4; - append(array); - }; - - appendWithLength(utf16); - appendWithLength(contentIdArray); - appendWithLength(cert); - - goog.asserts.assert( - offset == rebuiltInitData.length, 'Inconsistent init data length'); - return rebuiltInitData; - } - - /** - * Basic initDataTransform configuration. - * - * @param {!Uint8Array} initData - * @param {string} initDataType - * @param {?shaka.extern.DrmInfo} drmInfo - * @return {!Uint8Array} - * @private - */ - static basicInitDataTransform_(initData, initDataType, drmInfo) { - if (initDataType !== 'skd') { - return initData; - } - const StringUtils = shaka.util.StringUtils; - const FairPlayUtils = shaka.util.FairPlayUtils; - const cert = drmInfo.serverCertificate; - const initDataAsString = StringUtils.fromBytesAutoDetect(initData); - const contentId = initDataAsString.split('skd://').pop(); - return FairPlayUtils.initDataTransform(initData, contentId, cert); - } - - /** - * Verimatrix initDataTransform configuration. - * - * @param {!Uint8Array} initData - * @param {string} initDataType - * @param {?shaka.extern.DrmInfo} drmInfo - * @return {!Uint8Array} - * @export - */ - static verimatrixInitDataTransform(initData, initDataType, drmInfo) { - return shaka.util.FairPlayUtils.basicInitDataTransform_( - initData, initDataType, drmInfo); - } - - /** - * EZDRM initDataTransform configuration. - * - * @param {!Uint8Array} initData - * @param {string} initDataType - * @param {?shaka.extern.DrmInfo} drmInfo - * @return {!Uint8Array} - * @export - */ - static ezdrmInitDataTransform(initData, initDataType, drmInfo) { - if (initDataType !== 'skd') { - return initData; - } - const StringUtils = shaka.util.StringUtils; - const FairPlayUtils = shaka.util.FairPlayUtils; - const cert = drmInfo.serverCertificate; - const initDataAsString = StringUtils.fromBytesAutoDetect(initData); - const contentId = initDataAsString.split(';').pop(); - return FairPlayUtils.initDataTransform(initData, contentId, cert); - } - - /** - * Conax initDataTransform configuration. - * - * @param {!Uint8Array} initData - * @param {string} initDataType - * @param {?shaka.extern.DrmInfo} drmInfo - * @return {!Uint8Array} - * @export - */ - static conaxInitDataTransform(initData, initDataType, drmInfo) { - if (initDataType !== 'skd') { - return initData; - } - const StringUtils = shaka.util.StringUtils; - const FairPlayUtils = shaka.util.FairPlayUtils; - const cert = drmInfo.serverCertificate; - const initDataAsString = StringUtils.fromBytesAutoDetect(initData); - const skdValue = initDataAsString.split('skd://').pop().split('?').shift(); - const stringToArray = (string) => { - // 2 bytes for each char - const buffer = new ArrayBuffer(string.length * 2); - const array = shaka.util.BufferUtils.toUint16(buffer); - for (let i = 0, strLen = string.length; i < strLen; i++) { - array[i] = string.charCodeAt(i); - } - return array; - }; - const contentId = stringToArray(window.atob(skdValue)); - return FairPlayUtils.initDataTransform(initData, contentId, cert); - } - - /** - * ExpressPlay initDataTransform configuration. - * - * @param {!Uint8Array} initData - * @param {string} initDataType - * @param {?shaka.extern.DrmInfo} drmInfo - * @return {!Uint8Array} - * @export - */ - static expressplayInitDataTransform(initData, initDataType, drmInfo) { - return shaka.util.FairPlayUtils.basicInitDataTransform_( - initData, initDataType, drmInfo); - } - - /** - * Mux initDataTransform configuration. - * - * @param {!Uint8Array} initData - * @param {string} initDataType - * @param {?shaka.extern.DrmInfo} drmInfo - * @return {!Uint8Array} - * @export - */ - static muxInitDataTransform(initData, initDataType, drmInfo) { - return shaka.util.FairPlayUtils.basicInitDataTransform_( - initData, initDataType, drmInfo); - } - - /** - * Verimatrix FairPlay request. - * - * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.extern.Request} request - * @param {shaka.extern.RequestContext=} context - * @export - */ - static verimatrixFairPlayRequest(type, request, context) { - if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { - return; - } - - const drmInfo = request.drmInfo; - if (!drmInfo || - !shaka.drm.DrmUtils.isFairPlayKeySystem(drmInfo.keySystem)) { - return; - } - - const body = /** @type {!(ArrayBuffer|ArrayBufferView)} */(request.body); - const originalPayload = shaka.util.BufferUtils.toUint8(body); - const base64Payload = shaka.util.Uint8ArrayUtils.toBase64(originalPayload); - request.headers['Content-Type'] = 'application/x-www-form-urlencoded'; - request.body = shaka.util.StringUtils.toUTF8('spc=' + base64Payload); - } - - /** - * Set content-type to application/octet-stream in a FairPlay request. - * - * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.extern.Request} request - * @param {shaka.extern.RequestContext=} context - * @private - */ - static octetStreamFairPlayRequest_(type, request, context) { - if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { - return; - } - - const drmInfo = request.drmInfo; - if (!drmInfo || - !shaka.drm.DrmUtils.isFairPlayKeySystem(drmInfo.keySystem)) { - return; - } - - request.headers['Content-Type'] = 'application/octet-stream'; - } - - /** - * EZDRM FairPlay request. - * - * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.extern.Request} request - * @param {shaka.extern.RequestContext=} context - * @export - */ - static ezdrmFairPlayRequest(type, request, context) { - shaka.util.FairPlayUtils.octetStreamFairPlayRequest_(type, request); - } - - /** - * Conax FairPlay request. - * - * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.extern.Request} request - * @param {shaka.extern.RequestContext=} context - * @export - */ - static conaxFairPlayRequest(type, request, context) { - shaka.util.FairPlayUtils.octetStreamFairPlayRequest_(type, request); - } - - /** - * ExpressPlay FairPlay request. - * - * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.extern.Request} request - * @param {shaka.extern.RequestContext=} context - * @export - */ - static expressplayFairPlayRequest(type, request, context) { - if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { - return; - } - - const drmInfo = request.drmInfo; - if (!drmInfo || - !shaka.drm.DrmUtils.isFairPlayKeySystem(drmInfo.keySystem)) { - return; - } - - shaka.util.FairPlayUtils.octetStreamFairPlayRequest_(type, request); - } - - /** - * Mux FairPlay request. - * - * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.extern.Request} request - * @param {shaka.extern.RequestContext=} context - * @export - */ - static muxFairPlayRequest(type, request, context) { - shaka.util.FairPlayUtils.octetStreamFairPlayRequest_(type, request); - } - - /** - * Common FairPlay response transform for some DRMs providers. - * - * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.extern.Response} response - * @param {shaka.extern.RequestContext=} context - * @export - */ - static commonFairPlayResponse(type, response, context) { - if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { - return; - } - - const drmInfo = response.originalRequest.drmInfo; - if (!drmInfo || - !shaka.drm.DrmUtils.isFairPlayKeySystem(drmInfo.keySystem)) { - return; - } - - // In Apple's docs, responses can be of the form: - // '\nbase64encoded\n' or 'base64encoded' - // We have also seen responses in JSON format from some of our partners. - // In all of these text-based formats, the CKC data is base64-encoded. - - let responseText; - try { - // Convert it to text for further processing. - responseText = shaka.util.StringUtils.fromUTF8(response.data); - } catch (error) { - // Assume it's not a text format of any kind and leave it alone. - return; - } - - let licenseProcessing = false; - - // Trim whitespace. - responseText = responseText.trim(); - - // Look for wrapper and remove it. - if (responseText.substr(0, 5) === '' && - responseText.substr(-6) === '') { - responseText = responseText.slice(5, -6); - licenseProcessing = true; - } - - if (!licenseProcessing) { - // Look for a JSON wrapper and remove it. - try { - const responseObject = /** @type {!Object} */(JSON.parse(responseText)); - if (responseObject['ckc']) { - responseText = responseObject['ckc']; - licenseProcessing = true; - } - if (responseObject['CkcMessage']) { - responseText = responseObject['CkcMessage']; - licenseProcessing = true; - } - if (responseObject['License']) { - responseText = responseObject['License']; - licenseProcessing = true; - } - } catch (err) { - // It wasn't JSON. Fall through with other transformations. - } - } - - if (licenseProcessing) { - // Decode the base64-encoded data into the format the browser expects. - // It's not clear why FairPlay license servers don't just serve this - // directly. - response.data = shaka.util.BufferUtils.toArrayBuffer( - shaka.util.Uint8ArrayUtils.fromBase64(responseText)); - } - } +shaka.util.FairPlayUtils = class extends shaka.drm.FairPlay { + // DEPRECATED }; diff --git a/shaka-player.uncompiled.js b/shaka-player.uncompiled.js index b80c23c840..0d74defea3 100644 --- a/shaka-player.uncompiled.js +++ b/shaka-player.uncompiled.js @@ -16,6 +16,7 @@ goog.require('shaka.ads.AdManager'); goog.require('shaka.cast.CastProxy'); goog.require('shaka.cast.CastReceiver'); goog.require('shaka.dash.DashParser'); +goog.require('shaka.drm.FairPlay'); goog.require('shaka.hls.HlsParser'); goog.require('shaka.mss.MssParser'); goog.require('shaka.log'); From 2340794161aa8fa476f25b1c5e2133ddd8edb673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=CC=81lvaro=20Velad=20Galva=CC=81n?= Date: Thu, 6 Mar 2025 11:08:39 +0100 Subject: [PATCH 2/6] Update config --- lib/util/player_configuration.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index be29a4e8d1..ba7cc0d315 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -11,12 +11,12 @@ goog.require('shaka.abr.SimpleAbrManager'); goog.require('shaka.config.AutoShowText'); goog.require('shaka.config.CodecSwitchingStrategy'); goog.require('shaka.drm.DrmUtils'); +goog.require('shaka.drm.FairPlay'); goog.require('shaka.log'); goog.require('shaka.media.Capabilities'); goog.require('shaka.media.PreferenceBasedCriteria'); goog.require('shaka.net.NetworkingEngine'); goog.require('shaka.util.ConfigUtils'); -goog.require('shaka.util.FairPlayUtils'); goog.require('shaka.util.LanguageUtils'); goog.require('shaka.util.ManifestParserUtils'); goog.require('shaka.util.Platform'); @@ -80,8 +80,8 @@ shaka.util.PlayerConfiguration = class { initDataType == 'skd') { const cert = drmInfo.serverCertificate; const contentId = - shaka.util.FairPlayUtils.defaultGetContentId(initData); - initData = shaka.util.FairPlayUtils.initDataTransform( + shaka.drm.FairPlay.defaultGetContentId(initData); + initData = shaka.drm.FairPlay.initDataTransform( initData, contentId, cert); } return initData; From 22cfea64e6e8c706a76d9273f29cfccdb8d4eef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=CC=81lvaro=20Velad=20Galva=CC=81n?= Date: Thu, 6 Mar 2025 11:09:27 +0100 Subject: [PATCH 3/6] Update docs --- docs/tutorials/fairplay.md | 48 ++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/docs/tutorials/fairplay.md b/docs/tutorials/fairplay.md index b4b927d171..b85a1e00e6 100644 --- a/docs/tutorials/fairplay.md +++ b/docs/tutorials/fairplay.md @@ -86,7 +86,7 @@ player.configure('drm.initDataTransform', (initData, initDataType, drmInfo) => { const skdUri = shaka.util.StringUtils.fromBytesAutoDetect(initData); const contentId = getMyContentId(skdUri); const cert = drmInfo.serverCertificate; - return shaka.util.FairPlayUtils.initDataTransform(initData, contentId, cert); + return shaka.drm.shaka.drm.FairPlay.initDataTransform(initData, contentId, cert); }); ``` @@ -139,11 +139,10 @@ Note: Some providers support both Modern EME and legacy Apple Media Keys. For integration with EZDRM the following can be used: ```js -const FairPlayUtils = shaka.util.FairPlayUtils; player.getNetworkingEngine() - .registerRequestFilter(FairPlayUtils.ezdrmFairPlayRequest); + .registerRequestFilter(shaka.drm.FairPlay.ezdrmFairPlayRequest); player.getNetworkingEngine() - .registerResponseFilter(FairPlayUtils.commonFairPlayResponse); + .registerResponseFilter(shaka.drm.FairPlay.commonFairPlayResponse); ``` Note: If the url of the license server has to undergo any transformation @@ -155,8 +154,7 @@ player.getNetworkingEngine().registerRequestFilter((type, request, context) => { return; } const uri = request.uris[0]; - const FairPlayUtils = shaka.util.FairPlayUtils; - const contentId = FairPlayUtils.defaultGetContentId(request.initData); + const contentId = shaka.drm.FairPlay.defaultGetContentId(request.initData); const newUri = uri.replace('^assetId^', contentId); request.uris = [newUri]; request.headers['Content-Type'] = 'application/octet-stream' @@ -169,13 +167,12 @@ For integration with EZDRM the following can be used: ```js shaka.polyfill.PatchedMediaKeysApple.install(); -const FairPlayUtils = shaka.util.FairPlayUtils; player.getNetworkingEngine() - .registerRequestFilter(FairPlayUtils.ezdrmFairPlayRequest); + .registerRequestFilter(shaka.drm.FairPlay.ezdrmFairPlayRequest); player.getNetworkingEngine() - .registerResponseFilter(FairPlayUtils.commonFairPlayResponse); + .registerResponseFilter(shaka.drm.FairPlay.commonFairPlayResponse); player.configure('drm.initDataTransform', - FairPlayUtils.ezdrmInitDataTransform); + shaka.drm.FairPlay.ezdrmInitDataTransform); ``` Note: If the url of the license server has to undergo any transformation @@ -187,8 +184,7 @@ player.getNetworkingEngine().registerRequestFilter((type, request, context) => { return; } const uri = request.uris[0]; - const FairPlayUtils = shaka.util.FairPlayUtils; - const contentId = FairPlayUtils.defaultGetContentId(request.initData); + const contentId = shaka.drm.FairPlay.defaultGetContentId(request.initData); const newUri = uri.replace('^assetId^', contentId); request.uris = [newUri]; request.headers['Content-Type'] = 'application/octet-stream' @@ -201,13 +197,12 @@ For integration with Verimatrix the following can be used: ```js shaka.polyfill.PatchedMediaKeysApple.install(); -const FairPlayUtils = shaka.util.FairPlayUtils; player.getNetworkingEngine() - .registerRequestFilter(FairPlayUtils.verimatrixFairPlayRequest); + .registerRequestFilter(shaka.drm.FairPlay.verimatrixFairPlayRequest); player.getNetworkingEngine() - .registerResponseFilter(FairPlayUtils.commonFairPlayResponse); + .registerResponseFilter(shaka.drm.FairPlay.commonFairPlayResponse); player.configure('drm.initDataTransform', - FairPlayUtils.verimatrixInitDataTransform); + shaka.drm.FairPlay.verimatrixInitDataTransform); ``` #### Conax (legacy Apple Media Keys) @@ -216,13 +211,12 @@ For integration with Conax the following can be used: ```js shaka.polyfill.PatchedMediaKeysApple.install(); -const FairPlayUtils = shaka.util.FairPlayUtils; player.getNetworkingEngine() - .registerRequestFilter(FairPlayUtils.conaxFairPlayRequest); + .registerRequestFilter(shaka.drm.FairPlay.conaxFairPlayRequest); player.getNetworkingEngine() - .registerResponseFilter(FairPlayUtils.commonFairPlayResponse); + .registerResponseFilter(shaka.drm.FairPlay.commonFairPlayResponse); player.configure('drm.initDataTransform', - FairPlayUtils.conaxInitDataTransform); + shaka.drm.FairPlay.conaxInitDataTransform); ``` #### ExpressPlay (legacy Apple Media Keys) @@ -231,13 +225,12 @@ For integration with ExpressPlay the following can be used: ```js shaka.polyfill.PatchedMediaKeysApple.install(); -const FairPlayUtils = shaka.util.FairPlayUtils; player.getNetworkingEngine() - .registerRequestFilter(FairPlayUtils.expressplayFairPlayRequest); + .registerRequestFilter(shaka.drm.FairPlay.expressplayFairPlayRequest); player.getNetworkingEngine() - .registerResponseFilter(FairPlayUtils.commonFairPlayResponse); + .registerResponseFilter(shaka.drm.FairPlay.commonFairPlayResponse); player.configure('drm.initDataTransform', - FairPlayUtils.expressplayInitDataTransform); + shaka.drm.FairPlay.expressplayInitDataTransform); ``` #### Nagra (legacy Apple Media Keys) @@ -254,12 +247,11 @@ For integration with Mux the following can be used: ```js shaka.polyfill.PatchedMediaKeysApple.install(); -const FairPlayUtils = shaka.util.FairPlayUtils; player.getNetworkingEngine() - .registerRequestFilter(FairPlayUtils.muxFairPlayRequest); + .registerRequestFilter(shaka.drm.FairPlay.muxFairPlayRequest); player.getNetworkingEngine() - .registerResponseFilter(FairPlayUtils.commonFairPlayResponse); + .registerResponseFilter(shaka.drm.FairPlay.commonFairPlayResponse); player.configure('drm.initDataTransform', - FairPlayUtils.muxInitDataTransform); + shaka.drm.FairPlay.muxInitDataTransform); ``` From d30b02397942739527510ff0ef22134e8e26e384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=CC=81lvaro=20Velad=20Galva=CC=81n?= Date: Thu, 6 Mar 2025 11:10:01 +0100 Subject: [PATCH 4/6] Update implementation --- lib/util/fairplay_utils.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/util/fairplay_utils.js b/lib/util/fairplay_utils.js index 43aeea525a..08b84952a1 100644 --- a/lib/util/fairplay_utils.js +++ b/lib/util/fairplay_utils.js @@ -15,6 +15,4 @@ goog.require('shaka.drm.FairPlay'); * @deprecated * @export */ -shaka.util.FairPlayUtils = class extends shaka.drm.FairPlay { - // DEPRECATED -}; +shaka.util.FairPlayUtils = shaka.drm.FairPlay; From 5c450fe83e4a9ea1818f044698ac7ceec968ec67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=CC=81lvaro=20Velad=20Galva=CC=81n?= Date: Thu, 6 Mar 2025 11:11:06 +0100 Subject: [PATCH 5/6] Fix typo --- docs/tutorials/fairplay.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/fairplay.md b/docs/tutorials/fairplay.md index b85a1e00e6..3ca611d442 100644 --- a/docs/tutorials/fairplay.md +++ b/docs/tutorials/fairplay.md @@ -86,7 +86,7 @@ player.configure('drm.initDataTransform', (initData, initDataType, drmInfo) => { const skdUri = shaka.util.StringUtils.fromBytesAutoDetect(initData); const contentId = getMyContentId(skdUri); const cert = drmInfo.serverCertificate; - return shaka.drm.shaka.drm.FairPlay.initDataTransform(initData, contentId, cert); + return shaka.drm.FairPlay.initDataTransform(initData, contentId, cert); }); ``` From 11b61630615818dfee8efcef3d2dc612118932ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=CC=81lvaro=20Velad=20Galva=CC=81n?= Date: Thu, 6 Mar 2025 11:20:59 +0100 Subject: [PATCH 6/6] Revert "Update implementation" This reverts commit d30b02397942739527510ff0ef22134e8e26e384. --- lib/util/fairplay_utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/util/fairplay_utils.js b/lib/util/fairplay_utils.js index 08b84952a1..43aeea525a 100644 --- a/lib/util/fairplay_utils.js +++ b/lib/util/fairplay_utils.js @@ -15,4 +15,6 @@ goog.require('shaka.drm.FairPlay'); * @deprecated * @export */ -shaka.util.FairPlayUtils = shaka.drm.FairPlay; +shaka.util.FairPlayUtils = class extends shaka.drm.FairPlay { + // DEPRECATED +};