shaka-player 4.3.11 → 4.3.13
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/deflake.yaml +8 -5
- package/.github/workflows/release-please.yaml +47 -7
- package/.github/workflows/shaka-bot-commands/command-test.sh +1 -1
- package/CHANGELOG.md +34 -0
- package/CONTRIBUTORS +1 -0
- package/demo/common/asset.js +5 -0
- package/demo/common/assets.js +16 -1
- package/demo/common/message_ids.js +1 -0
- package/demo/custom.js +5 -5
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +1 -1
- package/dist/demo.compiled.debug.js +58 -56
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +58 -56
- package/dist/demo.compiled.map +3 -3
- package/dist/locales.js +30 -30
- package/dist/receiver.compiled.debug.js +24 -22
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +24 -22
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +258 -258
- package/dist/shaka-player.compiled.debug.d.ts +258 -258
- package/dist/shaka-player.compiled.debug.externs.js +6 -4
- package/dist/shaka-player.compiled.debug.js +58 -59
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +6 -4
- package/dist/shaka-player.compiled.js +700 -699
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +302 -302
- package/dist/shaka-player.ui.debug.d.ts +302 -302
- package/dist/shaka-player.ui.debug.externs.js +6 -4
- package/dist/shaka-player.ui.debug.js +205 -206
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +6 -4
- package/dist/shaka-player.ui.js +867 -866
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/faq.md +1 -1
- package/docs/tutorials/offline.md +1 -1
- package/lib/dash/dash_parser.js +4 -1
- package/lib/hls/hls_parser.js +60 -24
- package/lib/media/segment_reference.js +11 -5
- package/lib/media/streaming_engine.js +15 -25
- package/lib/player.js +8 -57
- package/lib/text/ttml_text_parser.js +2 -1
- package/lib/text/ui_text_displayer.js +49 -3
- package/lib/util/cmcd_manager.js +6 -1
- package/lib/util/manifest_parser_utils.js +10 -4
- package/lib/util/player_configuration.js +2 -2
- package/lib/util/stream_utils.js +13 -11
- package/lib/util/ts_parser.js +3 -3
- package/package.json +2 -2
- package/test/dash/dash_parser_manifest_unit.js +5 -0
- package/test/dash/dash_parser_segment_base_unit.js +5 -0
- package/test/dash/dash_parser_segment_list_unit.js +21 -17
- package/test/dash/dash_parser_segment_template_unit.js +5 -0
- package/test/hls/hls_live_unit.js +33 -0
- package/test/hls/hls_parser_unit.js +2 -57
- package/test/test/util/dash_parser_util.js +16 -7
- package/test/text/ttml_text_parser_unit.js +3 -4
- package/test/text/web_vtt_layout_integration.js +0 -1
- package/test/util/ts_parser_unit.js +1 -1
package/docs/tutorials/faq.md
CHANGED
|
@@ -153,7 +153,7 @@ In other environments, for example Electron, it is appropriate.
|
|
|
153
153
|
In those cases, before Shaka Player loads a manifest, you can register the
|
|
154
154
|
existing http plugin for `file://` requests:
|
|
155
155
|
```js
|
|
156
|
-
shaka.net.NetworkingEngine.registerScheme('file', shaka.net.HttpXHRPlugin);
|
|
156
|
+
shaka.net.NetworkingEngine.registerScheme('file', shaka.net.HttpXHRPlugin.parse);
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
<hr>
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -522,7 +522,10 @@ shaka.dash.DashParser = class {
|
|
|
522
522
|
// Just update the variants and text streams, which may change as periods
|
|
523
523
|
// are added or removed.
|
|
524
524
|
this.manifest_.variants = this.periodCombiner_.getVariants();
|
|
525
|
-
|
|
525
|
+
const textStreams = this.periodCombiner_.getTextStreams();
|
|
526
|
+
if (textStreams.length > 0) {
|
|
527
|
+
this.manifest_.textStreams = textStreams;
|
|
528
|
+
}
|
|
526
529
|
this.manifest_.imageStreams = this.periodCombiner_.getImageStreams();
|
|
527
530
|
|
|
528
531
|
// Re-filter the manifest. This will check any configured restrictions on
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -774,7 +774,7 @@ shaka.hls.HlsParser = class {
|
|
|
774
774
|
// enough info here to determine the presentation type and duration.
|
|
775
775
|
if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
|
|
776
776
|
if (this.isLive_()) {
|
|
777
|
-
this.changePresentationTimelineToLive_();
|
|
777
|
+
this.changePresentationTimelineToLive_(playlist);
|
|
778
778
|
const delay = this.updatePlaylistDelay_;
|
|
779
779
|
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
|
|
780
780
|
}
|
|
@@ -1440,7 +1440,14 @@ shaka.hls.HlsParser = class {
|
|
|
1440
1440
|
for (const tag of closedCaptionsTags) {
|
|
1441
1441
|
goog.asserts.assert(tag.name == 'EXT-X-MEDIA',
|
|
1442
1442
|
'Should only be called on media tags!');
|
|
1443
|
-
const
|
|
1443
|
+
const languageValue = tag.getAttributeValue('LANGUAGE');
|
|
1444
|
+
let language = this.getLanguage_(tag);
|
|
1445
|
+
if (!languageValue) {
|
|
1446
|
+
const nameValue = tag.getAttributeValue('NAME');
|
|
1447
|
+
if (nameValue) {
|
|
1448
|
+
language = nameValue;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1444
1451
|
|
|
1445
1452
|
// The GROUP-ID value is a quoted-string that specifies the group to which
|
|
1446
1453
|
// the Rendition belongs.
|
|
@@ -1449,7 +1456,9 @@ shaka.hls.HlsParser = class {
|
|
|
1449
1456
|
// The value of INSTREAM-ID is a quoted-string that specifies a Rendition
|
|
1450
1457
|
// within the segments in the Media Playlist. This attribute is REQUIRED
|
|
1451
1458
|
// if the TYPE attribute is CLOSED-CAPTIONS.
|
|
1452
|
-
|
|
1459
|
+
// We need replace SERVICE string by our internal svc string.
|
|
1460
|
+
const instreamId = tag.getRequiredAttrValue('INSTREAM-ID')
|
|
1461
|
+
.replace('SERVICE', 'svc');
|
|
1453
1462
|
if (!this.groupIdToClosedCaptionsMap_.get(groupId)) {
|
|
1454
1463
|
this.groupIdToClosedCaptionsMap_.set(groupId, new Map());
|
|
1455
1464
|
}
|
|
@@ -1709,7 +1718,7 @@ shaka.hls.HlsParser = class {
|
|
|
1709
1718
|
if (this.isLive_() && !wasLive) {
|
|
1710
1719
|
// Now that we know that the presentation is live, convert the timeline
|
|
1711
1720
|
// to live.
|
|
1712
|
-
this.changePresentationTimelineToLive_();
|
|
1721
|
+
this.changePresentationTimelineToLive_(playlist);
|
|
1713
1722
|
}
|
|
1714
1723
|
|
|
1715
1724
|
// Copy values from the real stream info to our initial one.
|
|
@@ -2323,9 +2332,10 @@ shaka.hls.HlsParser = class {
|
|
|
2323
2332
|
}
|
|
2324
2333
|
|
|
2325
2334
|
/**
|
|
2335
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
2326
2336
|
* @private
|
|
2327
2337
|
*/
|
|
2328
|
-
changePresentationTimelineToLive_() {
|
|
2338
|
+
changePresentationTimelineToLive_(playlist) {
|
|
2329
2339
|
// The live edge will be calculated from segments, so we don't need to
|
|
2330
2340
|
// set a presentation start time. We will assert later that this is
|
|
2331
2341
|
// working as expected.
|
|
@@ -2346,8 +2356,12 @@ shaka.hls.HlsParser = class {
|
|
|
2346
2356
|
} else if (this.lowLatencyPresentationDelay_) {
|
|
2347
2357
|
presentationDelay = this.lowLatencyPresentationDelay_;
|
|
2348
2358
|
} else {
|
|
2349
|
-
const
|
|
2350
|
-
|
|
2359
|
+
const playlistSegments = playlist.segments.length;
|
|
2360
|
+
let delaySegments = this.config_.hls.liveSegmentsDelay;
|
|
2361
|
+
if (delaySegments > (playlistSegments - 2)) {
|
|
2362
|
+
delaySegments = Math.max(1, playlistSegments - 2);
|
|
2363
|
+
}
|
|
2364
|
+
presentationDelay = this.maxTargetDuration_ * delaySegments;
|
|
2351
2365
|
}
|
|
2352
2366
|
|
|
2353
2367
|
this.presentationTimeline_.setPresentationStartTime(0);
|
|
@@ -2357,13 +2371,13 @@ shaka.hls.HlsParser = class {
|
|
|
2357
2371
|
|
|
2358
2372
|
/**
|
|
2359
2373
|
* Get the InitSegmentReference for a segment if it has a EXT-X-MAP tag.
|
|
2360
|
-
* @param {
|
|
2374
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
2361
2375
|
* @param {!Array.<!shaka.hls.Tag>} tags Segment tags
|
|
2362
2376
|
* @param {!Map.<string, string>} variables
|
|
2363
2377
|
* @return {shaka.media.InitSegmentReference}
|
|
2364
2378
|
* @private
|
|
2365
2379
|
*/
|
|
2366
|
-
getInitSegmentReference_(
|
|
2380
|
+
getInitSegmentReference_(playlist, tags, variables) {
|
|
2367
2381
|
/** @type {?shaka.hls.Tag} */
|
|
2368
2382
|
const mapTag = shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-MAP');
|
|
2369
2383
|
|
|
@@ -2374,7 +2388,7 @@ shaka.hls.HlsParser = class {
|
|
|
2374
2388
|
const verbatimInitSegmentUri = mapTag.getRequiredAttrValue('URI');
|
|
2375
2389
|
const absoluteInitSegmentUri = this.variableSubstitution_(
|
|
2376
2390
|
shaka.hls.Utils.constructAbsoluteUri(
|
|
2377
|
-
|
|
2391
|
+
playlist.absoluteUri, verbatimInitSegmentUri),
|
|
2378
2392
|
variables);
|
|
2379
2393
|
|
|
2380
2394
|
const mapTagKey = [
|
|
@@ -2382,8 +2396,18 @@ shaka.hls.HlsParser = class {
|
|
|
2382
2396
|
mapTag.getAttributeValue('BYTERANGE', ''),
|
|
2383
2397
|
].join('-');
|
|
2384
2398
|
if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
|
|
2399
|
+
/** @type {shaka.extern.HlsAes128Key|undefined} */
|
|
2400
|
+
let aes128Key = undefined;
|
|
2401
|
+
for (const tag of tags) {
|
|
2402
|
+
if (tag.name == 'EXT-X-KEY') {
|
|
2403
|
+
if (tag.getRequiredAttrValue('METHOD') == 'AES-128' &&
|
|
2404
|
+
tag.id < mapTag.id) {
|
|
2405
|
+
aes128Key = this.parseAES128DrmTag_(tag, playlist);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2385
2409
|
const initSegmentRef = this.createInitSegmentReference_(
|
|
2386
|
-
absoluteInitSegmentUri, mapTag);
|
|
2410
|
+
absoluteInitSegmentUri, mapTag, aes128Key);
|
|
2387
2411
|
this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
|
|
2388
2412
|
}
|
|
2389
2413
|
return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
|
|
@@ -2394,10 +2418,11 @@ shaka.hls.HlsParser = class {
|
|
|
2394
2418
|
* playlist.
|
|
2395
2419
|
* @param {string} absoluteInitSegmentUri
|
|
2396
2420
|
* @param {!shaka.hls.Tag} mapTag EXT-X-MAP
|
|
2421
|
+
* @param {shaka.extern.HlsAes128Key=} aes128Key
|
|
2397
2422
|
* @return {!shaka.media.InitSegmentReference}
|
|
2398
2423
|
* @private
|
|
2399
2424
|
*/
|
|
2400
|
-
createInitSegmentReference_(absoluteInitSegmentUri, mapTag) {
|
|
2425
|
+
createInitSegmentReference_(absoluteInitSegmentUri, mapTag, aes128Key) {
|
|
2401
2426
|
let startByte = 0;
|
|
2402
2427
|
let endByte = null;
|
|
2403
2428
|
const byterange = mapTag.getAttributeValue('BYTERANGE');
|
|
@@ -2408,12 +2433,25 @@ shaka.hls.HlsParser = class {
|
|
|
2408
2433
|
const byteLength = Number(blocks[0]);
|
|
2409
2434
|
startByte = Number(blocks[1]);
|
|
2410
2435
|
endByte = startByte + byteLength - 1;
|
|
2436
|
+
|
|
2437
|
+
if (aes128Key) {
|
|
2438
|
+
// MAP segment encrypted with method 'AES-128', when served with
|
|
2439
|
+
// HTTP Range, has the unencrypted size specified in the range.
|
|
2440
|
+
// See: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
|
2441
|
+
const length = (endByte + 1) - startByte;
|
|
2442
|
+
if (length % 16) {
|
|
2443
|
+
endByte += (16 - (length % 16));
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2411
2446
|
}
|
|
2412
2447
|
|
|
2413
2448
|
const initSegmentRef = new shaka.media.InitSegmentReference(
|
|
2414
2449
|
() => [absoluteInitSegmentUri],
|
|
2415
2450
|
startByte,
|
|
2416
|
-
endByte
|
|
2451
|
+
endByte,
|
|
2452
|
+
/* mediaQuality= */ null,
|
|
2453
|
+
/* timescale= */ null,
|
|
2454
|
+
aes128Key);
|
|
2417
2455
|
return initSegmentRef;
|
|
2418
2456
|
}
|
|
2419
2457
|
|
|
@@ -2427,15 +2465,13 @@ shaka.hls.HlsParser = class {
|
|
|
2427
2465
|
* @param {!Map.<string, string>} variables
|
|
2428
2466
|
* @param {string} absoluteMediaPlaylistUri
|
|
2429
2467
|
* @param {string} type
|
|
2430
|
-
* @param {number} timestampOffset
|
|
2431
2468
|
* @param {shaka.extern.HlsAes128Key=} hlsAes128Key
|
|
2432
2469
|
* @return {shaka.media.SegmentReference}
|
|
2433
2470
|
* @private
|
|
2434
2471
|
*/
|
|
2435
2472
|
createSegmentReference_(
|
|
2436
2473
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
2437
|
-
variables, absoluteMediaPlaylistUri, type,
|
|
2438
|
-
hlsAes128Key) {
|
|
2474
|
+
variables, absoluteMediaPlaylistUri, type, hlsAes128Key) {
|
|
2439
2475
|
const tags = hlsSegment.tags;
|
|
2440
2476
|
const absoluteSegmentUri = this.variableSubstitution_(
|
|
2441
2477
|
hlsSegment.absoluteUri, variables);
|
|
@@ -2539,7 +2575,7 @@ shaka.hls.HlsParser = class {
|
|
|
2539
2575
|
pStartByte,
|
|
2540
2576
|
pEndByte,
|
|
2541
2577
|
initSegmentReference,
|
|
2542
|
-
/* timestampOffset= */ 0,
|
|
2578
|
+
/* timestampOffset= */ 0,
|
|
2543
2579
|
/* appendWindowStart= */ 0,
|
|
2544
2580
|
/* appendWindowEnd= */ Infinity,
|
|
2545
2581
|
/* partialReferences= */ [],
|
|
@@ -2571,8 +2607,11 @@ shaka.hls.HlsParser = class {
|
|
|
2571
2607
|
return null;
|
|
2572
2608
|
}
|
|
2573
2609
|
endTime = startTime + duration;
|
|
2574
|
-
} else {
|
|
2610
|
+
} else if (partialSegmentRefs.length) {
|
|
2575
2611
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
2612
|
+
} else {
|
|
2613
|
+
// Skip segments without duration and without partialsegments
|
|
2614
|
+
return null;
|
|
2576
2615
|
}
|
|
2577
2616
|
|
|
2578
2617
|
// If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
|
|
@@ -2612,7 +2651,7 @@ shaka.hls.HlsParser = class {
|
|
|
2612
2651
|
startByte,
|
|
2613
2652
|
endByte,
|
|
2614
2653
|
initSegmentReference,
|
|
2615
|
-
timestampOffset
|
|
2654
|
+
/* timestampOffset= */ 0,
|
|
2616
2655
|
/* appendWindowStart= */ 0,
|
|
2617
2656
|
/* appendWindowEnd= */ Infinity,
|
|
2618
2657
|
partialSegmentRefs,
|
|
@@ -2697,7 +2736,6 @@ shaka.hls.HlsParser = class {
|
|
|
2697
2736
|
const references = [];
|
|
2698
2737
|
|
|
2699
2738
|
let previousReference = null;
|
|
2700
|
-
let lastDiscontinuityStartTime = firstStartTime;
|
|
2701
2739
|
|
|
2702
2740
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2703
2741
|
const item = hlsSegments[i];
|
|
@@ -2709,7 +2747,6 @@ shaka.hls.HlsParser = class {
|
|
|
2709
2747
|
item.tags, 'EXT-X-DISCONTINUITY');
|
|
2710
2748
|
if (discontinuityTag) {
|
|
2711
2749
|
discontinuitySequence++;
|
|
2712
|
-
lastDiscontinuityStartTime = startTime;
|
|
2713
2750
|
}
|
|
2714
2751
|
|
|
2715
2752
|
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
@@ -2725,8 +2762,8 @@ shaka.hls.HlsParser = class {
|
|
|
2725
2762
|
|
|
2726
2763
|
mediaSequenceToStartTime.set(position, startTime);
|
|
2727
2764
|
|
|
2728
|
-
initSegmentRef = this.getInitSegmentReference_(playlist.
|
|
2729
|
-
|
|
2765
|
+
initSegmentRef = this.getInitSegmentReference_(playlist, item.tags,
|
|
2766
|
+
variables);
|
|
2730
2767
|
|
|
2731
2768
|
// If the stream is low latency and the user has not configured the
|
|
2732
2769
|
// lowLatencyMode, but if it has been configured to activate the
|
|
@@ -2748,7 +2785,6 @@ shaka.hls.HlsParser = class {
|
|
|
2748
2785
|
variables,
|
|
2749
2786
|
playlist.absoluteUri,
|
|
2750
2787
|
type,
|
|
2751
|
-
lastDiscontinuityStartTime,
|
|
2752
2788
|
hlsAes128Key);
|
|
2753
2789
|
|
|
2754
2790
|
if (reference) {
|
|
@@ -27,11 +27,14 @@ shaka.media.InitSegmentReference = class {
|
|
|
27
27
|
* @param {?number} endByte The offset from the start of the resource
|
|
28
28
|
* to the end of the segment, inclusive. A value of null indicates that the
|
|
29
29
|
* segment extends to the end of the resource.
|
|
30
|
-
* @param {null|shaka.extern.MediaQualityInfo=} mediaQuality Information
|
|
31
|
-
* the quality of the media associated with this init segment.
|
|
32
|
-
* @param {number=} timescale
|
|
30
|
+
* @param {(null|shaka.extern.MediaQualityInfo)=} mediaQuality Information
|
|
31
|
+
* about the quality of the media associated with this init segment.
|
|
32
|
+
* @param {(null|number)=} timescale
|
|
33
|
+
* @param {?shaka.extern.HlsAes128Key=} aes128Key
|
|
34
|
+
* The segment's AES-128-CBC full segment encryption key and iv.
|
|
33
35
|
*/
|
|
34
|
-
constructor(uris, startByte, endByte, mediaQuality = null, timescale
|
|
36
|
+
constructor(uris, startByte, endByte, mediaQuality = null, timescale = null,
|
|
37
|
+
aes128Key = null) {
|
|
35
38
|
/** @type {function():!Array.<string>} */
|
|
36
39
|
this.getUris = uris;
|
|
37
40
|
|
|
@@ -44,8 +47,11 @@ shaka.media.InitSegmentReference = class {
|
|
|
44
47
|
/** @const {shaka.extern.MediaQualityInfo|null} */
|
|
45
48
|
this.mediaQuality = mediaQuality;
|
|
46
49
|
|
|
47
|
-
/** @type {number|
|
|
50
|
+
/** @type {number|null} */
|
|
48
51
|
this.timescale = timescale;
|
|
52
|
+
|
|
53
|
+
/** @type {?shaka.extern.HlsAes128Key} */
|
|
54
|
+
this.hlsAes128Key = aes128Key;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
/**
|
|
@@ -1357,16 +1357,11 @@ shaka.media.StreamingEngine = class {
|
|
|
1357
1357
|
'ReadableStream is not supported by the browser.');
|
|
1358
1358
|
}
|
|
1359
1359
|
const fetchSegment = this.fetch_(mediaState, reference);
|
|
1360
|
-
|
|
1360
|
+
const result = await fetchSegment;
|
|
1361
1361
|
this.destroyer_.ensureNotDestroyed();
|
|
1362
1362
|
if (this.fatalError_) {
|
|
1363
1363
|
return;
|
|
1364
1364
|
}
|
|
1365
|
-
if (reference.hlsAes128Key) {
|
|
1366
|
-
goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
|
|
1367
|
-
result = await this.aes128Decrypt_(result, reference, iter);
|
|
1368
|
-
}
|
|
1369
|
-
this.destroyer_.ensureNotDestroyed();
|
|
1370
1365
|
|
|
1371
1366
|
// If the text stream gets switched between fetch_() and append_(), the
|
|
1372
1367
|
// new text parser is initialized, but the new init segment is not
|
|
@@ -1450,13 +1445,13 @@ shaka.media.StreamingEngine = class {
|
|
|
1450
1445
|
|
|
1451
1446
|
/**
|
|
1452
1447
|
* @param {!BufferSource} rawResult
|
|
1453
|
-
* @param {
|
|
1448
|
+
* @param {shaka.extern.HlsAes128Key} aes128Key
|
|
1454
1449
|
* @param {!shaka.media.SegmentIterator} iter
|
|
1455
1450
|
* @return {!Promise.<!BufferSource>} finalResult
|
|
1456
1451
|
* @private
|
|
1457
1452
|
*/
|
|
1458
|
-
async aes128Decrypt_(rawResult,
|
|
1459
|
-
const key =
|
|
1453
|
+
async aes128Decrypt_(rawResult, aes128Key, iter) {
|
|
1454
|
+
const key = aes128Key;
|
|
1460
1455
|
if (!key.cryptoKey) {
|
|
1461
1456
|
goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
|
|
1462
1457
|
'preloaded, fetchKey function should be provided');
|
|
@@ -1693,10 +1688,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1693
1688
|
}
|
|
1694
1689
|
}
|
|
1695
1690
|
|
|
1696
|
-
const lastDiscontinuitySequence =
|
|
1697
|
-
mediaState.lastSegmentReference ?
|
|
1698
|
-
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1699
1691
|
if (this.manifest_.sequenceMode) {
|
|
1692
|
+
const lastDiscontinuitySequence =
|
|
1693
|
+
mediaState.lastSegmentReference ?
|
|
1694
|
+
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1700
1695
|
// Across discontinuity bounds, we should resync timestamps for
|
|
1701
1696
|
// sequence mode playbacks. The next segment appended should
|
|
1702
1697
|
// land at its theoretical timestamp from the segment index.
|
|
@@ -1706,18 +1701,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1706
1701
|
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1707
1702
|
mediaState.type, reference.startTime));
|
|
1708
1703
|
}
|
|
1709
|
-
} else {
|
|
1710
|
-
// In segments mode, we need to resync to set the timestampOffset
|
|
1711
|
-
// to the start of the current discontinuity sequence. This is
|
|
1712
|
-
// because individual discontinuity sequences may have internal
|
|
1713
|
-
// timestamps that overlap, so we adjust the timestampOffset to avoid
|
|
1714
|
-
// having the SourceBuffer get overwritten.
|
|
1715
|
-
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1716
|
-
operations.push(
|
|
1717
|
-
this.playerInterface_.mediaSourceEngine.resync(
|
|
1718
|
-
mediaState.type,
|
|
1719
|
-
reference.timestampOffset));
|
|
1720
|
-
}
|
|
1721
1704
|
}
|
|
1722
1705
|
|
|
1723
1706
|
await Promise.all(operations);
|
|
@@ -2104,7 +2087,14 @@ shaka.media.StreamingEngine = class {
|
|
|
2104
2087
|
mediaState.operation = op;
|
|
2105
2088
|
const response = await op.promise;
|
|
2106
2089
|
mediaState.operation = null;
|
|
2107
|
-
|
|
2090
|
+
|
|
2091
|
+
let result = response.data;
|
|
2092
|
+
if (reference.hlsAes128Key) {
|
|
2093
|
+
const iter = mediaState.segmentIterator;
|
|
2094
|
+
goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
|
|
2095
|
+
result = await this.aes128Decrypt_(result, reference.hlsAes128Key, iter);
|
|
2096
|
+
}
|
|
2097
|
+
return result;
|
|
2108
2098
|
}
|
|
2109
2099
|
|
|
2110
2100
|
|
package/lib/player.js
CHANGED
|
@@ -2651,16 +2651,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2651
2651
|
return;
|
|
2652
2652
|
}
|
|
2653
2653
|
|
|
2654
|
-
this.selectAudioLanguage(preferredAudioLanguage);
|
|
2655
|
-
|
|
2656
2654
|
const preferredVariantRole = this.config_.preferredVariantRole;
|
|
2657
|
-
|
|
2658
|
-
// If the user has not selected a role preference, the previous match is
|
|
2659
|
-
// selected.
|
|
2660
|
-
if (preferredVariantRole == '') {
|
|
2661
|
-
return;
|
|
2662
|
-
}
|
|
2663
|
-
|
|
2664
2655
|
this.selectAudioLanguage(preferredAudioLanguage, preferredVariantRole);
|
|
2665
2656
|
}
|
|
2666
2657
|
|
|
@@ -2671,7 +2662,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2671
2662
|
*/
|
|
2672
2663
|
setupPreferredTextOnSrc_() {
|
|
2673
2664
|
const preferredTextLanguage = this.config_.preferredTextLanguage;
|
|
2674
|
-
const preferForcedSubs = this.config_.preferForcedSubs;
|
|
2675
2665
|
|
|
2676
2666
|
// If the user has not selected a preference, the browser preference is
|
|
2677
2667
|
// left.
|
|
@@ -2679,16 +2669,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2679
2669
|
return;
|
|
2680
2670
|
}
|
|
2681
2671
|
|
|
2682
|
-
this.
|
|
2683
|
-
|
|
2672
|
+
const preferForcedSubs = this.config_.preferForcedSubs;
|
|
2684
2673
|
const preferredTextRole = this.config_.preferredTextRole;
|
|
2685
2674
|
|
|
2686
|
-
// If the user has not selected a role preference, the previous match is
|
|
2687
|
-
// selected.
|
|
2688
|
-
if (preferredTextRole == '') {
|
|
2689
|
-
return;
|
|
2690
|
-
}
|
|
2691
|
-
|
|
2692
2675
|
this.selectTextLanguage(preferredTextLanguage, preferredTextRole,
|
|
2693
2676
|
preferForcedSubs);
|
|
2694
2677
|
}
|
|
@@ -4141,8 +4124,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4141
4124
|
* @export
|
|
4142
4125
|
*/
|
|
4143
4126
|
selectAudioLanguage(language, role, channelsCount = 0) {
|
|
4144
|
-
const LanguageUtils = shaka.util.LanguageUtils;
|
|
4145
|
-
|
|
4146
4127
|
if (this.manifest_ && this.playhead_) {
|
|
4147
4128
|
this.currentAdaptationSetCriteria_ =
|
|
4148
4129
|
new shaka.media.PreferenceBasedCriteria(language, role || '',
|
|
@@ -4180,33 +4161,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4180
4161
|
// If we haven't switched yet, just use ABR to find a new track.
|
|
4181
4162
|
this.chooseVariantAndSwitch_();
|
|
4182
4163
|
} else if (this.video_ && this.video_.audioTracks) {
|
|
4183
|
-
const
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
let languageAndRoleMatch = null;
|
|
4188
|
-
|
|
4189
|
-
for (const audioTrack of audioTracks) {
|
|
4190
|
-
const track = shaka.util.StreamUtils.html5AudioTrackToTrack(audioTrack);
|
|
4191
|
-
|
|
4192
|
-
if (LanguageUtils.normalize(track.language) == selectedLanguage) {
|
|
4193
|
-
languageMatch = audioTrack;
|
|
4194
|
-
|
|
4195
|
-
if (role) {
|
|
4196
|
-
if (track.roles.includes(role)) {
|
|
4197
|
-
languageAndRoleMatch = audioTrack;
|
|
4198
|
-
}
|
|
4199
|
-
} else { // no role
|
|
4200
|
-
if (track.roles.length == 0) {
|
|
4201
|
-
languageAndRoleMatch = audioTrack;
|
|
4202
|
-
}
|
|
4203
|
-
}
|
|
4204
|
-
}
|
|
4205
|
-
}
|
|
4206
|
-
if (languageAndRoleMatch) {
|
|
4207
|
-
this.switchHtml5Track_(languageAndRoleMatch);
|
|
4208
|
-
} else if (languageMatch) {
|
|
4209
|
-
this.switchHtml5Track_(languageMatch);
|
|
4164
|
+
const track = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
|
|
4165
|
+
this.getVariantTracks(), language, role || '', false)[0];
|
|
4166
|
+
if (track) {
|
|
4167
|
+
this.selectVariantTrack(track);
|
|
4210
4168
|
}
|
|
4211
4169
|
}
|
|
4212
4170
|
}
|
|
@@ -4222,8 +4180,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4222
4180
|
* @export
|
|
4223
4181
|
*/
|
|
4224
4182
|
selectTextLanguage(language, role, forced = false) {
|
|
4225
|
-
const LanguageUtils = shaka.util.LanguageUtils;
|
|
4226
|
-
|
|
4227
4183
|
if (this.manifest_ && this.playhead_) {
|
|
4228
4184
|
this.currentTextLanguage_ = language;
|
|
4229
4185
|
this.currentTextRole_ = role || '';
|
|
@@ -4244,13 +4200,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4244
4200
|
}
|
|
4245
4201
|
}
|
|
4246
4202
|
} else {
|
|
4247
|
-
const
|
|
4248
|
-
|
|
4249
|
-
const track = this.getTextTracks().find((t) => {
|
|
4250
|
-
return LanguageUtils.normalize(t.language) == selectedLanguage &&
|
|
4251
|
-
(!role || t.roles.includes(role)) && t.forced == forced;
|
|
4252
|
-
});
|
|
4253
|
-
|
|
4203
|
+
const track = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
|
|
4204
|
+
this.getTextTracks(), language, role || '', forced)[0];
|
|
4254
4205
|
if (track) {
|
|
4255
4206
|
this.selectTextTrack(track);
|
|
4256
4207
|
}
|
|
@@ -7020,7 +6971,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
7020
6971
|
* @define {string} A version number taken from git at compile time.
|
|
7021
6972
|
* @export
|
|
7022
6973
|
*/
|
|
7023
|
-
shaka.Player.version = 'v4.3.
|
|
6974
|
+
shaka.Player.version = 'v4.3.13-uncompiled';
|
|
7024
6975
|
|
|
7025
6976
|
// Initialize the deprecation system using the version string we just set
|
|
7026
6977
|
// on the player.
|
|
@@ -214,11 +214,12 @@ shaka.text.TtmlTextParser = class {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
const parentIsContent = isContent;
|
|
218
217
|
if (cueNode.nodeName == 'p' || imageElement) {
|
|
219
218
|
isContent = true;
|
|
220
219
|
}
|
|
221
220
|
|
|
221
|
+
const parentIsContent = isContent;
|
|
222
|
+
|
|
222
223
|
const spaceStyle = cueElement.getAttribute('xml:space') ||
|
|
223
224
|
(whitespaceTrim ? 'default' : 'preserve');
|
|
224
225
|
|
|
@@ -481,6 +481,43 @@ shaka.text.UITextDisplayer = class {
|
|
|
481
481
|
this.currentCuesMap_.set(cue, {cueElement, wrapper, regionElement});
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Compute cue position alignment
|
|
486
|
+
* See https://www.w3.org/TR/webvtt1/#webvtt-cue-position-alignment
|
|
487
|
+
*
|
|
488
|
+
* @param {!shaka.extern.Cue} cue
|
|
489
|
+
* @private
|
|
490
|
+
*/
|
|
491
|
+
computeCuePositionAlignment_(cue) {
|
|
492
|
+
const Cue = shaka.text.Cue;
|
|
493
|
+
const {direction, positionAlign, textAlign} = cue;
|
|
494
|
+
|
|
495
|
+
if (positionAlign !== Cue.positionAlign.AUTO) {
|
|
496
|
+
// Position align is not AUTO: use it
|
|
497
|
+
return positionAlign;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Position align is AUTO: use text align to compute its value
|
|
501
|
+
|
|
502
|
+
if (textAlign === Cue.textAlign.LEFT ||
|
|
503
|
+
(textAlign === Cue.textAlign.START &&
|
|
504
|
+
direction === Cue.direction.HORIZONTAL_LEFT_TO_RIGHT) ||
|
|
505
|
+
(textAlign === Cue.textAlign.END &&
|
|
506
|
+
direction === Cue.direction.HORIZONTAL_RIGHT_TO_LEFT)) {
|
|
507
|
+
return Cue.positionAlign.LEFT;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (textAlign === Cue.textAlign.RIGHT ||
|
|
511
|
+
(textAlign === Cue.textAlign.START &&
|
|
512
|
+
direction === Cue.direction.HORIZONTAL_RIGHT_TO_LEFT) ||
|
|
513
|
+
(textAlign === Cue.textAlign.END &&
|
|
514
|
+
direction === Cue.direction.HORIZONTAL_LEFT_TO_RIGHT)) {
|
|
515
|
+
return Cue.positionAlign.RIGHT;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return Cue.positionAlign.CENTER;
|
|
519
|
+
}
|
|
520
|
+
|
|
484
521
|
/**
|
|
485
522
|
* @param {!HTMLElement} cueElement
|
|
486
523
|
* @param {!shaka.extern.Cue} cue
|
|
@@ -617,7 +654,15 @@ shaka.text.UITextDisplayer = class {
|
|
|
617
654
|
// TODO: Implement line alignment with line number.
|
|
618
655
|
// TODO: Implement lineAlignment of 'CENTER'.
|
|
619
656
|
if (cue.line != null) {
|
|
620
|
-
|
|
657
|
+
let lineInterpretation = cue.lineInterpretation;
|
|
658
|
+
// HACK: the current implementation of UITextDisplayer only handled
|
|
659
|
+
// PERCENTAGE, as cue.line=0 is zero, we can interpret it as percentage
|
|
660
|
+
// safely.
|
|
661
|
+
if (cue.line == 0 &&
|
|
662
|
+
lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
|
|
663
|
+
lineInterpretation = Cue.lineInterpretation.PERCENTAGE;
|
|
664
|
+
}
|
|
665
|
+
if (lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
|
|
621
666
|
style.position = 'absolute';
|
|
622
667
|
if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
|
|
623
668
|
style.width = '100%';
|
|
@@ -658,9 +703,10 @@ shaka.text.UITextDisplayer = class {
|
|
|
658
703
|
|
|
659
704
|
// The positionAlign attribute is an alignment for the text container in
|
|
660
705
|
// the dimension of the writing direction.
|
|
661
|
-
|
|
706
|
+
const computedPositionAlign = this.computeCuePositionAlignment_(cue);
|
|
707
|
+
if (computedPositionAlign == Cue.positionAlign.LEFT) {
|
|
662
708
|
style.cssFloat = 'left';
|
|
663
|
-
} else if (
|
|
709
|
+
} else if (computedPositionAlign == Cue.positionAlign.RIGHT) {
|
|
664
710
|
style.cssFloat = 'right';
|
|
665
711
|
}
|
|
666
712
|
|
package/lib/util/cmcd_manager.js
CHANGED
|
@@ -331,12 +331,17 @@ shaka.util.CmcdManager = class {
|
|
|
331
331
|
* @private
|
|
332
332
|
*/
|
|
333
333
|
getObjectTypeFromMimeType_(mimeType) {
|
|
334
|
-
switch (mimeType) {
|
|
334
|
+
switch (mimeType.toLowerCase()) {
|
|
335
335
|
case 'video/webm':
|
|
336
336
|
case 'video/mp4':
|
|
337
|
+
case 'video/mpeg':
|
|
338
|
+
case 'video/mp2t':
|
|
337
339
|
return shaka.util.CmcdManager.ObjectType.MUXED;
|
|
338
340
|
|
|
339
341
|
case 'application/x-mpegurl':
|
|
342
|
+
case 'application/vnd.apple.mpegurl':
|
|
343
|
+
case 'application/dash+xml':
|
|
344
|
+
case 'video/vnd.mpeg.dash.mpd':
|
|
340
345
|
return shaka.util.CmcdManager.ObjectType.MANIFEST;
|
|
341
346
|
|
|
342
347
|
default:
|
|
@@ -29,13 +29,19 @@ shaka.util.ManifestParserUtils = class {
|
|
|
29
29
|
return baseUris;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
if (baseUris.length == 1 && relativeUris.length == 1) {
|
|
33
|
+
const baseUri = new goog.Uri(baseUris[0]);
|
|
34
|
+
const relativeUri = new goog.Uri(relativeUris[0]);
|
|
35
|
+
return [baseUri.resolve(relativeUri).toString()];
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri));
|
|
33
39
|
// Resolve each URI relative to each base URI, creating an Array of Arrays.
|
|
34
40
|
// Then flatten the Arrays into a single Array.
|
|
35
|
-
return baseUris.map((uri) =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
return baseUris.map((uri) => {
|
|
42
|
+
const base = new goog.Uri(uri);
|
|
43
|
+
return relativeAsGoog.map((i) => base.resolve(i).toString());
|
|
44
|
+
}).reduce(Functional.collapseArrays, []);
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
|
|
@@ -69,8 +69,8 @@ shaka.util.PlayerConfiguration = class {
|
|
|
69
69
|
advanced: {}, // key is arbitrary key system ID, value is a record type
|
|
70
70
|
delayLicenseRequestUntilPlayed: false,
|
|
71
71
|
initDataTransform: (initData, initDataType, drmInfo) => {
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
if (shaka.util.Platform.isMediaKeysPolyfilled() &&
|
|
73
|
+
initDataType == 'skd') {
|
|
74
74
|
const cert = drmInfo.serverCertificate;
|
|
75
75
|
const contentId =
|
|
76
76
|
shaka.util.FairPlayUtils.defaultGetContentId(initData);
|