shaka-player 4.3.12 → 4.3.14

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 (140) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/question.md +7 -0
  3. package/.github/workflows/release-please.yaml +2 -27
  4. package/AUTHORS +5 -0
  5. package/CHANGELOG.md +108 -0
  6. package/CODE_OF_CONDUCT.md +1 -1
  7. package/CONTRIBUTORS +6 -0
  8. package/README.md +2 -0
  9. package/demo/common/asset.js +5 -1
  10. package/demo/common/assets.js +16 -2
  11. package/demo/common/message_ids.js +1 -0
  12. package/demo/custom.js +2 -0
  13. package/demo/locales/en.json +1 -0
  14. package/demo/locales/source.json +4 -0
  15. package/demo/main.js +1 -1
  16. package/dist/demo.compiled.debug.js +56 -54
  17. package/dist/demo.compiled.debug.map +3 -3
  18. package/dist/demo.compiled.js +56 -54
  19. package/dist/demo.compiled.map +3 -3
  20. package/dist/deps.js +6 -6
  21. package/dist/locales.js +31 -31
  22. package/dist/receiver.compiled.debug.js +24 -22
  23. package/dist/receiver.compiled.debug.map +3 -3
  24. package/dist/receiver.compiled.js +24 -22
  25. package/dist/receiver.compiled.map +3 -3
  26. package/dist/shaka-player.compiled.d.ts +26 -6
  27. package/dist/shaka-player.compiled.debug.d.ts +26 -6
  28. package/dist/shaka-player.compiled.debug.externs.js +79 -49
  29. package/dist/shaka-player.compiled.debug.js +426 -410
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +79 -49
  32. package/dist/shaka-player.compiled.js +912 -901
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +38 -7
  35. package/dist/shaka-player.ui.debug.d.ts +38 -7
  36. package/dist/shaka-player.ui.debug.externs.js +80 -49
  37. package/dist/shaka-player.ui.debug.js +460 -442
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +80 -49
  40. package/dist/shaka-player.ui.js +1060 -1049
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/ad_monetization.md +54 -0
  43. package/docs/tutorials/fairplay.md +2 -2
  44. package/docs/tutorials/index.json +1 -0
  45. package/docs/tutorials/offline.md +1 -1
  46. package/docs/tutorials/plugins.md +4 -0
  47. package/docs/tutorials/text-displayer.md +15 -0
  48. package/docs/tutorials/ui-customization.md +29 -0
  49. package/docs/tutorials/ui.md +4 -1
  50. package/externs/lcevc.js +6 -0
  51. package/externs/shaka/manifest_parser.js +8 -0
  52. package/externs/shaka/player.js +1 -1
  53. package/externs/shaka/text.js +3 -1
  54. package/lib/abr/simple_abr_manager.js +1 -1
  55. package/lib/ads/client_side_ad.js +9 -0
  56. package/lib/ads/client_side_ad_manager.js +7 -2
  57. package/lib/ads/server_side_ad_manager.js +4 -0
  58. package/lib/cast/cast_proxy.js +18 -0
  59. package/lib/cea/cea608_memory.js +1 -1
  60. package/lib/cea/cea_decoder.js +11 -1
  61. package/lib/dash/dash_parser.js +79 -42
  62. package/lib/dash/mpd_utils.js +39 -27
  63. package/lib/hls/hls_parser.js +148 -49
  64. package/lib/hls/hls_utils.js +5 -5
  65. package/lib/media/content_workarounds.js +30 -0
  66. package/lib/media/drm_engine.js +16 -12
  67. package/lib/media/media_source_engine.js +4 -2
  68. package/lib/media/play_rate_controller.js +1 -0
  69. package/lib/media/presentation_timeline.js +11 -0
  70. package/lib/media/quality_observer.js +1 -1
  71. package/lib/media/segment_reference.js +16 -7
  72. package/lib/media/streaming_engine.js +64 -54
  73. package/lib/media/transmuxer.js +1 -1
  74. package/lib/media/video_wrapper.js +4 -3
  75. package/lib/offline/offline_manifest_parser.js +5 -0
  76. package/lib/offline/session_deleter.js +0 -8
  77. package/lib/offline/storage.js +4 -1
  78. package/lib/player.js +79 -18
  79. package/lib/polyfill/media_capabilities.js +23 -3
  80. package/lib/text/mp4_ttml_parser.js +2 -2
  81. package/lib/text/simple_text_displayer.js +7 -6
  82. package/lib/text/srt_text_parser.js +2 -2
  83. package/lib/text/text_engine.js +3 -2
  84. package/lib/text/ttml_text_parser.js +33 -5
  85. package/lib/text/ui_text_displayer.js +81 -15
  86. package/lib/text/vtt_text_parser.js +13 -0
  87. package/lib/util/buffer_utils.js +26 -4
  88. package/lib/util/cmcd_manager.js +16 -1
  89. package/lib/util/fairplay_utils.js +1 -1
  90. package/lib/util/manifest_parser_utils.js +20 -8
  91. package/lib/util/object_utils.js +1 -1
  92. package/lib/util/periods.js +91 -36
  93. package/lib/util/platform.js +39 -2
  94. package/lib/util/player_configuration.js +5 -3
  95. package/lib/util/stream_utils.js +36 -28
  96. package/lib/util/ts_parser.js +3 -3
  97. package/lib/util/xml_utils.js +5 -0
  98. package/package.json +2 -2
  99. package/test/cea/cea_decoder_unit.js +40 -0
  100. package/test/dash/dash_parser_live_unit.js +5 -8
  101. package/test/dash/dash_parser_manifest_unit.js +5 -0
  102. package/test/dash/dash_parser_segment_base_unit.js +5 -0
  103. package/test/dash/dash_parser_segment_list_unit.js +21 -17
  104. package/test/dash/dash_parser_segment_template_unit.js +5 -0
  105. package/test/hls/hls_live_unit.js +33 -0
  106. package/test/hls/hls_parser_unit.js +52 -60
  107. package/test/media/buffering_observer_unit.js +13 -0
  108. package/test/media/content_workarounds_unit.js +1 -1
  109. package/test/media/drm_engine_unit.js +30 -3
  110. package/test/media/media_source_engine_unit.js +1 -1
  111. package/test/media/transmuxer_integration.js +2 -2
  112. package/test/offline/storage_integration.js +3 -0
  113. package/test/player_integration.js +40 -40
  114. package/test/player_unit.js +31 -0
  115. package/test/polyfill/media_capabilities_unit.js +12 -6
  116. package/test/test/util/dash_parser_util.js +16 -7
  117. package/test/test/util/fake_drm_engine.js +7 -0
  118. package/test/test/util/layout_tests.js +2 -1
  119. package/test/test/util/simple_fakes.js +2 -2
  120. package/test/test/util/test_scheme.js +3 -0
  121. package/test/test/util/waiter.js +5 -0
  122. package/test/text/mp4_ttml_parser_unit.js +4 -4
  123. package/test/text/simple_text_displayer_unit.js +1 -1
  124. package/test/text/srt_text_parser_unit.js +1 -1
  125. package/test/text/text_engine_unit.js +6 -0
  126. package/test/text/ttml_text_parser_unit.js +49 -18
  127. package/test/text/ui_text_displayer_unit.js +9 -0
  128. package/test/text/vtt_text_parser_unit.js +2 -17
  129. package/test/util/buffer_utils_unit.js +1 -1
  130. package/test/util/periods_unit.js +113 -0
  131. package/test/util/ts_parser_unit.js +1 -1
  132. package/third_party/closure-uri/uri.js +1 -1
  133. package/ui/externs/ui.js +24 -0
  134. package/ui/language_utils.js +11 -5
  135. package/ui/play_button.js +20 -0
  136. package/ui/playback_rate_selection.js +4 -0
  137. package/ui/presentation_time.js +5 -5
  138. package/ui/small_play_button.js +2 -2
  139. package/ui/volume_bar.js +10 -6
  140. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -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
