shaka-player 3.1.0 → 3.1.4

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 (181) hide show
  1. package/AUTHORS +8 -2
  2. package/CHANGELOG.md +209 -0
  3. package/CONTRIBUTORS +10 -3
  4. package/README.md +3 -0
  5. package/build/apps.py +2 -2
  6. package/build/check.py +2 -2
  7. package/build/conformance.textproto +0 -9
  8. package/build/generateExterns.js +1 -1
  9. package/build/misspellings.txt +1 -0
  10. package/build/shakaBuildHelpers.py +6 -5
  11. package/build/subprocessWindowsPatch.py +103 -0
  12. package/demo/common/asset.js +11 -0
  13. package/demo/common/assets.js +1 -1
  14. package/demo/common/message_ids.js +5 -0
  15. package/demo/config.js +2 -1
  16. package/demo/custom.js +84 -3
  17. package/demo/demo.less +13 -1
  18. package/demo/demo_utils.js +1 -0
  19. package/demo/input.js +31 -12
  20. package/demo/input_container.js +24 -1
  21. package/demo/locales/en.json +6 -1
  22. package/demo/locales/source.json +20 -0
  23. package/demo/main.js +5 -1
  24. package/dist/controls.css +2 -2
  25. package/dist/controls.css.map +1 -1
  26. package/dist/demo.compiled.debug.js +99 -91
  27. package/dist/demo.compiled.debug.map +3 -3
  28. package/dist/demo.compiled.js +99 -91
  29. package/dist/demo.compiled.map +3 -3
  30. package/dist/demo.css +1 -1
  31. package/dist/demo.css.map +1 -1
  32. package/dist/deps.js +9 -9
  33. package/dist/locales.js +36 -34
  34. package/dist/receiver.compiled.debug.js +74 -70
  35. package/dist/receiver.compiled.debug.map +3 -3
  36. package/dist/receiver.compiled.js +74 -70
  37. package/dist/receiver.compiled.map +3 -3
  38. package/dist/shaka-player.compiled.d.ts +165 -9
  39. package/dist/shaka-player.compiled.debug.d.ts +165 -9
  40. package/dist/shaka-player.compiled.debug.externs.js +179 -11
  41. package/dist/shaka-player.compiled.debug.js +367 -339
  42. package/dist/shaka-player.compiled.debug.map +4 -4
  43. package/dist/shaka-player.compiled.externs.js +179 -11
  44. package/dist/shaka-player.compiled.js +818 -807
  45. package/dist/shaka-player.compiled.map +4 -4
  46. package/dist/shaka-player.ui.d.ts +165 -9
  47. package/dist/shaka-player.ui.debug.d.ts +165 -9
  48. package/dist/shaka-player.ui.debug.externs.js +181 -13
  49. package/dist/shaka-player.ui.debug.js +459 -429
  50. package/dist/shaka-player.ui.debug.map +4 -4
  51. package/dist/shaka-player.ui.externs.js +181 -13
  52. package/dist/shaka-player.ui.js +973 -961
  53. package/dist/shaka-player.ui.map +4 -4
  54. package/docs/tutorials/fairplay.md +1 -1
  55. package/docs/tutorials/faq.md +25 -0
  56. package/docs/tutorials/index.json +2 -2
  57. package/docs/tutorials/upgrade-v3.md +38 -0
  58. package/docs/tutorials/upgrade.md +3 -3
  59. package/externs/ima.js +4 -0
  60. package/externs/media_capabilities.js +16 -0
  61. package/externs/shaka/ads.js +2 -0
  62. package/externs/shaka/net.js +2 -2
  63. package/externs/shaka/player.js +6 -0
  64. package/karma.conf.js +1 -0
  65. package/lib/abr/simple_abr_manager.js +1 -1
  66. package/lib/ads/ad_manager.js +29 -11
  67. package/lib/ads/client_side_ad_manager.js +76 -86
  68. package/lib/ads/server_side_ad_manager.js +26 -11
  69. package/lib/cast/cast_proxy.js +3 -2
  70. package/lib/cast/cast_receiver.js +2 -1
  71. package/lib/cast/cast_sender.js +2 -2
  72. package/lib/cea/sei_processor.js +24 -17
  73. package/lib/dash/dash_parser.js +107 -4
  74. package/lib/dash/segment_list.js +1 -1
  75. package/lib/dash/segment_template.js +45 -15
  76. package/lib/dependencies/all.js +15 -15
  77. package/lib/hls/hls_parser.js +46 -18
  78. package/lib/media/closed_caption_parser.js +3 -1
  79. package/lib/media/content_workarounds.js +18 -0
  80. package/lib/media/drm_engine.js +20 -1
  81. package/lib/media/gap_jumping_controller.js +8 -4
  82. package/lib/media/media_source_engine.js +1 -2
  83. package/lib/media/playhead.js +1 -1
  84. package/lib/media/presentation_timeline.js +3 -1
  85. package/lib/media/segment_index.js +38 -12
  86. package/lib/media/segment_reference.js +8 -0
  87. package/lib/media/streaming_engine.js +30 -21
  88. package/lib/net/http_fetch_plugin.js +2 -2
  89. package/lib/net/networking_engine.js +3 -2
  90. package/lib/offline/storage.js +4 -0
  91. package/lib/player.js +113 -56
  92. package/lib/polyfill/encryption_scheme.js +2 -0
  93. package/lib/polyfill/fullscreen.js +2 -0
  94. package/lib/polyfill/mathround.js +2 -0
  95. package/lib/polyfill/media_capabilities.js +62 -23
  96. package/lib/polyfill/mediasource.js +8 -0
  97. package/lib/polyfill/orientation.js +3 -1
  98. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  99. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  100. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  101. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  102. package/lib/polyfill/pip_webkit.js +2 -0
  103. package/lib/polyfill/storage_estimate.js +2 -0
  104. package/lib/polyfill/video_play_promise.js +2 -0
  105. package/lib/polyfill/videoplaybackquality.js +2 -0
  106. package/lib/polyfill/vttcue.js +2 -0
  107. package/lib/text/cue.js +11 -0
  108. package/lib/text/mp4_ttml_parser.js +3 -0
  109. package/lib/text/text_engine.js +14 -2
  110. package/lib/text/ttml_text_parser.js +52 -30
  111. package/lib/text/ui_text_displayer.js +255 -82
  112. package/lib/text/vtt_text_parser.js +4 -2
  113. package/lib/util/error.js +1 -4
  114. package/lib/util/fake_event.js +53 -7
  115. package/lib/util/mime_utils.js +33 -0
  116. package/lib/util/mp4_box_parsers.js +1 -1
  117. package/lib/util/mp4_parser.js +14 -0
  118. package/lib/util/networking.js +1 -1
  119. package/lib/util/periods.js +132 -37
  120. package/lib/util/platform.js +13 -1
  121. package/lib/util/stream_utils.js +46 -7
  122. package/package.json +3 -3
  123. package/test/cast/cast_proxy_unit.js +12 -6
  124. package/test/cast/cast_receiver_integration.js +1 -1
  125. package/test/cast/cast_utils_unit.js +1 -1
  126. package/test/cea/mp4_cea_parser_unit.js +26 -0
  127. package/test/dash/dash_parser_live_unit.js +1 -0
  128. package/test/dash/dash_parser_manifest_unit.js +187 -1
  129. package/test/dash/dash_parser_segment_template_unit.js +3 -3
  130. package/test/demo/demo_unit.js +0 -1
  131. package/test/hls/hls_parser_unit.js +45 -1
  132. package/test/media/closed_caption_parser_unit.js +20 -0
  133. package/test/media/drm_engine_unit.js +59 -3
  134. package/test/media/playhead_unit.js +27 -1
  135. package/test/media/presentation_timeline_unit.js +19 -0
  136. package/test/media/segment_index_unit.js +76 -3
  137. package/test/media/streaming_engine_unit.js +3 -3
  138. package/test/player_integration.js +38 -0
  139. package/test/player_unit.js +125 -6
  140. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  141. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  142. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  143. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  144. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  145. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  146. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  147. package/test/test/assets/3675/dash_0.mpd +30 -0
  148. package/test/test/assets/3675/dash_1.mpd +30 -0
  149. package/test/test/assets/3675/dash_2.mpd +30 -0
  150. package/test/test/assets/3675/dash_3.mpd +30 -0
  151. package/test/test/assets/3675/dash_4.mpd +30 -0
  152. package/test/test/assets/3675/dash_5.mpd +26 -0
  153. package/test/test/assets/3675/init-stream0.m4s +0 -0
  154. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  155. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  156. package/test/test/util/fake_ad_manager.js +4 -1
  157. package/test/test/util/simple_fakes.js +5 -3
  158. package/test/test/util/waiter.js +36 -2
  159. package/test/text/ttml_text_parser_unit.js +55 -0
  160. package/test/text/ui_text_displayer_unit.js +95 -18
  161. package/test/text/vtt_text_parser_unit.js +17 -1
  162. package/test/ui/localization_unit.js +15 -14
  163. package/test/ui/text_displayer_layout_unit.js +52 -1
  164. package/test/ui/ui_unit.js +4 -0
  165. package/test/util/mp4_box_parsers_unit.js +28 -0
  166. package/test/util/periods_unit.js +190 -2
  167. package/test/util/stream_utils_unit.js +56 -5
  168. package/ui/airplay_button.js +5 -0
  169. package/ui/big_play_button.js +10 -0
  170. package/ui/cast_button.js +2 -3
  171. package/ui/controls.js +9 -7
  172. package/ui/less/containers.less +10 -5
  173. package/ui/locales/en.json +1 -0
  174. package/ui/locales/ru.json +6 -6
  175. package/ui/locales/source.json +4 -0
  176. package/ui/localization.js +28 -51
  177. package/ui/pip_button.js +2 -3
  178. package/ui/play_button.js +10 -10
  179. package/ui/small_play_button.js +12 -0
  180. package/ui/ui.js +1 -1
  181. package/docs/tutorials/upgrade-v2-4.md +0 -481
