shaka-player 3.1.1 → 3.1.5

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 (151) hide show
  1. package/AUTHORS +7 -2
  2. package/CHANGELOG.md +199 -0
  3. package/CONTRIBUTORS +9 -3
  4. package/build/generateExterns.js +1 -1
  5. package/build/misspellings.txt +1 -0
  6. package/build/shakaBuildHelpers.py +6 -5
  7. package/build/subprocessWindowsPatch.py +103 -0
  8. package/demo/common/assets.js +1 -1
  9. package/dist/controls.css +2 -2
  10. package/dist/controls.css.map +1 -1
  11. package/dist/demo.compiled.debug.js +1 -1
  12. package/dist/demo.compiled.js +1 -1
  13. package/dist/deps.js +7 -7
  14. package/dist/locales.js +35 -33
  15. package/dist/receiver.compiled.debug.js +1 -1
  16. package/dist/receiver.compiled.js +1 -1
  17. package/dist/shaka-player.compiled.d.ts +171 -3
  18. package/dist/shaka-player.compiled.debug.d.ts +171 -3
  19. package/dist/shaka-player.compiled.debug.externs.js +179 -1
  20. package/dist/shaka-player.compiled.debug.js +365 -344
  21. package/dist/shaka-player.compiled.debug.map +3 -3
  22. package/dist/shaka-player.compiled.externs.js +179 -1
  23. package/dist/shaka-player.compiled.js +806 -798
  24. package/dist/shaka-player.compiled.map +3 -3
  25. package/dist/shaka-player.ui.d.ts +171 -3
  26. package/dist/shaka-player.ui.debug.d.ts +171 -3
  27. package/dist/shaka-player.ui.debug.externs.js +181 -3
  28. package/dist/shaka-player.ui.debug.js +458 -435
  29. package/dist/shaka-player.ui.debug.map +4 -4
  30. package/dist/shaka-player.ui.externs.js +181 -3
  31. package/dist/shaka-player.ui.js +927 -918
  32. package/dist/shaka-player.ui.map +4 -4
  33. package/docs/tutorials/fairplay.md +1 -1
  34. package/docs/tutorials/faq.md +14 -0
  35. package/docs/tutorials/index.json +2 -2
  36. package/docs/tutorials/upgrade-v3.md +38 -0
  37. package/docs/tutorials/upgrade.md +3 -3
  38. package/externs/ima.js +4 -0
  39. package/externs/shaka/ads.js +2 -0
  40. package/externs/shaka/player.js +6 -0
  41. package/externs/shaka/text.js +10 -0
  42. package/karma.conf.js +1 -0
  43. package/lib/ads/ad_manager.js +29 -11
  44. package/lib/ads/client_side_ad_manager.js +76 -86
  45. package/lib/ads/server_side_ad_manager.js +26 -11
  46. package/lib/cast/cast_proxy.js +3 -2
  47. package/lib/cast/cast_receiver.js +2 -1
  48. package/lib/cast/cast_sender.js +2 -2
  49. package/lib/cea/sei_processor.js +24 -17
  50. package/lib/dash/dash_parser.js +100 -5
  51. package/lib/dash/segment_list.js +1 -1
  52. package/lib/dash/segment_template.js +7 -4
  53. package/lib/hls/hls_parser.js +44 -17
  54. package/lib/media/closed_caption_parser.js +3 -1
  55. package/lib/media/content_workarounds.js +12 -0
  56. package/lib/media/drm_engine.js +20 -1
  57. package/lib/media/gap_jumping_controller.js +8 -4
  58. package/lib/media/media_source_engine.js +1 -2
  59. package/lib/media/playhead.js +1 -1
  60. package/lib/media/presentation_timeline.js +3 -1
  61. package/lib/media/segment_reference.js +8 -0
  62. package/lib/media/streaming_engine.js +28 -19
  63. package/lib/net/networking_engine.js +2 -1
  64. package/lib/player.js +108 -54
  65. package/lib/polyfill/encryption_scheme.js +2 -0
  66. package/lib/polyfill/fullscreen.js +2 -0
  67. package/lib/polyfill/mathround.js +2 -0
  68. package/lib/polyfill/media_capabilities.js +76 -22
  69. package/lib/polyfill/mediasource.js +8 -0
  70. package/lib/polyfill/orientation.js +3 -1
  71. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  72. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  73. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  74. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  75. package/lib/polyfill/pip_webkit.js +2 -0
  76. package/lib/polyfill/storage_estimate.js +2 -0
  77. package/lib/polyfill/video_play_promise.js +2 -0
  78. package/lib/polyfill/videoplaybackquality.js +2 -0
  79. package/lib/polyfill/vttcue.js +2 -0
  80. package/lib/text/cue.js +17 -0
  81. package/lib/text/mp4_ttml_parser.js +3 -0
  82. package/lib/text/text_engine.js +14 -2
  83. package/lib/text/ttml_text_parser.js +135 -87
  84. package/lib/text/ui_text_displayer.js +233 -83
  85. package/lib/text/vtt_text_parser.js +4 -2
  86. package/lib/util/fake_event.js +53 -7
  87. package/lib/util/mp4_box_parsers.js +1 -1
  88. package/lib/util/periods.js +111 -35
  89. package/lib/util/platform.js +13 -1
  90. package/lib/util/stream_utils.js +79 -10
  91. package/lib/util/xml_utils.js +19 -0
  92. package/package.json +2 -2
  93. package/test/cast/cast_proxy_unit.js +12 -6
  94. package/test/cast/cast_receiver_integration.js +1 -1
  95. package/test/cast/cast_utils_unit.js +1 -1
  96. package/test/cea/mp4_cea_parser_unit.js +26 -0
  97. package/test/dash/dash_parser_live_unit.js +1 -0
  98. package/test/dash/dash_parser_manifest_unit.js +220 -2
  99. package/test/dash/dash_parser_segment_template_unit.js +3 -3
  100. package/test/hls/hls_parser_unit.js +5 -1
  101. package/test/media/closed_caption_parser_unit.js +20 -0
  102. package/test/media/drm_engine_unit.js +56 -0
  103. package/test/media/playhead_unit.js +27 -1
  104. package/test/media/presentation_timeline_unit.js +19 -0
  105. package/test/media/streaming_engine_integration.js +2 -1
  106. package/test/media/streaming_engine_unit.js +148 -3
  107. package/test/player_integration.js +38 -0
  108. package/test/player_unit.js +101 -0
  109. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  110. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  111. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  112. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  113. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  114. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  115. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  116. package/test/test/assets/3675/dash_0.mpd +30 -0
  117. package/test/test/assets/3675/dash_1.mpd +30 -0
  118. package/test/test/assets/3675/dash_2.mpd +30 -0
  119. package/test/test/assets/3675/dash_3.mpd +30 -0
  120. package/test/test/assets/3675/dash_4.mpd +30 -0
  121. package/test/test/assets/3675/dash_5.mpd +26 -0
  122. package/test/test/assets/3675/init-stream0.m4s +0 -0
  123. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  124. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  125. package/test/test/util/fake_ad_manager.js +4 -1
  126. package/test/test/util/simple_fakes.js +5 -3
  127. package/test/test/util/ttml_utils.js +81 -0
  128. package/test/text/mp4_ttml_parser_unit.js +11 -18
  129. package/test/text/ttml_text_parser_unit.js +542 -220
  130. package/test/text/ui_text_displayer_unit.js +95 -18
  131. package/test/text/vtt_text_parser_unit.js +17 -1
  132. package/test/ui/localization_unit.js +15 -14
  133. package/test/ui/text_displayer_layout_unit.js +52 -1
  134. package/test/util/mp4_box_parsers_unit.js +28 -0
  135. package/test/util/periods_unit.js +190 -2
  136. package/test/util/stream_utils_unit.js +59 -2
  137. package/ui/airplay_button.js +5 -0
  138. package/ui/big_play_button.js +10 -0
  139. package/ui/cast_button.js +2 -3
  140. package/ui/controls.js +8 -6
  141. package/ui/less/buttons.less +0 -4
  142. package/ui/less/containers.less +10 -5
  143. package/ui/locales/en.json +1 -0
  144. package/ui/locales/ru.json +6 -6
  145. package/ui/locales/source.json +4 -0
  146. package/ui/localization.js +28 -51
  147. package/ui/pip_button.js +2 -3
  148. package/ui/play_button.js +10 -10
  149. package/ui/small_play_button.js +12 -0
  150. package/ui/text_selection.js +1 -0
  151. package/docs/tutorials/upgrade-v2-4.md +0 -481
