mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
feat: add modern EME support for FairPlay (#3776)
Add support for HLS com.apple.streamingkeydelivery through MSE/EME implementation. Close #3346 ## Tests Tested on: - Mac 11.6 Safari 15.2 - iOS 15.2 Safari 15.2 - Mac 11.6 Chrome 96 (for potential regressions on Widevine keySystem) | Mode | DRM API | TS | CMAF (mono-key and multi-keys) |---|---|---|---| | file | EME | ✅ | ✅ | | file | Legacy-prefixed | ✅ | ✅ | | media-source | EME | **mux-js**: `encrypted` never fired<br />**real MSE**: `encrypted` event received, but with incorrect `sinf` initData (*1) | ✅ | | media-source | Legacy-prefixed | **mux-js**: `webkitneedkey` never fired<br/>**real MSE**: TBD | 🔴 fails to append media segment to SourceBuffer (init segment ok) `(video:4) – "failed fetch and append: code=3015"` | ## Support table | Mode | DRM API | TS | CMAF (mono-key and multi-keys) |---|---|---|---| | file | EME | ✅ | ✅ | | file | Legacy-prefixed | ✅ | ✅ | | media-source | EME | 🚫 `4040: HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED` | ✅ | | media-source | Legacy-prefixed | 🚫 `4041: HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED` |🚫 `4041: HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED` | ⚠️ Use EME APIs with multi-keys CMAF makes the video stalling with the audio continuing alone after a short time (~3 minutes in the stream, could be shorter, could be longer). Didn't find an explanation to that yet. I've observed the same behaviour with hls.js code so I don't think this is a player issue.
This commit is contained in:
@@ -171,7 +171,9 @@ NOTES:
|
||||
|HLS |**Y** |**Y** |**Y** ¹ | - |
|
||||
|
||||
NOTES:
|
||||
- ¹: We support FairPlay through Apple's native HLS player.
|
||||
- ¹: By default, FairPlay is handled using Apple's native HLS player, when on
|
||||
Safari. We do support FairPlay through MSE/EME, however. See the
|
||||
`streaming.useNativeHlsOnSafari` configuration value.
|
||||
|
||||
|
||||
## Media container and subtitle support
|
||||
|
||||
@@ -184,6 +184,11 @@ Microsoft Documentation: https://docs.microsoft.com/en-us/playready/overview/sec
|
||||
|
||||
NB: Audio Hardware DRM is not supported (PlayReady limitation)
|
||||
|
||||
##### FairPlay
|
||||
|
||||
Based on [Apple's Documentation](https://developer.apple.com/streaming/fps/),
|
||||
you should provide an empty string as robustness
|
||||
|
||||
##### Other key-systems
|
||||
|
||||
Values for other key systems are not known to us at this time.
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
# FairPlay Support
|
||||
|
||||
When using native `src=` playback, we support using FairPlay on Safari.
|
||||
We support FairPlay with EME on compatible environments or native `src=`.
|
||||
Adding FairPlay support involves a bit more work than other key systems.
|
||||
|
||||
|
||||
## Server certificate
|
||||
|
||||
All FairPlay content requires setting a server certificate. This is set in the
|
||||
Player configuration:
|
||||
All FairPlay content requires setting a server certificate. You can either
|
||||
provide it directly or set a serverCertificateUri for Shaka to fetch it for
|
||||
you.
|
||||
|
||||
```js
|
||||
const req = await fetch('https://example.com/cert.der');
|
||||
const cert = await req.arrayBuffer();
|
||||
|
||||
player.configure('drm.advanced.com\\.apple\\.fps\\.1_0.serverCertificate',
|
||||
player.configure('drm.advanced.com\\.apple\\.fps\\.serverCertificate',
|
||||
new Uint8Array(cert));
|
||||
```
|
||||
|
||||
```js
|
||||
player.configure('drm.advanced.com\\.apple\\.fps\\.serverCertificateUri',
|
||||
'https://example.com/cert.der');
|
||||
```
|
||||
|
||||
## Content ID
|
||||
|
||||
Note: This only applies when legacy Apple Media Keys is used.
|
||||
|
||||
Some FairPlay content use custom signaling for the content ID. The content ID
|
||||
is used by the browser to generate the license request. If you don't use the
|
||||
default content ID derivation, you need to specify a custom init data transform:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Externs for Shaka polyfills
|
||||
*
|
||||
* @externs
|
||||
*/
|
||||
|
||||
|
||||
/** @type {boolean} */
|
||||
window.shakaMediaKeysPolyfill;
|
||||
@@ -934,8 +934,9 @@ shaka.extern.ManifestConfiguration;
|
||||
* @property {boolean} useNativeHlsOnSafari
|
||||
* Desktop Safari has both MediaSource and their native HLS implementation.
|
||||
* Depending on the application's needs, it may prefer one over the other.
|
||||
* Examples: FairPlay is only supported via Safari's native HLS, but it
|
||||
* doesn't have an API for selecting specific tracks.
|
||||
* Warning when disabled: Where single-key DRM streams work fine, multi-keys
|
||||
* streams is showing unexpected behaviours (stall, audio playing with video
|
||||
* freezes, ...). Use with care.
|
||||
* @property {number} inaccurateManifestTolerance
|
||||
* The maximum difference, in seconds, between the times in the manifest and
|
||||
* the times in the segments. Larger values allow us to compensate for more
|
||||
|
||||
+53
-19
@@ -40,6 +40,7 @@ goog.require('shaka.util.Networking');
|
||||
goog.require('shaka.util.OperationManager');
|
||||
goog.require('shaka.util.Pssh');
|
||||
goog.require('shaka.util.Timer');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.requireType('shaka.hls.Segment');
|
||||
|
||||
|
||||
@@ -1391,6 +1392,21 @@ shaka.hls.HlsParser = class {
|
||||
shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY);
|
||||
}
|
||||
|
||||
/** @type {!Array.<!shaka.hls.Tag>} */
|
||||
const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
|
||||
'EXT-X-DEFINE');
|
||||
|
||||
const mediaVariables = this.parseMediaVariables_(variablesTags);
|
||||
|
||||
goog.asserts.assert(playlist.segments != null,
|
||||
'Media playlist should have segments!');
|
||||
|
||||
this.determinePresentationType_(playlist);
|
||||
|
||||
/** @type {string} */
|
||||
const mimeType = await this.guessMimeType_(type, codecs, playlist,
|
||||
mediaVariables);
|
||||
|
||||
/** @type {!Array.<!shaka.hls.Tag>} */
|
||||
const drmTags = [];
|
||||
if (playlist.segments) {
|
||||
@@ -1425,7 +1441,7 @@ shaka.hls.HlsParser = class {
|
||||
const drmParser =
|
||||
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
|
||||
|
||||
const drmInfo = drmParser ? drmParser(drmTag) : null;
|
||||
const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
|
||||
if (drmInfo) {
|
||||
if (drmInfo.keyIds) {
|
||||
for (const keyId of drmInfo.keyIds) {
|
||||
@@ -1446,21 +1462,6 @@ shaka.hls.HlsParser = class {
|
||||
shaka.util.Error.Code.HLS_KEYFORMATS_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
/** @type {!Array.<!shaka.hls.Tag>} */
|
||||
const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
|
||||
'EXT-X-DEFINE');
|
||||
|
||||
const mediaVariables = this.parseMediaVariables_(variablesTags);
|
||||
|
||||
goog.asserts.assert(playlist.segments != null,
|
||||
'Media playlist should have segments!');
|
||||
|
||||
this.determinePresentationType_(playlist);
|
||||
|
||||
/** @type {string} */
|
||||
const mimeType = await this.guessMimeType_(type, codecs, playlist,
|
||||
mediaVariables);
|
||||
|
||||
// MediaSource expects no codec strings combined with raw formats.
|
||||
// TODO(#2337): Instead, create a Stream flag indicating a raw format.
|
||||
if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
|
||||
@@ -2807,6 +2808,41 @@ shaka.hls.HlsParser = class {
|
||||
return op.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!shaka.hls.Tag} drmTag
|
||||
* @param {string} mimeType
|
||||
* @return {?shaka.extern.DrmInfo}
|
||||
* @private
|
||||
*/
|
||||
static fairplayDrmParser_(drmTag, mimeType) {
|
||||
if (mimeType == 'video/mp2t') {
|
||||
throw new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.MANIFEST,
|
||||
shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
if (shaka.util.Platform.isMediaKeysPolyfilled()) {
|
||||
throw new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.MANIFEST,
|
||||
shaka.util.Error.Code
|
||||
.HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
/*
|
||||
* Even if we're not able to construct initData through the HLS tag, adding
|
||||
* a DRMInfo will allow DRM Engine to request a media key system access
|
||||
* with the correct keySystem and initDataType
|
||||
*/
|
||||
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
||||
'com.apple.fps', [
|
||||
{initDataType: 'sinf', initData: new Uint8Array(0)},
|
||||
]);
|
||||
|
||||
return drmInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!shaka.hls.Tag} drmTag
|
||||
* @return {?shaka.extern.DrmInfo}
|
||||
@@ -3029,7 +3065,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {function(!shaka.hls.Tag):?shaka.extern.DrmInfo}
|
||||
* @typedef {function(!shaka.hls.Tag, string):?shaka.extern.DrmInfo}
|
||||
* @private
|
||||
*/
|
||||
shaka.hls.HlsParser.DrmParser_;
|
||||
@@ -3040,10 +3076,8 @@ shaka.hls.HlsParser.DrmParser_;
|
||||
* @private
|
||||
*/
|
||||
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
|
||||
/* TODO: https://github.com/google/shaka-player/issues/382
|
||||
'com.apple.streamingkeydelivery':
|
||||
shaka.hls.HlsParser.fairplayDrmParser_,
|
||||
*/
|
||||
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
|
||||
shaka.hls.HlsParser.widevineDrmParser_,
|
||||
'com.microsoft.playready':
|
||||
|
||||
+111
-32
@@ -128,6 +128,9 @@ shaka.media.DrmEngine = class {
|
||||
|
||||
/** @private {boolean} */
|
||||
this.srcEquals_ = false;
|
||||
|
||||
/** @private {Promise} */
|
||||
this.mediaKeysAttached_ = null;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
@@ -184,6 +187,7 @@ shaka.media.DrmEngine = class {
|
||||
this.onError_ = () => {};
|
||||
this.playerInterface_ = null;
|
||||
this.srcEquals_ = false;
|
||||
this.mediaKeysAttached_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,7 +391,59 @@ shaka.media.DrmEngine = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach MediaKeys to the video element and start processing events.
|
||||
* Attach MediaKeys to the video element
|
||||
* @return {Promise}
|
||||
* @private
|
||||
*/
|
||||
async attachMediaKeys_() {
|
||||
if (this.video_.mediaKeys) {
|
||||
return;
|
||||
}
|
||||
|
||||
// An attach process has already started, let's wait it out
|
||||
if (this.mediaKeysAttached_) {
|
||||
await this.mediaKeysAttached_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.mediaKeysAttached_ = this.video_.setMediaKeys(this.mediaKeys_);
|
||||
|
||||
await this.mediaKeysAttached_;
|
||||
} catch (exception) {
|
||||
goog.asserts.assert(exception instanceof Error, 'Wrong error type!');
|
||||
|
||||
this.onError_(new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.DRM,
|
||||
shaka.util.Error.Code.FAILED_TO_ATTACH_TO_VIDEO,
|
||||
exception.message));
|
||||
}
|
||||
|
||||
this.destroyer_.ensureNotDestroyed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes encrypted event and start licence challenging
|
||||
* @return {!Promise}
|
||||
* @private
|
||||
*/
|
||||
async onEncryptedEvent_(event) {
|
||||
/**
|
||||
* MediaKeys should be added when receiving an encrypted event. Setting
|
||||
* mediaKeys before could result into encrypted event not being fired on
|
||||
* some browsers
|
||||
*/
|
||||
await this.attachMediaKeys_();
|
||||
|
||||
this.newInitData(
|
||||
event.initDataType,
|
||||
shaka.util.BufferUtils.toUint8(event.initData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing events.
|
||||
* @param {HTMLMediaElement} video
|
||||
* @return {!Promise}
|
||||
*/
|
||||
@@ -420,27 +476,30 @@ shaka.media.DrmEngine = class {
|
||||
() => this.closeOpenSessions_());
|
||||
}
|
||||
|
||||
let setMediaKeys = this.video_.setMediaKeys(this.mediaKeys_);
|
||||
setMediaKeys = setMediaKeys.catch((exception) => {
|
||||
goog.asserts.assert(exception instanceof Error, 'Wrong error type!');
|
||||
return Promise.reject(new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.DRM,
|
||||
shaka.util.Error.Code.FAILED_TO_ATTACH_TO_VIDEO,
|
||||
exception.message));
|
||||
});
|
||||
const manifestInitData = this.currentDrmInfo_.initData.find(
|
||||
(initDataOverride) => initDataOverride.initData.length > 0);
|
||||
|
||||
await setMediaKeys;
|
||||
this.destroyer_.ensureNotDestroyed();
|
||||
/**
|
||||
* We can attach media keys before the playback actually begins when:
|
||||
* - Using legacy implementations requires MediaKeys to be set before
|
||||
* having webkitneedkey / msneedkey event, which will be translated as
|
||||
* an encrypted event by the polyfills
|
||||
* - Some initData already has been generated (through the manifest)
|
||||
* - In case of an offline session
|
||||
*/
|
||||
if (manifestInitData ||
|
||||
shaka.util.Platform.isMediaKeysPolyfilled() ||
|
||||
this.offlineSessionIds_.length) {
|
||||
await this.attachMediaKeys_();
|
||||
}
|
||||
|
||||
this.createOrLoad();
|
||||
if (!this.currentDrmInfo_.initData.length &&
|
||||
!this.offlineSessionIds_.length) {
|
||||
// Explicit init data for any one stream or an offline session is
|
||||
// sufficient to suppress 'encrypted' events for all streams.
|
||||
const cb = (e) => this.newInitData(
|
||||
e.initDataType, shaka.util.BufferUtils.toUint8(e.initData));
|
||||
this.eventManager_.listen(this.video_, 'encrypted', cb);
|
||||
|
||||
// Explicit init data for any one stream or an offline session is
|
||||
// sufficient to suppress 'encrypted' events for all streams.
|
||||
if (!manifestInitData && !this.offlineSessionIds_.length) {
|
||||
this.eventManager_.listen(
|
||||
this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,6 +648,10 @@ shaka.media.DrmEngine = class {
|
||||
* @param {!Uint8Array} initData
|
||||
*/
|
||||
newInitData(initDataType, initData) {
|
||||
if (!initData.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Suppress duplicate init data.
|
||||
// Note that some init data are extremely large and can't portably be used
|
||||
// as keys in a dictionary.
|
||||
@@ -1188,20 +1251,26 @@ shaka.media.DrmEngine = class {
|
||||
};
|
||||
this.activeSessions_.set(session, metadata);
|
||||
|
||||
try {
|
||||
initData = this.config_.initDataTransform(
|
||||
initData, initDataType, this.currentDrmInfo_);
|
||||
} catch (error) {
|
||||
let shakaError = error;
|
||||
if (!(error instanceof shaka.util.Error)) {
|
||||
shakaError = new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.DRM,
|
||||
shaka.util.Error.Code.INIT_DATA_TRANSFORM_ERROR,
|
||||
error);
|
||||
/**
|
||||
* initDataTransform is only necessary when using legacy protection
|
||||
* APIs, so prevent doing any transform when using the EME HTML5 spec
|
||||
*/
|
||||
if (shaka.util.Platform.isMediaKeysPolyfilled()) {
|
||||
try {
|
||||
initData = this.config_.initDataTransform(
|
||||
initData, initDataType, this.currentDrmInfo_);
|
||||
} catch (error) {
|
||||
let shakaError = error;
|
||||
if (!(error instanceof shaka.util.Error)) {
|
||||
shakaError = new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.DRM,
|
||||
shaka.util.Error.Code.INIT_DATA_TRANSFORM_ERROR,
|
||||
error);
|
||||
}
|
||||
this.onError_(shakaError);
|
||||
return;
|
||||
}
|
||||
this.onError_(shakaError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config_.logLicenseExchange) {
|
||||
@@ -1641,6 +1710,16 @@ shaka.media.DrmEngine = class {
|
||||
|
||||
const testSystem = async (keySystem) => {
|
||||
try {
|
||||
// Our Polyfill will reject anything apart com.apple.fps key systems.
|
||||
// It seems the Safari modern EME API will allow to request a
|
||||
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
|
||||
// session but playback will never start
|
||||
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
|
||||
if (keySystem === 'org.w3.clearkey' &&
|
||||
shaka.util.Platform.isSafari()) {
|
||||
throw new Error('Unsupported keySystem');
|
||||
}
|
||||
|
||||
const access = await navigator.requestMediaKeySystemAccess(
|
||||
keySystem, configs);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ goog.require('shaka.util.FakeEventTarget');
|
||||
goog.require('shaka.util.MediaReadyState');
|
||||
goog.require('shaka.util.PublicPromise');
|
||||
goog.require('shaka.util.StringUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,10 +36,6 @@ shaka.polyfill.PatchedMediaKeysApple = class {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Unprefixed EME disabled. See:
|
||||
https://github.com/google/shaka-player/pull/3021#issuecomment-766999811
|
||||
|
||||
// Only tested in Safari 14.
|
||||
const safariVersion = shaka.util.Platform.safariVersion();
|
||||
if (navigator.requestMediaKeySystemAccess &&
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
@@ -47,7 +44,6 @@ shaka.polyfill.PatchedMediaKeysApple = class {
|
||||
// Unprefixed EME is preferable.
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
shaka.log.info('Using Apple-prefixed EME');
|
||||
|
||||
@@ -69,6 +65,8 @@ shaka.polyfill.PatchedMediaKeysApple = class {
|
||||
window.MediaKeySystemAccess = PatchedMediaKeysApple.MediaKeySystemAccess;
|
||||
navigator.requestMediaKeySystemAccess =
|
||||
PatchedMediaKeysApple.requestMediaKeySystemAccess;
|
||||
|
||||
window.shakaMediaKeysPolyfill = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,8 @@ shaka.polyfill.PatchedMediaKeysMs = class {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
HTMLMediaElement.prototype.setMediaKeys =
|
||||
PatchedMediaKeysMs.MediaKeySystemAccess.setMediaKeys;
|
||||
|
||||
window.shakaMediaKeysPolyfill = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,8 @@ shaka.polyfill.PatchedMediaKeysNop = class {
|
||||
// These are not usable, but allow Player.isBrowserSupported to pass.
|
||||
window.MediaKeys = PatchedMediaKeysNop.MediaKeys;
|
||||
window.MediaKeySystemAccess = PatchedMediaKeysNop.MediaKeySystemAccess;
|
||||
|
||||
window.shakaMediaKeysPolyfill = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,6 +72,8 @@ shaka.polyfill.PatchedMediaKeysWebkit = class {
|
||||
PatchedMediaKeysWebkit.setMediaKeys;
|
||||
window.MediaKeys = PatchedMediaKeysWebkit.MediaKeys;
|
||||
window.MediaKeySystemAccess = PatchedMediaKeysWebkit.MediaKeySystemAccess;
|
||||
|
||||
window.shakaMediaKeysPolyfill = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -686,6 +686,17 @@ shaka.util.Error.Code = {
|
||||
*/
|
||||
'HLS_VARIABLE_NOT_FOUND': 4039,
|
||||
|
||||
/**
|
||||
* We do not support playing encrypted mp2t with MSE
|
||||
*/
|
||||
'HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED': 4040,
|
||||
|
||||
/**
|
||||
* We do not support playing encrypted content (different than mp2t) with MSE
|
||||
* and legacy Apple MediaKeys API.
|
||||
*/
|
||||
'HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED': 4041,
|
||||
|
||||
|
||||
// RETIRED: 'INCONSISTENT_BUFFER_STATE': 5000,
|
||||
// RETIRED: 'INVALID_SEGMENT_INDEX': 5001,
|
||||
|
||||
@@ -228,6 +228,16 @@ shaka.util.Platform = class {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Apple Safari
|
||||
* or Safari-based iOS browsers.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isSafari() {
|
||||
return !!shaka.util.Platform.safariVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses if the platform is a mobile one (iOS or Android).
|
||||
*
|
||||
@@ -318,6 +328,19 @@ shaka.util.Platform = class {
|
||||
const Platform = shaka.util.Platform;
|
||||
return Platform.isTizen() || Platform.isXboxOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if MediaKeys is polyfilled
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isMediaKeysPolyfilled() {
|
||||
if (window.shakaMediaKeysPolyfill) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/** @private {shaka.util.Timer} */
|
||||
|
||||
@@ -2231,6 +2231,42 @@ describe('HlsParser', () => {
|
||||
await testHlsParser(master, media, manifest);
|
||||
});
|
||||
|
||||
it('constructs DrmInfo for FairPlay', async () => {
|
||||
const master = [
|
||||
'#EXTM3U\n',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
||||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||||
'video\n',
|
||||
].join('');
|
||||
|
||||
const media = [
|
||||
'#EXTM3U\n',
|
||||
'#EXT-X-TARGETDURATION:6\n',
|
||||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||||
'KEYFORMAT="com.apple.streamingkeydelivery",',
|
||||
'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
|
||||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||||
'#EXTINF:5,\n',
|
||||
'#EXT-X-BYTERANGE:121090@616\n',
|
||||
'main.mp4',
|
||||
].join('');
|
||||
|
||||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||||
manifest.anyTimeline();
|
||||
manifest.addPartialVariant((variant) => {
|
||||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||||
stream.encrypted = true;
|
||||
stream.addDrmInfo('com.apple.fps', (drmInfo) => {
|
||||
drmInfo.addInitData('sinf', new Uint8Array(0));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await testHlsParser(master, media, manifest);
|
||||
});
|
||||
|
||||
it('falls back to mp4 if HEAD request fails', async () => {
|
||||
const master = [
|
||||
'#EXTM3U\n',
|
||||
@@ -2427,6 +2463,34 @@ describe('HlsParser', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('if FairPlay encryption with MSE and mp2t content', async () => {
|
||||
const master = [
|
||||
'#EXTM3U\n',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
||||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||||
'video\n',
|
||||
].join('');
|
||||
|
||||
const media = [
|
||||
'#EXTM3U\n',
|
||||
'#EXT-X-TARGETDURATION:6\n',
|
||||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||||
'KEYFORMAT="com.apple.streamingkeydelivery",',
|
||||
'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
|
||||
'#EXTINF:5,\n',
|
||||
'#EXT-X-BYTERANGE:121090@616\n',
|
||||
'main.ts',
|
||||
].join('');
|
||||
|
||||
const error = new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.MANIFEST,
|
||||
Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
||||
|
||||
await verifyError(master, media, error);
|
||||
});
|
||||
|
||||
describe('if required tags are missing', () => {
|
||||
/**
|
||||
* @param {string} master
|
||||
|
||||
+87
-121
@@ -776,11 +776,6 @@ describe('DrmEngine', () => {
|
||||
expect(mockVideo.setMediaKeys).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sets MediaKeys for encrypted content', async () => {
|
||||
await initAndAttach();
|
||||
expect(mockVideo.setMediaKeys).toHaveBeenCalledWith(mockMediaKeys);
|
||||
});
|
||||
|
||||
it('sets server certificate if present in config', async () => {
|
||||
const cert = new Uint8Array(1);
|
||||
config.advanced['drm.abc'] = createAdvancedConfig(cert);
|
||||
@@ -877,7 +872,7 @@ describe('DrmEngine', () => {
|
||||
/** @type {!Uint8Array} */
|
||||
const initData1 = new Uint8Array(5);
|
||||
/** @type {!Uint8Array} */
|
||||
const initData2 = new Uint8Array(0);
|
||||
const initData2 = new Uint8Array(1);
|
||||
/** @type {!Uint8Array} */
|
||||
const initData3 = new Uint8Array(10);
|
||||
|
||||
@@ -1019,12 +1014,23 @@ describe('DrmEngine', () => {
|
||||
mockVideo.setMediaKeys.and.returnValue(Promise.reject(
|
||||
new Error('whoops!')));
|
||||
|
||||
const expected = Util.jasmineError(new shaka.util.Error(
|
||||
tweakDrmInfos((drmInfos) => {
|
||||
drmInfos[0].initData = [
|
||||
{initData: new Uint8Array(1), initDataType: 'cenc', keyId: null},
|
||||
];
|
||||
});
|
||||
|
||||
onErrorSpy.and.stub();
|
||||
|
||||
await initAndAttach();
|
||||
|
||||
expect(onErrorSpy).toHaveBeenCalled();
|
||||
const error = onErrorSpy.calls.argsFor(0)[0];
|
||||
shaka.test.Util.expectToEqualError(error, new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.DRM,
|
||||
shaka.util.Error.Code.FAILED_TO_ATTACH_TO_VIDEO,
|
||||
'whoops!'));
|
||||
await expectAsync(initAndAttach()).toBeRejectedWith(expected);
|
||||
});
|
||||
|
||||
it('fails with an error if setServerCertificate fails', async () => {
|
||||
@@ -1087,10 +1093,8 @@ describe('DrmEngine', () => {
|
||||
const initData1 = new Uint8Array(1);
|
||||
const initData2 = new Uint8Array(2);
|
||||
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'cenc', initData: initData2, keyId: null});
|
||||
await sendEncryptedEvent('webm', initData1);
|
||||
await sendEncryptedEvent('cenc', initData2);
|
||||
|
||||
expect(mockMediaKeys.createSession).toHaveBeenCalledTimes(2);
|
||||
expect(session1.generateRequest)
|
||||
@@ -1101,32 +1105,23 @@ describe('DrmEngine', () => {
|
||||
|
||||
it('suppresses duplicate initDatas', async () => {
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
const initData2 = new Uint8Array(1); // identical to initData1
|
||||
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'cenc', initData: initData2, keyId: null});
|
||||
const initData1 = new Uint8Array(1);
|
||||
|
||||
await sendEncryptedEvent('webm', initData1);
|
||||
await sendEncryptedEvent('cenc'); // identical to webm initData
|
||||
|
||||
expect(mockMediaKeys.createSession).toHaveBeenCalledTimes(1);
|
||||
expect(session1.generateRequest)
|
||||
.toHaveBeenCalledWith('webm', initData1);
|
||||
});
|
||||
|
||||
it('is ignored when init data is in DrmInfo', async () => {
|
||||
// Set up an init data override in the manifest:
|
||||
tweakDrmInfos((drmInfos) => {
|
||||
drmInfos[0].initData = [
|
||||
{initData: new Uint8Array(0), initDataType: 'cenc', keyId: null},
|
||||
];
|
||||
});
|
||||
|
||||
it('set media keys when not already done at startup', async () => {
|
||||
await initAndAttach();
|
||||
// We already created a session for the init data override.
|
||||
await sendEncryptedEvent();
|
||||
|
||||
expect(mockVideo.setMediaKeys).toHaveBeenCalledTimes(1);
|
||||
expect(mockMediaKeys.createSession).toHaveBeenCalledTimes(1);
|
||||
// We aren't even listening for 'encrypted' events.
|
||||
expect(mockVideo.on['encrypted']).toBe(undefined);
|
||||
});
|
||||
|
||||
it('dispatches an error if createSession fails', async () => {
|
||||
@@ -1134,9 +1129,7 @@ describe('DrmEngine', () => {
|
||||
onErrorSpy.and.stub();
|
||||
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
expect(onErrorSpy).toHaveBeenCalled();
|
||||
const error = onErrorSpy.calls.argsFor(0)[0];
|
||||
@@ -1156,9 +1149,7 @@ describe('DrmEngine', () => {
|
||||
onErrorSpy.and.stub();
|
||||
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
expect(onErrorSpy).toHaveBeenCalled();
|
||||
const error = onErrorSpy.calls.argsFor(0)[0];
|
||||
@@ -1172,9 +1163,7 @@ describe('DrmEngine', () => {
|
||||
describe('message', () => {
|
||||
it('is listened for', async () => {
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
expect(session1.addEventListener).toHaveBeenCalledWith(
|
||||
'message', jasmine.any(Function), jasmine.anything());
|
||||
@@ -1214,9 +1203,7 @@ describe('DrmEngine', () => {
|
||||
onErrorSpy.and.stub();
|
||||
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
// Simulate a permission error from the web server.
|
||||
const netError = new shaka.util.Error(
|
||||
@@ -1252,9 +1239,7 @@ describe('DrmEngine', () => {
|
||||
async function sendMessageTest(
|
||||
expectedUrl, messageType = 'license-request') {
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const operation = shaka.util.AbortableOperation.completed({});
|
||||
fakeNetEngine.request.and.returnValue(operation);
|
||||
@@ -1276,9 +1261,7 @@ describe('DrmEngine', () => {
|
||||
describe('keystatuseschange', () => {
|
||||
it('is listened for', async () => {
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
expect(session1.addEventListener).toHaveBeenCalledWith(
|
||||
'keystatuseschange', jasmine.any(Function), jasmine.anything());
|
||||
@@ -1286,9 +1269,7 @@ describe('DrmEngine', () => {
|
||||
|
||||
it('triggers callback', async () => {
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const keyId1 = makeKeyId(1);
|
||||
const keyId2 = makeKeyId(2);
|
||||
@@ -1314,10 +1295,7 @@ describe('DrmEngine', () => {
|
||||
// See https://github.com/google/shaka-player/issues/1541
|
||||
it('does not update public key statuses before callback', async () => {
|
||||
await initAndAttach();
|
||||
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const keyId1 = makeKeyId(1);
|
||||
const keyId2 = makeKeyId(2);
|
||||
@@ -1394,9 +1372,7 @@ describe('DrmEngine', () => {
|
||||
await initAndAttach();
|
||||
expect(onErrorSpy).not.toHaveBeenCalled();
|
||||
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const keyId1 = makeKeyId(1);
|
||||
const keyId2 = makeKeyId(2);
|
||||
@@ -1443,9 +1419,7 @@ describe('DrmEngine', () => {
|
||||
await initAndAttach();
|
||||
expect(onErrorSpy).not.toHaveBeenCalled();
|
||||
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const keyId1 = makeKeyId(1);
|
||||
const keyId2 = makeKeyId(2);
|
||||
@@ -1489,9 +1463,7 @@ describe('DrmEngine', () => {
|
||||
const license = new Uint8Array(0);
|
||||
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
fakeNetEngine.setResponseValue('http://abc.drm/license', license);
|
||||
const message = new Uint8Array(0);
|
||||
@@ -1550,9 +1522,8 @@ describe('DrmEngine', () => {
|
||||
|
||||
it('publishes an event if update succeeds', async () => {
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(1);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
session1.on['message']({target: session1, message: message});
|
||||
session1.update.and.returnValue(Promise.resolve());
|
||||
@@ -1568,9 +1539,7 @@ describe('DrmEngine', () => {
|
||||
const license = new Uint8Array(0);
|
||||
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
fakeNetEngine.setResponseValue('http://abc.drm/license', license);
|
||||
const message = new Uint8Array(0);
|
||||
@@ -1591,12 +1560,9 @@ describe('DrmEngine', () => {
|
||||
describe('destroy', () => {
|
||||
it('tears down MediaKeys and active sessions', async () => {
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
const initData2 = new Uint8Array(2);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData2, keyId: null});
|
||||
|
||||
await sendEncryptedEvent('webm');
|
||||
await sendEncryptedEvent('cenc', new Uint8Array(2));
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
session1.on['message']({target: session1, message: message});
|
||||
@@ -1621,12 +1587,8 @@ describe('DrmEngine', () => {
|
||||
drmEngine.configure(config);
|
||||
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
const initData2 = new Uint8Array(2);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData2, keyId: null});
|
||||
await sendEncryptedEvent('webm');
|
||||
await sendEncryptedEvent('cenc', new Uint8Array(2));
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
session1.on['message']({target: session1, message: message});
|
||||
@@ -1647,12 +1609,8 @@ describe('DrmEngine', () => {
|
||||
|
||||
it('swallows errors when closing sessions', async () => {
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
const initData2 = new Uint8Array(2);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData2, keyId: null});
|
||||
await sendEncryptedEvent('webm');
|
||||
await sendEncryptedEvent('cenc', new Uint8Array(2));
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
session1.on['message']({target: session1, message: message});
|
||||
@@ -1668,12 +1626,8 @@ describe('DrmEngine', () => {
|
||||
|
||||
it('swallows errors when clearing MediaKeys', async () => {
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
const initData2 = new Uint8Array(2);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData2, keyId: null});
|
||||
await sendEncryptedEvent('webm');
|
||||
await sendEncryptedEvent('cenc', new Uint8Array(2));
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
session1.on['message']({target: session1, message: message});
|
||||
@@ -1752,6 +1706,14 @@ describe('DrmEngine', () => {
|
||||
const p1 = new shaka.util.PublicPromise();
|
||||
mockVideo.setMediaKeys.and.returnValue(p1);
|
||||
|
||||
onErrorSpy.and.stub();
|
||||
|
||||
tweakDrmInfos((drmInfos) => {
|
||||
drmInfos[0].initData = [
|
||||
{initData: new Uint8Array(1), initDataType: 'cenc', keyId: null},
|
||||
];
|
||||
});
|
||||
|
||||
const init = expectAsync(initAndAttach()).toBeRejected();
|
||||
|
||||
await shaka.test.Util.shortDelay();
|
||||
@@ -1765,10 +1727,12 @@ describe('DrmEngine', () => {
|
||||
const destroy = drmEngine.destroy();
|
||||
const fail = async () => {
|
||||
await shaka.test.Util.shortDelay();
|
||||
p1.reject(new Error(''));
|
||||
shaka.log.warning('fail');
|
||||
p1.reject(new Error('titi'));
|
||||
};
|
||||
const success = async () => {
|
||||
await shaka.test.Util.shortDelay();
|
||||
shaka.log.warning('success');
|
||||
p2.resolve();
|
||||
};
|
||||
await Promise.all([init, destroy, fail(), success()]);
|
||||
@@ -1780,6 +1744,12 @@ describe('DrmEngine', () => {
|
||||
const p1 = new shaka.util.PublicPromise();
|
||||
mockVideo.setMediaKeys.and.returnValue(p1);
|
||||
|
||||
tweakDrmInfos((drmInfos) => {
|
||||
drmInfos[0].initData = [
|
||||
{initData: new Uint8Array(1), initDataType: 'cenc', keyId: null},
|
||||
];
|
||||
});
|
||||
|
||||
const init = expectAsync(initAndAttach()).toBeRejected();
|
||||
|
||||
await shaka.test.Util.shortDelay();
|
||||
@@ -1857,9 +1827,7 @@ describe('DrmEngine', () => {
|
||||
session1.generateRequest.and.returnValue(p);
|
||||
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
// We are now blocked on generateRequest:
|
||||
expect(session1.generateRequest).toHaveBeenCalledTimes(1);
|
||||
@@ -1878,9 +1846,7 @@ describe('DrmEngine', () => {
|
||||
fakeNetEngine.request.and.returnValue(operation);
|
||||
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
session1.on['message']({target: session1, message: message});
|
||||
@@ -1909,9 +1875,7 @@ describe('DrmEngine', () => {
|
||||
fakeNetEngine.request.and.returnValue(operation);
|
||||
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
session1.on['message']({target: session1, message: message});
|
||||
@@ -1939,9 +1903,7 @@ describe('DrmEngine', () => {
|
||||
session1.update.and.returnValue(p);
|
||||
|
||||
await initAndAttach();
|
||||
const initData1 = new Uint8Array(1);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
session1.on['message']({target: session1, message: message});
|
||||
@@ -1975,12 +1937,8 @@ describe('DrmEngine', () => {
|
||||
session1.close.and.returnValue(rejected);
|
||||
session2.close.and.returnValue(rejected);
|
||||
|
||||
const initData1 = new Uint8Array(1);
|
||||
const initData2 = new Uint8Array(2);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData1, keyId: null});
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData2, keyId: null});
|
||||
await sendEncryptedEvent('webm');
|
||||
await sendEncryptedEvent('cenc', new Uint8Array(2));
|
||||
|
||||
// Still resolve these since we are mocking close and closed. This
|
||||
// ensures DrmEngine is in the correct state.
|
||||
@@ -2205,9 +2163,7 @@ describe('DrmEngine', () => {
|
||||
mockVideo.paused = true;
|
||||
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const operation = shaka.util.AbortableOperation.completed({});
|
||||
fakeNetEngine.request.and.returnValue(operation);
|
||||
@@ -2233,9 +2189,7 @@ describe('DrmEngine', () => {
|
||||
mockVideo.paused = true;
|
||||
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const operation = shaka.util.AbortableOperation.completed({});
|
||||
fakeNetEngine.request.and.returnValue(operation);
|
||||
@@ -2350,10 +2304,9 @@ describe('DrmEngine', () => {
|
||||
session1.expiration = NaN;
|
||||
|
||||
await initAndAttach();
|
||||
const initData = new Uint8Array(0);
|
||||
await sendEncryptedEvent();
|
||||
|
||||
const message = new Uint8Array(0);
|
||||
mockVideo.on['encrypted'](
|
||||
{initDataType: 'webm', initData: initData, keyId: null});
|
||||
session1.on['message']({target: session1, message: message});
|
||||
session1.update.and.returnValue(Promise.resolve());
|
||||
});
|
||||
@@ -2516,4 +2469,17 @@ describe('DrmEngine', () => {
|
||||
callback(manifest.variants[0].audio.drmInfos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} initDataType
|
||||
* @param {Uint8Array} initData
|
||||
* @param {string|null} keyId
|
||||
*/
|
||||
async function sendEncryptedEvent(
|
||||
initDataType = 'cenc', initData = new Uint8Array(1), keyId = null) {
|
||||
mockVideo.on['encrypted']({initDataType, initData, keyId});
|
||||
|
||||
await Util.shortDelay();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -433,7 +433,7 @@ shaka.test.ManifestGenerator.DrmInfo = class {
|
||||
if (!this.initData) {
|
||||
this.initData = [];
|
||||
}
|
||||
this.initData.push({initData: buffer, initDataType: type, keyId: null});
|
||||
this.initData.push({initData: buffer, initDataType: type});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user