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.
Files changed (62) hide show
  1. package/.github/workflows/deflake.yaml +8 -5
  2. package/.github/workflows/release-please.yaml +47 -7
  3. package/.github/workflows/shaka-bot-commands/command-test.sh +1 -1
  4. package/CHANGELOG.md +34 -0
  5. package/CONTRIBUTORS +1 -0
  6. package/demo/common/asset.js +5 -0
  7. package/demo/common/assets.js +16 -1
  8. package/demo/common/message_ids.js +1 -0
  9. package/demo/custom.js +5 -5
  10. package/demo/locales/en.json +1 -0
  11. package/demo/locales/source.json +4 -0
  12. package/demo/main.js +1 -1
  13. package/dist/demo.compiled.debug.js +58 -56
  14. package/dist/demo.compiled.debug.map +3 -3
  15. package/dist/demo.compiled.js +58 -56
  16. package/dist/demo.compiled.map +3 -3
  17. package/dist/locales.js +30 -30
  18. package/dist/receiver.compiled.debug.js +24 -22
  19. package/dist/receiver.compiled.debug.map +3 -3
  20. package/dist/receiver.compiled.js +24 -22
  21. package/dist/receiver.compiled.map +3 -3
  22. package/dist/shaka-player.compiled.d.ts +258 -258
  23. package/dist/shaka-player.compiled.debug.d.ts +258 -258
  24. package/dist/shaka-player.compiled.debug.externs.js +6 -4
  25. package/dist/shaka-player.compiled.debug.js +58 -59
  26. package/dist/shaka-player.compiled.debug.map +3 -3
  27. package/dist/shaka-player.compiled.externs.js +6 -4
  28. package/dist/shaka-player.compiled.js +700 -699
  29. package/dist/shaka-player.compiled.map +3 -3
  30. package/dist/shaka-player.ui.d.ts +302 -302
  31. package/dist/shaka-player.ui.debug.d.ts +302 -302
  32. package/dist/shaka-player.ui.debug.externs.js +6 -4
  33. package/dist/shaka-player.ui.debug.js +205 -206
  34. package/dist/shaka-player.ui.debug.map +3 -3
  35. package/dist/shaka-player.ui.externs.js +6 -4
  36. package/dist/shaka-player.ui.js +867 -866
  37. package/dist/shaka-player.ui.map +3 -3
  38. package/docs/tutorials/faq.md +1 -1
  39. package/docs/tutorials/offline.md +1 -1
  40. package/lib/dash/dash_parser.js +4 -1
  41. package/lib/hls/hls_parser.js +60 -24
  42. package/lib/media/segment_reference.js +11 -5
  43. package/lib/media/streaming_engine.js +15 -25
  44. package/lib/player.js +8 -57
  45. package/lib/text/ttml_text_parser.js +2 -1
  46. package/lib/text/ui_text_displayer.js +49 -3
  47. package/lib/util/cmcd_manager.js +6 -1
  48. package/lib/util/manifest_parser_utils.js +10 -4
  49. package/lib/util/player_configuration.js +2 -2
  50. package/lib/util/stream_utils.js +13 -11
  51. package/lib/util/ts_parser.js +3 -3
  52. package/package.json +2 -2
  53. package/test/dash/dash_parser_manifest_unit.js +5 -0
  54. package/test/dash/dash_parser_segment_base_unit.js +5 -0
  55. package/test/dash/dash_parser_segment_list_unit.js +21 -17
  56. package/test/dash/dash_parser_segment_template_unit.js +5 -0
  57. package/test/hls/hls_live_unit.js +33 -0
  58. package/test/hls/hls_parser_unit.js +2 -57
  59. package/test/test/util/dash_parser_util.js +16 -7
  60. package/test/text/ttml_text_parser_unit.js +3 -4
  61. package/test/text/web_vtt_layout_integration.js +0 -1
  62. package/test/util/ts_parser_unit.js +1 -1
@@ -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>
@@ -537,7 +537,7 @@ function downloadContent(manifestUri, title) {
537
537
  'downloaded': Date()
538
538
  };
539
539
 
540
- return window.storage.store(manifestUri, metadata);
540
+ return window.storage.store(manifestUri, metadata).promise;
541
541
  }
542
542
 
543
543
  /*
@@ -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
- this.manifest_.textStreams = this.periodCombiner_.getTextStreams();
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
@@ -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 language = this.getLanguage_(tag);
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
- const instreamId = tag.getRequiredAttrValue('INSTREAM-ID');
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 numberOfSegments = this.config_.hls.liveSegmentsDelay;
2350
- presentationDelay = this.maxTargetDuration_ * numberOfSegments;
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 {string} playlistUri The absolute uri of the media playlist.
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_(playlistUri, tags, variables) {
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
- playlistUri, verbatimInitSegmentUri),
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, timestampOffset,
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, // This value is ignored in sequence mode.
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, // This value is ignored in sequence mode.
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.absoluteUri,
2729
- item.tags, variables);
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 about
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|undefined} */
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
- let result = await fetchSegment;
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 {!shaka.media.SegmentReference} reference
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, reference, iter) {
1459
- const key = reference.hlsAes128Key;
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
- return response.data;
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.selectTextLanguage(preferredTextLanguage, '', preferForcedSubs);
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 audioTracks = Array.from(this.video_.audioTracks);
4184
- const selectedLanguage = LanguageUtils.normalize(language);
4185
-
4186
- let languageMatch = null;
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 selectedLanguage = LanguageUtils.normalize(language);
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.11-uncompiled';
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
- if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
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
- if (cue.positionAlign == Cue.positionAlign.LEFT) {
706
+ const computedPositionAlign = this.computeCuePositionAlignment_(cue);
707
+ if (computedPositionAlign == Cue.positionAlign.LEFT) {
662
708
  style.cssFloat = 'left';
663
- } else if (cue.positionAlign == Cue.positionAlign.RIGHT) {
709
+ } else if (computedPositionAlign == Cue.positionAlign.RIGHT) {
664
710
  style.cssFloat = 'right';
665
711
  }
666
712
 
@@ -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) => new goog.Uri(uri))
36
- .map((base) => relativeAsGoog.map((i) => base.resolve(i)))
37
- .reduce(Functional.collapseArrays, [])
38
- .map((uri) => uri.toString());
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
- const keySystem = drmInfo.keySystem;
73
- if (keySystem == 'com.apple.fps.1_0' && initDataType == 'skd') {
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);