@@ -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
  }
@@ -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
 
@@ -621,7 +623,7 @@ shaka.media.StreamingEngine = class {
621
623
 
622
624
  // Don't clear the buffer unless something is buffered. This extra
623
625
  // check prevents extra, useless calls to clear the buffer.
624
- if (somethingBuffered) {
626
+ if (somethingBuffered || mediaState.performingUpdate) {
625
627
  this.forceClearBuffer_(mediaState);
626
628
  streamCleared = true;
627
629
  }
@@ -975,7 +977,10 @@ shaka.media.StreamingEngine = class {
975
977
  unscaledBufferingGoal * this.bufferingGoalScale_;
976
978
 
977
979
  // Check if we've buffered to the end of the presentation.
978
- if (timeNeeded >= this.manifest_.presentationTimeline.getDuration()) {
980
+ const timeUntilEnd =
981
+ this.manifest_.presentationTimeline.getDuration() - timeNeeded;
982
+ const oneMicrosecond = 1e-6;
983
+ if (timeUntilEnd < oneMicrosecond) {
979
984
  // We shouldn't rebuffer if the playhead is close to the end of the
980
985
  // presentation.
981
986
  shaka.log.debug(logPrefix, 'buffered to end of presentation');
@@ -1188,10 +1193,14 @@ shaka.media.StreamingEngine = class {
1188
1193
 
1189
1194
  mediaState.performingUpdate = true;
1190
1195
 
1191
- const initSourceBuffer = this.initSourceBuffer_(mediaState, reference);
1192
-
1193
- shaka.log.v2(logPrefix, 'fetching segment');
1194
1196
  try {
1197
+ await this.initSourceBuffer_(mediaState, reference);
1198
+ this.destroyer_.ensureNotDestroyed();
1199
+ if (this.fatalError_) {
1200
+ return;
1201
+ }
1202
+
1203
+ shaka.log.v2(logPrefix, 'fetching segment');
1195
1204
  const isMP4 = stream.mimeType == 'video/mp4' ||
1196
1205
  stream.mimeType == 'audio/mp4';
1197
1206
  const isReadableStreamSupported = window.ReadableStream;
@@ -1199,7 +1208,6 @@ shaka.media.StreamingEngine = class {
1199
1208
  if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4) {
1200
1209
  let remaining = new Uint8Array(0);
1201
1210
  const streamDataCallback = async (data) => {
1202
- await initSourceBuffer;
1203
1211
  this.destroyer_.ensureNotDestroyed();
1204
1212
  if (this.fatalError_) {
1205
1213
  return;
@@ -1237,7 +1245,7 @@ shaka.media.StreamingEngine = class {
1237
1245
  'ReadableStream is not supported by the browser.');
1238
1246
  }
1239
1247
  const fetchSegment = this.fetch_(mediaState, reference);
1240
- const results = await Promise.all([initSourceBuffer, fetchSegment]);
1248
+ const result = await fetchSegment;
1241
1249
  this.destroyer_.ensureNotDestroyed();
1242
1250
  if (this.fatalError_) {
1243
1251
  return;
@@ -1254,7 +1262,7 @@ shaka.media.StreamingEngine = class {
1254
1262
  return;
1255
1263
  }
1256
1264
  await this.append_(
1257
- mediaState, presentationTime, stream, reference, results[1]);
1265
+ mediaState, presentationTime, stream, reference, result);
1258
1266
  }
1259
1267
 
1260
1268
  this.destroyer_.ensureNotDestroyed();
@@ -1292,7 +1300,13 @@ shaka.media.StreamingEngine = class {
1292
1300
 
1293
1301
  mediaState.performingUpdate = false;
1294
1302
 
1295
- if (mediaState.type == ContentType.TEXT &&
1303
+ if (error.code == shaka.util.Error.Code.OPERATION_ABORTED) {
1304
+ // If the network slows down, abort the current fetch request and start
1305
+ // a new one, and ignore the error message.
1306
+ mediaState.performingUpdate = false;
1307
+ mediaState.updateTimer = null;
1308
+ this.scheduleUpdate_(mediaState, 0);
1309
+ } else if (mediaState.type == ContentType.TEXT &&
1296
1310
  this.config_.ignoreTextStreamFailures) {
1297
1311
  if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS) {
1298
1312
  shaka.log.warning(logPrefix,
@@ -1302,12 +1316,6 @@ shaka.media.StreamingEngine = class {
1302
1316
  'Text stream failed to parse. Proceeding without it.');
1303
1317
  }
1304
1318
  this.mediaStates_.delete(ContentType.TEXT);
1305
- } else if (error.code == shaka.util.Error.Code.OPERATION_ABORTED) {
1306
- // If the network slows down, abort the current fetch request and start
1307
- // a new one, and ignore the error message.
1308
- mediaState.performingUpdate = false;
1309
- mediaState.updateTimer = null;
1310
- this.scheduleUpdate_(mediaState, 0);
1311
1319
  } else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
1312
1320
  this.handleQuotaExceeded_(mediaState, error);
1313
1321
  } else if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
@@ -1657,7 +1665,8 @@ shaka.media.StreamingEngine = class {
1657
1665
 
1658
1666
  // Dispatch an event to notify the application about the emsg box.
1659
1667
  const eventName = shaka.Player.EventName.Emsg;
1660
- const event = new shaka.util.FakeEvent(eventName, {'detail': emsg});
1668
+ const data = (new Map()).set('detail', emsg);
1669
+ const event = new shaka.util.FakeEvent(eventName, data);
1661
1670
  this.playerInterface_.onEvent(event);
1662
1671
  }
1663
1672
  }
@@ -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.
package/lib/player.js CHANGED
@@ -596,7 +596,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
596
596
  enterNode: (node, has, wants) => {
597
597
  this.dispatchEvent(this.makeEvent_(
598
598
  /* name= */ shaka.Player.EventName.OnStateChange,
599
- /* data= */ {'state': node.name}));
599
+ /* data= */ (new Map()).set('state', node.name)));
600
600
 
601
601
  const action = actions.get(node);
602
602
  return action(has, wants);
@@ -627,7 +627,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
627
627
  onIdle: (node) => {
628
628
  this.dispatchEvent(this.makeEvent_(
629
629
  /* name= */ shaka.Player.EventName.OnStateIdle,
630
- /* data= */ {'state': node.name}));
630
+ /* data= */ (new Map()).set('state', node.name)));
631
631
  },
632
632
  };
633
633
 
@@ -647,7 +647,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
647
647
 
648
648
  /**
649
649
  * @param {!shaka.Player.EventName} name
650
- * @param {Object=} data
650
+ * @param {Map.<string, Object>=} data
651
651
  * @return {!shaka.util.FakeEvent}
652
652
  * @private
653
653
  */
@@ -1155,6 +1155,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1155
1155
  'mpg': 'video/mpeg',
1156
1156
  'mpeg': 'video/mpeg',
1157
1157
  'm3u8': 'application/x-mpegurl',
1158
+ 'mpd': 'application/dash+xml',
1158
1159
  'mp3': 'audio/mpeg',
1159
1160
  'aac': 'audio/aac',
1160
1161
  'flac': 'audio/flac',
@@ -1285,6 +1286,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1285
1286
  has.mediaElement = null;
1286
1287
  }
1287
1288
 
1289
+ if (this.adManager_) {
1290
+ // The ad manager is specific to the video, so detach it too.
1291
+ this.adManager_.release();
1292
+ }
1293
+
1288
1294
  // Clear our cached copy of the media element.
1289
1295
  this.video_ = null;
1290
1296
 
@@ -1844,6 +1850,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1844
1850
 
1845
1851
  this.currentTextLanguage_ = this.config_.preferredTextLanguage;
1846
1852
  this.currentTextRole_ = this.config_.preferredTextRole;
1853
+ this.currentTextForced_ = this.config_.preferForcedSubs;
1847
1854
 
1848
1855
  shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
1849
1856
  this.config_.playRangeStart,
@@ -1883,36 +1890,55 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1883
1890
  this.dispatchEvent(this.makeEvent_(shaka.Player.EventName.Streaming));
1884
1891
 
1885
1892
  // Pick the initial streams to play.
1886
- const initialVariant = this.chooseVariant_();
1887
- goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
1888
- this.addVariantToSwitchHistory_(
1889
- initialVariant, /* fromAdaptation= */ true);
1890
- this.streamingEngine_.switchVariant(
1891
- initialVariant, /* clearBuffer= */ false, /* safeMargin= */ 0);
1893
+ // however, we would skip switch to initial variant
1894
+ // if user already pick variant track (via selectVariantTrack api)
1895
+ let initialVariant = null;
1896
+ const activeVariantTrack = this.getVariantTracks().find((t) => t.active);
1897
+
1898
+ if (!activeVariantTrack) {
1899
+ initialVariant = this.chooseVariant_();
1900
+ goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
1901
+ this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
1902
+ /* clearBuffer= */ false, /* safeMargin= */ 0);
1903
+
1904
+ // Now that we have initial streams, we may adjust the start time to align
1905
+ // to a segment boundary.
1906
+ if (this.config_.streaming.startAtSegmentBoundary) {
1907
+ const startTime = this.playhead_.getTime();
1908
+ const adjustedTime =
1909
+ await this.adjustStartTime_(initialVariant, startTime);
1910
+
1911
+ this.playhead_.setStartTime(adjustedTime);
1912
+ }
1892
1913
 
1893
- // Decide if text should be shown automatically.
1894
- const initialTextStream = this.chooseTextStream_();
1895
- if (initialTextStream) {
1896
- this.addTextStreamToSwitchHistory_(
1897
- initialTextStream, /* fromAdaptation= */ true);
1914
+ // Since the first streams just became active, send an adaptation event.
1915
+ this.onAdaptation_();
1898
1916
  }
1899
1917
 
1900
- this.setInitialTextState_(initialVariant, initialTextStream);
1901
- // Don't initialize with a text stream unless we should be streaming text.
1902
- if (initialTextStream && this.shouldStreamText_()) {
1903
- this.streamingEngine_.switchTextStream(initialTextStream);
1904
- }
1918
+ // Decide if text should be shown automatically.
1919
+ // similar to video/audio track, we would skip switch initial text track
1920
+ // if user already pick text track (via selectTextTrack api)
1921
+ const activeTextTrack = this.getTextTracks().find((t) => t.active);
1905
1922
 
1906
- // Now that we have initial streams, we may adjust the start time to align
1907
- // to a segment boundary.
1908
- if (this.config_.streaming.startAtSegmentBoundary) {
1909
- const startTime = this.playhead_.getTime();
1910
- const adjustedTime =
1911
- await this.adjustStartTime_(initialVariant, startTime);
1923
+ if (!activeTextTrack) {
1924
+ const initialTextStream = this.chooseTextStream_();
1912
1925
 
1913
- this.playhead_.setStartTime(adjustedTime);
1926
+ if (initialTextStream) {
1927
+ this.addTextStreamToSwitchHistory_(
1928
+ initialTextStream, /* fromAdaptation= */ true);
1929
+ }
1930
+
1931
+ if (initialVariant) {
1932
+ this.setInitialTextState_(initialVariant, initialTextStream);
1933
+ }
1934
+
1935
+ // Don't initialize with a text stream unless we should be streaming text.
1936
+ if (initialTextStream && this.shouldStreamText_()) {
1937
+ this.streamingEngine_.switchTextStream(initialTextStream);
1938
+ }
1914
1939
  }
1915
1940
 
1941
+
1916
1942
  // Start streaming content. This will start the flow of content down to
1917
1943
  // media source.
1918
1944
  await this.streamingEngine_.start();
@@ -1926,8 +1952,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1926
1952
  this.filterManifestByCurrentVariant_();
1927
1953
  // Dispatch a 'trackschanged' event now that all initial filtering is done.
1928
1954
  this.onTracksChanged_();
1929
- // Since the first streams just became active, send an adaptation event.
1930
- this.onAdaptation_();
1931
1955
 
1932
1956
  // Now that we've filtered out variants that aren't compatible with the
1933
1957
  // active one, update abr manager with filtered variants.
@@ -2046,6 +2070,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2046
2070
  decodingInfos: [],
2047
2071
  };
2048
2072
 
2073
+ this.drmEngine_.setSrcEquals(/* srcEquals= */ true);
2049
2074
  await this.drmEngine_.initForPlayback(
2050
2075
  [variant], /* offlineSessionIds= */ []);
2051
2076
  await this.drmEngine_.attach(has.mediaElement);
@@ -2391,12 +2416,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2391
2416
  goog.asserts.assert(!endTime || startTime <= endTime,
2392
2417
  'Metadata start time should be less or equal to the end time!');
2393
2418
  const eventName = shaka.Player.EventName.Metadata;
2394
- const data = {
2395
- startTime: startTime,
2396
- endTime: endTime,
2397
- metadataType: metadataType,
2398
- payload: payload,
2399
- };
2419
+ const data = new Map()
2420
+ .set('startTime', startTime)
2421
+ .set('endTime', endTime)
2422
+ .set('metadataType', metadataType)
2423
+ .set('payload', payload);
2400
2424
  this.dispatchEvent(this.makeEvent_(eventName, data));
2401
2425
  }
2402
2426
 
@@ -2458,7 +2482,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2458
2482
  this.abrManager_.segmentDownloaded(deltaTimeMs, bytesDownloaded);
2459
2483
  }
2460
2484
  };