- // Append the data with complete boxes.
1301
- // Every time streamDataCallback gets called, append the new data to
1302
- // the remaining data.
1303
- // Find the last fully completed Mdat box, and slice the data into two
1304
- // parts: the first part with completed Mdat boxes, and the second
1305
- // part with an incomplete box.
1306
- // Append the first part, and save the second part as remaining data,
1307
- // and handle it with the next streamDataCallback call.
1308
- remaining = this.concatArray_(remaining, data);
1309
- let sawMDAT = false;
1310
- let offset = 0;
1311
- new shaka.util.Mp4Parser()
1312
- .box('mdat', (box) => {
1313
- offset = box.size + box.start;
1314
- sawMDAT = true;
1315
- })
1316
- .parse(remaining, /* partialOkay= */ false,
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
- if (sawMDAT) {
1319
- const dataToAppend = remaining.subarray(0, offset);
1320
- remaining = remaining.subarray(offset);
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
@@ -1357,16 +1369,11 @@ shaka.media.StreamingEngine = class {
1357
1369
  'ReadableStream is not supported by the browser.');
1358
1370
  }
1359
1371
  const fetchSegment = this.fetch_(mediaState, reference);
1360
- let result = await fetchSegment;
1372
+ const result = await fetchSegment;
1361
1373
  this.destroyer_.ensureNotDestroyed();
1362
1374
  if (this.fatalError_) {
1363
1375
  return;
1364
1376
  }
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
1377
 
1371
1378
  // If the text stream gets switched between fetch_() and append_(), the
1372
1379
  // new text parser is initialized, but the new init segment is not
@@ -1450,13 +1457,13 @@ shaka.media.StreamingEngine = class {
1450
1457
 
1451
1458
  /**
1452
1459
  * @param {!BufferSource} rawResult
1453
- * @param {!shaka.media.SegmentReference} reference
1460
+ * @param {shaka.extern.HlsAes128Key} aes128Key
1454
1461
  * @param {!shaka.media.SegmentIterator} iter
1455
1462
  * @return {!Promise.<!BufferSource>} finalResult
1456
1463
  * @private
1457
1464
  */
1458
- async aes128Decrypt_(rawResult, reference, iter) {
1459
- const key = reference.hlsAes128Key;
1465
+ async aes128Decrypt_(rawResult, aes128Key, iter) {
1466
+ const key = aes128Key;
1460
1467
  if (!key.cryptoKey) {
1461
1468
  goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
1462
1469
  'preloaded, fetchKey function should be provided');
@@ -1693,10 +1700,10 @@ shaka.media.StreamingEngine = class {
1693
1700
  }
1694
1701
  }
1695
1702
 
1696
- const lastDiscontinuitySequence =
1697
- mediaState.lastSegmentReference ?
1698
- mediaState.lastSegmentReference.discontinuitySequence : null;
1699
1703
  if (this.manifest_.sequenceMode) {
1704
+ const lastDiscontinuitySequence =
1705
+ mediaState.lastSegmentReference ?
1706
+ mediaState.lastSegmentReference.discontinuitySequence : null;
1700
1707
  // Across discontinuity bounds, we should resync timestamps for
1701
1708
  // sequence mode playbacks. The next segment appended should
1702
1709
  // land at its theoretical timestamp from the segment index.
@@ -1706,18 +1713,6 @@ shaka.media.StreamingEngine = class {
1706
1713
  operations.push(this.playerInterface_.mediaSourceEngine.resync(
1707
1714
  mediaState.type, reference.startTime));
1708
1715
  }
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
1716
  }
1722
1717
 
1723
1718
  await Promise.all(operations);
@@ -2021,8 +2016,8 @@ shaka.media.StreamingEngine = class {
2021
2016
  const bufferedBehind = presentationTime - startTime;
2022
2017
 
2023
2018
  const overflow = bufferedBehind - bufferBehind;
2024
- // See: https://github.com/shaka-project/shaka-player/issues/2982
2025
- if (overflow <= 0.01) {
2019
+ // See: https://github.com/shaka-project/shaka-player/issues/6240
2020
+ if (overflow <= 1) {
2026
2021
  shaka.log.v2(logPrefix,
2027
2022
  'buffer behind okay:',
2028
2023
  'presentationTime=' + presentationTime,
@@ -2073,7 +2068,6 @@ shaka.media.StreamingEngine = class {
2073
2068
  *
2074
2069
  * @return {!Promise.<BufferSource>}
2075
2070
  * @private
2076
- * @suppress {strictMissingProperties}
2077
2071
  */
2078
2072
  async fetch_(mediaState, reference, streamDataCallback) {
2079
2073
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
@@ -2088,12 +2082,17 @@ shaka.media.StreamingEngine = class {
2088
2082
  shaka.log.v2('fetching: reference=', reference);
2089
2083
 
2090
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;
2091
2090
  this.playerInterface_.modifySegmentRequest(
2092
2091
  request,
2093
2092
  {
2094
2093
  type: stream.type,
2095
- init: reference instanceof shaka.media.InitSegmentReference,
2096
- duration: reference.endTime - reference.startTime,
2094
+ init: isInit,
2095
+ duration,
2097
2096
  mimeType: stream.mimeType,
2098
2097
  codecs: stream.codecs,
2099
2098
  bandwidth: stream.bandwidth,
@@ -2104,7 +2103,18 @@ shaka.media.StreamingEngine = class {
2104
2103
  mediaState.operation = op;
2105
2104
  const response = await op.promise;
2106
2105
  mediaState.operation = null;
2107
- return response.data;
2106
+
2107
+ let result = response.data;
2108
+ if (reference.hlsAes128Key) {
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
+ }
2114
+ goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
2115
+ result = await this.aes128Decrypt_(result, reference.hlsAes128Key, iter);
2116
+ }
2117
+ return result;
2108
2118
  }
2109
2119
 
2110
2120
 
@@ -208,7 +208,7 @@ shaka.media.Transmuxer = class {
208
208
  * Returns the original mimetype of the transmuxer.
209
209
  * @return {string}
210
210
  */
211
- getOrginalMimeType() {
211
+ getOriginalMimeType() {
212
212
  return this.originalMimeType_;
213
213
  }
214
214
 
@@ -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 100ms,
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
- * TODO: File a bug on Edge about this.
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
@@ -112,6 +112,11 @@ shaka.offline.OfflineManifestParser = class {
112
112
  await muxer.destroy();
113
113
  }
114
114
  }
115
+
116
+ /** @override */
117
+ onInitialVariantChosen(variant) {
118
+ // No-op
119
+ }
115
120
  };
116
121
 
117
122
 
@@ -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) => {
@@ -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;
package/lib/player.js CHANGED
@@ -479,6 +479,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
479
479
  */
480
480
  this.loadEventManager_ = new shaka.util.EventManager();
481
481
 
482
+ /**
483
+ * For listeners scoped to the lifetime of the loaded content.
484
+ * @private {shaka.util.EventManager}
485
+ */
486
+ this.trickPlayEventManager_ = new shaka.util.EventManager();
487
+
482
488
  /** @private {shaka.net.NetworkingEngine} */
483
489
  this.networkingEngine_ = null;
484
490
 
@@ -897,6 +903,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
897
903
  this.loadEventManager_.release();
898
904
  this.loadEventManager_ = null;
899
905
  }
906
+ if (this.trickPlayEventManager_) {
907
+ this.trickPlayEventManager_.release();
908
+ this.trickPlayEventManager_ = null;
909
+ }
900
910
 
901
911
  this.abrManagerFactory_ = null;
902
912
  this.config_ = null;
@@ -1555,6 +1565,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1555
1565
  // be if there was an error and we, by chance, did not have a media element.
1556
1566
  if (has.mediaElement) {
1557
1567
  this.loadEventManager_.removeAll();
1568
+ this.trickPlayEventManager_.removeAll();
1558
1569
  }
1559
1570
 
1560
1571
  // Stop the variant checker timer
@@ -2012,8 +2023,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2012
2023
 
2013
2024
  this.drmEngine_.configure(this.config_.drm);
2014
2025
 
2026
+ const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
2027
+ this.manifest_.variants, this.config_.restrictions, this.maxHwRes_);
2028
+ if (tracksChanged && this.streamingEngine_) {
2029
+ this.onTracksChanged_();
2030
+ }
2031
+
2032
+ const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
2033
+ this.manifest_.variants);
2034
+
2015
2035
  await this.drmEngine_.initForPlayback(
2016
- this.manifest_.variants,
2036
+ playableVariants,
2017
2037
  this.manifest_.offlineSessionIds);
2018
2038
 
2019
2039
  await this.drmEngine_.attach(has.mediaElement);
@@ -2192,6 +2212,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2192
2212
  await Promise.all(createSegmentIndexPromises);
2193
2213
  }
2194
2214
 
2215
+ if (this.parser_ && this.parser_.onInitialVariantChosen) {
2216
+ this.parser_.onInitialVariantChosen(toLazyLoad);
2217
+ }
2218
+
2195
2219
  shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
2196
2220
  this.config_.playRangeStart,
2197
2221
  this.config_.playRangeEnd);
@@ -3705,6 +3729,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3705
3729
  shaka.log.alwaysWarn('A trick play rate of 0 is unsupported!');
3706
3730
  return;
3707
3731
  }
3732
+ this.trickPlayEventManager_.removeAll();
3708
3733
 
3709
3734
  if (this.video_.paused) {
3710
3735
  // Our fast forward is implemented with playbackRate and needs the video
@@ -3718,6 +3743,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3718
3743
  this.abrManager_.playbackRateChanged(rate);
3719
3744
  this.streamingEngine_.setTrickPlay(Math.abs(rate) > 1);
3720
3745
  }
3746
+ if (this.isLive()) {
3747
+ this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
3748
+ const currentTime = this.video_.currentTime;
3749
+ const seekRange = this.seekRange();
3750
+ const safeSeekOffset = this.config_.streaming.safeSeekOffset;
3751
+ if (rate > 0) {
3752
+ if (currentTime >= seekRange.end) {
3753
+ this.cancelTrickPlay();
3754
+ }
3755
+ } else {
3756
+ if (currentTime <= (seekRange.start + safeSeekOffset)) {
3757
+ this.cancelTrickPlay();
3758
+ }
3759
+ }
3760
+ });
3761
+ }
3721
3762
  }
3722
3763
 
3723
3764
  /**
@@ -3737,6 +3778,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3737
3778
  this.abrManager_.playbackRateChanged(defaultPlaybackRate);
3738
3779
  this.streamingEngine_.setTrickPlay(false);
3739
3780
  }
3781
+ this.trickPlayEventManager_.removeAll();
3740
3782
  }
3741
3783
 
3742
3784
  /**
@@ -4635,7 +4677,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4635
4677
 
4636
4678
  if (this.isLive()) {
4637
4679
  const now = this.getPresentationStartTimeAsDate().valueOf() +
4638
- this.seekRange().end * 1000;
4680
+ element.currentTime * 1000;
4639
4681
  const latency = (Date.now() - now) / 1000;
4640
4682
  this.stats_.setLiveLatency(latency);
4641
4683
  }
@@ -4689,6 +4731,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4689
4731
  shaka.util.Error.Code.CONTENT_NOT_LOADED);
4690
4732
  }
4691
4733
 
4734
+ if (kind != 'subtitles' && kind != 'captions') {
4735
+ shaka.log.alwaysWarn(
4736
+ 'Using a kind value different of `subtitles` or `captions` can ' +
4737
+ 'cause unwanted issues.');
4738
+ }
4739
+
4692
4740
  if (!mimeType) {
4693
4741
  mimeType = await this.getTextMimetype_(uri);
4694
4742
  }
@@ -4707,9 +4755,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4707
4755
  }
4708
4756
  await this.addSrcTrackElement_(uri, language, kind, mimeType, label || '',
4709
4757
  adCuePoints);
4758
+
4759
+ const LanguageUtils = shaka.util.LanguageUtils;
4760
+ const languageNormalized = LanguageUtils.normalize(language);
4761
+
4710
4762
  const textTracks = this.getTextTracks();
4711
4763
  const srcTrack = textTracks.find((t) => {
4712
- return t.language == language &&
4764
+ return LanguageUtils.normalize(t.language) == languageNormalized &&
4713
4765
  t.label == (label || '') &&
4714
4766
  t.kind == kind;
4715
4767
  });
@@ -4871,16 +4923,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4871
4923
  vttOffset: 0,
4872
4924
  };
4873
4925
  const data = shaka.util.BufferUtils.toUint8(buffer);
4874
- const cues = TextParser.parseMedia(data, time);
4926
+ const cues = TextParser.parseMedia(data, time, uri);
4875
4927
 
4876
4928
  const references = [];
4877
4929
  for (const cue of cues) {
4878
- const imageUri = shaka.util.ManifestParserUtils.resolveUris(
4879
- [uri], [cue.payload])[0];
4930
+ let uris = null;
4931
+ const getUris = () => {
4932
+ if (uris == null) {
4933
+ uris = shaka.util.ManifestParserUtils.resolveUris(
4934
+ [uri], [cue.payload]);
4935
+ }
4936
+ return uris || [];
4937
+ };
4880
4938
  const reference = new shaka.media.SegmentReference(
4881
4939
  cue.startTime,
4882
4940
  cue.endTime,
4883
- () => [imageUri],
4941
+ getUris,
4884
4942
  /* startByte= */ 0,
4885
4943
  /* endByte= */ null,
4886
4944
  /* initSegmentReference= */ null,
@@ -4888,8 +4946,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4888
4946
  /* appendWindowStart= */ 0,
4889
4947
  /* appendWindowEnd= */ Infinity,
4890
4948
  );
4891
- if (imageUri.includes('#xywh')) {
4892
- const spriteInfo = imageUri.split('#xywh=')[1].split(',');
4949
+ if (cue.payload.includes('#xywh')) {
4950
+ const spriteInfo = cue.payload.split('#xywh=')[1].split(',');
4893
4951
  if (spriteInfo.length === 4) {
4894
4952
  reference.setThumbnailSprite({
4895
4953
  height: parseInt(spriteInfo[3], 10),
@@ -5129,7 +5187,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5129
5187
  vttOffset: 0,
5130
5188
  };
5131
5189
  const data = shaka.util.BufferUtils.toUint8(buffer);
5132
- const cues = obj.parseMedia(data, time);
5190
+ const cues = obj.parseMedia(data, time, /* uri= */ null);
5133
5191
  return shaka.text.WebVttGenerator.convert(cues, adCuePoints);
5134
5192
  }
5135
5193
  throw new shaka.util.Error(
@@ -5248,7 +5306,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5248
5306
  return new shaka.text.UITextDisplayer(
5249
5307
  this.video_, this.videoContainer_);
5250
5308
  } else {
5251
- return new shaka.text.SimpleTextDisplayer(this.video_);
5309
+ return new shaka.text.SimpleTextDisplayer(
5310
+ this.video_, shaka.Player.TextTrackLabel);
5252
5311
  }
5253
5312
  };
5254
5313
  return config;
@@ -6263,6 +6322,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6263
6322
  const restrictedStatuses = shaka.Player.restrictedStatuses_;
6264
6323
  let tracksChanged = false;
6265
6324
 
6325
+ goog.asserts.assert(this.drmEngine_, 'drmEngine should be non-null here.');
6326
+
6266
6327
  // Only filter tracks for keys if we have some key statuses to look at.
6267
6328
  if (keyIds.length) {
6268
6329
  for (const variant of this.manifest_.variants) {
@@ -6278,8 +6339,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6278
6339
 
6279
6340
  for (const keyId of stream.keyIds) {
6280
6341
  const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
6281
- variant.allowedByKeySystem = variant.allowedByKeySystem &&
6282
- !!keyStatus && !restrictedStatuses.includes(keyStatus);
6342
+ if (keyStatus || this.drmEngine_.hasManifestInitData()) {
6343
+ variant.allowedByKeySystem = variant.allowedByKeySystem &&
6344
+ !!keyStatus && !restrictedStatuses.includes(keyStatus);
6345
+ }
6283
6346
  }
6284
6347
  }
6285
6348
 
@@ -6291,6 +6354,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6291
6354
  } // if (keyIds.size)
6292
6355
 
6293
6356
  if (tracksChanged) {
6357
+ this.onTracksChanged_();
6294
6358
  const variantsUpdated = this.updateAbrManagerVariants_();
6295
6359
  if (!variantsUpdated) {
6296
6360
  return;
@@ -6302,10 +6366,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6302
6366
  shaka.log.debug('Choosing new streams after key status changed');
6303
6367
  this.chooseVariantAndSwitch_();
6304
6368
  }
6305
-
6306
- if (tracksChanged) {
6307
- this.onTracksChanged_();
6308
- }
6309
6369
  }
6310
6370
 
6311
6371
  /**
@@ -6971,7 +7031,8 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6971
7031
  * @define {string} A version number taken from git at compile time.
6972
7032
  * @export
6973
7033
  */
6974
- shaka.Player.version = 'v4.3.12-uncompiled';
7034
+ // eslint-disable-next-line no-useless-concat
7035
+ shaka.Player.version = 'v4.3.14' + '-uncompiled'; // x-release-please-version
6975
7036
 
6976
7037
  // Initialize the deprecation system using the version string we just set
6977
7038
  // on the player.
@@ -141,8 +141,15 @@ shaka.polyfill.MediaCapabilities = class {
141
141
  }
142
142
 
143
143
  if (audioConfig) {
144
- const contentType = audioConfig.contentType;
145
- const isSupported = Capabilities.isTypeSupported(contentType);
144
+ let isSupported;
145
+ if (shaka.util.Platform.isChromecast() &&
146
+ audioConfig.spatialRendering) {
147
+ const extendedType =
148
+ audioConfig.contentType + '; spatialRendering=true';
149
+ isSupported = Capabilities.isTypeSupported(extendedType);
150
+ } else {
151
+ isSupported = Capabilities.isTypeSupported(audioConfig.contentType);
152
+ }
146
153
  if (!isSupported) {
147
154
  return res;
148
155
  }
@@ -184,10 +191,23 @@ shaka.polyfill.MediaCapabilities = class {
184
191
  const videoCapabilities = [];
185
192
 
186
193
  if (mediaCapkeySystemConfig.audio) {
187
- const capability = {
194
+ let capability = {
188
195
  robustness: mediaCapkeySystemConfig.audio.robustness || '',
189
196
  contentType: mediaDecodingConfig.audio.contentType,
190
197
  };
198
+
199
+ // Some Tizen devices seem to misreport AC-3 support, but correctly
200
+ // report EC-3 support. So query EC-3 as a fallback for AC-3.
201
+ // See https://github.com/shaka-project/shaka-player/issues/2989 for
202
+ // details.
203
+ if (shaka.util.Platform.isTizen() &&
204
+ mediaDecodingConfig.audio.contentType.includes('codecs="ac-3"')) {
205
+ capability = {
206
+ robustness: capability.robustness,
207
+ contentType: 'audio/mp4; codecs="ec-3"',
208
+ };
209
+ }
210
+
191
211
  audioCapabilities.push(capability);
192
212
  }
193
213
 
@@ -67,7 +67,7 @@ shaka.text.Mp4TtmlParser = class {
67
67
  * @override
68
68
  * @export
69
69
  */
70
- parseMedia(data, time) {
70
+ parseMedia(data, time, uri) {
71
71
  const Mp4Parser = shaka.util.Mp4Parser;
72
72
 
73
73
  let sawMDAT = false;
@@ -78,7 +78,7 @@ shaka.text.Mp4TtmlParser = class {
78
78
  sawMDAT = true;
79
79
  // Join this to any previous payload, in case the mp4 has multiple
80
80
  // mdats.
81
- payload = payload.concat(this.parser_.parseMedia(data, time));
81
+ payload = payload.concat(this.parser_.parseMedia(data, time, uri));
82
82
  }));
83
83
  parser.parse(data, /* partialOkay= */ false);
84
84
 
@@ -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.text.SimpleTextDisplayer');
@@ -24,8 +23,11 @@ goog.require('shaka.text.Utils');
24
23
  * @export
25
24
  */
26
25
  shaka.text.SimpleTextDisplayer = class {
27
- /** @param {HTMLMediaElement} video */
28
- constructor(video) {
26
+ /**
27
+ * @param {HTMLMediaElement} video
28
+ * @param {string} label
29
+ */
30
+ constructor(video, label) {
29
31
  /** @private {TextTrack} */
30
32
  this.textTrack_ = null;
31
33
 
@@ -39,7 +41,7 @@ shaka.text.SimpleTextDisplayer = class {
39
41
  // element.
40
42
  track.mode = 'disabled';
41
43
 
42
- if (track.label == shaka.Player.TextTrackLabel) {
44
+ if (track.label == label) {
43
45
  this.textTrack_ = track;
44
46
  }
45
47
  }
@@ -49,8 +51,7 @@ shaka.text.SimpleTextDisplayer = class {
49
51
  // kind to 'subtitles' or 'captions' when creating the TextTrack object.
50
52
  // The individual text tracks from the manifest will still have their own
51
53
  // kinds which can be displayed in the app's UI.
52
- this.textTrack_ = video.addTextTrack(
53
- 'subtitles', shaka.Player.TextTrackLabel);
54
+ this.textTrack_ = video.addTextTrack('subtitles', label);
54
55
  }
55
56
  this.textTrack_.mode = 'hidden';
56
57
  }
@@ -47,7 +47,7 @@ shaka.text.SrtTextParser = class {
47
47
  * @override
48
48
  * @export
49
49
  */
50
- parseMedia(data, time) {
50
+ parseMedia(data, time, uri) {
51
51
  const SrtTextParser = shaka.text.SrtTextParser;
52
52
  const BufferUtils = shaka.util.BufferUtils;
53
53
  const StringUtils = shaka.util.StringUtils;
@@ -59,7 +59,7 @@ shaka.text.SrtTextParser = class {
59
59
 
60
60
  const newData = BufferUtils.toUint8(StringUtils.toUTF8(vvtText));
61
61
 
62
- return this.parser_.parseMedia(newData, time);
62
+ return this.parser_.parseMedia(newData, time, uri);
63
63
  }
64
64
 
65
65
  /**