@@ -414,18 +414,16 @@ shaka.hls.HlsParser = class {
414
414
  const uri = tag.getAttributeValue('URI');
415
415
  const language = tag.getAttributeValue('LANGUAGE');
416
416
  const value = tag.getAttributeValue('VALUE');
417
- const data = {
418
- 'id': id,
419
- };
417
+ const data = (new Map()).set('id', id);
420
418
  if (uri) {
421
- data.uri =
422
- shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri);
419
+ data.set('uri',
420
+ shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
423
421
  }
424
422
  if (language) {
425
- data.language = language;
423
+ data.set('language', language);
426
424
  }
427
425
  if (value) {
428
- data.value = value;
426
+ data.set('value', value);
429
427
  }
430
428
  const event = new shaka.util.FakeEvent('sessiondata', data);
431
429
  if (this.playerInterface_) {
@@ -529,6 +527,30 @@ shaka.hls.HlsParser = class {
529
527
  }
530
528
  }
531
529
 
530
+ // Now that the content has been fit, notify segments.
531
+ this.segmentsToNotifyByStream_ = [];
532
+ const streamsToNotify = [];
533
+ for (const variant of variants) {
534
+ for (const stream of [variant.video, variant.audio]) {
535
+ if (stream) {
536
+ streamsToNotify.push(stream);
537
+ }
538
+ }
539
+ }
540
+ await Promise.all(streamsToNotify.map(async (stream) => {
541
+ await stream.createSegmentIndex();
542
+ }));
543
+ for (const stream of streamsToNotify) {
544
+ this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
545
+ }
546
+ this.notifySegments_();
547
+
548
+ // This asserts that the live edge is being calculated from segment times.
549
+ // For VOD and event streams, this check should still pass.
550
+ goog.asserts.assert(
551
+ !this.presentationTimeline_.usingPresentationStartTime(),
552
+ 'We should not be using the presentation start time in HLS!');
553
+
532
554
  this.manifest_ = {
533
555
  presentationTimeline: this.presentationTimeline_,
534
556
  variants,
@@ -699,7 +721,8 @@ shaka.hls.HlsParser = class {
699
721
  // Create variants for each variant tag.
700
722
  const variantsPromises = tags.map(async (tag) => {
701
723
  const frameRate = tag.getAttributeValue('FRAME-RATE');
702
- const bandwidth = Number(tag.getRequiredAttrValue('BANDWIDTH'));
724
+ const bandwidth = Number(tag.getAttributeValue('AVERAGE-BANDWIDTH')) ||
725
+ Number(tag.getRequiredAttrValue('BANDWIDTH'));
703
726
 
704
727
  const resolution = tag.getAttributeValue('RESOLUTION');
705
728
  const [width, height] = resolution ? resolution.split('x') : [null, null];
@@ -1150,7 +1173,7 @@ shaka.hls.HlsParser = class {
1150
1173
  // "AUTOSELECT=YES". A value of "AUTOSELECT=NO" would imply that it may
1151
1174
  // only be selected explicitly by the user, and we don't have a way to
1152
1175
  // represent that in our model.
1153
- const defaultAttrValue = tag.getAttribute('DEFAULT');
1176
+ const defaultAttrValue = tag.getAttributeValue('DEFAULT');
1154
1177
  const primary = defaultAttrValue == 'YES';
1155
1178
 
1156
1179
  const channelsCount = type == 'audio' ? this.getChannelsCount_(tag) : null;
@@ -1537,12 +1560,6 @@ shaka.hls.HlsParser = class {
1537
1560
  }
1538
1561
 
1539
1562
  this.notifySegments_();
1540
-
1541
- // This asserts that the live edge is being calculated from segment times.
1542
- // For VOD and event streams, this check should still pass.
1543
- goog.asserts.assert(
1544
- !this.presentationTimeline_.usingPresentationStartTime(),
1545
- 'We should not be using the presentation start time in HLS!');
1546
1563
  }
1547
1564
 
1548
1565
  /**
@@ -1914,9 +1931,6 @@ shaka.hls.HlsParser = class {
1914
1931
  }
1915
1932
  }
1916
1933
 
1917
- this.segmentsToNotifyByStream_.push(references);
1918
- this.notifySegments_();
1919
-
1920
1934
  return references;
1921
1935
  }
1922
1936
 
@@ -2252,6 +2266,12 @@ shaka.hls.HlsParser = class {
2252
2266
  reader.rewind(1);
2253
2267
  };
2254
2268
 
2269
+ // We will look a few packet-lengths forward to find the first sync byte.
2270
+ // Note that we are using this method on what is already a subset of the
2271
+ // file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
2272
+ // we can't look too far ahead to begin with.
2273
+ let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
2274
+
2255
2275
  // TODO: refactor this while loop for better readability.
2256
2276
  // eslint-disable-next-line no-constant-condition
2257
2277
  while (true) {
@@ -2260,8 +2280,16 @@ shaka.hls.HlsParser = class {
2260
2280
 
2261
2281
  syncByte = reader.readUint8();
2262
2282
  if (syncByte != 0x47) {
2283
+ if (syncByteScanLength > 0) {
2284
+ // This file could have started with a cut-off TS packet. Scan forward
2285
+ // until we find a sync byte.
2286
+ syncByteScanLength -= 1;
2287
+ continue;
2288
+ }
2263
2289
  fail();
2264
2290
  }
2291
+ // If we've found a sync byte, stop scanning forward for future packets.
2292
+ syncByteScanLength = 0;
2265
2293
 
2266
2294
  const flagsAndPacketId = reader.readUint16();
2267
2295
  const packetId = flagsAndPacketId & 0x1fff;
@@ -87,7 +87,9 @@ shaka.media.ClosedCaptionParser = class {
87
87
  for (const captionPacket of captionPackets) {
88
88
  const uint8ArrayData =
89
89
  shaka.util.BufferUtils.toUint8(captionPacket.packet);
90
- this.ceaDecoder_.extract(uint8ArrayData, captionPacket.pts);
90
+ if (uint8ArrayData.length > 0) {
91
+ this.ceaDecoder_.extract(uint8ArrayData, captionPacket.pts);
92
+ }
91
93
  }
92
94
 
93
95
  // Decode and return the parsed captions.
@@ -75,6 +75,24 @@ shaka.media.ContentWorkarounds = class {
75
75
  newType: ContentWorkarounds.BOX_TYPE_ENCV_,
76
76
  });
77
77
  })
78
+ .fullBox('avc3', (box) => {
79
+ boxesToModify.push({
80
+ box,
81
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
82
+ });
83
+ })
84
+ .fullBox('ac-3', (box) => {
85
+ boxesToModify.push({
86
+ box,
87
+ newType: ContentWorkarounds.BOX_TYPE_ENCA_,
88
+ });
89
+ })
90
+ .fullBox('ec-3', (box) => {
91
+ boxesToModify.push({
92
+ box,
93
+ newType: ContentWorkarounds.BOX_TYPE_ENCA_,
94
+ });
95
+ })
78
96
  .fullBox('mp4a', (box) => {
79
97
  boxesToModify.push({
80
98
  box,
@@ -126,6 +126,9 @@ shaka.media.DrmEngine = class {
126
126
 
127
127
  /** @const {!shaka.util.Destroyer} */
128
128
  this.destroyer_ = new shaka.util.Destroyer(() => this.destroyNow_());
129
+
130
+ /** @private {boolean} */
131
+ this.srcEquals_ = false;
129
132
  }
130
133
 
131
134
  /** @override */
@@ -181,6 +184,7 @@ shaka.media.DrmEngine = class {
181
184
  this.config_ = null;
182
185
  this.onError_ = () => {};
183
186
  this.playerInterface_ = null;
187
+ this.srcEquals_ = false;
184
188
  }
185
189
 
186
190
  /**
@@ -194,6 +198,13 @@ shaka.media.DrmEngine = class {
194
198
  this.config_ = config;
195
199
  }
196
200
 
201
+ /**
202
+ * @param {!boolean} value
203
+ */
204
+ setSrcEquals(value) {
205
+ this.srcEquals_ = value;
206
+ }
207
+
197
208
  /**
198
209
  * Initialize the drm engine for storing and deleting stored content.
199
210
  *
@@ -356,7 +367,7 @@ shaka.media.DrmEngine = class {
356
367
  // in the drm infos, and before queryMediaKeys_().
357
368
  if (useMediaCapabilities) {
358
369
  await shaka.util.StreamUtils.getDecodingInfosForVariants(variants,
359
- this.usePersistentLicenses_);
370
+ this.usePersistentLicenses_, this.srcEquals_);
360
371
  } else {
361
372
  configsByKeySystem =
362
373
  this.prepareMediaKeyConfigsForVariants_(variants);
@@ -512,6 +523,7 @@ shaka.media.DrmEngine = class {
512
523
  tasks.push(session.remove());
513
524
 
514
525
  await Promise.all(tasks);
526
+ this.activeSessions_.delete(session);
515
527
  }
516
528
 
517
529
  /**
@@ -1877,9 +1889,16 @@ shaka.media.DrmEngine = class {
1877
1889
  // audio adaptations, so we shouldn't have to worry about checking
1878
1890
  // robustness.
1879
1891
  if (drm1.keySystem == drm2.keySystem) {
1892
+ /** @type {Array<shaka.extern.InitDataOverride>} */
1880
1893
  let initData = [];
1881
1894
  initData = initData.concat(drm1.initData || []);
1882
1895
  initData = initData.concat(drm2.initData || []);
1896
+ initData = initData.filter((d, i) => {
1897
+ return d.keyId === undefined || i === initData.findIndex((d2) => {
1898
+ return d2.keyId === d.keyId;
1899
+ });
1900
+ });
1901
+
1883
1902
  const keyIds = drm1.keyIds && drm2.keyIds ?
1884
1903
  new Set([...drm1.keyIds, ...drm2.keyIds]) :
1885
1904
  drm1.keyIds || drm2.keyIds;
@@ -154,8 +154,10 @@ shaka.media.GapJumpingController = class {
154
154
  }
155
155
  // Don't gap jump while paused, so that you don't constantly jump ahead
156
156
  // while paused on a livestream. We make an exception for time 0, since we
157
- // may be _required_ to seek on startup before play can begin.
158
- if (this.video_.paused && this.video_.currentTime != 0) {
157
+ // may be _required_ to seek on startup before play can begin, but only if
158
+ // autoplay is enabled.
159
+ if (this.video_.paused && (this.video_.currentTime != 0 ||
160
+ (!this.video_.autoplay && this.video_.currentTime == 0))) {
159
161
  return;
160
162
  }
161
163
 
@@ -215,8 +217,10 @@ shaka.media.GapJumpingController = class {
215
217
  this.didFireLargeGap_ = true;
216
218
 
217
219
  // Event firing is synchronous.
218
- const event = new shaka.util.FakeEvent(
219
- 'largegap', {'currentTime': currentTime, 'gapSize': jumpSize});
220
+ const data = new Map()
221
+ .set('currentTime', currentTime)
222
+ .set('gapSize', jumpSize);
223
+ const event = new shaka.util.FakeEvent('largegap', data);
220
224
  event.cancelable = true;
221
225
  this.onEvent_(event);
222
226
 
@@ -48,8 +48,7 @@ shaka.media.MediaSourceEngine = class {
48
48
  * @param {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)=}
49
49
  * onMetadata
50
50
  */
51
- constructor(video, closedCaptionParser, textDisplayer,
52
- onMetadata) {
51
+ constructor(video, closedCaptionParser, textDisplayer, onMetadata) {
53
52
  /** @private {HTMLMediaElement} */
54
53
  this.video_ = video;
55
54
 
@@ -80,7 +80,7 @@ shaka.media.SrcEqualsPlayhead = class {
80
80
  // We listen for the loaded-data-event so that we know when we can
81
81
  // interact with |currentTime|.
82
82
  const onLoaded = () => {
83
- if (this.startTime_ == null) {
83
+ if (this.startTime_ == null || this.startTime_ == 0) {
84
84
  this.started_ = true;
85
85
  } else {
86
86
  // Startup is complete only when the video element acknowledges the
@@ -361,8 +361,10 @@ shaka.media.PresentationTimeline = class {
361
361
  */
362
362
  getSegmentAvailabilityEnd() {
363
363
  if (!this.isLive() && !this.isInProgress()) {
364
- return this.duration_;
364
+ // It's a static manifest (can also be a dynamic->static conversion)
365
+ return this.maxSegmentEndTime_ || this.duration_;
365
366
  }
367
+ // Can be either live or "in-progress recording" (live with known duration)
366
368
  return Math.min(this.getLiveEdge_() + this.availabilityTimeOffset_,
367
369
  this.duration_);
368
370
  }
@@ -181,7 +181,9 @@ shaka.media.SegmentIndex = class {
181
181
 
182
182
  /**
183
183
  * Merges the given SegmentReferences. Supports extending the original
184
- * references only. Will not replace old references or interleave new ones.
184
+ * references only. Will replace old references with equivalent new ones, and
185
+ * keep any unique old ones.
186
+ *
185
187
  * Used, for example, by the DASH and HLS parser, where manifests may not list
186
188
  * all available references, so we must keep available references in memory to
187
189
  * fill the availability window.
@@ -189,6 +191,8 @@ shaka.media.SegmentIndex = class {
189
191
  * @param {!Array.<!shaka.media.SegmentReference>} references The list of
190
192
  * SegmentReferences, which must be sorted first by their start times
191
193
  * (ascending) and second by their end times (ascending).
194
+ * @deprecated Not used directly by our own parsers, so will become private in
195
+ * v4. Use mergeAndEvict() instead.
192
196
  * @export
193
197
  */
194
198
  merge(references) {
@@ -199,13 +203,13 @@ shaka.media.SegmentIndex = class {
199
203
  return;
200
204
  }
201
205
 
202
- // Partial segments are used for live edge, and should be removed when they
203
- // get older. Remove the old SegmentReferences after the first new
204
- // reference's start time.
205
206
  if (!references.length) {
206
207
  return;
207
208
  }
208
209
 
210
+ // Partial segments are used for live edge, and should be removed when they
211
+ // get older. Remove the old SegmentReferences after the first new
212
+ // reference's start time.
209
213
  this.references = this.references.filter((r) => {
210
214
  return r.startTime < references[0].startTime;
211
215
  });
@@ -233,13 +237,27 @@ shaka.media.SegmentIndex = class {
233
237
  * @export
234
238
  */
235
239
  mergeAndEvict(references, windowStart) {
236
- // FIlter out the references that are no longer available to avoid
240
+ // Filter out the references that are no longer available to avoid
237
241
  // repeatedly evicting them and messing up eviction count.
238
242
  references = references.filter((r) => {
239
- return r.endTime > windowStart;
243
+ return r.endTime > windowStart &&
244
+ (this.references.length == 0 ||
245
+ r.endTime > this.references[0].startTime);
240
246
  });
241
247
 
248
+ const oldFirstRef = this.references[0];
242
249
  this.merge(references);
250
+ const newFirstRef = this.references[0];
251
+
252
+ if (oldFirstRef) {
253
+ // We don't compare the actual ref, since the object could legitimately be
254
+ // replaced with an equivalent. Even the URIs could change due to
255
+ // load-balancing actions taken by the server. However, if the time
256
+ // changes, its not an equivalent reference.
257
+ goog.asserts.assert(oldFirstRef.startTime == newFirstRef.startTime,
258
+ 'SegmentIndex.merge should not change the first reference time!');
259
+ }
260
+
243
261
  this.evict(windowStart);
244
262
  }
245
263
 
@@ -363,22 +381,25 @@ shaka.media.SegmentIndex = class {
363
381
 
364
382
  /** @return {!shaka.media.SegmentIterator} */
365
383
  [Symbol.iterator]() {
366
- return this.getIteratorForTime(0);
384
+ const iter = this.getIteratorForTime(0);
385
+ goog.asserts.assert(iter != null, 'Iterator for 0 should never be null!');
386
+ return iter;
367
387
  }
368
388
 
369
389
  /**
370
390
  * Returns a new iterator that initially points to the segment that contains
371
391
  * the given time. Like the normal iterator, next() must be called first to
372
- * get to the first element.
392
+ * get to the first element. Returns null if we do not find a segment at the
393
+ * requested time.
373
394
  *
374
395
  * @param {number} time
375
- * @return {!shaka.media.SegmentIterator}
396
+ * @return {?shaka.media.SegmentIterator}
376
397
  * @export
377
398
  */
378
399
  getIteratorForTime(time) {
379
400
  let index = this.find(time);
380
401
  if (index == null) {
381
- index = -1;
402
+ return null;
382
403
  } else {
383
404
  index--;
384
405
  }
@@ -497,8 +518,13 @@ shaka.media.SegmentIterator = class {
497
518
  'Please use SegmentIndex.getIteratorForTime instead of seek().');
498
519
 
499
520
  const iter = this.segmentIndex_.getIteratorForTime(time);
500
- this.currentPosition_ = iter.currentPosition_;
501
- this.currentPartialPosition_ = iter.currentPartialPosition_;
521
+ if (iter) {
522
+ this.currentPosition_ = iter.currentPosition_;
523
+ this.currentPartialPosition_ = iter.currentPartialPosition_;
524
+ } else {
525
+ this.currentPosition_ = Number.MAX_VALUE;
526
+ this.currentPartialPosition_ = 0;
527
+ }
502
528
  return this.next().value;
503
529
  }
504
530
 
@@ -153,6 +153,14 @@ shaka.media.SegmentReference = class {
153
153
  /** @type {number} */
154
154
  this.endTime = endTime;
155
155
 
156
+ /**
157
+ * The "true" end time of the segment, without considering the period end
158
+ * time. This is necessary for thumbnail segments, where timing requires us
159
+ * to know the original segment duration as described in the manifest.
160
+ * @type {number}
161
+ */
162
+ this.trueEndTime = endTime;
163
+
156
164
  /** @type {function():!Array.<string>} */
157
165
  this.getUrisInner = uris;
158
166
 
@@ -317,8 +317,10 @@ shaka.media.StreamingEngine = class {
317
317
  * @param {shaka.extern.Variant} variant
318
318
  * @param {boolean=} clearBuffer
319
319
  * @param {number=} safeMargin
320
+ * @param {boolean=} force
321
+ * If true, reload the variant even if it did not change.
320
322
  */
321
- switchVariant(variant, clearBuffer = false, safeMargin = 0) {
323
+ switchVariant(variant, clearBuffer = false, safeMargin = 0, force = false) {
322
324
  this.currentVariant_ = variant;
323
325
 
324
326
  if (!this.startupComplete_) {
@@ -329,12 +331,12 @@ shaka.media.StreamingEngine = class {
329
331
  if (variant.video) {
330
332
  this.switchInternal_(
331
333
  variant.video, /* clearBuffer= */ clearBuffer,
332
- /* safeMargin= */ safeMargin, /* force= */ false);
334
+ /* safeMargin= */ safeMargin, /* force= */ force);
333
335
  }
334
336
  if (variant.audio) {
335
337
  this.switchInternal_(
336
338
  variant.audio, /* clearBuffer= */ clearBuffer,
337
- /* safeMargin= */ safeMargin, /* force= */ false);
339
+ /* safeMargin= */ safeMargin, /* force= */ force);
338
340
  }
339
341
  }
340
342
 
@@ -1113,7 +1115,8 @@ shaka.media.StreamingEngine = class {
1113
1115
 
1114
1116
  mediaState.segmentIterator =
1115
1117
  mediaState.stream.segmentIndex.getIteratorForTime(time);
1116
- const ref = mediaState.segmentIterator.next().value;
1118
+ const ref = mediaState.segmentIterator &&
1119
+ mediaState.segmentIterator.next().value;
1117
1120
  if (ref == null) {
1118
1121
  shaka.log.warning(logPrefix, 'cannot find segment', 'endTime:', time);
1119
1122
  }
@@ -1135,14 +1138,16 @@ shaka.media.StreamingEngine = class {
1135
1138
  if (inaccurateTolerance) {
1136
1139
  mediaState.segmentIterator =
1137
1140
  mediaState.stream.segmentIndex.getIteratorForTime(lookupTime);
1138
- ref = mediaState.segmentIterator.next().value;
1141
+ ref = mediaState.segmentIterator &&
1142
+ mediaState.segmentIterator.next().value;
1139
1143
  }
1140
1144
  if (!ref) {
1141
1145
  // If we can't find a valid segment with the drifted time, look for a
1142
1146
  // segment with the presentation time.
1143
1147
  mediaState.segmentIterator =
1144
1148
  mediaState.stream.segmentIndex.getIteratorForTime(presentationTime);
1145
- ref = mediaState.segmentIterator.next().value;
1149
+ ref = mediaState.segmentIterator &&
1150
+ mediaState.segmentIterator.next().value;
1146
1151
  }
1147
1152
  if (ref == null) {
1148
1153
  shaka.log.warning(logPrefix, 'cannot find segment',
@@ -1185,10 +1190,14 @@ shaka.media.StreamingEngine = class {
1185
1190
 
1186
1191
  mediaState.performingUpdate = true;
1187
1192
 
1188
- const initSourceBuffer = this.initSourceBuffer_(mediaState, reference);
1189
-
1190
- shaka.log.v2(logPrefix, 'fetching segment');
1191
1193
  try {
1194
+ await this.initSourceBuffer_(mediaState, reference);
1195
+ this.destroyer_.ensureNotDestroyed();
1196
+ if (this.fatalError_) {
1197
+ return;
1198
+ }
1199
+
1200
+ shaka.log.v2(logPrefix, 'fetching segment');
1192
1201
  const isMP4 = stream.mimeType == 'video/mp4' ||
1193
1202
  stream.mimeType == 'audio/mp4';
1194
1203
  const isReadableStreamSupported = window.ReadableStream;
@@ -1196,7 +1205,6 @@ shaka.media.StreamingEngine = class {
1196
1205
  if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4) {
1197
1206
  let remaining = new Uint8Array(0);
1198
1207
  const streamDataCallback = async (data) => {
1199
- await initSourceBuffer;
1200
1208
  this.destroyer_.ensureNotDestroyed();
1201
1209
  if (this.fatalError_) {
1202
1210
  return;
@@ -1234,7 +1242,7 @@ shaka.media.StreamingEngine = class {
1234
1242
  'ReadableStream is not supported by the browser.');
1235
1243
  }
1236
1244
  const fetchSegment = this.fetch_(mediaState, reference);
1237
- const results = await Promise.all([initSourceBuffer, fetchSegment]);
1245
+ const result = await fetchSegment;
1238
1246
  this.destroyer_.ensureNotDestroyed();
1239
1247
  if (this.fatalError_) {
1240
1248
  return;
@@ -1251,7 +1259,7 @@ shaka.media.StreamingEngine = class {
1251
1259
  return;
1252
1260
  }
1253
1261
  await this.append_(
1254
- mediaState, presentationTime, stream, reference, results[1]);
1262
+ mediaState, presentationTime, stream, reference, result);
1255
1263
  }
1256
1264
 
1257
1265
  this.destroyer_.ensureNotDestroyed();
@@ -1289,7 +1297,13 @@ shaka.media.StreamingEngine = class {
1289
1297
 
1290
1298
  mediaState.performingUpdate = false;
1291
1299
 
1292
- if (mediaState.type == ContentType.TEXT &&
1300
+ if (error.code == shaka.util.Error.Code.OPERATION_ABORTED) {
1301
+ // If the network slows down, abort the current fetch request and start
1302
+ // a new one, and ignore the error message.
1303
+ mediaState.performingUpdate = false;
1304
+ mediaState.updateTimer = null;
1305
+ this.scheduleUpdate_(mediaState, 0);
1306
+ } else if (mediaState.type == ContentType.TEXT &&
1293
1307
  this.config_.ignoreTextStreamFailures) {
1294
1308
  if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS) {
1295
1309
  shaka.log.warning(logPrefix,
@@ -1299,12 +1313,6 @@ shaka.media.StreamingEngine = class {
1299
1313
  'Text stream failed to parse. Proceeding without it.');
1300
1314
  }
1301
1315
  this.mediaStates_.delete(ContentType.TEXT);
1302
- } else if (error.code == shaka.util.Error.Code.OPERATION_ABORTED) {
1303
- // If the network slows down, abort the current fetch request and start
1304
- // a new one, and ignore the error message.
1305
- mediaState.performingUpdate = false;
1306
- mediaState.updateTimer = null;
1307
- this.scheduleUpdate_(mediaState, 0);
1308
1316
  } else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
1309
1317
  this.handleQuotaExceeded_(mediaState, error);
1310
1318
  } else if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
@@ -1654,7 +1662,8 @@ shaka.media.StreamingEngine = class {
1654
1662
 
1655
1663
  // Dispatch an event to notify the application about the emsg box.
1656
1664
  const eventName = shaka.Player.EventName.Emsg;
1657
- const event = new shaka.util.FakeEvent(eventName, {'detail': emsg});
1665
+ const data = (new Map()).set('detail', emsg);
1666
+ const event = new shaka.util.FakeEvent(eventName, data);
1658
1667
  this.playerInterface_.onEvent(event);
1659
1668
  }
1660
1669
  }
@@ -1738,7 +1747,7 @@ shaka.media.StreamingEngine = class {
1738
1747
  * @param {!shaka.media.StreamingEngine.MediaState_} mediaState
1739
1748
  * @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)}
1740
1749
  * reference
1741
- * @param {?function(BufferSource)=} streamDataCallback
1750
+ * @param {?function(BufferSource):!Promise=} streamDataCallback
1742
1751
  *
1743
1752
  * @return {!Promise.<BufferSource>}
1744
1753
  * @private
@@ -92,7 +92,7 @@ shaka.net.HttpFetchPlugin = class {
92
92
  * @param {!RequestInit} init
93
93
  * @param {shaka.net.HttpFetchPlugin.AbortStatus} abortStatus
94
94
  * @param {shaka.extern.ProgressUpdated} progressUpdated
95
- * @param {?function(BufferSource)} streamDataCallback
95
+ * @param {?function(BufferSource):!Promise} streamDataCallback
96
96
  * @return {!Promise<!shaka.extern.Response>}
97
97
  * @private
98
98
  */
@@ -140,7 +140,7 @@ shaka.net.HttpFetchPlugin = class {
140
140
  if (!readObj.done) {
141
141
  loaded += readObj.value.byteLength;
142
142
  if (streamDataCallback) {
143
- streamDataCallback(readObj.value);
143
+ await streamDataCallback(readObj.value);
144
144
  }
145
145
  }
146
146
 
@@ -196,7 +196,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
196
196
  *
197
197
  * @param {!Array.<string>} uris
198
198
  * @param {shaka.extern.RetryParameters} retryParams
199
- * @param {?function(BufferSource)=} streamDataCallback
199
+ * @param {?function(BufferSource):!Promise=} streamDataCallback
200
200
  * @return {shaka.extern.Request}
201
201
  * @export
202
202
  */
@@ -544,7 +544,8 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
544
544
  }
545
545
 
546
546
  if (error.severity == shaka.util.Error.Severity.RECOVERABLE) {
547
- const event = new shaka.util.FakeEvent('retry', {'error': error});
547
+ const data = (new Map()).set('error', error);
548
+ const event = new shaka.util.FakeEvent('retry', data);
548
549
  this.dispatchEvent(event);
549
550
 
550
551
  // Move to the next URI.
@@ -539,6 +539,10 @@ shaka.offline.Storage = class {
539
539
  shaka.util.Error.Code.STORAGE_LIMIT_REACHED);
540
540
  }
541
541
  } catch (e) {
542
+ // It is necessary to be able to catch the STORAGE_LIMIT_REACHED error
543
+ if (e instanceof shaka.util.Error) {
544
+ throw e;
545
+ }
542
546
  shaka.log.warning(
543
547
  'downloadSizeCallback has produced an unexpected error', e);
544
548
  throw new shaka.util.Error(