shaka-player 4.3.13 → 4.3.15
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/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +7 -0
- package/.github/workflows/release-please.yaml +2 -27
- package/.github/workflows/shaka-bot-commands/lib.sh +37 -12
- package/.github/workflows/shaka-bot-commands/main.sh +4 -2
- package/.github/workflows/talk-to-shaka-bot.yaml +1 -0
- package/AUTHORS +5 -0
- package/CHANGELOG.md +96 -0
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTORS +6 -0
- package/README.md +2 -0
- package/demo/common/asset.js +0 -1
- package/demo/common/assets.js +0 -1
- package/demo/custom.js +2 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.debug.map +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/demo.compiled.map +1 -1
- package/dist/deps.js +6 -6
- package/dist/locales.js +30 -30
- package/dist/receiver.compiled.debug.map +1 -1
- package/dist/receiver.compiled.map +1 -1
- package/dist/shaka-player.compiled.d.ts +25 -5
- package/dist/shaka-player.compiled.debug.d.ts +25 -5
- package/dist/shaka-player.compiled.debug.externs.js +73 -45
- package/dist/shaka-player.compiled.debug.js +426 -406
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +73 -45
- package/dist/shaka-player.compiled.js +912 -903
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +37 -6
- package/dist/shaka-player.ui.debug.d.ts +37 -6
- package/dist/shaka-player.ui.debug.externs.js +74 -45
- package/dist/shaka-player.ui.debug.js +449 -428
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +74 -45
- package/dist/shaka-player.ui.js +1060 -1051
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/ad_monetization.md +54 -0
- package/docs/tutorials/fairplay.md +2 -2
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/plugins.md +4 -0
- package/docs/tutorials/text-displayer.md +15 -0
- package/docs/tutorials/ui-customization.md +29 -0
- package/docs/tutorials/ui.md +4 -1
- package/externs/lcevc.js +6 -0
- package/externs/shaka/manifest_parser.js +8 -0
- package/externs/shaka/player.js +1 -1
- package/externs/shaka/text.js +3 -1
- package/lib/abr/simple_abr_manager.js +1 -1
- package/lib/ads/client_side_ad.js +9 -0
- package/lib/ads/client_side_ad_manager.js +7 -2
- package/lib/ads/server_side_ad_manager.js +4 -0
- package/lib/cast/cast_proxy.js +18 -0
- package/lib/cea/cea608_memory.js +1 -1
- package/lib/cea/cea_decoder.js +11 -1
- package/lib/dash/dash_parser.js +75 -41
- package/lib/dash/mpd_utils.js +39 -27
- package/lib/deprecate/deprecate.js +1 -1
- package/lib/hls/hls_parser.js +106 -28
- package/lib/hls/hls_utils.js +5 -5
- package/lib/media/content_workarounds.js +30 -0
- package/lib/media/drm_engine.js +16 -12
- package/lib/media/manifest_parser.js +2 -1
- package/lib/media/media_source_engine.js +4 -2
- package/lib/media/play_rate_controller.js +1 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/quality_observer.js +1 -1
- package/lib/media/segment_reference.js +5 -2
- package/lib/media/streaming_engine.js +50 -30
- package/lib/media/transmuxer.js +1 -1
- package/lib/media/video_wrapper.js +4 -3
- package/lib/offline/offline_manifest_parser.js +5 -0
- package/lib/offline/session_deleter.js +0 -8
- package/lib/offline/storage.js +4 -1
- package/lib/player.js +80 -19
- package/lib/polyfill/media_capabilities.js +23 -3
- package/lib/polyfill/mediasource.js +5 -36
- package/lib/text/mp4_ttml_parser.js +2 -2
- package/lib/text/simple_text_displayer.js +7 -6
- package/lib/text/srt_text_parser.js +2 -2
- package/lib/text/text_engine.js +3 -2
- package/lib/text/ttml_text_parser.js +33 -5
- package/lib/text/ui_text_displayer.js +81 -15
- package/lib/text/vtt_text_parser.js +17 -3
- package/lib/util/buffer_utils.js +26 -4
- package/lib/util/cmcd_manager.js +10 -0
- package/lib/util/error.js +1 -0
- package/lib/util/fairplay_utils.js +1 -1
- package/lib/util/manifest_parser_utils.js +14 -8
- package/lib/util/object_utils.js +1 -1
- package/lib/util/periods.js +91 -36
- package/lib/util/platform.js +39 -2
- package/lib/util/player_configuration.js +5 -3
- package/lib/util/stream_utils.js +36 -28
- package/lib/util/xml_utils.js +5 -0
- package/package.json +3 -3
- package/test/cea/cea_decoder_unit.js +40 -0
- package/test/dash/dash_parser_live_unit.js +5 -8
- package/test/hls/hls_parser_unit.js +78 -3
- package/test/media/buffering_observer_unit.js +13 -0
- package/test/media/content_workarounds_unit.js +1 -1
- package/test/media/drm_engine_unit.js +30 -3
- package/test/media/media_source_engine_unit.js +1 -1
- package/test/media/transmuxer_integration.js +2 -2
- package/test/offline/storage_integration.js +3 -0
- package/test/player_integration.js +49 -41
- package/test/player_load_graph_integration.js +13 -7
- package/test/player_src_equals_integration.js +2 -2
- package/test/player_unit.js +40 -3
- package/test/polyfill/media_capabilities_unit.js +12 -6
- package/test/test/util/fake_drm_engine.js +7 -0
- package/test/test/util/layout_tests.js +2 -1
- package/test/test/util/simple_fakes.js +2 -2
- package/test/test/util/test_scheme.js +3 -0
- package/test/test/util/waiter.js +5 -0
- package/test/text/mp4_ttml_parser_unit.js +4 -4
- package/test/text/simple_text_displayer_unit.js +1 -1
- package/test/text/srt_text_parser_unit.js +1 -1
- package/test/text/text_engine_unit.js +6 -0
- package/test/text/ttml_text_parser_unit.js +49 -18
- package/test/text/ui_text_displayer_unit.js +9 -0
- package/test/text/vtt_text_parser_unit.js +31 -0
- package/test/util/buffer_utils_unit.js +1 -1
- package/test/util/periods_unit.js +113 -0
- package/third_party/closure-uri/uri.js +1 -1
- package/ui/externs/ui.js +24 -0
- package/ui/language_utils.js +11 -5
- package/ui/less/containers.less +1 -0
- package/ui/play_button.js +20 -0
- package/ui/playback_rate_selection.js +4 -0
- package/ui/presentation_time.js +5 -5
- package/ui/small_play_button.js +2 -2
- package/ui/volume_bar.js +10 -6
- /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
package/lib/hls/hls_parser.js
CHANGED
|
@@ -215,6 +215,9 @@ shaka.hls.HlsParser = class {
|
|
|
215
215
|
* {!Map.<string, !shaka.media.InitSegmentReference>} */
|
|
216
216
|
this.mapTagToInitSegmentRefMap_ = new Map();
|
|
217
217
|
|
|
218
|
+
/** @private {Map.<string, !shaka.extern.HlsAes128Key>} */
|
|
219
|
+
this.aesKeyInfoMap_ = new Map();
|
|
220
|
+
|
|
218
221
|
/** @private {boolean} */
|
|
219
222
|
this.lowLatencyMode_ = false;
|
|
220
223
|
}
|
|
@@ -277,6 +280,8 @@ shaka.hls.HlsParser = class {
|
|
|
277
280
|
this.groupIdToStreamInfosMap_.clear();
|
|
278
281
|
this.groupIdToCodecsMap_.clear();
|
|
279
282
|
this.globalVariables_.clear();
|
|
283
|
+
this.mapTagToInitSegmentRefMap_.clear();
|
|
284
|
+
this.aesKeyInfoMap_.clear();
|
|
280
285
|
|
|
281
286
|
return Promise.all(pending);
|
|
282
287
|
}
|
|
@@ -435,6 +440,14 @@ shaka.hls.HlsParser = class {
|
|
|
435
440
|
// No-op
|
|
436
441
|
}
|
|
437
442
|
|
|
443
|
+
/**
|
|
444
|
+
* @override
|
|
445
|
+
* @exportInterface
|
|
446
|
+
*/
|
|
447
|
+
onInitialVariantChosen(variant) {
|
|
448
|
+
// No-op
|
|
449
|
+
}
|
|
450
|
+
|
|
438
451
|
/**
|
|
439
452
|
* Align the streams by sequence number by dropping early segments. Then
|
|
440
453
|
* offset the streams to begin at presentation time 0.
|
|
@@ -833,6 +846,22 @@ shaka.hls.HlsParser = class {
|
|
|
833
846
|
this.presentationTimeline_.setSegmentAvailabilityDuration(
|
|
834
847
|
segmentAvailabilityDuration);
|
|
835
848
|
}
|
|
849
|
+
|
|
850
|
+
if (!this.presentationTimeline_.isStartTimeLocked()) {
|
|
851
|
+
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
|
|
852
|
+
const activeStreamInfos =
|
|
853
|
+
streamInfos.filter((s) => s.stream.segmentIndex);
|
|
854
|
+
for (const streamInfo of activeStreamInfos) {
|
|
855
|
+
if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const firstReference = streamInfo.stream.segmentIndex.get(0);
|
|
859
|
+
if (firstReference && firstReference.syncTime) {
|
|
860
|
+
const syncTime = firstReference.syncTime;
|
|
861
|
+
this.presentationTimeline_.setPresentationStartTime(syncTime);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
836
865
|
} else {
|
|
837
866
|
// Use the minimum duration as the presentation duration.
|
|
838
867
|
this.presentationTimeline_.setDuration(this.getMinDuration_());
|
|
@@ -1291,9 +1320,15 @@ shaka.hls.HlsParser = class {
|
|
|
1291
1320
|
// the value NONE. If the value is a quoted-string, it MUST match the
|
|
1292
1321
|
// value of the GROUP-ID attribute of an EXT-X-MEDIA tag elsewhere in the
|
|
1293
1322
|
// Playlist whose TYPE attribute is CLOSED-CAPTIONS.
|
|
1294
|
-
if (type == ContentType.VIDEO
|
|
1295
|
-
|
|
1296
|
-
|
|
1323
|
+
if (type == ContentType.VIDEO) {
|
|
1324
|
+
if (closedCaptionsAttr && closedCaptionsAttr != 'NONE') {
|
|
1325
|
+
return this.groupIdToClosedCaptionsMap_.get(closedCaptionsAttr);
|
|
1326
|
+
} else if (!closedCaptionsAttr &&
|
|
1327
|
+
this.groupIdToClosedCaptionsMap_.size) {
|
|
1328
|
+
for (const key of this.groupIdToClosedCaptionsMap_.keys()) {
|
|
1329
|
+
return this.groupIdToClosedCaptionsMap_.get(key);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1297
1332
|
}
|
|
1298
1333
|
return null;
|
|
1299
1334
|
}
|
|
@@ -2083,8 +2118,15 @@ shaka.hls.HlsParser = class {
|
|
|
2083
2118
|
}
|
|
2084
2119
|
}
|
|
2085
2120
|
|
|
2086
|
-
|
|
2087
|
-
|
|
2121
|
+
let kind = undefined;
|
|
2122
|
+
if (type == shaka.util.ManifestParserUtils.ContentType.TEXT) {
|
|
2123
|
+
if (roles.includes('public.accessibility.transcribes-spoken-dialog') &&
|
|
2124
|
+
roles.includes('public.accessibility.describes-music-and-sound')) {
|
|
2125
|
+
kind = shaka.util.ManifestParserUtils.TextStreamKind.CLOSED_CAPTION;
|
|
2126
|
+
} else {
|
|
2127
|
+
kind = shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2088
2130
|
|
|
2089
2131
|
// If there are no roles, and we have defaulted to the subtitle "kind" for
|
|
2090
2132
|
// this track, add the implied subtitle role.
|
|
@@ -2240,30 +2282,36 @@ shaka.hls.HlsParser = class {
|
|
|
2240
2282
|
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
2241
2283
|
[keyUri], this.config_.retryParameters);
|
|
2242
2284
|
|
|
2243
|
-
const
|
|
2285
|
+
const aesKeyInfoKey = `${drmTag.toString()}-${firstMediaSequenceNumber}`;
|
|
2286
|
+
if (!this.aesKeyInfoMap_.has(aesKeyInfoKey)) {
|
|
2287
|
+
const keyInfo = {method: 'AES-128', iv, firstMediaSequenceNumber};
|
|
2244
2288
|
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2289
|
+
// Don't download the key object until the segment is parsed, to avoid a
|
|
2290
|
+
// startup delay for long manifests with lots of keys.
|
|
2291
|
+
keyInfo.fetchKey = async () => {
|
|
2292
|
+
const keyResponse = await this.makeNetworkRequest_(
|
|
2293
|
+
request, requestType);
|
|
2249
2294
|
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2295
|
+
// keyResponse.status is undefined when URI is
|
|
2296
|
+
// "data:text/plain;base64,"
|
|
2297
|
+
if (!keyResponse.data || keyResponse.data.byteLength != 16) {
|
|
2298
|
+
throw new shaka.util.Error(
|
|
2299
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2300
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2301
|
+
shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
|
|
2302
|
+
}
|
|
2257
2303
|
|
|
2258
|
-
|
|
2259
|
-
|
|
2304
|
+
const algorithm = {
|
|
2305
|
+
name: 'AES-CBC',
|
|
2306
|
+
};
|
|
2307
|
+
keyInfo.cryptoKey = await window.crypto.subtle.importKey(
|
|
2308
|
+
'raw', keyResponse.data, algorithm, true, ['decrypt']);
|
|
2309
|
+
keyInfo.fetchKey = undefined; // No longer needed.
|
|
2260
2310
|
};
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
keyInfo.fetchKey = undefined; // No longer needed.
|
|
2264
|
-
};
|
|
2311
|
+
this.aesKeyInfoMap_.set(aesKeyInfoKey, keyInfo);
|
|
2312
|
+
}
|
|
2265
2313
|
|
|
2266
|
-
return
|
|
2314
|
+
return this.aesKeyInfoMap_.get(aesKeyInfoKey);
|
|
2267
2315
|
}
|
|
2268
2316
|
|
|
2269
2317
|
|
|
@@ -2398,16 +2446,19 @@ shaka.hls.HlsParser = class {
|
|
|
2398
2446
|
if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
|
|
2399
2447
|
/** @type {shaka.extern.HlsAes128Key|undefined} */
|
|
2400
2448
|
let aes128Key = undefined;
|
|
2449
|
+
let byteRangeTag = null;
|
|
2401
2450
|
for (const tag of tags) {
|
|
2402
2451
|
if (tag.name == 'EXT-X-KEY') {
|
|
2403
2452
|
if (tag.getRequiredAttrValue('METHOD') == 'AES-128' &&
|
|
2404
2453
|
tag.id < mapTag.id) {
|
|
2405
2454
|
aes128Key = this.parseAES128DrmTag_(tag, playlist);
|
|
2406
2455
|
}
|
|
2456
|
+
} else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) {
|
|
2457
|
+
byteRangeTag = tag;
|
|
2407
2458
|
}
|
|
2408
2459
|
}
|
|
2409
2460
|
const initSegmentRef = this.createInitSegmentReference_(
|
|
2410
|
-
absoluteInitSegmentUri, mapTag, aes128Key);
|
|
2461
|
+
absoluteInitSegmentUri, mapTag, byteRangeTag, aes128Key);
|
|
2411
2462
|
this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
|
|
2412
2463
|
}
|
|
2413
2464
|
return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
|
|
@@ -2418,14 +2469,19 @@ shaka.hls.HlsParser = class {
|
|
|
2418
2469
|
* playlist.
|
|
2419
2470
|
* @param {string} absoluteInitSegmentUri
|
|
2420
2471
|
* @param {!shaka.hls.Tag} mapTag EXT-X-MAP
|
|
2472
|
+
* @param {shaka.hls.Tag=} byteRangeTag EXT-X-BYTERANGE
|
|
2421
2473
|
* @param {shaka.extern.HlsAes128Key=} aes128Key
|
|
2422
2474
|
* @return {!shaka.media.InitSegmentReference}
|
|
2423
2475
|
* @private
|
|
2424
2476
|
*/
|
|
2425
|
-
createInitSegmentReference_(absoluteInitSegmentUri, mapTag,
|
|
2477
|
+
createInitSegmentReference_(absoluteInitSegmentUri, mapTag, byteRangeTag,
|
|
2478
|
+
aes128Key) {
|
|
2426
2479
|
let startByte = 0;
|
|
2427
2480
|
let endByte = null;
|
|
2428
|
-
|
|
2481
|
+
let byterange = mapTag.getAttributeValue('BYTERANGE');
|
|
2482
|
+
if (!byterange && byteRangeTag) {
|
|
2483
|
+
byterange = byteRangeTag.value;
|
|
2484
|
+
}
|
|
2429
2485
|
// If a BYTERANGE attribute is not specified, the segment consists
|
|
2430
2486
|
// of the entire resource.
|
|
2431
2487
|
if (byterange) {
|
|
@@ -2731,6 +2787,13 @@ shaka.hls.HlsParser = class {
|
|
|
2731
2787
|
if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
|
|
2732
2788
|
firstStartTime = mediaSequenceToStartTime.get(position);
|
|
2733
2789
|
}
|
|
2790
|
+
// This is for recovering from disconnects.
|
|
2791
|
+
if (firstStartTime === 0 &&
|
|
2792
|
+
this.presentationType_ == shaka.hls.HlsParser.PresentationType_.LIVE &&
|
|
2793
|
+
mediaSequenceToStartTime.size > 0 &&
|
|
2794
|
+
!mediaSequenceToStartTime.has(position)) {
|
|
2795
|
+
firstStartTime = this.presentationTimeline_.getSegmentAvailabilityStart();
|
|
2796
|
+
}
|
|
2734
2797
|
|
|
2735
2798
|
/** @type {!Array.<!shaka.media.SegmentReference>} */
|
|
2736
2799
|
const references = [];
|
|
@@ -3319,7 +3382,22 @@ shaka.hls.HlsParser = class {
|
|
|
3319
3382
|
* @return {?shaka.extern.DrmInfo}
|
|
3320
3383
|
* @private
|
|
3321
3384
|
*/
|
|
3322
|
-
static identityDrmParser_(drmTag) {
|
|
3385
|
+
static identityDrmParser_(drmTag, mimeType) {
|
|
3386
|
+
if (mimeType == 'video/mp2t') {
|
|
3387
|
+
throw new shaka.util.Error(
|
|
3388
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
3389
|
+
shaka.util.Error.Category.MANIFEST,
|
|
3390
|
+
shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
if (shaka.util.Platform.isMediaKeysPolyfilled()) {
|
|
3394
|
+
throw new shaka.util.Error(
|
|
3395
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
3396
|
+
shaka.util.Error.Category.MANIFEST,
|
|
3397
|
+
shaka.util.Error.Code
|
|
3398
|
+
.HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3323
3401
|
const method = drmTag.getRequiredAttrValue('METHOD');
|
|
3324
3402
|
const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR'];
|
|
3325
3403
|
if (!VALID_METHODS.includes(method)) {
|
package/lib/hls/hls_utils.js
CHANGED
|
@@ -45,12 +45,12 @@ shaka.hls.Utils = class {
|
|
|
45
45
|
* @return {?shaka.hls.Tag}
|
|
46
46
|
*/
|
|
47
47
|
static getFirstTagWithName(tags, name) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
for (const tag of tags) {
|
|
49
|
+
if (tag.name === name) {
|
|
50
|
+
return tag;
|
|
51
|
+
}
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
-
return tagsWithName[0];
|
|
53
|
+
return null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -69,6 +69,30 @@ shaka.media.ContentWorkarounds = class {
|
|
|
69
69
|
})
|
|
70
70
|
.fullBox('encv', onEncryptionMetadataBox)
|
|
71
71
|
.fullBox('enca', onEncryptionMetadataBox)
|
|
72
|
+
.fullBox('dvav', (box) => {
|
|
73
|
+
boxesToModify.push({
|
|
74
|
+
box,
|
|
75
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
76
|
+
});
|
|
77
|
+
})
|
|
78
|
+
.fullBox('dva1', (box) => {
|
|
79
|
+
boxesToModify.push({
|
|
80
|
+
box,
|
|
81
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
84
|
+
.fullBox('dvh1', (box) => {
|
|
85
|
+
boxesToModify.push({
|
|
86
|
+
box,
|
|
87
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
88
|
+
});
|
|
89
|
+
})
|
|
90
|
+
.fullBox('dvhe', (box) => {
|
|
91
|
+
boxesToModify.push({
|
|
92
|
+
box,
|
|
93
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
94
|
+
});
|
|
95
|
+
})
|
|
72
96
|
.fullBox('hev1', (box) => {
|
|
73
97
|
boxesToModify.push({
|
|
74
98
|
box,
|
|
@@ -105,6 +129,12 @@ shaka.media.ContentWorkarounds = class {
|
|
|
105
129
|
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
106
130
|
});
|
|
107
131
|
})
|
|
132
|
+
.fullBox('ac-4', (box) => {
|
|
133
|
+
boxesToModify.push({
|
|
134
|
+
box,
|
|
135
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
136
|
+
});
|
|
137
|
+
})
|
|
108
138
|
.fullBox('mp4a', (box) => {
|
|
109
139
|
boxesToModify.push({
|
|
110
140
|
box,
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -520,6 +520,15 @@ shaka.media.DrmEngine = class {
|
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Returns true if the manifest has init data.
|
|
525
|
+
*
|
|
526
|
+
* @return {boolean}
|
|
527
|
+
*/
|
|
528
|
+
hasManifestInitData() {
|
|
529
|
+
return !!this.manifestInitData_;
|
|
530
|
+
}
|
|
531
|
+
|
|
523
532
|
/**
|
|
524
533
|
* Sets the server certificate based on the current DrmInfo.
|
|
525
534
|
*
|
|
@@ -1920,19 +1929,14 @@ shaka.media.DrmEngine = class {
|
|
|
1920
1929
|
|
|
1921
1930
|
for (const drm1 of drms1) {
|
|
1922
1931
|
for (const drm2 of drms2) {
|
|
1923
|
-
// This method is only called to compare drmInfos of a video and an
|
|
1924
|
-
// audio adaptations, so we shouldn't have to worry about checking
|
|
1925
|
-
// robustness.
|
|
1926
1932
|
if (drm1.keySystem == drm2.keySystem) {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
});
|
|
1935
|
-
});
|
|
1933
|
+
const initDataMap = new Map();
|
|
1934
|
+
const bothInitDatas = (drm1.initData || [])
|
|
1935
|
+
.concat(drm2.initData || []);
|
|
1936
|
+
for (const d of bothInitDatas) {
|
|
1937
|
+
initDataMap.set(d.keyId, d);
|
|
1938
|
+
}
|
|
1939
|
+
const initData = Array.from(initDataMap.values());
|
|
1936
1940
|
|
|
1937
1941
|
const keyIds = drm1.keyIds && drm2.keyIds ?
|
|
1938
1942
|
new Set([...drm1.keyIds, ...drm2.keyIds]) :
|
|
@@ -202,6 +202,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
202
202
|
'audio/mp4; codecs="mp4a.40.2"',
|
|
203
203
|
'audio/mp4; codecs="ac-3"',
|
|
204
204
|
'audio/mp4; codecs="ec-3"',
|
|
205
|
+
'audio/mp4; codecs="ac-4"',
|
|
205
206
|
'audio/mp4; codecs="opus"',
|
|
206
207
|
'audio/mp4; codecs="flac"',
|
|
207
208
|
'audio/mp4; codecs="dtsc"', // DTS Digital Surround
|
|
@@ -601,14 +602,15 @@ shaka.media.MediaSourceEngine = class {
|
|
|
601
602
|
await this.textEngine_.appendBuffer(
|
|
602
603
|
data,
|
|
603
604
|
reference ? reference.startTime : null,
|
|
604
|
-
reference ? reference.endTime : null
|
|
605
|
+
reference ? reference.endTime : null,
|
|
606
|
+
reference ? reference.getUris()[0] : null);
|
|
605
607
|
return;
|
|
606
608
|
}
|
|
607
609
|
|
|
608
610
|
const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
|
|
609
611
|
let mimeType = this.sourceBufferTypes_[contentType];
|
|
610
612
|
if (this.transmuxers_[contentType]) {
|
|
611
|
-
mimeType = this.transmuxers_[contentType].
|
|
613
|
+
mimeType = this.transmuxers_[contentType].getOriginalMimeType();
|
|
612
614
|
}
|
|
613
615
|
if (shaka.util.TsParser.probe(uint8ArrayData)) {
|
|
614
616
|
const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
|
|
@@ -288,6 +288,17 @@ shaka.media.PresentationTimeline = class {
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Returns if the presentation timeline's start time is locked.
|
|
293
|
+
*
|
|
294
|
+
* @return {boolean}
|
|
295
|
+
* @export
|
|
296
|
+
*/
|
|
297
|
+
isStartTimeLocked() {
|
|
298
|
+
return this.startTimeLocked_;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
291
302
|
/**
|
|
292
303
|
* Gives PresentationTimeline a Stream's minimum segment start time.
|
|
293
304
|
*
|
|
@@ -261,7 +261,7 @@ shaka.media.QualityObserver.QualityChangePosition;
|
|
|
261
261
|
*
|
|
262
262
|
* @description
|
|
263
263
|
* Contains media quality information for a specific content type
|
|
264
|
-
* e.g video or audio.
|
|
264
|
+
* e.g. video or audio.
|
|
265
265
|
*
|
|
266
266
|
* @property {!Array.<shaka.media.QualityObserver.QualityChangePosition>}
|
|
267
267
|
* qualityChangePositions
|
|
@@ -107,12 +107,15 @@ shaka.media.InitSegmentReference = class {
|
|
|
107
107
|
*/
|
|
108
108
|
static equal(reference1, reference2) {
|
|
109
109
|
const ArrayUtils = shaka.util.ArrayUtils;
|
|
110
|
-
if (
|
|
110
|
+
if (reference1 === reference2) {
|
|
111
|
+
return true;
|
|
112
|
+
} else if (!reference1 || !reference2) {
|
|
111
113
|
return reference1 == reference2;
|
|
112
114
|
} else {
|
|
113
115
|
return reference1.getStartByte() == reference2.getStartByte() &&
|
|
114
116
|
reference1.getEndByte() == reference2.getEndByte() &&
|
|
115
|
-
ArrayUtils.equal(
|
|
117
|
+
ArrayUtils.equal(
|
|
118
|
+
reference1.getUris().sort(), reference2.getUris().sort());
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
};
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @fileoverview
|
|
9
|
-
* @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
goog.provide('shaka.media.StreamingEngine');
|
|
@@ -19,6 +18,8 @@ goog.require('shaka.media.SegmentIterator');
|
|
|
19
18
|
goog.require('shaka.media.SegmentReference');
|
|
20
19
|
goog.require('shaka.net.Backoff');
|
|
21
20
|
goog.require('shaka.net.NetworkingEngine');
|
|
21
|
+
goog.require('shaka.util.BufferUtils');
|
|
22
|
+
goog.require('shaka.util.CmcdManager');
|
|
22
23
|
goog.require('shaka.util.DelayedTick');
|
|
23
24
|
goog.require('shaka.util.Destroyer');
|
|
24
25
|
goog.require('shaka.util.Error');
|
|
@@ -27,6 +28,7 @@ goog.require('shaka.util.IDestroyable');
|
|
|
27
28
|
goog.require('shaka.util.Id3Utils');
|
|
28
29
|
goog.require('shaka.util.ManifestParserUtils');
|
|
29
30
|
goog.require('shaka.util.MimeUtils');
|
|
31
|
+
goog.require('shaka.util.Mp4BoxParsers');
|
|
30
32
|
goog.require('shaka.util.Mp4Parser');
|
|
31
33
|
goog.require('shaka.util.Networking');
|
|
32
34
|
|
|
@@ -1285,6 +1287,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1285
1287
|
let remaining = new Uint8Array(0);
|
|
1286
1288
|
let processingResult = false;
|
|
1287
1289
|
let callbackCalled = false;
|
|
1290
|
+
let streamDataCallbackError;
|
|
1288
1291
|
const streamDataCallback = async (data) => {
|
|
1289
1292
|
if (processingResult) {
|
|
1290
1293
|
// If the fallback result processing was triggered, don't also
|
|
@@ -1297,35 +1300,44 @@ shaka.media.StreamingEngine = class {
|
|
|
1297
1300
|
if (this.fatalError_) {
|
|
1298
1301
|
return;
|
|
1299
1302
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1303
|
+
try {
|
|
1304
|
+
// Append the data with complete boxes.
|
|
1305
|
+
// Every time streamDataCallback gets called, append the new data
|
|
1306
|
+
// to the remaining data.
|
|
1307
|
+
// Find the last fully completed Mdat box, and slice the data into
|
|
1308
|
+
// two parts: the first part with completed Mdat boxes, and the
|
|
1309
|
+
// second part with an incomplete box.
|
|
1310
|
+
// Append the first part, and save the second part as remaining
|
|
1311
|
+
// data, and handle it with the next streamDataCallback call.
|
|
1312
|
+
remaining = this.concatArray_(remaining, data);
|
|
1313
|
+
let sawMDAT = false;
|
|
1314
|
+
let offset = 0;
|
|
1315
|
+
new shaka.util.Mp4Parser()
|
|
1316
|
+
.box('mdat', (box) => {
|
|
1317
|
+
offset = box.size + box.start;
|
|
1318
|
+
sawMDAT = true;
|
|
1319
|
+
})
|
|
1320
|
+
.parse(remaining, /* partialOkay= */ false,
|
|
1321
|
+
/* isChunkedData= */ true);
|
|
1322
|
+
if (sawMDAT) {
|
|
1323
|
+
const dataToAppend = remaining.subarray(0, offset);
|
|
1324
|
+
remaining = remaining.subarray(offset);
|
|
1325
|
+
await this.append_(
|
|
1326
|
+
mediaState, presentationTime, stream, reference, dataToAppend,
|
|
1317
1327
|
/* isChunkedData= */ true);
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
await this.append_(
|
|
1322
|
-
mediaState, presentationTime, stream, reference, dataToAppend,
|
|
1323
|
-
/* isChunkedData= */ true);
|
|
1328
|
+
}
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
streamDataCallbackError = error;
|
|
1324
1331
|
}
|
|
1325
1332
|
};
|
|
1326
1333
|
|
|
1327
1334
|
const result =
|
|
1328
1335
|
await this.fetch_(mediaState, reference, streamDataCallback);
|
|
1336
|
+
|
|
1337
|
+
if (streamDataCallbackError) {
|
|
1338
|
+
throw streamDataCallbackError;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1329
1341
|
if (!callbackCalled) {
|
|
1330
1342
|
// In some environments, we might be forced to use network plugins
|
|
1331
1343
|
// that don't support streamDataCallback. In those cases, as a
|
|
@@ -2004,8 +2016,8 @@ shaka.media.StreamingEngine = class {
|
|
|
2004
2016
|
const bufferedBehind = presentationTime - startTime;
|
|
2005
2017
|
|
|
2006
2018
|
const overflow = bufferedBehind - bufferBehind;
|
|
2007
|
-
// See: https://github.com/shaka-project/shaka-player/issues/
|
|
2008
|
-
if (overflow <=
|
|
2019
|
+
// See: https://github.com/shaka-project/shaka-player/issues/6240
|
|
2020
|
+
if (overflow <= 1) {
|
|
2009
2021
|
shaka.log.v2(logPrefix,
|
|
2010
2022
|
'buffer behind okay:',
|
|
2011
2023
|
'presentationTime=' + presentationTime,
|
|
@@ -2056,7 +2068,6 @@ shaka.media.StreamingEngine = class {
|
|
|
2056
2068
|
*
|
|
2057
2069
|
* @return {!Promise.<BufferSource>}
|
|
2058
2070
|
* @private
|
|
2059
|
-
* @suppress {strictMissingProperties}
|
|
2060
2071
|
*/
|
|
2061
2072
|
async fetch_(mediaState, reference, streamDataCallback) {
|
|
2062
2073
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
@@ -2071,12 +2082,17 @@ shaka.media.StreamingEngine = class {
|
|
|
2071
2082
|
shaka.log.v2('fetching: reference=', reference);
|
|
2072
2083
|
|
|
2073
2084
|
const stream = mediaState.stream;
|
|
2085
|
+
const isInit = reference instanceof shaka.media.InitSegmentReference;
|
|
2086
|
+
const mediaReference =
|
|
2087
|
+
/** @type {!shaka.media.SegmentReference} */(reference);
|
|
2088
|
+
const duration = isInit ? 0 :
|
|
2089
|
+
mediaReference.endTime - mediaReference.startTime;
|
|
2074
2090
|
this.playerInterface_.modifySegmentRequest(
|
|
2075
2091
|
request,
|
|
2076
2092
|
{
|
|
2077
2093
|
type: stream.type,
|
|
2078
|
-
init:
|
|
2079
|
-
duration
|
|
2094
|
+
init: isInit,
|
|
2095
|
+
duration,
|
|
2080
2096
|
mimeType: stream.mimeType,
|
|
2081
2097
|
codecs: stream.codecs,
|
|
2082
2098
|
bandwidth: stream.bandwidth,
|
|
@@ -2090,7 +2106,11 @@ shaka.media.StreamingEngine = class {
|
|
|
2090
2106
|
|
|
2091
2107
|
let result = response.data;
|
|
2092
2108
|
if (reference.hlsAes128Key) {
|
|
2093
|
-
|
|
2109
|
+
let iter = mediaState.segmentIterator;
|
|
2110
|
+
const time = isInit ? null : mediaReference.startTime;
|
|
2111
|
+
if (!iter && time != null) {
|
|
2112
|
+
iter = mediaState.stream.segmentIndex.getIteratorForTime(time);
|
|
2113
|
+
}
|
|
2094
2114
|
goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
|
|
2095
2115
|
result = await this.aes128Decrypt_(result, reference.hlsAes128Key, iter);
|
|
2096
2116
|
}
|
package/lib/media/transmuxer.js
CHANGED
|
@@ -175,8 +175,8 @@ shaka.media.VideoWrapper = class {
|
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
177
|
* A class used to move the playhead away from its current time. Sometimes,
|
|
178
|
-
* Edge ignores re-seeks. After changing the current time, check every
|
|
179
|
-
* retrying if the change was not accepted.
|
|
178
|
+
* legacy Edge ignores re-seeks. After changing the current time, check every
|
|
179
|
+
* 100ms, retrying if the change was not accepted.
|
|
180
180
|
*
|
|
181
181
|
* Delay stats over 100 runs of a re-seeking integration test:
|
|
182
182
|
* Edge - 0ms - 2%
|
|
@@ -186,7 +186,8 @@ shaka.media.VideoWrapper = class {
|
|
|
186
186
|
* Edge - 400ms - 2%
|
|
187
187
|
* Chrome - 0ms - 100%
|
|
188
188
|
*
|
|
189
|
-
*
|
|
189
|
+
* Unfortunately, legacy Edge is not receiving updates anymore, but it still
|
|
190
|
+
* must be supported as it is used for web browsers on XBox.
|
|
190
191
|
*
|
|
191
192
|
* @implements {shaka.util.IReleasable}
|
|
192
193
|
* @final
|
|
@@ -71,14 +71,6 @@ shaka.offline.SessionDeleter = class {
|
|
|
71
71
|
return [];
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
try {
|
|
75
|
-
await drmEngine.setServerCertificate();
|
|
76
|
-
} catch (e) {
|
|
77
|
-
shaka.log.warning('Error setting server certificate', e);
|
|
78
|
-
await drmEngine.destroy();
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
74
|
/** @type {!Array.<string>} */
|
|
83
75
|
const sessionIds = [];
|
|
84
76
|
await Promise.all(bucket.sessionIds.map(async (sessionId) => {
|
package/lib/offline/storage.js
CHANGED
|
@@ -396,6 +396,10 @@ shaka.offline.Storage = class {
|
|
|
396
396
|
downloader, config, activeHandle.cell, manifest, drmEngine);
|
|
397
397
|
this.ensureNotDestroyed_();
|
|
398
398
|
|
|
399
|
+
this.setManifestDrmFields_(manifest, manifestDB, drmEngine, config);
|
|
400
|
+
await activeHandle.cell.updateManifest(manifestId, manifestDB);
|
|
401
|
+
this.ensureNotDestroyed_();
|
|
402
|
+
|
|
399
403
|
const offlineUri = shaka.offline.OfflineUri.manifest(
|
|
400
404
|
activeHandle.path.mechanism, activeHandle.path.cell, manifestId);
|
|
401
405
|
|
|
@@ -1231,7 +1235,6 @@ shaka.offline.Storage = class {
|
|
|
1231
1235
|
drmEngine.configure(config.drm);
|
|
1232
1236
|
await drmEngine.initForStorage(
|
|
1233
1237
|
manifest.variants, config.offline.usePersistentLicense);
|
|
1234
|
-
await drmEngine.setServerCertificate();
|
|
1235
1238
|
await drmEngine.createOrLoad();
|
|
1236
1239
|
|
|
1237
1240
|
return drmEngine;
|