shaka-player 4.5.0 → 4.6.1

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 (214) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/AUTHORS +2 -0
  3. package/CHANGELOG.md +109 -0
  4. package/CODE_OF_CONDUCT.md +1 -1
  5. package/CONTRIBUTORS +2 -0
  6. package/README.md +38 -32
  7. package/demo/common/asset.js +11 -17
  8. package/demo/common/assets.js +117 -11
  9. package/demo/config.js +28 -1
  10. package/demo/custom.js +52 -38
  11. package/demo/main.js +11 -3
  12. package/demo/search.js +7 -0
  13. package/dist/controls.css +1 -1
  14. package/dist/controls.css.map +1 -1
  15. package/dist/demo.compiled.debug.js +133 -124
  16. package/dist/demo.compiled.debug.map +3 -3
  17. package/dist/demo.compiled.js +133 -124
  18. package/dist/demo.compiled.map +3 -3
  19. package/dist/deps.js +20 -18
  20. package/dist/locales.js +34 -30
  21. package/dist/receiver.compiled.debug.js +58 -50
  22. package/dist/receiver.compiled.debug.map +3 -3
  23. package/dist/receiver.compiled.js +58 -50
  24. package/dist/receiver.compiled.map +3 -3
  25. package/dist/shaka-player.compiled.d.ts +440 -390
  26. package/dist/shaka-player.compiled.debug.d.ts +440 -390
  27. package/dist/shaka-player.compiled.debug.externs.js +162 -77
  28. package/dist/shaka-player.compiled.debug.js +789 -738
  29. package/dist/shaka-player.compiled.debug.map +4 -4
  30. package/dist/shaka-player.compiled.externs.js +162 -77
  31. package/dist/shaka-player.compiled.js +1147 -1113
  32. package/dist/shaka-player.compiled.map +4 -4
  33. package/dist/shaka-player.ui.d.ts +491 -437
  34. package/dist/shaka-player.ui.debug.d.ts +491 -437
  35. package/dist/shaka-player.ui.debug.externs.js +167 -77
  36. package/dist/shaka-player.ui.debug.js +857 -791
  37. package/dist/shaka-player.ui.debug.map +4 -4
  38. package/dist/shaka-player.ui.externs.js +167 -77
  39. package/dist/shaka-player.ui.js +1359 -1314
  40. package/dist/shaka-player.ui.map +4 -4
  41. package/docs/tutorials/ad_monetization.md +54 -0
  42. package/docs/tutorials/basic-usage.md +2 -1
  43. package/docs/tutorials/errors.md +2 -1
  44. package/docs/tutorials/offline.md +10 -8
  45. package/docs/tutorials/plugins.md +5 -0
  46. package/docs/tutorials/ui-customization.md +13 -0
  47. package/docs/tutorials/ui.md +2 -1
  48. package/docs/tutorials/upgrade.md +3 -0
  49. package/externs/managedmediasource.js +24 -0
  50. package/externs/shaka/abr_manager.js +6 -2
  51. package/externs/shaka/cea.js +7 -0
  52. package/externs/shaka/codecs.js +5 -3
  53. package/externs/shaka/manifest.js +21 -5
  54. package/externs/shaka/manifest_parser.js +17 -0
  55. package/externs/shaka/net.js +2 -2
  56. package/externs/shaka/offline.js +9 -2
  57. package/externs/shaka/player.js +53 -7
  58. package/externs/shaka/text.js +3 -1
  59. package/externs/shaka/transmuxer.js +1 -1
  60. package/lib/abr/simple_abr_manager.js +25 -8
  61. package/lib/ads/media_tailor_ad_manager.js +14 -3
  62. package/lib/cast/cast_receiver.js +1 -1
  63. package/lib/cea/cea_decoder.js +28 -0
  64. package/lib/cea/dummy_caption_decoder.js +5 -0
  65. package/lib/dash/dash_parser.js +251 -84
  66. package/lib/dash/mpd_utils.js +52 -31
  67. package/lib/dash/segment_base.js +4 -5
  68. package/lib/dash/segment_list.js +5 -4
  69. package/lib/dash/segment_template.js +92 -29
  70. package/lib/hls/hls_classes.js +28 -43
  71. package/lib/hls/hls_parser.js +770 -652
  72. package/lib/hls/hls_utils.js +64 -5
  73. package/lib/hls/manifest_text_parser.js +7 -11
  74. package/lib/media/adaptation_set.js +20 -13
  75. package/lib/media/adaptation_set_criteria.js +70 -18
  76. package/lib/media/closed_caption_parser.js +13 -0
  77. package/lib/media/content_workarounds.js +6 -0
  78. package/lib/media/drm_engine.js +8 -13
  79. package/lib/media/manifest_parser.js +8 -113
  80. package/lib/media/media_source_capabilities.js +10 -3
  81. package/lib/media/media_source_engine.js +75 -21
  82. package/lib/media/play_rate_controller.js +8 -38
  83. package/lib/media/presentation_timeline.js +7 -1
  84. package/lib/media/quality_observer.js +1 -1
  85. package/lib/media/segment_index.js +5 -5
  86. package/lib/media/segment_prefetch.js +92 -24
  87. package/lib/media/segment_reference.js +67 -1
  88. package/lib/media/segment_utils.js +343 -0
  89. package/lib/media/stall_detector.js +0 -3
  90. package/lib/media/streaming_engine.js +72 -44
  91. package/lib/mss/mss_parser.js +18 -2
  92. package/lib/net/networking_engine.js +66 -12
  93. package/lib/net/networking_utils.js +95 -0
  94. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  95. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  96. package/lib/offline/manifest_converter.js +2 -0
  97. package/lib/offline/offline_manifest_parser.js +10 -0
  98. package/lib/offline/storage.js +11 -7
  99. package/lib/player.js +998 -1503
  100. package/lib/polyfill/mediasource.js +22 -1
  101. package/lib/text/mp4_ttml_parser.js +2 -2
  102. package/lib/text/srt_text_parser.js +2 -2
  103. package/lib/text/stub_text_displayer.js +51 -0
  104. package/lib/text/text_engine.js +3 -2
  105. package/lib/text/ttml_text_parser.js +28 -5
  106. package/lib/text/ui_text_displayer.js +4 -0
  107. package/lib/text/vtt_text_parser.js +7 -2
  108. package/lib/transmuxer/aac_transmuxer.js +1 -1
  109. package/lib/transmuxer/ac3_transmuxer.js +1 -1
  110. package/lib/transmuxer/ec3_transmuxer.js +1 -1
  111. package/lib/transmuxer/h264.js +5 -4
  112. package/lib/transmuxer/h265.js +5 -4
  113. package/lib/transmuxer/mp3_transmuxer.js +1 -1
  114. package/lib/transmuxer/mpeg_ts_transmuxer.js +1 -1
  115. package/lib/transmuxer/mss_transmuxer.js +1 -1
  116. package/lib/transmuxer/ts_transmuxer.js +33 -13
  117. package/lib/util/buffer_utils.js +26 -4
  118. package/lib/util/content_steering_manager.js +358 -0
  119. package/lib/util/error.js +2 -6
  120. package/lib/util/fairplay_utils.js +1 -1
  121. package/lib/util/fake_event.js +5 -1
  122. package/lib/util/id3_utils.js +50 -1
  123. package/lib/util/manifest_parser_utils.js +12 -8
  124. package/lib/util/mp4_generator.js +2 -4
  125. package/lib/util/mutex.js +59 -0
  126. package/lib/util/object_utils.js +1 -1
  127. package/lib/util/periods.js +154 -179
  128. package/lib/util/platform.js +4 -3
  129. package/lib/util/player_configuration.js +35 -6
  130. package/lib/util/stream_utils.js +105 -98
  131. package/lib/util/ts_parser.js +124 -73
  132. package/lib/util/uint8array_utils.js +5 -1
  133. package/lib/util/xml_utils.js +5 -0
  134. package/maintained-branches.md +2 -2
  135. package/package.json +1 -1
  136. package/roadmap.md +15 -5
  137. package/shaka-player.uncompiled.js +0 -1
  138. package/test/abr/simple_abr_manager_unit.js +33 -33
  139. package/test/ads/ad_manager_unit.js +3 -2
  140. package/test/cast/cast_receiver_integration.js +3 -4
  141. package/test/cast/cast_utils_unit.js +2 -0
  142. package/test/cea/cea_decoder_unit.js +7 -0
  143. package/test/codec_switching/codec_switching_integration.js +2 -1
  144. package/test/dash/dash_parser_integration.js +2 -1
  145. package/test/dash/dash_parser_live_unit.js +0 -19
  146. package/test/dash/dash_parser_manifest_unit.js +494 -2
  147. package/test/dash/mpd_utils_unit.js +61 -30
  148. package/test/hls/hls_live_unit.js +12 -3
  149. package/test/hls/hls_parser_unit.js +417 -27
  150. package/test/hls/manifest_text_parser_unit.js +32 -99
  151. package/test/media/adaptation_set_criteria_unit.js +172 -21
  152. package/test/media/adaptation_set_unit.js +1 -0
  153. package/test/media/content_workarounds_unit.js +1 -1
  154. package/test/media/drm_engine_unit.js +3 -3
  155. package/test/media/media_source_engine_unit.js +1 -1
  156. package/test/media/play_rate_controller_unit.js +0 -48
  157. package/test/media/segment_index_unit.js +2 -1
  158. package/test/mss/mss_player_integration.js +3 -2
  159. package/test/net/networking_engine_unit.js +3 -3
  160. package/test/{media/manifest_parser_unit.js → net/networking_utils_unit.js} +2 -2
  161. package/test/offline/manifest_convert_unit.js +9 -0
  162. package/test/offline/offline_integration.js +2 -1
  163. package/test/offline/storage_integration.js +21 -9
  164. package/test/player_external.js +4 -2
  165. package/test/player_integration.js +15 -4
  166. package/test/player_load_graph_integration.js +47 -151
  167. package/test/player_src_equals_external.js +1 -0
  168. package/test/player_src_equals_integration.js +71 -4
  169. package/test/player_unit.js +80 -27
  170. package/test/test/util/fake_media_source_engine.js +5 -0
  171. package/test/test/util/fake_networking_engine.js +9 -1
  172. package/test/test/util/fake_segment_prefetch.js +3 -0
  173. package/test/test/util/manifest_generator.js +4 -0
  174. package/test/test/util/manifest_parser_util.js +14 -2
  175. package/test/test/util/offline_utils.js +1 -0
  176. package/test/test/util/simple_fakes.js +2 -2
  177. package/test/test/util/streaming_engine_util.js +3 -0
  178. package/test/test/util/test_scheme.js +6 -0
  179. package/test/test/util/ui_utils.js +4 -3
  180. package/test/text/mp4_ttml_parser_unit.js +4 -4
  181. package/test/text/srt_text_parser_unit.js +1 -1
  182. package/test/text/text_displayer_layout_unit.js +1 -1
  183. package/test/text/text_engine_unit.js +6 -0
  184. package/test/text/ttml_text_parser_unit.js +33 -2
  185. package/test/transmuxer/transmuxer_engine_integration.js +9 -0
  186. package/test/transmuxer/transmuxer_integration.js +33 -3
  187. package/test/ui/ad_ui_unit.js +2 -2
  188. package/test/ui/ui_customization_unit.js +17 -16
  189. package/test/ui/ui_integration.js +30 -20
  190. package/test/ui/ui_unit.js +25 -19
  191. package/test/util/buffer_utils_unit.js +1 -1
  192. package/test/util/content_steering_manager_unit.js +427 -0
  193. package/test/util/id3_utils_unit.js +52 -0
  194. package/test/util/stream_utils_unit.js +17 -113
  195. package/test/util/ts_parser_unit.js +3 -3
  196. package/third_party/closure-uri/uri.js +13 -1
  197. package/ui/audio_language_selection.js +9 -2
  198. package/ui/externs/ui.js +17 -2
  199. package/ui/language_utils.js +43 -8
  200. package/ui/less/range_elements.less +33 -0
  201. package/ui/locales/en.json +1 -0
  202. package/ui/locales/es-419.json +1 -0
  203. package/ui/locales/es.json +1 -0
  204. package/ui/locales/source.json +4 -0
  205. package/ui/localization.js +10 -0
  206. package/ui/remote_button.js +3 -1
  207. package/ui/resolution_selection.js +47 -10
  208. package/ui/seek_bar.js +296 -3
  209. package/ui/text_selection.js +2 -1
  210. package/ui/ui.js +6 -2
  211. package/lib/routing/node.js +0 -23
  212. package/lib/routing/payload.js +0 -47
  213. package/lib/routing/walker.js +0 -587
  214. package/test/routing/walker_unit.js +0 -505