2461
-
2462
2485
  return new shaka.net.NetworkingEngine(onProgressUpdated_);
2463
2486
  }
2464
2487
 
@@ -3217,6 +3240,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3217
3240
 
3218
3241
  const tracks = [];
3219
3242
 
3243
+ let activeTracks = 0;
3244
+
3220
3245
  // Convert each variant to a track.
3221
3246
  for (const variant of this.manifest_.variants) {
3222
3247
  if (!shaka.util.StreamUtils.isPlayable(variant)) {
@@ -3225,10 +3250,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3225
3250
 
3226
3251
  const track = shaka.util.StreamUtils.variantToTrack(variant);
3227
3252
  track.active = variant == currentVariant;
3253
+ if (!track.active && activeTracks != 1 && currentVariant != null &&
3254
+ variant.video == currentVariant.video &&
3255
+ variant.audio == currentVariant.audio) {
3256
+ track.active = true;
3257
+ }
3258
+
3259
+ if (track.active) {
3260
+ activeTracks++;
3261
+ }
3228
3262
 
3229
3263
  tracks.push(track);
3230
3264
  }
3231
3265
 
3266
+ goog.asserts.assert(activeTracks <= 1,
3267
+ 'It should only have one active track');
3268
+
3232
3269
  return tracks;
3233
3270
  } else if (this.video_ && this.video_.audioTracks) {
3234
3271
  // Safari's native HLS always shows a single element in videoTracks.
@@ -3333,9 +3370,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3333
3370
  const rows = parseInt(match[2], 10);
3334
3371
  const width = fullImageWidth / columns;
3335
3372
  const height = fullImageHeight / rows;
3373
+ const totalImages = columns * rows;
3374
+ const segmentDuration = reference.trueEndTime - reference.startTime;
3375
+ const thumbnailDuration = segmentDuration / totalImages;
3376
+ let thumbnailTime = reference.startTime;
3336
3377
  let positionX = 0;
3337
3378
  let positionY = 0;
3338
- const totalImages = columns * rows;
3339
3379
  // If the number of images in the segment is greater than 1, we have to
3340
3380
  // find the correct image. For that we will return to the app the
3341
3381
  // coordinates of the position of the correct image.
@@ -3343,21 +3383,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3343
3383
  // Note: The time between images within the segment is always
3344
3384
  // equidistant.
3345
3385
  //
3346
- // Eg: Total images 5, tileLayout 5x1, segmentTime 5, thumbnailTime 2
3386
+ // Eg: Total images 5, tileLayout 5x1, segmentDuration 5, thumbnailTime 2
3347
3387
  // positionX = 0.4 * fullImageWidth
3348
3388
  // positionY = 0
3349
3389
  if (totalImages > 1) {
3350
- const thumbnailTime = time - reference.startTime;
3351
- const segmentTime = reference.endTime - reference.startTime;
3352
3390
  const thumbnailPosition =
3353
- Math.floor(thumbnailTime * totalImages / segmentTime);
3354
- positionX = (thumbnailPosition % columns) / columns * fullImageWidth;
3355
- positionY = (thumbnailPosition % rows) / rows * fullImageHeight;
3391
+ Math.floor((time - reference.startTime) / thumbnailDuration);
3392
+ thumbnailTime = reference.startTime +
3393
+ (thumbnailPosition * thumbnailDuration);
3394
+ positionX = (thumbnailPosition % columns) * width;
3395
+ positionY = Math.floor(thumbnailPosition / columns) * height;
3356
3396
  }
3357
3397
  return {
3358
3398
  height: height,
3359
3399
  positionX: positionX,
3360
3400
  positionY: positionY,
3401
+ startTime: thumbnailTime,
3402
+ duration: thumbnailDuration,
3361
3403
  uris: reference.getUris(),
3362
3404
  width: width,
3363
3405
  };
@@ -4773,7 +4815,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4773
4815
  // Surface the buffering event so that the app knows if/when we are
4774
4816
  // buffering.
4775
4817
  const eventName = shaka.Player.EventName.Buffering;
4776
- this.dispatchEvent(this.makeEvent_(eventName, {'buffering': isBuffering}));
4818
+ const data = (new Map()).set('buffering', isBuffering);
4819
+ this.dispatchEvent(this.makeEvent_(eventName, data));
4777
4820
  }
