shaka-player 3.3.5 → 4.0.2
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/config.yml +1 -0
- package/.github/workflows/demo-version-index.yaml +8 -0
- package/.github/workflows/release-please.yaml +18 -2
- 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 +215 -41
- package/CONTRIBUTORS +1 -0
- package/README.md +14 -11
- 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/checkversion.py +1 -1
- package/build/compiler.py +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shaka-lab.yaml +0 -2
- 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.less +0 -1
- 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 +3 -3
- package/dist/controls.css.map +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 +17 -20
- package/dist/locales.js +33 -31
- 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 +311 -314
- package/dist/shaka-player.compiled.debug.d.ts +311 -314
- package/dist/shaka-player.compiled.debug.externs.js +126 -126
- package/dist/shaka-player.compiled.debug.js +430 -484
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +126 -126
- package/dist/shaka-player.compiled.js +829 -845
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +357 -358
- package/dist/shaka-player.ui.debug.d.ts +357 -358
- package/dist/shaka-player.ui.debug.externs.js +134 -126
- package/dist/shaka-player.ui.debug.js +489 -541
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +134 -126
- package/dist/shaka-player.ui.js +988 -1000
- 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 +85 -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 -21
- package/externs/shaka/text.js +12 -12
- package/externs/sourcebuffer.js +15 -0
- package/lib/abr/simple_abr_manager.js +2 -1
- 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 -0
- package/lib/hls/hls_parser.js +390 -747
- package/lib/hls/manifest_text_parser.js +2 -3
- package/lib/media/drm_engine.js +105 -37
- package/lib/media/gap_jumping_controller.js +10 -45
- package/lib/media/media_source_engine.js +150 -30
- 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 +28 -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 +8 -41
- 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 +27 -5
- 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 +16 -5
- package/lib/util/platform.js +45 -0
- package/lib/util/player_configuration.js +17 -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 +5 -2
- package/roadmap.md +19 -19
- package/shaka-player.uncompiled.js +0 -3
- package/test/dash/dash_parser_content_protection_unit.js +44 -0
- 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 +155 -230
- 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 +25 -3
- package/test/media/media_source_engine_unit.js +3 -2
- package/test/media/playhead_unit.js +27 -191
- package/test/media/segment_index_unit.js +15 -25
- package/test/media/streaming_engine_integration.js +2 -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 +116 -59
- 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 +4 -4
- package/ui/less/general.less +1 -1
- package/ui/less/range_elements.less +1 -0
- 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/.github/pull_request_template.md +0 -10
- 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/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,
|
|
@@ -1239,23 +1303,6 @@ shaka.media.DrmEngine = class {
|
|
|
1239
1303
|
});
|
|
1240
1304
|
}
|
|
1241
1305
|
|
|
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
1306
|
/**
|
|
1260
1307
|
* @param {!MediaKeyMessageEvent} event
|
|
1261
1308
|
* @private
|
|
@@ -1316,6 +1363,11 @@ shaka.media.DrmEngine = class {
|
|
|
1316
1363
|
request.method = 'POST';
|
|
1317
1364
|
request.licenseRequestType = event.messageType;
|
|
1318
1365
|
request.sessionId = session.sessionId;
|
|
1366
|
+
request.drmInfo = this.currentDrmInfo_;
|
|
1367
|
+
if (metadata) {
|
|
1368
|
+
request.initData = metadata.initData;
|
|
1369
|
+
request.initDataType = metadata.initDataType;
|
|
1370
|
+
}
|
|
1319
1371
|
// NOTE: allowCrossSiteCredentials can be set in a request filter.
|
|
1320
1372
|
|
|
1321
1373
|
if (shaka.media.DrmEngine.isPlayReadyKeySystem(
|
|
@@ -1372,6 +1424,9 @@ shaka.media.DrmEngine = class {
|
|
|
1372
1424
|
}
|
|
1373
1425
|
return;
|
|
1374
1426
|
}
|
|
1427
|
+
if (this.destroyer_.destroyed()) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1375
1430
|
|
|
1376
1431
|
const updateEvent = new shaka.util.FakeEvent('drmsessionupdate');
|
|
1377
1432
|
this.playerInterface_.onEvent(updateEvent);
|
|
@@ -1642,6 +1697,16 @@ shaka.media.DrmEngine = class {
|
|
|
1642
1697
|
|
|
1643
1698
|
const testSystem = async (keySystem) => {
|
|
1644
1699
|
try {
|
|
1700
|
+
// Our Polyfill will reject anything apart com.apple.fps key systems.
|
|
1701
|
+
// It seems the Safari modern EME API will allow to request a
|
|
1702
|
+
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
|
|
1703
|
+
// session but playback will never start
|
|
1704
|
+
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
|
|
1705
|
+
if (keySystem === 'org.w3.clearkey' &&
|
|
1706
|
+
shaka.util.Platform.isSafari()) {
|
|
1707
|
+
throw new Error('Unsupported keySystem');
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1645
1710
|
const access = await navigator.requestMediaKeySystemAccess(
|
|
1646
1711
|
keySystem, configs);
|
|
1647
1712
|
|
|
@@ -2262,6 +2327,7 @@ shaka.media.DrmEngine = class {
|
|
|
2262
2327
|
* @typedef {{
|
|
2263
2328
|
* loaded: boolean,
|
|
2264
2329
|
* initData: Uint8Array,
|
|
2330
|
+
* initDataType: ?string,
|
|
2265
2331
|
* oldExpiration: number,
|
|
2266
2332
|
* type: string,
|
|
2267
2333
|
* updatePromise: shaka.util.PublicPromise
|
|
@@ -2273,6 +2339,8 @@ shaka.media.DrmEngine = class {
|
|
|
2273
2339
|
* does not mean the session is 'usable'.
|
|
2274
2340
|
* @property {Uint8Array} initData
|
|
2275
2341
|
* The init data used to create the session.
|
|
2342
|
+
* @property {?string} initDataType
|
|
2343
|
+
* The init data type used to create the session.
|
|
2276
2344
|
* @property {!MediaKeySession} session
|
|
2277
2345
|
* The session object.
|
|
2278
2346
|
* @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
|
|
|
@@ -71,6 +71,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
71
71
|
/** @private {shaka.text.TextEngine} */
|
|
72
72
|
this.textEngine_ = null;
|
|
73
73
|
|
|
74
|
+
/** @private {boolean} */
|
|
75
|
+
this.segmentRelativeVttTiming_ = false;
|
|
76
|
+
|
|
74
77
|
const onMetadataNoOp = (metadata, timestampOffset, segmentEnd) => {};
|
|
75
78
|
|
|
76
79
|
/** @private {!function(!Array.<shaka.extern.ID3Metadata>,
|
|
@@ -103,6 +106,12 @@ shaka.media.MediaSourceEngine = class {
|
|
|
103
106
|
|
|
104
107
|
/** @private {string} */
|
|
105
108
|
this.url_ = '';
|
|
109
|
+
|
|
110
|
+
/** @private {boolean} */
|
|
111
|
+
this.sequenceMode_ = false;
|
|
112
|
+
|
|
113
|
+
/** @private {!shaka.util.PublicPromise.<number>} */
|
|
114
|
+
this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
/**
|
|
@@ -201,6 +210,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
201
210
|
// TTML types
|
|
202
211
|
'application/ttml+xml',
|
|
203
212
|
'application/mp4; codecs="stpp"',
|
|
213
|
+
// Containerless types
|
|
214
|
+
...shaka.media.MediaSourceEngine.RAW_FORMATS,
|
|
204
215
|
];
|
|
205
216
|
|
|
206
217
|
const support = {};
|
|
@@ -315,14 +326,19 @@ shaka.media.MediaSourceEngine = class {
|
|
|
315
326
|
* according to MediaSourceEngine.isStreamSupported.
|
|
316
327
|
* @param {boolean} forceTransmuxTS
|
|
317
328
|
* If true, this will transmux TS content even if it is natively supported.
|
|
329
|
+
* @param {boolean=} sequenceMode
|
|
330
|
+
* If true, the media segments are appended to the SourceBuffer in strict
|
|
331
|
+
* sequence.
|
|
318
332
|
*
|
|
319
333
|
* @return {!Promise}
|
|
320
334
|
*/
|
|
321
|
-
async init(streamsByType, forceTransmuxTS) {
|
|
335
|
+
async init(streamsByType, forceTransmuxTS, sequenceMode=false) {
|
|
322
336
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
323
337
|
|
|
324
338
|
await this.mediaSourceOpen_;
|
|
325
339
|
|
|
340
|
+
this.sequenceMode_ = sequenceMode;
|
|
341
|
+
|
|
326
342
|
for (const contentType of streamsByType.keys()) {
|
|
327
343
|
const stream = streamsByType.get(contentType);
|
|
328
344
|
goog.asserts.assert(
|
|
@@ -332,7 +348,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
332
348
|
let mimeType = shaka.util.MimeUtils.getFullType(
|
|
333
349
|
stream.mimeType, stream.codecs);
|
|
334
350
|
if (contentType == ContentType.TEXT) {
|
|
335
|
-
this.reinitText(mimeType);
|
|
351
|
+
this.reinitText(mimeType, sequenceMode);
|
|
336
352
|
} else {
|
|
337
353
|
if ((forceTransmuxTS || !MediaSource.isTypeSupported(mimeType)) &&
|
|
338
354
|
shaka.media.Transmuxer.isSupported(mimeType, contentType)) {
|
|
@@ -340,7 +356,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
340
356
|
mimeType =
|
|
341
357
|
shaka.media.Transmuxer.convertTsCodecs(contentType, mimeType);
|
|
342
358
|
}
|
|
359
|
+
|
|
343
360
|
const sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
|
|
361
|
+
|
|
344
362
|
this.eventManager_.listen(
|
|
345
363
|
sourceBuffer, 'error',
|
|
346
364
|
() => this.onError_(contentType));
|
|
@@ -358,12 +376,14 @@ shaka.media.MediaSourceEngine = class {
|
|
|
358
376
|
/**
|
|
359
377
|
* Reinitialize the TextEngine for a new text type.
|
|
360
378
|
* @param {string} mimeType
|
|
379
|
+
* @param {boolean} sequenceMode
|
|
361
380
|
*/
|
|
362
|
-
reinitText(mimeType) {
|
|
381
|
+
reinitText(mimeType, sequenceMode) {
|
|
363
382
|
if (!this.textEngine_) {
|
|
364
383
|
this.textEngine_ = new shaka.text.TextEngine(this.textDisplayer_);
|
|
365
384
|
}
|
|
366
|
-
this.textEngine_.initParser(mimeType
|
|
385
|
+
this.textEngine_.initParser(mimeType, sequenceMode,
|
|
386
|
+
this.segmentRelativeVttTiming_);
|
|
367
387
|
}
|
|
368
388
|
|
|
369
389
|
/**
|
|
@@ -410,17 +430,15 @@ shaka.media.MediaSourceEngine = class {
|
|
|
410
430
|
*
|
|
411
431
|
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
412
432
|
* @param {number} time Playhead time
|
|
413
|
-
* @param {number=} smallGapLimit
|
|
414
433
|
* @return {boolean}
|
|
415
434
|
*/
|
|
416
|
-
isBuffered(contentType, time
|
|
435
|
+
isBuffered(contentType, time) {
|
|
417
436
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
418
437
|
if (contentType == ContentType.TEXT) {
|
|
419
438
|
return this.textEngine_.isBuffered(time);
|
|
420
439
|
} else {
|
|
421
440
|
const buffered = this.getBuffered_(contentType);
|
|
422
|
-
return shaka.media.TimeRangesUtils.isBuffered(
|
|
423
|
-
buffered, time, smallGapLimit);
|
|
441
|
+
return shaka.media.TimeRangesUtils.isBuffered(buffered, time);
|
|
424
442
|
}
|
|
425
443
|
}
|
|
426
444
|
|
|
@@ -502,20 +520,30 @@ shaka.media.MediaSourceEngine = class {
|
|
|
502
520
|
* @param {?number} endTime relative to the start of the presentation
|
|
503
521
|
* @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
|
|
504
522
|
* captions
|
|
523
|
+
* @param {boolean=} seeked True if we just seeked
|
|
505
524
|
* @return {!Promise}
|
|
506
525
|
*/
|
|
507
|
-
async appendBuffer(
|
|
526
|
+
async appendBuffer(
|
|
527
|
+
contentType, data, startTime, endTime, hasClosedCaptions, seeked) {
|
|
508
528
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
509
529
|
|
|
510
530
|
if (contentType == ContentType.TEXT) {
|
|
531
|
+
if (this.sequenceMode_) {
|
|
532
|
+
// This won't be known until the first video segment is appended.
|
|
533
|
+
const offset = await this.textSequenceModeOffset_;
|
|
534
|
+
this.textEngine_.setTimestampOffset(offset);
|
|
535
|
+
}
|
|
511
536
|
await this.textEngine_.appendBuffer(data, startTime, endTime);
|
|
512
|
-
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (this.transmuxers_[contentType]) {
|
|
513
541
|
const transmuxedData =
|
|
514
542
|
await this.transmuxers_[contentType].transmux(data);
|
|
515
543
|
// For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
|
|
516
544
|
// the video stream, so textEngine may not have been initialized.
|
|
517
545
|
if (!this.textEngine_) {
|
|
518
|
-
this.reinitText('text/vtt');
|
|
546
|
+
this.reinitText('text/vtt', this.sequenceMode_);
|
|
519
547
|
}
|
|
520
548
|
|
|
521
549
|
if (transmuxedData.metadata) {
|
|
@@ -534,15 +562,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
534
562
|
closedCaptions, startTime, endTime, videoOffset);
|
|
535
563
|
}
|
|
536
564
|
|
|
537
|
-
|
|
538
|
-
transmuxedSegment = this.workAroundBrokenPlatforms_(
|
|
539
|
-
transmuxedSegment, startTime, contentType);
|
|
540
|
-
|
|
541
|
-
await this.enqueueOperation_(
|
|
542
|
-
contentType, () => this.append_(contentType, transmuxedSegment));
|
|
565
|
+
data = transmuxedData.data;
|
|
543
566
|
} else if (hasClosedCaptions) {
|
|
544
567
|
if (!this.textEngine_) {
|
|
545
|
-
this.reinitText('text/vtt');
|
|
568
|
+
this.reinitText('text/vtt', this.sequenceMode_);
|
|
546
569
|
}
|
|
547
570
|
// If it is the init segment for closed captions, initialize the closed
|
|
548
571
|
// caption parser.
|
|
@@ -557,19 +580,78 @@ shaka.media.MediaSourceEngine = class {
|
|
|
557
580
|
closedCaptions, startTime, endTime, videoOffset);
|
|
558
581
|
}
|
|
559
582
|
}
|
|
583
|
+
}
|
|
560
584
|
|
|
561
|
-
|
|
585
|
+
data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
|
|
586
|
+
|
|
587
|
+
const sourceBuffer = this.sourceBuffers_[contentType];
|
|
588
|
+
const SEQUENCE = shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
|
|
589
|
+
|
|
590
|
+
if (this.sequenceMode_ && sourceBuffer.mode != SEQUENCE &&
|
|
591
|
+
startTime != null) {
|
|
592
|
+
// This is the first media segment to be appended to a SourceBuffer in
|
|
593
|
+
// sequence mode. We set the mode late so that we can trick MediaSource
|
|
594
|
+
// into extracting a timestamp for us to align text segments in sequence
|
|
595
|
+
// mode.
|
|
596
|
+
|
|
597
|
+
// Timestamps can only be reliably extracted from video, not audio.
|
|
598
|
+
// Packed audio formats do not have internal timestamps at all.
|
|
599
|
+
// Prefer video for this when available.
|
|
600
|
+
const isBestSourceBufferForTimestamps =
|
|
601
|
+
contentType == ContentType.VIDEO ||
|
|
602
|
+
!(ContentType.VIDEO in this.sourceBuffers_);
|
|
603
|
+
if (isBestSourceBufferForTimestamps) {
|
|
604
|
+
// Append the segment in segments mode first, with offset of 0 and an
|
|
605
|
+
// open append window.
|
|
606
|
+
const originalRange =
|
|
607
|
+
[sourceBuffer.appendWindowStart, sourceBuffer.appendWindowEnd];
|
|
608
|
+
sourceBuffer.appendWindowStart = 0;
|
|
609
|
+
sourceBuffer.appendWindowEnd = Infinity;
|
|
610
|
+
|
|
611
|
+
const originalOffset = sourceBuffer.timestampOffset;
|
|
612
|
+
sourceBuffer.timestampOffset = 0;
|
|
613
|
+
|
|
614
|
+
await this.enqueueOperation_(
|
|
615
|
+
contentType, () => this.append_(contentType, data));
|
|
616
|
+
|
|
617
|
+
// Reset the offset and append window.
|
|
618
|
+
sourceBuffer.timestampOffset = originalOffset;
|
|
619
|
+
sourceBuffer.appendWindowStart = originalRange[0];
|
|
620
|
+
sourceBuffer.appendWindowEnd = originalRange[1];
|
|
621
|
+
|
|
622
|
+
// Now get the timestamp of the segment and compute the offset for text
|
|
623
|
+
// segments.
|
|
624
|
+
const mediaStartTime = shaka.media.TimeRangesUtils.bufferStart(
|
|
625
|
+
this.getBuffered_(contentType));
|
|
626
|
+
const textOffset = (startTime || 0) - (mediaStartTime || 0);
|
|
627
|
+
this.textSequenceModeOffset_.resolve(textOffset);
|
|
628
|
+
|
|
629
|
+
// Finally, clear the buffer.
|
|
630
|
+
await this.enqueueOperation_(
|
|
631
|
+
contentType,
|
|
632
|
+
() => this.remove_(contentType, 0, this.mediaSource_.duration));
|
|
633
|
+
}
|
|
562
634
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
} else {
|
|
567
|
-
data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
|
|
635
|
+
// Now switch to sequence mode and fall through to our normal operations.
|
|
636
|
+
sourceBuffer.mode = SEQUENCE;
|
|
637
|
+
}
|
|
568
638
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
639
|
+
if (startTime != null && this.sequenceMode_ &&
|
|
640
|
+
contentType != ContentType.TEXT) {
|
|
641
|
+
// In sequence mode, for non-text streams, if we just cleared the buffer
|
|
642
|
+
// and are performing an unbuffered seek, we need to set a new
|
|
643
|
+
// timestampOffset on the sourceBuffer.
|
|
644
|
+
if (seeked) {
|
|
645
|
+
const timestampOffset = /** @type {number} */ (startTime);
|
|
646
|
+
this.enqueueOperation_(
|
|
647
|
+
contentType,
|
|
648
|
+
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
649
|
+
}
|
|
572
650
|
}
|
|
651
|
+
|
|
652
|
+
await this.enqueueOperation_(
|
|
653
|
+
contentType,
|
|
654
|
+
() => this.append_(contentType, data));
|
|
573
655
|
}
|
|
574
656
|
|
|
575
657
|
/**
|
|
@@ -670,13 +752,18 @@ shaka.media.MediaSourceEngine = class {
|
|
|
670
752
|
* @param {number} appendWindowEnd The timestamp to set the append window end
|
|
671
753
|
* to. For future appends, frames/samples with timestamps greater than this
|
|
672
754
|
* value will be dropped.
|
|
755
|
+
* @param {boolean} sequenceMode If true, the timestampOffset will not be
|
|
756
|
+
* applied in this step.
|
|
673
757
|
* @return {!Promise}
|
|
674
758
|
*/
|
|
675
759
|
async setStreamProperties(
|
|
676
|
-
contentType, timestampOffset, appendWindowStart, appendWindowEnd
|
|
760
|
+
contentType, timestampOffset, appendWindowStart, appendWindowEnd,
|
|
761
|
+
sequenceMode) {
|
|
677
762
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
678
763
|
if (contentType == ContentType.TEXT) {
|
|
679
|
-
|
|
764
|
+
if (!sequenceMode) {
|
|
765
|
+
this.textEngine_.setTimestampOffset(timestampOffset);
|
|
766
|
+
}
|
|
680
767
|
this.textEngine_.setAppendWindow(appendWindowStart, appendWindowEnd);
|
|
681
768
|
return;
|
|
682
769
|
}
|
|
@@ -693,7 +780,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
693
780
|
this.enqueueOperation_(
|
|
694
781
|
contentType,
|
|
695
782
|
() => this.abort_(contentType)),
|
|
696
|
-
|
|
783
|
+
// Don't set the timestampOffset here when in sequenceMode, since we
|
|
784
|
+
// use timestampOffset for a different purpose in that mode (e.g. to
|
|
785
|
+
// indicate where the current segment is).
|
|
786
|
+
sequenceMode ? Promise.resolve() : this.enqueueOperation_(
|
|
697
787
|
contentType,
|
|
698
788
|
() => this.setTimestampOffset_(contentType, timestampOffset)),
|
|
699
789
|
this.enqueueOperation_(
|
|
@@ -1085,6 +1175,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1085
1175
|
}
|
|
1086
1176
|
}
|
|
1087
1177
|
|
|
1178
|
+
/**
|
|
1179
|
+
* @param {boolean} segmentRelativeVttTiming
|
|
1180
|
+
*/
|
|
1181
|
+
setSegmentRelativeVttTiming(segmentRelativeVttTiming) {
|
|
1182
|
+
this.segmentRelativeVttTiming_ = segmentRelativeVttTiming;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1088
1185
|
/**
|
|
1089
1186
|
* Apply platform-specific transformations to this segment to work around
|
|
1090
1187
|
* issues in the platform.
|
|
@@ -1145,3 +1242,26 @@ shaka.media.MediaSourceEngine.createObjectURL = window.URL.createObjectURL;
|
|
|
1145
1242
|
* The PublicPromise which is associated with this operation.
|
|
1146
1243
|
*/
|
|
1147
1244
|
shaka.media.MediaSourceEngine.Operation;
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* @enum {string}
|
|
1249
|
+
* @private
|
|
1250
|
+
*/
|
|
1251
|
+
shaka.media.MediaSourceEngine.SourceBufferMode_ = {
|
|
1252
|
+
SEQUENCE: 'sequence',
|
|
1253
|
+
SEGMENTS: 'segments',
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* MIME types of raw formats.
|
|
1259
|
+
*
|
|
1260
|
+
* @const {!Array.<string>}
|
|
1261
|
+
*/
|
|
1262
|
+
shaka.media.MediaSourceEngine.RAW_FORMATS = [
|
|
1263
|
+
'audio/aac',
|
|
1264
|
+
'audio/ac3',
|
|
1265
|
+
'audio/ec3',
|
|
1266
|
+
'audio/mpeg',
|
|
1267
|
+
];
|