@@ -1054,6 +1054,14 @@ shaka.media.StreamingEngine = class {
1054
1054
  this.playerInterface_.mediaSourceEngine.clearSelectedClosedCaptionId();
1055
1055
  }
1056
1056
 
1057
+ if (!this.playerInterface_.mediaSourceEngine.isStreamingAllowed() &&
1058
+ mediaState.type != ContentType.TEXT) {
1059
+ // It is not allowed to add segments yet, so we schedule an update to
1060
+ // check again later. So any prediction we make now could be terribly
1061
+ // invalid soon.
1062
+ return this.config_.updateIntervalSeconds / 2;
1063
+ }
1064
+
1057
1065
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1058
1066
 
1059
1067
  // Compute how far we've buffered ahead of the playhead.
@@ -1163,6 +1171,12 @@ shaka.media.StreamingEngine = class {
1163
1171
  }
1164
1172
 
1165
1173
  if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1174
+ const initSegmentReference = reference.initSegmentReference;
1175
+ if (initSegmentReference && (!mediaState.lastSegmentReference ||
1176
+ !shaka.media.InitSegmentReference.equal(
1177
+ initSegmentReference, mediaState.lastInitSegmentReference))) {
1178
+ mediaState.segmentPrefetch.prefetchInitSegment(initSegmentReference);
1179
+ }
1166
1180
  mediaState.segmentPrefetch.prefetchSegments(reference);
