shaka-player 3.3.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-test.yaml +0 -1
- package/.github/workflows/demo-version-index.yaml +8 -0
- package/.github/workflows/release-please.yaml +23 -2
- package/.github/workflows/selenium-lab-tests.yaml +0 -1
- package/.github/workflows/sync-labels.yaml +33 -0
- package/.github/workflows/update-issues.yaml +29 -0
- package/.github/workflows/validate-pr-title.yaml +35 -0
- package/AUTHORS +1 -0
- package/CHANGELOG.md +198 -32
- package/CONTRIBUTORS +1 -0
- package/README.md +10 -7
- package/app-engine/demo-version-index/README.md +3 -3
- package/app-engine/demo-version-index/app.yaml +27 -1
- package/app-engine/demo-version-index/generate.py +164 -0
- package/app-engine/demo-version-index/main.py +4 -155
- package/app-engine/demo-version-index/requirements.txt +0 -1
- package/build/README.md +2 -2
- package/build/compiler.py +1 -1
- package/build/generateTsDefs.py +0 -2
- package/build/shaka-lab.yaml +0 -2
- package/build/test.py +7 -1
- package/build/types/core +0 -1
- package/build/types/polyfill +0 -3
- package/demo/common/asset.js +17 -1
- package/demo/common/assets.js +29 -0
- package/demo/common/message_ids.js +6 -3
- package/demo/config.js +10 -11
- package/demo/custom.js +39 -0
- package/demo/demo_utils.js +0 -1
- package/demo/locales/en.json +6 -3
- package/demo/locales/source.json +22 -10
- package/demo/main.js +9 -6
- package/demo/search.js +2 -1
- package/dist/controls.css +1 -1
- package/dist/demo.compiled.debug.js +71 -64
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +71 -64
- package/dist/demo.compiled.map +3 -3
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +16 -20
- package/dist/locales.js +31 -29
- package/dist/receiver.compiled.debug.js +32 -28
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +32 -28
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +78 -83
- package/dist/shaka-player.compiled.debug.d.ts +78 -83
- package/dist/shaka-player.compiled.debug.externs.js +126 -126
- package/dist/shaka-player.compiled.debug.js +426 -483
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +126 -126
- package/dist/shaka-player.compiled.js +838 -856
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +80 -83
- package/dist/shaka-player.ui.debug.d.ts +80 -83
- package/dist/shaka-player.ui.debug.externs.js +134 -126
- package/dist/shaka-player.ui.debug.js +488 -543
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +134 -126
- package/dist/shaka-player.ui.js +980 -994
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/config.md +0 -2
- package/docs/tutorials/drm-config.md +5 -0
- package/docs/tutorials/fairplay.md +7 -22
- package/docs/tutorials/faq.md +0 -11
- package/docs/tutorials/index.json +2 -8
- package/docs/tutorials/plugins.md +1 -3
- package/docs/tutorials/upgrade.md +83 -11
- package/externs/polyfill.js +15 -0
- package/externs/shaka/manifest.js +5 -1
- package/externs/shaka/net.js +12 -0
- package/externs/shaka/offline.js +5 -1
- package/externs/shaka/player.js +39 -32
- package/externs/shaka/text.js +12 -12
- package/externs/sourcebuffer.js +15 -0
- package/lib/ads/client_side_ad.js +13 -2
- package/lib/ads/client_side_ad_manager.js +22 -4
- package/lib/cast/cast_utils.js +0 -1
- package/lib/dash/content_protection.js +85 -5
- package/lib/dash/dash_parser.js +1 -1
- package/lib/hls/hls_parser.js +378 -743
- package/lib/hls/manifest_text_parser.js +2 -3
- package/lib/media/drm_engine.js +102 -53
- package/lib/media/gap_jumping_controller.js +10 -45
- package/lib/media/media_source_engine.js +87 -15
- package/lib/media/playhead.js +2 -5
- package/lib/media/presentation_timeline.js +1 -2
- package/lib/media/segment_index.js +13 -46
- package/lib/media/segment_reference.js +47 -1
- package/lib/media/streaming_engine.js +29 -11
- package/lib/media/time_ranges_utils.js +4 -9
- package/lib/net/networking_engine.js +3 -0
- package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/offline/storage.js +100 -132
- package/lib/player.js +17 -201
- package/lib/polyfill/media_capabilities.js +12 -6
- package/lib/polyfill/mediasource.js +0 -28
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
- package/lib/text/cue.js +0 -6
- package/lib/text/lrc_text_parser.js +8 -0
- package/lib/text/mp4_ttml_parser.js +8 -0
- package/lib/text/mp4_vtt_parser.js +8 -0
- package/lib/text/sbv_text_parser.js +8 -0
- package/lib/text/simple_text_displayer.js +1 -7
- package/lib/text/srt_text_parser.js +8 -0
- package/lib/text/ssa_text_parser.js +8 -0
- package/lib/text/text_engine.js +19 -3
- package/lib/text/ttml_text_parser.js +8 -0
- package/lib/text/ui_text_displayer.js +1 -7
- package/lib/text/vtt_text_parser.js +24 -2
- package/lib/text/web_vtt_generator.js +1 -7
- package/lib/util/error.js +9 -13
- package/lib/util/fairplay_utils.js +109 -0
- package/lib/util/functional.js +0 -42
- package/lib/util/mime_utils.js +2 -0
- package/lib/util/platform.js +45 -0
- package/lib/util/player_configuration.js +12 -6
- package/lib/util/pssh.js +23 -4
- package/lib/util/stream_utils.js +92 -22
- package/lib/util/uint8array_utils.js +0 -16
- package/lib/util/xml_utils.js +1 -1
- package/package.json +1 -1
- package/roadmap.md +19 -19
- package/shaka-player.uncompiled.js +0 -3
- package/test/cast/cast_utils_unit.js +1 -0
- package/test/dash/dash_parser_content_protection_unit.js +44 -0
- package/test/dash/dash_parser_manifest_unit.js +0 -24
- package/test/dash/dash_parser_segment_template_unit.js +3 -4
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_live_unit.js +99 -220
- package/test/hls/hls_parser_unit.js +515 -432
- package/test/media/drm_engine_unit.js +86 -120
- package/test/media/media_source_engine_integration.js +26 -3
- package/test/media/media_source_engine_unit.js +7 -6
- package/test/media/playhead_unit.js +27 -191
- package/test/media/segment_index_unit.js +15 -25
- package/test/media/streaming_engine_integration.js +4 -39
- package/test/media/streaming_engine_unit.js +3 -2
- package/test/media/time_ranges_utils_unit.js +0 -5
- package/test/offline/manifest_convert_unit.js +34 -4
- package/test/offline/offline_manifest_parser_unit.js +1 -0
- package/test/offline/storage_integration.js +5 -4
- package/test/player_external.js +0 -6
- package/test/player_integration.js +16 -0
- package/test/player_unit.js +26 -0
- package/test/test/util/fake_media_source_engine.js +11 -4
- package/test/test/util/manifest_generator.js +3 -1
- package/test/test/util/manifest_parser_util.js +4 -1
- package/test/test/util/offline_utils.js +1 -0
- package/test/test/util/streaming_engine_util.js +1 -0
- package/test/test/util/test_scheme.js +24 -1
- package/test/test/util/util.js +4 -4
- package/test/text/cue_integration.js +2 -2
- package/test/text/lrc_text_parser_unit.js +6 -7
- package/test/text/mp4_ttml_parser_unit.js +6 -4
- package/test/text/mp4_vtt_parser_unit.js +6 -5
- package/test/text/sbv_text_parser_unit.js +5 -6
- package/test/text/srt_text_parser_unit.js +5 -6
- package/test/text/ssa_text_parser_unit.js +17 -29
- package/test/text/text_engine_unit.js +45 -5
- package/test/text/ttml_text_parser_unit.js +77 -77
- package/test/text/vtt_text_parser_unit.js +55 -57
- package/test/ui/ui_integration.js +0 -16
- package/test/ui/ui_unit.js +27 -0
- package/test/util/cmcd_manager_unit.js +3 -0
- package/test/util/stream_utils_unit.js +23 -7
- package/ui/controls.js +65 -40
- package/ui/fullscreen_button.js +26 -6
- package/ui/less/containers.less +3 -3
- package/ui/locales/en.json +1 -0
- package/ui/locales/source.json +5 -0
- package/ui/resolution_selection.js +31 -33
- package/ui/ui.js +0 -17
- package/docs/tutorials/upgrade-v2-5.md +0 -441
- package/docs/tutorials/upgrade-v3.md +0 -38
- package/lib/polyfill/mathround.js +0 -49
- package/lib/polyfill/patchedmediakeys_apple.js +0 -744
- package/lib/polyfill/patchedmediakeys_ms.js +0 -698
- package/lib/util/mutex.js +0 -52
- package/test/util/functional_unit.js +0 -82
|
@@ -179,9 +179,8 @@ shaka.hls.ManifestTextParser = class {
|
|
|
179
179
|
segmentTags.push(currentMapTag);
|
|
180
180
|
}
|
|
181
181
|
// The URI appears after all of the tags describing the segment.
|
|
182
|
-
const segment =
|
|
183
|
-
|
|
184
|
-
partialSegmentTags);
|
|
182
|
+
const segment = new shaka.hls.Segment(absoluteSegmentUri, segmentTags,
|
|
183
|
+
partialSegmentTags);
|
|
185
184
|
segments.push(segment);
|
|
186
185
|
segmentTags = [];
|
|
187
186
|
partialSegmentTags = [];
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -14,7 +14,6 @@ goog.require('shaka.util.BufferUtils');
|
|
|
14
14
|
goog.require('shaka.util.Destroyer');
|
|
15
15
|
goog.require('shaka.util.Error');
|
|
16
16
|
goog.require('shaka.util.EventManager');
|
|
17
|
-
goog.require('shaka.util.FairPlayUtils');
|
|
18
17
|
goog.require('shaka.util.FakeEvent');
|
|
19
18
|
goog.require('shaka.util.IDestroyable');
|
|
20
19
|
goog.require('shaka.util.Iterables');
|
|
@@ -128,6 +127,9 @@ shaka.media.DrmEngine = class {
|
|
|
128
127
|
|
|
129
128
|
/** @private {boolean} */
|
|
130
129
|
this.srcEquals_ = false;
|
|
130
|
+
|
|
131
|
+
/** @private {Promise} */
|
|
132
|
+
this.mediaKeysAttached_ = null;
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
/** @override */
|
|
@@ -184,6 +186,7 @@ shaka.media.DrmEngine = class {
|
|
|
184
186
|
this.onError_ = () => {};
|
|
185
187
|
this.playerInterface_ = null;
|
|
186
188
|
this.srcEquals_ = false;
|
|
189
|
+
this.mediaKeysAttached_ = null;
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
/**
|
|
@@ -387,7 +390,59 @@ shaka.media.DrmEngine = class {
|
|
|
387
390
|
}
|
|
388
391
|
|
|
389
392
|
/**
|
|
390
|
-
* Attach MediaKeys to the video element
|
|
393
|
+
* Attach MediaKeys to the video element
|
|
394
|
+
* @return {Promise}
|
|
395
|
+
* @private
|
|
396
|
+
*/
|
|
397
|
+
async attachMediaKeys_() {
|
|
398
|
+
if (this.video_.mediaKeys) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// An attach process has already started, let's wait it out
|
|
403
|
+
if (this.mediaKeysAttached_) {
|
|
404
|
+
await this.mediaKeysAttached_;
|
|
405
|
+
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
this.mediaKeysAttached_ = this.video_.setMediaKeys(this.mediaKeys_);
|
|
411
|
+
|
|
412
|
+
await this.mediaKeysAttached_;
|
|
413
|
+
} catch (exception) {
|
|
414
|
+
goog.asserts.assert(exception instanceof Error, 'Wrong error type!');
|
|
415
|
+
|
|
416
|
+
this.onError_(new shaka.util.Error(
|
|
417
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
418
|
+
shaka.util.Error.Category.DRM,
|
|
419
|
+
shaka.util.Error.Code.FAILED_TO_ATTACH_TO_VIDEO,
|
|
420
|
+
exception.message));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.destroyer_.ensureNotDestroyed();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Processes encrypted event and start licence challenging
|
|
428
|
+
* @return {!Promise}
|
|
429
|
+
* @private
|
|
430
|
+
*/
|
|
431
|
+
async onEncryptedEvent_(event) {
|
|
432
|
+
/**
|
|
433
|
+
* MediaKeys should be added when receiving an encrypted event. Setting
|
|
434
|
+
* mediaKeys before could result into encrypted event not being fired on
|
|
435
|
+
* some browsers
|
|
436
|
+
*/
|
|
437
|
+
await this.attachMediaKeys_();
|
|
438
|
+
|
|
439
|
+
this.newInitData(
|
|
440
|
+
event.initDataType,
|
|
441
|
+
shaka.util.BufferUtils.toUint8(event.initData));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Start processing events.
|
|
391
446
|
* @param {HTMLMediaElement} video
|
|
392
447
|
* @return {!Promise}
|
|
393
448
|
*/
|
|
@@ -420,27 +475,30 @@ shaka.media.DrmEngine = class {
|
|
|
420
475
|
() => this.closeOpenSessions_());
|
|
421
476
|
}
|
|
422
477
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
goog.asserts.assert(exception instanceof Error, 'Wrong error type!');
|
|
426
|
-
return Promise.reject(new shaka.util.Error(
|
|
427
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
428
|
-
shaka.util.Error.Category.DRM,
|
|
429
|
-
shaka.util.Error.Code.FAILED_TO_ATTACH_TO_VIDEO,
|
|
430
|
-
exception.message));
|
|
431
|
-
});
|
|
478
|
+
const manifestInitData = this.currentDrmInfo_.initData.find(
|
|
479
|
+
(initDataOverride) => initDataOverride.initData.length > 0);
|
|
432
480
|
|
|
433
|
-
|
|
434
|
-
|
|
481
|
+
/**
|
|
482
|
+
* We can attach media keys before the playback actually begins when:
|
|
483
|
+
* - Using legacy implementations requires MediaKeys to be set before
|
|
484
|
+
* having webkitneedkey / msneedkey event, which will be translated as
|
|
485
|
+
* an encrypted event by the polyfills
|
|
486
|
+
* - Some initData already has been generated (through the manifest)
|
|
487
|
+
* - In case of an offline session
|
|
488
|
+
*/
|
|
489
|
+
if (manifestInitData ||
|
|
490
|
+
shaka.util.Platform.isMediaKeysPolyfilled() ||
|
|
491
|
+
this.offlineSessionIds_.length) {
|
|
492
|
+
await this.attachMediaKeys_();
|
|
493
|
+
}
|
|
435
494
|
|
|
436
495
|
this.createOrLoad();
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
this.eventManager_.listen(this.video_, 'encrypted', cb);
|
|
496
|
+
|
|
497
|
+
// Explicit init data for any one stream or an offline session is
|
|
498
|
+
// sufficient to suppress 'encrypted' events for all streams.
|
|
499
|
+
if (!manifestInitData && !this.offlineSessionIds_.length) {
|
|
500
|
+
this.eventManager_.listen(
|
|
501
|
+
this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e));
|
|
444
502
|
}
|
|
445
503
|
}
|
|
446
504
|
|
|
@@ -589,6 +647,10 @@ shaka.media.DrmEngine = class {
|
|
|
589
647
|
* @param {!Uint8Array} initData
|
|
590
648
|
*/
|
|
591
649
|
newInitData(initDataType, initData) {
|
|
650
|
+
if (!initData.length) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
592
654
|
// Suppress duplicate init data.
|
|
593
655
|
// Note that some init data are extremely large and can't portably be used
|
|
594
656
|
// as keys in a dictionary.
|
|
@@ -1106,6 +1168,7 @@ shaka.media.DrmEngine = class {
|
|
|
1106
1168
|
|
|
1107
1169
|
const metadata = {
|
|
1108
1170
|
initData: null,
|
|
1171
|
+
initDataType: null,
|
|
1109
1172
|
loaded: false,
|
|
1110
1173
|
oldExpiration: Infinity,
|
|
1111
1174
|
updatePromise: null,
|
|
@@ -1182,6 +1245,7 @@ shaka.media.DrmEngine = class {
|
|
|
1182
1245
|
|
|
1183
1246
|
const metadata = {
|
|
1184
1247
|
initData: initData,
|
|
1248
|
+
initDataType: initDataType,
|
|
1185
1249
|
loaded: false,
|
|
1186
1250
|
oldExpiration: Infinity,
|
|
1187
1251
|
updatePromise: null,
|
|
@@ -1189,22 +1253,6 @@ shaka.media.DrmEngine = class {
|
|
|
1189
1253
|
};
|
|
1190
1254
|
this.activeSessions_.set(session, metadata);
|
|
1191
1255
|
|
|
1192
|
-
try {
|
|
1193
|
-
initData = this.config_.initDataTransform(
|
|
1194
|
-
initData, initDataType, this.currentDrmInfo_);
|
|
1195
|
-
} catch (error) {
|
|
1196
|
-
let shakaError = error;
|
|
1197
|
-
if (!(error instanceof shaka.util.Error)) {
|
|
1198
|
-
shakaError = new shaka.util.Error(
|
|
1199
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
1200
|
-
shaka.util.Error.Category.DRM,
|
|
1201
|
-
shaka.util.Error.Code.INIT_DATA_TRANSFORM_ERROR,
|
|
1202
|
-
error);
|
|
1203
|
-
}
|
|
1204
|
-
this.onError_(shakaError);
|
|
1205
|
-
return;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
1256
|
if (this.config_.logLicenseExchange) {
|
|
1209
1257
|
const str = shaka.util.Uint8ArrayUtils.toBase64(initData);
|
|
1210
1258
|
shaka.log.info('EME init data: type=', initDataType, 'data=', str);
|
|
@@ -1239,23 +1287,6 @@ shaka.media.DrmEngine = class {
|
|
|
1239
1287
|
});
|
|
1240
1288
|
}
|
|
1241
1289
|
|
|
1242
|
-
/**
|
|
1243
|
-
* @param {!Uint8Array} initData
|
|
1244
|
-
* @param {string} initDataType
|
|
1245
|
-
* @param {?shaka.extern.DrmInfo} drmInfo
|
|
1246
|
-
* @return {!Uint8Array}
|
|
1247
|
-
*/
|
|
1248
|
-
static defaultInitDataTransform(initData, initDataType, drmInfo) {
|
|
1249
|
-
if (initDataType == 'skd') {
|
|
1250
|
-
const cert = drmInfo.serverCertificate;
|
|
1251
|
-
const contentId =
|
|
1252
|
-
shaka.util.FairPlayUtils.defaultGetContentId(initData);
|
|
1253
|
-
initData = shaka.util.FairPlayUtils.initDataTransform(
|
|
1254
|
-
initData, contentId, cert);
|
|
1255
|
-
}
|
|
1256
|
-
return initData;
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
1290
|
/**
|
|
1260
1291
|
* @param {!MediaKeyMessageEvent} event
|
|
1261
1292
|
* @private
|
|
@@ -1316,6 +1347,11 @@ shaka.media.DrmEngine = class {
|
|
|
1316
1347
|
request.method = 'POST';
|
|
1317
1348
|
request.licenseRequestType = event.messageType;
|
|
1318
1349
|
request.sessionId = session.sessionId;
|
|
1350
|
+
request.drmInfo = this.currentDrmInfo_;
|
|
1351
|
+
if (metadata) {
|
|
1352
|
+
request.initData = metadata.initData;
|
|
1353
|
+
request.initDataType = metadata.initDataType;
|
|
1354
|
+
}
|
|
1319
1355
|
// NOTE: allowCrossSiteCredentials can be set in a request filter.
|
|
1320
1356
|
|
|
1321
1357
|
if (shaka.media.DrmEngine.isPlayReadyKeySystem(
|
|
@@ -1642,6 +1678,16 @@ shaka.media.DrmEngine = class {
|
|
|
1642
1678
|
|
|
1643
1679
|
const testSystem = async (keySystem) => {
|
|
1644
1680
|
try {
|
|
1681
|
+
// Our Polyfill will reject anything apart com.apple.fps key systems.
|
|
1682
|
+
// It seems the Safari modern EME API will allow to request a
|
|
1683
|
+
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
|
|
1684
|
+
// session but playback will never start
|
|
1685
|
+
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
|
|
1686
|
+
if (keySystem === 'org.w3.clearkey' &&
|
|
1687
|
+
shaka.util.Platform.isSafari()) {
|
|
1688
|
+
throw new Error('Unsupported keySystem');
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1645
1691
|
const access = await navigator.requestMediaKeySystemAccess(
|
|
1646
1692
|
keySystem, configs);
|
|
1647
1693
|
|
|
@@ -2262,6 +2308,7 @@ shaka.media.DrmEngine = class {
|
|
|
2262
2308
|
* @typedef {{
|
|
2263
2309
|
* loaded: boolean,
|
|
2264
2310
|
* initData: Uint8Array,
|
|
2311
|
+
* initDataType: ?string,
|
|
2265
2312
|
* oldExpiration: number,
|
|
2266
2313
|
* type: string,
|
|
2267
2314
|
* updatePromise: shaka.util.PublicPromise
|
|
@@ -2273,6 +2320,8 @@ shaka.media.DrmEngine = class {
|
|
|
2273
2320
|
* does not mean the session is 'usable'.
|
|
2274
2321
|
* @property {Uint8Array} initData
|
|
2275
2322
|
* The init data used to create the session.
|
|
2323
|
+
* @property {?string} initDataType
|
|
2324
|
+
* The init data type used to create the session.
|
|
2276
2325
|
* @property {!MediaKeySession} session
|
|
2277
2326
|
* The session object.
|
|
2278
2327
|
* @property {number} oldExpiration
|
|
@@ -11,7 +11,6 @@ goog.require('shaka.media.PresentationTimeline');
|
|
|
11
11
|
goog.require('shaka.media.StallDetector');
|
|
12
12
|
goog.require('shaka.media.TimeRangesUtils');
|
|
13
13
|
goog.require('shaka.util.EventManager');
|
|
14
|
-
goog.require('shaka.util.FakeEvent');
|
|
15
14
|
goog.require('shaka.util.IReleasable');
|
|
16
15
|
goog.require('shaka.util.Timer');
|
|
17
16
|
|
|
@@ -33,10 +32,8 @@ shaka.media.GapJumpingController = class {
|
|
|
33
32
|
* playable region. The gap jumping controller takes ownership over the
|
|
34
33
|
* stall detector.
|
|
35
34
|
* If no stall detection logic is desired, |null| may be provided.
|
|
36
|
-
* @param {function(!Event)} onEvent Called when an event is raised to be sent
|
|
37
|
-
* to the application.
|
|
38
35
|
*/
|
|
39
|
-
constructor(video, timeline, config, stallDetector
|
|
36
|
+
constructor(video, timeline, config, stallDetector) {
|
|
40
37
|
/** @private {HTMLMediaElement} */
|
|
41
38
|
this.video_ = video;
|
|
42
39
|
|
|
@@ -46,9 +43,6 @@ shaka.media.GapJumpingController = class {
|
|
|
46
43
|
/** @private {?shaka.extern.StreamingConfiguration} */
|
|
47
44
|
this.config_ = config;
|
|
48
45
|
|
|
49
|
-
/** @private {?function(!Event)} */
|
|
50
|
-
this.onEvent_ = onEvent;
|
|
51
|
-
|
|
52
46
|
/** @private {shaka.util.EventManager} */
|
|
53
47
|
this.eventManager_ = new shaka.util.EventManager();
|
|
54
48
|
|
|
@@ -58,9 +52,6 @@ shaka.media.GapJumpingController = class {
|
|
|
58
52
|
/** @private {number} */
|
|
59
53
|
this.prevReadyState_ = video.readyState;
|
|
60
54
|
|
|
61
|
-
/** @private {boolean} */
|
|
62
|
-
this.didFireLargeGap_ = false;
|
|
63
|
-
|
|
64
55
|
/**
|
|
65
56
|
* The stall detector tries to keep the playhead moving forward. It is
|
|
66
57
|
* managed by the gap-jumping controller to avoid conflicts. On some
|
|
@@ -107,7 +98,6 @@ shaka.media.GapJumpingController = class {
|
|
|
107
98
|
this.stallDetector_ = null;
|
|
108
99
|
}
|
|
109
100
|
|
|
110
|
-
this.onEvent_ = null;
|
|
111
101
|
this.timeline_ = null;
|
|
112
102
|
this.video_ = null;
|
|
113
103
|
}
|
|
@@ -128,7 +118,6 @@ shaka.media.GapJumpingController = class {
|
|
|
128
118
|
onSeeking() {
|
|
129
119
|
this.seekingEventReceived_ = true;
|
|
130
120
|
this.hadSegmentAppended_ = false;
|
|
131
|
-
this.didFireLargeGap_ = false;
|
|
132
121
|
}
|
|
133
122
|
|
|
134
123
|
|
|
@@ -165,7 +154,6 @@ shaka.media.GapJumpingController = class {
|
|
|
165
154
|
// When the ready state changes, we have moved on, so we should fire the
|
|
166
155
|
// large gap event if we see one.
|
|
167
156
|
if (this.video_.readyState != this.prevReadyState_) {
|
|
168
|
-
this.didFireLargeGap_ = false;
|
|
169
157
|
this.prevReadyState_ = this.video_.readyState;
|
|
170
158
|
}
|
|
171
159
|
|
|
@@ -175,7 +163,6 @@ shaka.media.GapJumpingController = class {
|
|
|
175
163
|
}
|
|
176
164
|
|
|
177
165
|
|
|
178
|
-
const smallGapLimit = this.config_.smallGapLimit;
|
|
179
166
|
const currentTime = this.video_.currentTime;
|
|
180
167
|
const buffered = this.video_.buffered;
|
|
181
168
|
const gapDetectionThreshold = this.config_.gapDetectionThreshold;
|
|
@@ -203,8 +190,6 @@ shaka.media.GapJumpingController = class {
|
|
|
203
190
|
}
|
|
204
191
|
|
|
205
192
|
const jumpSize = jumpTo - currentTime;
|
|
206
|
-
const isGapSmall = jumpSize <= smallGapLimit;
|
|
207
|
-
let jumpLargeGap = false;
|
|
208
193
|
|
|
209
194
|
// If we jump to exactly the gap start, we may detect a small gap due to
|
|
210
195
|
// rounding errors or browser bugs. We can ignore these extremely small
|
|
@@ -213,37 +198,17 @@ shaka.media.GapJumpingController = class {
|
|
|
213
198
|
return;
|
|
214
199
|
}
|
|
215
200
|
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
event.cancelable = true;
|
|
225
|
-
this.onEvent_(event);
|
|
226
|
-
|
|
227
|
-
if (this.config_.jumpLargeGaps && !event.defaultPrevented) {
|
|
228
|
-
jumpLargeGap = true;
|
|
229
|
-
} else {
|
|
230
|
-
shaka.log.info('Ignoring large gap at', currentTime, 'size', jumpSize);
|
|
231
|
-
}
|
|
201
|
+
if (gapIndex == 0) {
|
|
202
|
+
shaka.log.info(
|
|
203
|
+
'Jumping forward', jumpSize,
|
|
204
|
+
'seconds because of gap before start time of', jumpTo);
|
|
205
|
+
} else {
|
|
206
|
+
shaka.log.info(
|
|
207
|
+
'Jumping forward', jumpSize, 'seconds because of gap starting at',
|
|
208
|
+
buffered.end(gapIndex - 1), 'and ending at', jumpTo);
|
|
232
209
|
}
|
|
233
210
|
|
|
234
|
-
|
|
235
|
-
if (gapIndex == 0) {
|
|
236
|
-
shaka.log.info(
|
|
237
|
-
'Jumping forward', jumpSize,
|
|
238
|
-
'seconds because of gap before start time of', jumpTo);
|
|
239
|
-
} else {
|
|
240
|
-
shaka.log.info(
|
|
241
|
-
'Jumping forward', jumpSize, 'seconds because of gap starting at',
|
|
242
|
-
buffered.end(gapIndex - 1), 'and ending at', jumpTo);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
this.video_.currentTime = jumpTo;
|
|
246
|
-
}
|
|
211
|
+
this.video_.currentTime = jumpTo;
|
|
247
212
|
}
|
|
248
213
|
};
|
|
249
214
|
|
|
@@ -63,9 +63,17 @@ shaka.media.MediaSourceEngine = class {
|
|
|
63
63
|
string>} */
|
|
64
64
|
this.sourceBufferTypes_ = {};
|
|
65
65
|
|
|
66
|
+
|
|
67
|
+
/** @private {!Object.<shaka.util.ManifestParserUtils.ContentType,
|
|
68
|
+
boolean>} */
|
|
69
|
+
this.expectedEncryption_ = {};
|
|
70
|
+
|
|
66
71
|
/** @private {shaka.text.TextEngine} */
|
|
67
72
|
this.textEngine_ = null;
|
|
68
73
|
|
|
74
|
+
/** @private {boolean} */
|
|
75
|
+
this.segmentRelativeVttTiming_ = false;
|
|
76
|
+
|
|
69
77
|
const onMetadataNoOp = (metadata, timestampOffset, segmentEnd) => {};
|
|
70
78
|
|
|
71
79
|
/** @private {!function(!Array.<shaka.extern.ID3Metadata>,
|
|
@@ -196,6 +204,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
196
204
|
// TTML types
|
|
197
205
|
'application/ttml+xml',
|
|
198
206
|
'application/mp4; codecs="stpp"',
|
|
207
|
+
// Containerless types
|
|
208
|
+
...shaka.media.MediaSourceEngine.RAW_FORMATS,
|
|
199
209
|
];
|
|
200
210
|
|
|
201
211
|
const support = {};
|
|
@@ -310,10 +320,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
310
320
|
* according to MediaSourceEngine.isStreamSupported.
|
|
311
321
|
* @param {boolean} forceTransmuxTS
|
|
312
322
|
* If true, this will transmux TS content even if it is natively supported.
|
|
323
|
+
* @param {boolean=} sequenceMode
|
|
324
|
+
* If true, the media segments are appended to the SourceBuffer in strict
|
|
325
|
+
* sequence.
|
|
313
326
|
*
|
|
314
327
|
* @return {!Promise}
|
|
315
328
|
*/
|
|
316
|
-
async init(streamsByType, forceTransmuxTS) {
|
|
329
|
+
async init(streamsByType, forceTransmuxTS, sequenceMode=false) {
|
|
317
330
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
318
331
|
|
|
319
332
|
await this.mediaSourceOpen_;
|
|
@@ -327,7 +340,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
327
340
|
let mimeType = shaka.util.MimeUtils.getFullType(
|
|
328
341
|
stream.mimeType, stream.codecs);
|
|
329
342
|
if (contentType == ContentType.TEXT) {
|
|
330
|
-
this.reinitText(mimeType);
|
|
343
|
+
this.reinitText(mimeType, sequenceMode);
|
|
331
344
|
} else {
|
|
332
345
|
if ((forceTransmuxTS || !MediaSource.isTypeSupported(mimeType)) &&
|
|
333
346
|
shaka.media.Transmuxer.isSupported(mimeType, contentType)) {
|
|
@@ -336,6 +349,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
336
349
|
shaka.media.Transmuxer.convertTsCodecs(contentType, mimeType);
|
|
337
350
|
}
|
|
338
351
|
const sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
|
|
352
|
+
if (sequenceMode) {
|
|
353
|
+
sourceBuffer.mode =
|
|
354
|
+
shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
|
|
355
|
+
}
|
|
339
356
|
this.eventManager_.listen(
|
|
340
357
|
sourceBuffer, 'error',
|
|
341
358
|
() => this.onError_(contentType));
|
|
@@ -345,6 +362,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
345
362
|
this.sourceBuffers_[contentType] = sourceBuffer;
|
|
346
363
|
this.sourceBufferTypes_[contentType] = mimeType;
|
|
347
364
|
this.queues_[contentType] = [];
|
|
365
|
+
this.expectedEncryption_[contentType] = !!stream.drmInfos.length;
|
|
348
366
|
}
|
|
349
367
|
}
|
|
350
368
|
}
|
|
@@ -352,12 +370,14 @@ shaka.media.MediaSourceEngine = class {
|
|
|
352
370
|
/**
|
|
353
371
|
* Reinitialize the TextEngine for a new text type.
|
|
354
372
|
* @param {string} mimeType
|
|
373
|
+
* @param {boolean} sequenceMode
|
|
355
374
|
*/
|
|
356
|
-
reinitText(mimeType) {
|
|
375
|
+
reinitText(mimeType, sequenceMode) {
|
|
357
376
|
if (!this.textEngine_) {
|
|
358
377
|
this.textEngine_ = new shaka.text.TextEngine(this.textDisplayer_);
|
|
359
378
|
}
|
|
360
|
-
this.textEngine_.initParser(mimeType
|
|
379
|
+
this.textEngine_.initParser(mimeType, sequenceMode,
|
|
380
|
+
this.segmentRelativeVttTiming_);
|
|
361
381
|
}
|
|
362
382
|
|
|
363
383
|
/**
|
|
@@ -404,17 +424,15 @@ shaka.media.MediaSourceEngine = class {
|
|
|
404
424
|
*
|
|
405
425
|
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
406
426
|
* @param {number} time Playhead time
|
|
407
|
-
* @param {number=} smallGapLimit
|
|
408
427
|
* @return {boolean}
|
|
409
428
|
*/
|
|
410
|
-
isBuffered(contentType, time
|
|
429
|
+
isBuffered(contentType, time) {
|
|
411
430
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
412
431
|
if (contentType == ContentType.TEXT) {
|
|
413
432
|
return this.textEngine_.isBuffered(time);
|
|
414
433
|
} else {
|
|
415
434
|
const buffered = this.getBuffered_(contentType);
|
|
416
|
-
return shaka.media.TimeRangesUtils.isBuffered(
|
|
417
|
-
buffered, time, smallGapLimit);
|
|
435
|
+
return shaka.media.TimeRangesUtils.isBuffered(buffered, time);
|
|
418
436
|
}
|
|
419
437
|
}
|
|
420
438
|
|
|
@@ -496,11 +514,27 @@ shaka.media.MediaSourceEngine = class {
|
|
|
496
514
|
* @param {?number} endTime relative to the start of the presentation
|
|
497
515
|
* @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
|
|
498
516
|
* captions
|
|
517
|
+
* @param {boolean=} seeked True if we just seeked
|
|
518
|
+
* @param {boolean=} sequenceMode True if sequence mode
|
|
499
519
|
* @return {!Promise}
|
|
500
520
|
*/
|
|
501
|
-
async appendBuffer(contentType, data, startTime, endTime, hasClosedCaptions
|
|
521
|
+
async appendBuffer(contentType, data, startTime, endTime, hasClosedCaptions,
|
|
522
|
+
seeked, sequenceMode) {
|
|
502
523
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
503
524
|
|
|
525
|
+
if (startTime != null && sequenceMode && contentType != ContentType.TEXT) {
|
|
526
|
+
// If we just cleared buffer and is on an unbuffered seek, we need to set
|
|
527
|
+
// the new timestampOffset of the sourceBuffer.
|
|
528
|
+
// Don't do this for text streams, though, since they don't use
|
|
529
|
+
// MediaSource anyway.
|
|
530
|
+
if (seeked) {
|
|
531
|
+
const timestampOffset = /** @type {number} */ (startTime);
|
|
532
|
+
this.enqueueOperation_(
|
|
533
|
+
contentType,
|
|
534
|
+
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
504
538
|
if (contentType == ContentType.TEXT) {
|
|
505
539
|
await this.textEngine_.appendBuffer(data, startTime, endTime);
|
|
506
540
|
} else if (this.transmuxers_[contentType]) {
|
|
@@ -509,7 +543,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
509
543
|
// For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
|
|
510
544
|
// the video stream, so textEngine may not have been initialized.
|
|
511
545
|
if (!this.textEngine_) {
|
|
512
|
-
this.reinitText('text/vtt');
|
|
546
|
+
this.reinitText('text/vtt', sequenceMode || false);
|
|
513
547
|
}
|
|
514
548
|
|
|
515
549
|
if (transmuxedData.metadata) {
|
|
@@ -536,7 +570,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
536
570
|
contentType, () => this.append_(contentType, transmuxedSegment));
|
|
537
571
|
} else if (hasClosedCaptions) {
|
|
538
572
|
if (!this.textEngine_) {
|
|
539
|
-
this.reinitText('text/vtt');
|
|
573
|
+
this.reinitText('text/vtt', sequenceMode || false);
|
|
540
574
|
}
|
|
541
575
|
// If it is the init segment for closed captions, initialize the closed
|
|
542
576
|
// caption parser.
|
|
@@ -664,13 +698,18 @@ shaka.media.MediaSourceEngine = class {
|
|
|
664
698
|
* @param {number} appendWindowEnd The timestamp to set the append window end
|
|
665
699
|
* to. For future appends, frames/samples with timestamps greater than this
|
|
666
700
|
* value will be dropped.
|
|
701
|
+
* @param {boolean} sequenceMode If true, the timestampOffset will not be
|
|
702
|
+
* applied in this step.
|
|
667
703
|
* @return {!Promise}
|
|
668
704
|
*/
|
|
669
705
|
async setStreamProperties(
|
|
670
|
-
contentType, timestampOffset, appendWindowStart, appendWindowEnd
|
|
706
|
+
contentType, timestampOffset, appendWindowStart, appendWindowEnd,
|
|
707
|
+
sequenceMode) {
|
|
671
708
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
672
709
|
if (contentType == ContentType.TEXT) {
|
|
673
|
-
|
|
710
|
+
if (!sequenceMode) {
|
|
711
|
+
this.textEngine_.setTimestampOffset(timestampOffset);
|
|
712
|
+
}
|
|
674
713
|
this.textEngine_.setAppendWindow(appendWindowStart, appendWindowEnd);
|
|
675
714
|
return;
|
|
676
715
|
}
|
|
@@ -687,7 +726,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
687
726
|
this.enqueueOperation_(
|
|
688
727
|
contentType,
|
|
689
728
|
() => this.abort_(contentType)),
|
|
690
|
-
|
|
729
|
+
// Don't set the timestampOffset here when in sequenceMode, since we
|
|
730
|
+
// use timestampOffset for a different purpose in that mode (e.g. to
|
|
731
|
+
// indicate where the current segment is).
|
|
732
|
+
sequenceMode ? Promise.resolve() : this.enqueueOperation_(
|
|
691
733
|
contentType,
|
|
692
734
|
() => this.setTimestampOffset_(contentType, timestampOffset)),
|
|
693
735
|
this.enqueueOperation_(
|
|
@@ -1079,6 +1121,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1079
1121
|
}
|
|
1080
1122
|
}
|
|
1081
1123
|
|
|
1124
|
+
/**
|
|
1125
|
+
* @param {boolean} segmentRelativeVttTiming
|
|
1126
|
+
*/
|
|
1127
|
+
setSegmentRelativeVttTiming(segmentRelativeVttTiming) {
|
|
1128
|
+
this.segmentRelativeVttTiming_ = segmentRelativeVttTiming;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1082
1131
|
/**
|
|
1083
1132
|
* Apply platform-specific transformations to this segment to work around
|
|
1084
1133
|
* issues in the platform.
|
|
@@ -1091,7 +1140,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1091
1140
|
*/
|
|
1092
1141
|
workAroundBrokenPlatforms_(segment, startTime, contentType) {
|
|
1093
1142
|
const isInitSegment = startTime == null;
|
|
1094
|
-
const encryptionExpected = this.
|
|
1143
|
+
const encryptionExpected = this.expectedEncryption_[contentType];
|
|
1095
1144
|
|
|
1096
1145
|
// If:
|
|
1097
1146
|
// 1. this is an init segment,
|
|
@@ -1139,3 +1188,26 @@ shaka.media.MediaSourceEngine.createObjectURL = window.URL.createObjectURL;
|
|
|
1139
1188
|
* The PublicPromise which is associated with this operation.
|
|
1140
1189
|
*/
|
|
1141
1190
|
shaka.media.MediaSourceEngine.Operation;
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* @enum {string}
|
|
1195
|
+
* @private
|
|
1196
|
+
*/
|
|
1197
|
+
shaka.media.MediaSourceEngine.SourceBufferMode_ = {
|
|
1198
|
+
SEQUENCE: 'sequence',
|
|
1199
|
+
SEGMENTS: 'segments',
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* MIME types of raw formats.
|
|
1205
|
+
*
|
|
1206
|
+
* @const {!Array.<string>}
|
|
1207
|
+
*/
|
|
1208
|
+
shaka.media.MediaSourceEngine.RAW_FORMATS = [
|
|
1209
|
+
'audio/aac',
|
|
1210
|
+
'audio/ac3',
|
|
1211
|
+
'audio/ec3',
|
|
1212
|
+
'audio/mpeg',
|
|
1213
|
+
];
|
package/lib/media/playhead.js
CHANGED
|
@@ -160,10 +160,8 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
160
160
|
* @param {function()} onSeek
|
|
161
161
|
* Called when the user agent seeks to a time within the presentation
|
|
162
162
|
* timeline.
|
|
163
|
-
* @param {function(!Event)} onEvent
|
|
164
|
-
* Called when an event is raised to be sent to the application.
|
|
165
163
|
*/
|
|
166
|
-
constructor(mediaElement, manifest, config, startTime, onSeek
|
|
164
|
+
constructor(mediaElement, manifest, config, startTime, onSeek) {
|
|
167
165
|
/**
|
|
168
166
|
* The seek range must be at least this number of seconds long. If it is
|
|
169
167
|
* smaller than this, change it to be this big so we don't repeatedly seek
|
|
@@ -199,8 +197,7 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
199
197
|
mediaElement,
|
|
200
198
|
manifest.presentationTimeline,
|
|
201
199
|
config,
|
|
202
|
-
this.createStallDetector_(mediaElement, config)
|
|
203
|
-
onEvent);
|
|
200
|
+
this.createStallDetector_(mediaElement, config));
|
|
204
201
|
|
|
205
202
|
/** @private {shaka.media.VideoWrapper} */
|
|
206
203
|
this.videoWrapper_ = new shaka.media.VideoWrapper(
|
|
@@ -249,8 +249,7 @@ shaka.media.PresentationTimeline = class {
|
|
|
249
249
|
* @param {number} startTime
|
|
250
250
|
* @export
|
|
251
251
|
*/
|
|
252
|
-
notifyMinSegmentStartTime(
|
|
253
|
-
startTime) {
|
|
252
|
+
notifyMinSegmentStartTime(startTime) {
|
|
254
253
|
if (this.minSegmentStartTime_ == null) {
|
|
255
254
|
// No data yet, and Math.min(null, startTime) is always 0. So just store
|
|
256
255
|
// startTime.
|