shaka-player 4.5.0 → 4.6.0

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 +98 -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 +35 -31
  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 +786 -736
  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 +1154 -1121
  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 +877 -811
  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 +1362 -1318
  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 +249 -83
  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 +83 -26
  70. package/lib/hls/hls_classes.js +28 -43
  71. package/lib/hls/hls_parser.js +761 -649
  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 +41 -21
  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 +993 -1502
  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 +377 -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
 
@@ -1373,6 +1387,11 @@ shaka.media.StreamingEngine = class {
1373
1387
  await this.append_(
1374
1388
  mediaState, presentationTime, stream, reference, dataToAppend,
1375
1389
  /* isChunkedData= */ true);
1390
+
1391
+ if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1392
+ mediaState.segmentPrefetch.prefetchSegments(
1393
+ reference, /* skipFirst= */ true);
1394
+ }
1376
1395
  }
1377
1396
  };
1378
1397
 
@@ -1403,6 +1422,11 @@ shaka.media.StreamingEngine = class {
1403
1422
  await this.append_(
1404
1423
  mediaState, presentationTime, stream, reference, result);
1405
1424
  }
1425
+
1426
+ if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1427
+ mediaState.segmentPrefetch.prefetchSegments(
1428
+ reference, /* skipFirst= */ true);
1429
+ }
1406
1430
  } else {
1407
1431
  if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
1408
1432
  shaka.log.warning('Low latency streaming mode is enabled, but ' +
@@ -1499,11 +1523,11 @@ shaka.media.StreamingEngine = class {
1499
1523
  /**
1500
1524
  * @param {!BufferSource} rawResult
1501
1525
  * @param {shaka.extern.aes128Key} aes128Key
1502
- * @param {!shaka.media.SegmentIterator} iter
1526
+ * @param {number} position
1503
1527
  * @return {!Promise.<!BufferSource>} finalResult
1504
1528
  * @private
1505
1529
  */
1506
- async aes128Decrypt_(rawResult, aes128Key, iter) {
1530
+ async aes128Decrypt_(rawResult, aes128Key, position) {
1507
1531
  const key = aes128Key;
1508
1532
  if (!key.cryptoKey) {
1509
1533
  goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
@@ -1514,7 +1538,7 @@ shaka.media.StreamingEngine = class {
1514
1538
  let iv = key.iv;
1515
1539
  if (!iv) {
1516
1540
  iv = shaka.util.BufferUtils.toUint8(new ArrayBuffer(16));
1517
- let sequence = key.firstMediaSequenceNumber + iter.currentPosition();
1541
+ let sequence = key.firstMediaSequenceNumber + position;
1518
1542
  for (let i = iv.byteLength - 1; i >= 0; i--) {
1519
1543
  iv[i] = sequence & 0xff;
1520
1544
  sequence >>= 8;
@@ -1764,8 +1788,7 @@ shaka.media.StreamingEngine = class {
1764
1788
  shaka.log.v1(logPrefix, 'fetching init segment');
1765
1789
 
1766
1790
  const fetchInit =
1767
- this.fetch_(mediaState, reference.initSegmentReference,
1768
- /* streamDataCallback= */ undefined, /* isInit= */ true);
1791
+ this.fetch_(mediaState, reference.initSegmentReference);
1769
1792
  const append = async () => {
1770
1793
  try {
1771
1794
  const initSegment = await fetchInit;
@@ -2169,13 +2192,12 @@ shaka.media.StreamingEngine = class {
2169
2192
  * @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)}
2170
2193
  * reference
2171
2194
  * @param {?function(BufferSource):!Promise=} streamDataCallback
2172
- * @param {boolean=} isInit
2173
2195
  *
2174
2196
  * @return {!Promise.<BufferSource>}
2175
2197
  * @private
2176
2198
  * @suppress {strictMissingProperties}
2177
2199
  */
2178
- async fetch_(mediaState, reference, streamDataCallback, isInit) {
2200
+ async fetch_(mediaState, reference, streamDataCallback) {
2179
2201
  if (reference instanceof shaka.media.InitSegmentReference) {
2180
2202
  const segmentData = reference.getSegmentData();
2181
2203
  if (segmentData) {
@@ -2183,17 +2205,18 @@ shaka.media.StreamingEngine = class {
2183
2205
  }
2184
2206
  }
2185
2207
  let op = null;
2186
- if (
2187
- mediaState.segmentPrefetch &&
2188
- reference instanceof shaka.media.SegmentReference
2189
- ) {
2208
+ if (mediaState.segmentPrefetch) {
2190
2209
  op = mediaState.segmentPrefetch.getPrefetchedSegment(
2191
2210
  reference, streamDataCallback);
2192
2211
  }
2193
2212
  if (!op) {
2194
2213
  op = this.dispatchFetch_(
2195
- reference, mediaState.stream, streamDataCallback, isInit,
2196
- );
2214
+ reference, mediaState.stream, streamDataCallback);
2215
+ }
2216
+
2217
+ let position = 0;
2218
+ if (mediaState.segmentIterator) {
2219
+ position = mediaState.segmentIterator.currentPosition();
2197
2220
  }
2198
2221
 
2199
2222
  mediaState.operation = op;
@@ -2201,9 +2224,7 @@ shaka.media.StreamingEngine = class {
2201
2224
  mediaState.operation = null;
2202
2225
  let result = response.data;
2203
2226
  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);
2227
+ result = await this.aes128Decrypt_(result, reference.aes128Key, position);
2207
2228
  }
2208
2229
  return result;
2209
2230
  }
@@ -2215,18 +2236,17 @@ shaka.media.StreamingEngine = class {
2215
2236
  * @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)}
2216
2237
  * reference
2217
2238
  * @param {?function(BufferSource):!Promise=} streamDataCallback
2218
- * @param {boolean=} isInit
2219
2239
  *
2220
2240
  * @return {!shaka.net.NetworkingEngine.PendingRequest}
2221
2241
  * @private
2222
2242
  */
2223
- dispatchFetch_(reference, stream, streamDataCallback, isInit) {
2243
+ dispatchFetch_(reference, stream, streamDataCallback) {
2224
2244
  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
2245
  const segment = reference instanceof shaka.media.SegmentReference ?
2229
2246
  reference : undefined;
2247
+ const type = segment ?
2248
+ shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT :
2249
+ shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT;
2230
2250
  const request = shaka.util.Networking.createSegmentRequest(
2231
2251
  reference.getUris(),
2232
2252
  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
 
@@ -12,6 +12,7 @@ goog.require('shaka.log');
12
12
  goog.require('shaka.media.DrmEngine');
13
13
  goog.require('shaka.media.ManifestParser');
14
14
  goog.require('shaka.net.NetworkingEngine');
15
+ goog.require('shaka.net.NetworkingUtils');
15
16
  goog.require('shaka.offline.DownloadInfo');
16
17
  goog.require('shaka.offline.DownloadManager');
17
18
  goog.require('shaka.offline.OfflineUri');
@@ -271,10 +272,13 @@ shaka.offline.Storage = class {
271
272
  goog.asserts.assert(
272
273
  this.networkingEngine_, 'Should not call |store| after |destroy|');
273
274
 
274
- const factory = await shaka.media.ManifestParser.getFactory(
275
+ if (!mimeType) {
276
+ mimeType = await shaka.net.NetworkingUtils.getMimeType(
277
+ uri, this.networkingEngine_, config.manifest.retryParameters);
278
+ }
279
+
280
+ const factory = shaka.media.ManifestParser.getFactory(
275
281
  uri,
276
- this.networkingEngine_,
277
- config.manifest.retryParameters,
278
282
  mimeType || null);
279
283
 
280
284
  return factory();
@@ -656,20 +660,19 @@ shaka.offline.Storage = class {
656
660
  // Filter the manifest based on what we know MediaCapabilities will be able
657
661
  // to play later (no point storing something we can't play).
658
662
  await shaka.util.StreamUtils.filterManifestByMediaCapabilities(
659
- manifest, config.offline.usePersistentLicense);
663
+ drmEngine, manifest, config.offline.usePersistentLicense);
660
664
 
661
665
  // Gather all tracks.
662
666
  const allTracks = [];
663
667
 
664
668
  // Choose the codec that has the lowest average bandwidth.
665
- const preferredAudioChannelCount = config.preferredAudioChannelCount;
666
669
  const preferredDecodingAttributes = config.preferredDecodingAttributes;
667
670
  const preferredVideoCodecs = config.preferredVideoCodecs;
668
671
  const preferredAudioCodecs = config.preferredAudioCodecs;
669
672
 
670
673
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
671
674
  manifest, preferredVideoCodecs, preferredAudioCodecs,
672
- preferredAudioChannelCount, preferredDecodingAttributes);
675
+ preferredDecodingAttributes);
673
676
 
674
677
  for (const variant of manifest.variants) {
675
678
  goog.asserts.assert(
@@ -1230,7 +1233,6 @@ shaka.offline.Storage = class {
1230
1233
  drmEngine.configure(config.drm);
1231
1234
  await drmEngine.initForStorage(
1232
1235
  manifest.variants, config.offline.usePersistentLicense);
1233
- await drmEngine.setServerCertificate();
1234
1236
  await drmEngine.createOrLoad();
1235
1237
 
1236
1238
  return drmEngine;
@@ -1309,6 +1311,7 @@ shaka.offline.Storage = class {
1309
1311
  frameRate: stream.frameRate,
1310
1312
  pixelAspectRatio: stream.pixelAspectRatio,
1311
1313
  hdr: stream.hdr,
1314
+ videoLayout: stream.videoLayout,
1312
1315
  kind: stream.kind,
1313
1316
  language: stream.language,
1314
1317
  originalLanguage: stream.originalLanguage,
@@ -1327,6 +1330,7 @@ shaka.offline.Storage = class {
1327
1330
  closedCaptions: stream.closedCaptions,
1328
1331
  tilesLayout: stream.tilesLayout,
1329
1332
  external: stream.external,
1333
+ fastSwitching: stream.fastSwitching,
1330
1334
  };
1331
1335
 
1332
1336
  const startTime =