1167
1181
  }
1168
1182
 
@@ -1337,6 +1351,7 @@ shaka.media.StreamingEngine = class {
1337
1351
  let remaining = new Uint8Array(0);
1338
1352
  let processingResult = false;
1339
1353
  let callbackCalled = false;
1354
+ let streamDataCallbackError;
1340
1355
  const streamDataCallback = async (data) => {
1341
1356
  if (processingResult) {
1342
1357
  // If the fallback result processing was triggered, don't also
@@ -1349,35 +1364,47 @@ shaka.media.StreamingEngine = class {
1349
1364
  if (this.fatalError_) {
1350
1365
  return;
1351
1366
  }
1352
- // Append the data with complete boxes.
1353
- // Every time streamDataCallback gets called, append the new data to
1354
- // the remaining data.
1355
- // Find the last fully completed Mdat box, and slice the data into two
1356
- // parts: the first part with completed Mdat boxes, and the second
1357
- // part with an incomplete box.
1358
- // Append the first part, and save the second part as remaining data,
1359
- // and handle it with the next streamDataCallback call.
1360
- remaining = this.concatArray_(remaining, data);
1361
- let sawMDAT = false;
1362
- let offset = 0;
1363
- new shaka.util.Mp4Parser()
1364
- .box('mdat', (box) => {
1365
- offset = box.size + box.start;
1366
- sawMDAT = true;
1367
- })
1368
- .parse(remaining, /* partialOkay= */ false,
1367
+ try {
1368
+ // Append the data with complete boxes.
1369
+ // Every time streamDataCallback gets called, append the new data
1370
+ // to the remaining data.
1371
+ // Find the last fully completed Mdat box, and slice the data into
1372
+ // two parts: the first part with completed Mdat boxes, and the
1373
+ // second part with an incomplete box.
1374
+ // Append the first part, and save the second part as remaining
1375
+ // data, and handle it with the next streamDataCallback call.
1376
+ remaining = this.concatArray_(remaining, data);
1377
+ let sawMDAT = false;
1378
+ let offset = 0;
1379
+ new shaka.util.Mp4Parser()
1380
+ .box('mdat', (box) => {
1381
+ offset = box.size + box.start;
1382
+ sawMDAT = true;
1383
+ })
1384
+ .parse(remaining, /* partialOkay= */ false,
1385
+ /* isChunkedData= */ true);
1386
+ if (sawMDAT) {
1387
+ const dataToAppend = remaining.subarray(0, offset);
1388
+ remaining = remaining.subarray(offset);
1389
+ await this.append_(
1390
+ mediaState, presentationTime, stream, reference, dataToAppend,
1369
1391
  /* isChunkedData= */ true);
1370
- if (sawMDAT) {
1371
- const dataToAppend = remaining.subarray(0, offset);
1372
- remaining = remaining.subarray(offset);
1373
- await this.append_(
1374
- mediaState, presentationTime, stream, reference, dataToAppend,
1375
- /* isChunkedData= */ true);
1392
+
1393
+ if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1394
+ mediaState.segmentPrefetch.prefetchSegments(
1395
+ reference, /* skipFirst= */ true);
1396
+ }
1397
+ }
1398
+ } catch (error) {
1399
+ streamDataCallbackError = error;
1376
1400
  }
1377
1401
  };
1378
1402
 
1379
1403
  const result =
1380
1404
  await this.fetch_(mediaState, reference, streamDataCallback);
1405
+ if (streamDataCallbackError) {
1406
+ throw streamDataCallbackError;
1407
+ }
1381
1408
  if (!callbackCalled) {
1382
1409
  // In some environments, we might be forced to use network plugins
1383
1410
  // that don't support streamDataCallback. In those cases, as a
@@ -1403,6 +1430,11 @@ shaka.media.StreamingEngine = class {
1403
1430
  await this.append_(
1404
1431
  mediaState, presentationTime, stream, reference, result);
1405
1432
  }
1433
+
1434
+ if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1435
+ mediaState.segmentPrefetch.prefetchSegments(
1436
+ reference, /* skipFirst= */ true);
1437
+ }
1406
1438
  } else {
1407
1439
  if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
1408
1440
  shaka.log.warning('Low latency streaming mode is enabled, but ' +
@@ -1499,11 +1531,11 @@ shaka.media.StreamingEngine = class {
1499
1531
  /**
1500
1532
  * @param {!BufferSource} rawResult
1501
1533
  * @param {shaka.extern.aes128Key} aes128Key
1502
- * @param {!shaka.media.SegmentIterator} iter
1534
+ * @param {number} position
1503
1535
  * @return {!Promise.<!BufferSource>} finalResult
1504
1536
  * @private
1505
1537
  */
1506
- async aes128Decrypt_(rawResult, aes128Key, iter) {
1538
+ async aes128Decrypt_(rawResult, aes128Key, position) {
1507
1539
  const key = aes128Key;
1508
1540
  if (!key.cryptoKey) {
1509
1541
  goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
@@ -1514,7 +1546,7 @@ shaka.media.StreamingEngine = class {
1514
1546
  let iv = key.iv;
1515
1547
  if (!iv) {
1516
1548
  iv = shaka.util.BufferUtils.toUint8(new ArrayBuffer(16));
1517
- let sequence = key.firstMediaSequenceNumber + iter.currentPosition();
1549
+ let sequence = key.firstMediaSequenceNumber + position;
1518
1550
  for (let i = iv.byteLength - 1; i >= 0; i--) {
1519
1551
  iv[i] = sequence & 0xff;
1520
1552
  sequence >>= 8;
@@ -1764,8 +1796,7 @@ shaka.media.StreamingEngine = class {
1764
1796
  shaka.log.v1(logPrefix, 'fetching init segment');
1765
1797
 
1766
1798
  const fetchInit =
1767
- this.fetch_(mediaState, reference.initSegmentReference,
1768
- /* streamDataCallback= */ undefined, /* isInit= */ true);
1799
+ this.fetch_(mediaState, reference.initSegmentReference);
1769
1800
  const append = async () => {
1770
1801
  try {
1771
1802
  const initSegment = await fetchInit;
@@ -2169,13 +2200,12 @@ shaka.media.StreamingEngine = class {
2169
2200
  * @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)}
2170
2201
  * reference
2171
2202
  * @param {?function(BufferSource):!Promise=} streamDataCallback
2172
- * @param {boolean=} isInit
2173
2203
  *
2174
2204
  * @return {!Promise.<BufferSource>}
2175
2205
  * @private
2176
2206
  * @suppress {strictMissingProperties}
2177
2207
  */
2178
- async fetch_(mediaState, reference, streamDataCallback, isInit) {
2208
+ async fetch_(mediaState, reference, streamDataCallback) {
2179
2209
  if (reference instanceof shaka.media.InitSegmentReference) {
2180
2210
  const segmentData = reference.getSegmentData();
2181
2211
  if (segmentData) {
@@ -2183,17 +2213,18 @@ shaka.media.StreamingEngine = class {
2183
2213
  }
2184
2214
  }
2185
2215
  let op = null;
2186
- if (
2187
- mediaState.segmentPrefetch &&
2188
- reference instanceof shaka.media.SegmentReference
2189
- ) {
2216
+ if (mediaState.segmentPrefetch) {
2190
2217
  op = mediaState.segmentPrefetch.getPrefetchedSegment(
2191
2218
  reference, streamDataCallback);
2192
2219
  }
2193
2220
  if (!op) {
2194
2221
  op = this.dispatchFetch_(
2195
- reference, mediaState.stream, streamDataCallback, isInit,
2196
- );
2222
+ reference, mediaState.stream, streamDataCallback);
2223
+ }
2224
+
2225
+ let position = 0;
2226
+ if (mediaState.segmentIterator) {
2227
+ position = mediaState.segmentIterator.currentPosition();
2197
2228
  }
2198
2229
 
2199
2230
  mediaState.operation = op;
@@ -2201,9 +2232,7 @@ shaka.media.StreamingEngine = class {
2201
2232
  mediaState.operation = null;
2202
2233
  let result = response.data;
2203
2234
  if (reference.aes128Key) {
2204
- const iter = mediaState.segmentIterator;
2205
- goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
2206
- result = await this.aes128Decrypt_(result, reference.aes128Key, iter);
2235
+ result = await this.aes128Decrypt_(result, reference.aes128Key, position);
2207
2236
  }
2208
2237
  return result;
2209
2238
  }
@@ -2215,18 +2244,17 @@ shaka.media.StreamingEngine = class {
2215
2244
  * @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)}
2216
2245
  * reference
2217
2246
  * @param {?function(BufferSource):!Promise=} streamDataCallback
2218
- * @param {boolean=} isInit
2219
2247
  *
2220
2248
  * @return {!shaka.net.NetworkingEngine.PendingRequest}
2221
2249
  * @private
2222
2250
  */
2223
- dispatchFetch_(reference, stream, streamDataCallback, isInit) {
2251
+ dispatchFetch_(reference, stream, streamDataCallback) {
2224
2252
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2225
- const type = isInit ?
2226
- shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT :
2227
- shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
2228
2253
  const segment = reference instanceof shaka.media.SegmentReference ?
2229
2254
  reference : undefined;
2255
+ const type = segment ?
2256
+ shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT :
2257
+ shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT;
2230
2258
  const request = shaka.util.Networking.createSegmentRequest(
2231
2259
  reference.getUris(),
2232
2260
  reference.startByte,
@@ -213,6 +213,22 @@ shaka.mss.MssParser = class {
213
213
  // No-op
214
214
  }
215
215
 
216
+ /**
217
+ * @override
218
+ * @exportInterface
219
+ */
220
+ onInitialVariantChosen(variant) {
221
+ // No-op
222
+ }
223
+
224
+ /**
225
+ * @override
226
+ * @exportInterface
227
+ */
228
+ banLocation(uri) {
229
+ // No-op
230
+ }
231
+
216
232
  /**
217
233
  * Makes a network request for the manifest and parses the resulting data.
218
234
  *
@@ -529,6 +545,7 @@ shaka.mss.MssParser = class {
529
545
  spatialAudio: false,
530
546
  closedCaptions: null,
531
547
  hdr: undefined,
548
+ videoLayout: undefined,
532
549
  tilesLayout: undefined,
533
550
  matchedStreams: [],
534
551
  mssPrivateData: {
@@ -538,6 +555,7 @@ shaka.mss.MssParser = class {
538
555
  },
539
556
  accessibilityPurpose: null,
540
557
  external: false,
558
+ fastSwitching: false,
541
559
  };
542
560
 
543
561
  // This is specifically for text tracks.
@@ -1077,7 +1095,5 @@ shaka.mss.MssParser.Context;
1077
1095
  */
1078
1096
  shaka.mss.MssParser.TimeRange;
1079
1097
 
1080
- shaka.media.ManifestParser.registerParserByExtension(
1081
- 'ism', () => new shaka.mss.MssParser());
1082
1098
  shaka.media.ManifestParser.registerParserByMime(
1083
1099
  'application/vnd.ms-sstr+xml', () => new shaka.mss.MssParser());
@@ -46,18 +46,21 @@ goog.require('shaka.util.Timer');
46
46
  */
47
47
  shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
48
48
  /**
49
- * @param {function(number, number)=} onProgressUpdated Called when a progress
50
- * event is triggered. Passed the duration, in milliseconds, that the
51
- * request took, and the number of bytes transferred.
49
+ * @param {function(number, number, boolean)=} onProgressUpdated Called when
50
+ * a progress event is triggered. Passed the duration, in milliseconds,
51
+ * that the request took, the number of bytes transferred, and the boolean
52
+ * of whether the switching is allowed.
52
53
  * @param {shaka.net.NetworkingEngine.OnHeadersReceived=} onHeadersReceived
53
54
  * Called when the headers are received for a download.
54
55
  * @param {shaka.net.NetworkingEngine.OnDownloadFailed=} onDownloadFailed
55
56
  * Called when a download fails, for any reason.
56
57
  * @param {shaka.net.NetworkingEngine.OnRequest=} onRequest
57
58
  * Called when a request is made
59
+ * @param {shaka.net.NetworkingEngine.OnRetry=} onRetry
60
+ * Called when a request retry is made
58
61
  */
59
62
  constructor(onProgressUpdated, onHeadersReceived, onDownloadFailed,
60
- onRequest) {
63
+ onRequest, onRetry) {
61
64
  super();
62
65
 
63
66
  /** @private {boolean} */
@@ -72,7 +75,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
72
75
  /** @private {!Set.<shaka.extern.ResponseFilter>} */
73
76
  this.responseFilters_ = new Set();
74
77
 
75
- /** @private {?function(number, number)} */
78
+ /** @private {?function(number, number, boolean)} */
76
79
  this.onProgressUpdated_ = onProgressUpdated || null;
77
80
 
78
81
  /** @private {?shaka.net.NetworkingEngine.OnHeadersReceived} */
@@ -84,6 +87,9 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
84
87
  /** @private {?shaka.net.NetworkingEngine.OnRequest} */
85
88
  this.onRequest_ = onRequest || null;
86
89
 
90
+ /** @private {?shaka.net.NetworkingEngine.OnRetry} */
91
+ this.onRetry_ = onRetry || null;
92
+
87
93
  /** @private {boolean} */
88
94
  this.forceHTTPS_ = false;
89
95
  }
@@ -295,7 +301,8 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
295
301
  // Apply the registered filters to the request.
296
302
  const requestFilterOperation = this.filterRequest_(type, request, context);
297
303
  const requestOperation = requestFilterOperation.chain(
298
- () => this.makeRequestWithRetry_(type, request, numBytesRemainingObj));
304
+ () => this.makeRequestWithRetry_(type, request, context,
305
+ numBytesRemainingObj));
299
306
  const responseFilterOperation = requestOperation.chain(
300
307
  (responseAndGotProgress) =>
301
308
  this.filterResponse_(type, responseAndGotProgress, context));
@@ -322,7 +329,9 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
322
329
  !response.fromCache &&
323
330
  request.method != 'HEAD' &&
324
331
  type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
325
- this.onProgressUpdated_(response.timeMs, response.data.byteLength);
332
+ const allowSwitch = this.allowSwitch_(context);
333
+ this.onProgressUpdated_(
334
+ response.timeMs, response.data.byteLength, allowSwitch);
326
335
  }
327
336
  return response;
328
337
  }, (e) => {
@@ -399,18 +408,19 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
399
408
  /**
400
409
  * @param {shaka.net.NetworkingEngine.RequestType} type
401
410
  * @param {shaka.extern.Request} request
411
+ * @param {(shaka.extern.RequestContext|undefined)} context
402
412
  * @param {shaka.net.NetworkingEngine.NumBytesRemainingClass}
403
413
  * numBytesRemainingObj
404
414
  * @return {!shaka.extern.IAbortableOperation.<
405
415
  * shaka.net.NetworkingEngine.ResponseAndGotProgress>}
406
416
  * @private
407
417
  */
408
- makeRequestWithRetry_(type, request, numBytesRemainingObj) {
418
+ makeRequestWithRetry_(type, request, context, numBytesRemainingObj) {
409
419
  const backoff = new shaka.net.Backoff(
410
420
  request.retryParameters, /* autoReset= */ false);
411
421
  const index = 0;
412
422
  return this.send_(
413
- type, request, backoff, index, /* lastError= */ null,
423
+ type, request, context, backoff, index, /* lastError= */ null,
414
424
  numBytesRemainingObj);
415
425
  }
416
426
 
@@ -419,6 +429,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
419
429
  *
420
430
  * @param {shaka.net.NetworkingEngine.RequestType} type
421
431
  * @param {shaka.extern.Request} request
432
+ * @param {(shaka.extern.RequestContext|undefined)} context
422
433
  * @param {!shaka.net.Backoff} backoff
423
434
  * @param {number} index
424
435
  * @param {?shaka.util.Error} lastError
@@ -428,11 +439,18 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
428
439
  * shaka.net.NetworkingEngine.ResponseAndGotProgress>}
429
440
  * @private
430
441
  */
431
- send_(type, request, backoff, index, lastError, numBytesRemainingObj) {
442
+ send_(type, request, context, backoff, index, lastError,
443
+ numBytesRemainingObj) {
432
444
  if (this.forceHTTPS_) {
433
445
  request.uris[index] = request.uris[index].replace('http://', 'https://');
434
446
  }
435
447
 
448
+ if (index > 0 && this.onRetry_) {
449
+ const newUri = request.uris[index];
450
+ const oldUri = request.uris[index - 1];
451
+ this.onRetry_(type, context, newUri, oldUri);
452
+ }
453
+
436
454
  const uri = new goog.Uri(request.uris[index]);
437
455
  let scheme = uri.getScheme();
438
456
  // Whether it got a progress event.
@@ -501,7 +519,8 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
501
519
  stallTimer.tickAfter(stallTimeoutMs / 1000);
502
520
  }
503
521
  if (this.onProgressUpdated_ && type == segment) {
504
- this.onProgressUpdated_(time, bytes);
522
+ const allowSwitch = this.allowSwitch_(context);
523
+ this.onProgressUpdated_(time, bytes, allowSwitch);
505
524
  gotProgress = true;
506
525
  numBytesRemainingObj.setBytes(numBytesRemaining);
507
526
  }
@@ -610,7 +629,8 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
610
629
  // Move to the next URI.
611
630
  index = (index + 1) % request.uris.length;
612
631
  return this.send_(
613
- type, request, backoff, index, error, numBytesRemainingObj);
632
+ type, request, context, backoff, index, error,
633
+ numBytesRemainingObj);
614
634
  }
615
635
  }
616
636
 
@@ -670,6 +690,24 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
670
690
  });
671
691
  }
672
692
 
693
+ /**
694
+ * @param {(shaka.extern.RequestContext|undefined)} context
695
+ * @return {boolean}
696
+ * @private
697
+ */
698
+ allowSwitch_(context) {
699
+ if (context) {
700
+ const segment = context.segment;
701
+ const stream = context.stream;
702
+ if (segment && stream && stream.fastSwitching) {
703
+ if (segment.isPartial() && !segment.isLastPartial()) {
704
+ return false;
705
+ }
706
+ }
707
+ }
708
+ return true;
709
+ }
710
+
673
711
  /**
674
712
  * This is here only for testability. We can't mock location in our tests on
675
713
  * all browsers, so instead we mock this.
@@ -770,6 +808,7 @@ shaka.net.NetworkingEngine.RequestType = {
770
808
  'SERVER_CERTIFICATE': 5,
771
809
  'KEY': 6,
772
810
  'ADS': 7,
811
+ 'CONTENT_STEERING': 8,
773
812
  };
774
813
 
775
814
  /**
@@ -887,3 +926,18 @@ shaka.net.NetworkingEngine.OnDownloadFailed;
887
926
  * @export
888
927
  */
889
928
  shaka.net.NetworkingEngine.OnRequest;
929
+
930
+
931
+ /**
932
+ * @typedef {function(
933
+ * !shaka.net.NetworkingEngine.RequestType,
934
+ * (shaka.extern.RequestContext|undefined),
935
+ * string,
936
+ * string)}
937
+ *
938
+ * @description
939
+ * A callback function called on every request retry. The first string is the
940
+ * new URI and the second string is the old URI.
941
+ * @export
942
+ */
943
+ shaka.net.NetworkingEngine.OnRetry;
@@ -0,0 +1,95 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.net.NetworkingUtils');
8
+
9
+ goog.require('goog.Uri');
10
+ goog.require('shaka.net.NetworkingEngine');
11
+
12
+
13
+ /**
14
+ * @summary Networking utility functions.
15
+ */
16
+ shaka.net.NetworkingUtils = class {
17
+ /**
18
+ * @param {string} uri
19
+ * @param {!shaka.net.NetworkingEngine} netEngine
20
+ * @param {shaka.extern.RetryParameters} retryParams
21
+ * @return {!Promise.<string>}
22
+ */
23
+ static async getMimeType(uri, netEngine, retryParams) {
24
+ const extension = shaka.net.NetworkingUtils.getExtension_(uri);
25
+ let mimeType =
26
+ shaka.net.NetworkingUtils.EXTENSIONS_TO_MIME_TYPES_[extension];
27
+ if (mimeType) {
28
+ return mimeType;
29
+ }
30
+ const type = shaka.net.NetworkingEngine.RequestType.MANIFEST;
31
+
32
+ const request = shaka.net.NetworkingEngine.makeRequest([uri], retryParams);
33
+ request.method = 'HEAD';
34
+
35
+ const response = await netEngine.request(type, request).promise;
36
+
37
+ // https://bit.ly/2K9s9kf says this header should always be available,
38
+ // but just to be safe:
39
+ mimeType = response.headers['content-type'];
40
+ return mimeType ? mimeType.toLowerCase().split(';').shift() : '';
41
+ }
42
+
43
+
44
+ /**
45
+ * @param {string} uri
46
+ * @return {string}
47
+ * @private
48
+ */
49
+ static getExtension_(uri) {
50
+ const uriObj = new goog.Uri(uri);
51
+ const uriPieces = uriObj.getPath().split('/');
52
+ const uriFilename = uriPieces.pop();
53
+ const filenamePieces = uriFilename.split('.');
54
+
55
+ // Only one piece means there is no extension.
56
+ if (filenamePieces.length == 1) {
57
+ return '';
58
+ }
59
+
60
+ return filenamePieces.pop().toLowerCase();
61
+ }
62
+ };
63
+
64
+ /**
65
+ * @const {!Object.<string, string>}
66
+ * @private
67
+ */
68
+ shaka.net.NetworkingUtils.EXTENSIONS_TO_MIME_TYPES_ = {
69
+ 'mp4': 'video/mp4',
70
+ 'm4v': 'video/mp4',
71
+ 'm4a': 'audio/mp4',
72
+ 'webm': 'video/webm',
73
+ 'weba': 'audio/webm',
74
+ 'mkv': 'video/webm', // Chromium browsers supports it.
75
+ 'ts': 'video/mp2t',
76
+ 'ogv': 'video/ogg',
77
+ 'ogg': 'audio/ogg',
78
+ 'mpg': 'video/mpeg',
79
+ 'mpeg': 'video/mpeg',
80
+ 'm3u8': 'application/x-mpegurl',
81
+ 'mpd': 'application/dash+xml',
82
+ 'ism': 'application/vnd.ms-sstr+xml',
83
+ 'mp3': 'audio/mpeg',
84
+ 'aac': 'audio/aac',
85
+ 'flac': 'audio/flac',
86
+ 'wav': 'audio/wav',
87
+ 'sbv': 'text/x-subviewer',
88
+ 'srt': 'text/srt',
89
+ 'vtt': 'text/vtt',
90
+ 'webvtt': 'text/vtt',
91
+ 'ttml': 'application/ttml+xml',
92
+ 'lrc': 'application/x-subtitle-lrc',
93
+ 'ssa': 'text/x-ssa',
94
+ 'ass': 'text/x-ssa',
95
+ };
@@ -158,6 +158,7 @@ shaka.offline.indexeddb.V1StorageCell = class
158
158
  frameRate: old.frameRate,
159
159
  pixelAspectRatio: undefined,
160
160
  hdr: undefined,
161
+ videoLayout: undefined,
161
162
  kind: old.kind,
162
163
  language: old.language,
163
164
  originalLanguage: old.language || null,
@@ -179,6 +180,7 @@ shaka.offline.indexeddb.V1StorageCell = class
179
180
  closedCaptions: null,
180
181
  tilesLayout: undefined,
181
182
  external: false,
183
+ fastSwitching: false,
182
184
  };
183
185
  }
184
186
 
@@ -107,6 +107,7 @@ shaka.offline.indexeddb.V2StorageCell = class
107
107
  frameRate: old.frameRate,
108
108
  pixelAspectRatio: old.pixelAspectRatio,
109
109
  hdr: undefined,
110
+ videoLayout: undefined,
110
111
  kind: old.kind,
111
112
  language: old.language,
112
113
  originalLanguage: old.language || null,
@@ -128,6 +129,7 @@ shaka.offline.indexeddb.V2StorageCell = class
128
129
  closedCaptions: null,
129
130
  tilesLayout: undefined,
130
131
  external: false,
132
+ fastSwitching: false,
131
133
  };
132
134
  }
133
135
 
@@ -190,6 +190,7 @@ shaka.offline.ManifestConverter = class {
190
190
  frameRate: streamDB.frameRate,
191
191
  pixelAspectRatio: streamDB.pixelAspectRatio,
192
192
  hdr: streamDB.hdr,
193
+ videoLayout: streamDB.videoLayout,
193
194
  kind: streamDB.kind,
194
195
  encrypted: streamDB.encrypted,
195
196
  drmInfos: [],
@@ -210,6 +211,7 @@ shaka.offline.ManifestConverter = class {
210
211
  tilesLayout: streamDB.tilesLayout,
211
212
  accessibilityPurpose: null,
212
213
  external: streamDB.external,
214
+ fastSwitching: streamDB.fastSwitching,
213
215
  };
214
216
 
215
217
  return stream;
@@ -112,6 +112,16 @@ shaka.offline.OfflineManifestParser = class {
112
112
  await muxer.destroy();
113
113
  }
114
114
  }
115
+
116
+ /** @override */
117
+ onInitialVariantChosen(variant) {
118
+ // No-op
119
+ }
120
+
121
+ /** @override */
122
+ banLocation(uri) {
123
+ // No-op
124
+ }
115
125
  };
116
126
 
117
127