4778
4821
 
4779
4822
  /**
@@ -4958,6 +5001,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4958
5001
  const currentVariant = this.streamingEngine_.getCurrentVariant();
4959
5002
  if (variant == currentVariant) {
4960
5003
  shaka.log.debug('Variant already selected.');
5004
+ // If you want to clear the buffer, we force to reselect the same variant
5005
+ if (clearBuffer) {
5006
+ this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin,
5007
+ /* force= */ true);
5008
+ }
4961
5009
  return;
4962
5010
  }
4963
5011
 
@@ -4986,6 +5034,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4986
5034
  // This will reset the "enabled" of other tracks to false.
4987
5035
  track.enabled = true;
4988
5036
 
5037
+ // AirPlay does not reset the "enabled" of other tracks to false, so
5038
+ // it must be changed by hand.
5039
+ if (track.id !== currentTrack.id) {
5040
+ currentTrack.enabled = false;
5041
+ }
5042
+
4989
5043
  const oldTrack =
4990
5044
  shaka.util.StreamUtils.html5AudioTrackToTrack(currentTrack);
4991
5045
  const newTrack =
@@ -5144,10 +5198,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5144
5198
  onVariantChanged_(from, to) {
5145
5199
  // Delay the 'variantchanged' event so StreamingEngine has time to absorb
5146
5200
  // the changes before the user tries to query it.
5147
- const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, {
5148
- oldTrack: from,
5149
- newTrack: to,
5150
- });
5201
+ const data = new Map()
5202
+ .set('oldTrack', from)
5203
+ .set('newTrack', to);
5204
+ const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, data);
5151
5205
  this.delayDispatchEvent_(event);
5152
5206
  }
5153
5207
 
@@ -5170,10 +5224,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5170
5224
 
5171
5225
  /** @private */
5172
5226
  onAbrStatusChanged_() {
5173
- const event = this.makeEvent_(shaka.Player.EventName.AbrStatusChanged, {
5174
- newStatus: this.config_.abr.enabled,
5175
- });
5176
- this.delayDispatchEvent_(event);
5227
+ const data = (new Map()).set('newStatus', this.config_.abr.enabled);
5228
+ this.delayDispatchEvent_(this.makeEvent_(
5229
+ shaka.Player.EventName.AbrStatusChanged, data));
5177
5230
  }
5178
5231
 
5179
5232
  /**
@@ -5190,7 +5243,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5190
5243
  }
5191
5244
 
5192
5245
  const eventName = shaka.Player.EventName.Error;
5193
- const event = this.makeEvent_(eventName, {'detail': error});
5246
+ const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
5194
5247
  this.dispatchEvent(event);
5195
5248
  if (event.defaultPrevented) {
5196
5249
  error.handled = true;
@@ -5219,7 +5272,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5219
5272
  eventElement: region.eventElement,
5220
5273
  };
5221
5274
 
5222
- this.dispatchEvent(this.makeEvent_(eventName, {detail: clone}));
5275
+ const data = (new Map()).set('detail', clone);
5276
+ this.dispatchEvent(this.makeEvent_(eventName, data));
5223
5277
  }
5224
5278
 
5225
5279
  /**
@@ -6048,7 +6102,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6048
6102
  * @define {string} A version number taken from git at compile time.
6049
6103
  * @export
6050
6104
  */
6051
- shaka.Player.version = 'v3.1.1-uncompiled';
6105
+ shaka.Player.version = 'v3.1.5-uncompiled';
6052
6106
 
6053
6107
  // Initialize the deprecation system using the version string we just set
6054
6108
  // on the player.
@@ -13,12 +13,14 @@ goog.require('shaka.polyfill');
13
13
  * @see https://wicg.github.io/encrypted-media-encryption-scheme/
14
14
  * @see https://github.com/w3c/encrypted-media/pull/457
15
15
  * @see https://github.com/google/eme-encryption-scheme-polyfill
16
+ * @export
16
17
  */
17
18
  shaka.polyfill.EncryptionScheme = class {
18
19
  /**
19
20
  * Install the polyfill if needed.
20
21
  *
21
22
  * @suppress {missingRequire}
23
+ * @export
22
24
  */
23
25
  static install() {
24
26
  EncryptionSchemePolyfills.install();
@@ -14,10 +14,12 @@ goog.require('shaka.polyfill');
14
14
  * Many browsers have prefixed fullscreen methods on Element and document.
15
15
  * See {@link https://mzl.la/2K0xcHo Using fullscreen mode} on MDN for more
16
16
  * information.
17
+ * @export
17
18
  */
18
19
  shaka.polyfill.Fullscreen = class {
19
20
  /**
20
21
  * Install the polyfill if needed.
22
+ * @export
21
23
  */
22
24
  static install() {
23
25
  if (!window.Document) {
@@ -11,10 +11,12 @@ goog.require('shaka.polyfill');
11
11
  /**
12
12
  * @summary A polyfill to patch math round bug on some browsers.
13
13
  * @see https://stackoverflow.com/q/12830742
14
+ * @export
14
15
  */
15
16
  shaka.polyfill.MathRound = class {
16
17
  /**
17
18
  * Install the polyfill if needed.
19
+ * @export
18
20
  */
19
21
  static install() {
20
22
  shaka.log.debug('mathRound.install');