shaka-player 4.7.12 → 4.7.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/AUTHORS +1 -0
  2. package/CONTRIBUTORS +1 -0
  3. package/dist/deps.js +3 -3
  4. package/dist/locales.js +34 -34
  5. package/dist/shaka-player.compiled.d.ts +3 -2
  6. package/dist/shaka-player.compiled.debug.d.ts +3 -2
  7. package/dist/shaka-player.compiled.debug.externs.js +4 -0
  8. package/dist/shaka-player.compiled.debug.js +265 -257
  9. package/dist/shaka-player.compiled.debug.map +3 -3
  10. package/dist/shaka-player.compiled.externs.js +4 -0
  11. package/dist/shaka-player.compiled.js +1150 -1140
  12. package/dist/shaka-player.compiled.map +4 -4
  13. package/dist/shaka-player.ui.d.ts +3 -2
  14. package/dist/shaka-player.ui.debug.d.ts +3 -2
  15. package/dist/shaka-player.ui.debug.externs.js +4 -0
  16. package/dist/shaka-player.ui.debug.js +394 -386
  17. package/dist/shaka-player.ui.debug.map +3 -3
  18. package/dist/shaka-player.ui.externs.js +4 -0
  19. package/dist/shaka-player.ui.js +1306 -1296
  20. package/dist/shaka-player.ui.map +4 -4
  21. package/externs/shaka/manifest.js +11 -1
  22. package/externs/shaka/offline.js +7 -1
  23. package/lib/ads/client_side_ad_manager.js +4 -2
  24. package/lib/dash/dash_parser.js +10 -3
  25. package/lib/dash/segment_base.js +8 -1
  26. package/lib/dash/segment_list.js +24 -18
  27. package/lib/dash/segment_template.js +45 -3
  28. package/lib/hls/hls_parser.js +18 -1
  29. package/lib/media/drm_engine.js +17 -3
  30. package/lib/media/media_source_engine.js +87 -27
  31. package/lib/media/segment_index.js +31 -20
  32. package/lib/media/segment_reference.js +12 -0
  33. package/lib/media/streaming_engine.js +10 -28
  34. package/lib/mss/mss_parser.js +4 -0
  35. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  36. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  37. package/lib/offline/manifest_converter.js +10 -3
  38. package/lib/offline/storage.js +2 -0
  39. package/lib/player.js +57 -17
  40. package/lib/text/ui_text_displayer.js +10 -1
  41. package/lib/util/mime_utils.js +10 -5
  42. package/lib/util/periods.js +5 -37
  43. package/lib/util/platform.js +31 -9
  44. package/lib/util/stream_utils.js +340 -273
  45. package/package.json +1 -1
  46. package/ui/resolution_selection.js +1 -0
@@ -363,26 +363,27 @@ shaka.media.SegmentIndex = class {
363
363
 
364
364
  // Adjust the last SegmentReference.
365
365
  const lastReference = this.references[this.references.length - 1];
366
- this.references[this.references.length - 1] =
367
- new shaka.media.SegmentReference(
368
- lastReference.startTime,
369
- /* endTime= */ windowEnd,
370
- lastReference.getUrisInner,
371
- lastReference.startByte,
372
- lastReference.endByte,
373
- lastReference.initSegmentReference,
374
- lastReference.timestampOffset,
375
- lastReference.appendWindowStart,
376
- lastReference.appendWindowEnd,
377
- lastReference.partialReferences,
378
- lastReference.tilesLayout,
379
- lastReference.tileDuration,
380
- lastReference.syncTime,
381
- lastReference.status,
382
- lastReference.aes128Key,
383
- );
384
- this.references[this.references.length - 1].discontinuitySequence =
385
- lastReference.discontinuitySequence;
366
+ const newReference = new shaka.media.SegmentReference(
367
+ lastReference.startTime,
368
+ /* endTime= */ windowEnd,
369
+ lastReference.getUrisInner,
370
+ lastReference.startByte,
371
+ lastReference.endByte,
372
+ lastReference.initSegmentReference,
373
+ lastReference.timestampOffset,
374
+ lastReference.appendWindowStart,
375
+ lastReference.appendWindowEnd,
376
+ lastReference.partialReferences,
377
+ lastReference.tilesLayout,
378
+ lastReference.tileDuration,
379
+ lastReference.syncTime,
380
+ lastReference.status,
381
+ lastReference.aes128Key,
382
+ );
383
+ newReference.mimeType = lastReference.mimeType;
384
+ newReference.codecs = lastReference.codecs;
385
+ newReference.discontinuitySequence = lastReference.discontinuitySequence;
386
+ this.references[this.references.length - 1] = newReference;
386
387
  }
387
388
 
388
389
 
@@ -693,6 +694,16 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
693
694
  this.indexes_ = [];
694
695
  }
695
696
 
697
+ /**
698
+ * @override
699
+ * @export
700
+ */
701
+ forEachTopLevelReference(fn) {
702
+ for (const index of this.indexes_) {
703
+ index.forEachTopLevelReference(fn);
704
+ }
705
+ }
706
+
696
707
  /**
697
708
  * @override
698
709
  * @export
@@ -598,6 +598,18 @@ shaka.media.SegmentReference = class {
598
598
  this.offset(offset);
599
599
  }
600
600
  }
601
+
602
+ /**
603
+ * Updates the init segment reference and propagates the update to all partial
604
+ * references.
605
+ * @param {shaka.media.InitSegmentReference} initSegmentReference
606
+ */
607
+ updateInitSegmentReference(initSegmentReference) {
608
+ this.initSegmentReference = initSegmentReference;
609
+ for (const partialReference of this.partialReferences) {
610
+ partialReference.updateInitSegmentReference(initSegmentReference);
611
+ }
612
+ }
601
613
  };
602
614
 
603
615
 
@@ -881,7 +881,6 @@ shaka.media.StreamingEngine = class {
881
881
  // The playhead might be seeking on startup, if a start time is set, so
882
882
  // start "seeked" as true.
883
883
  seeked: true,
884
- needsResync: false,
885
884
  recovering: false,
886
885
  hasError: false,
887
886
  operation: null,
@@ -1248,11 +1247,6 @@ shaka.media.StreamingEngine = class {
1248
1247
  shaka.log.v1(
1249
1248
  logPrefix, 'looking up segment from new stream endTime:', time);
1250
1249
 
1251
- // Using a new iterator means we need to resync the stream in sequence
1252
- // mode. The buffered range might not align perfectly with the last
1253
- // segment end time, so we may end up repeating a segment. Resyncing
1254
- // makes this safe to do.
1255
- mediaState.needsResync = true;
1256
1250
  mediaState.segmentIterator =
1257
1251
  mediaState.stream.segmentIndex.getIteratorForTime(time);
1258
1252
  const ref = mediaState.segmentIterator &&
@@ -1730,7 +1724,7 @@ shaka.media.StreamingEngine = class {
1730
1724
  const isResetMediaSourceNecessary =
1731
1725
  mediaState.lastCodecs && mediaState.lastMimeType &&
1732
1726
  this.playerInterface_.mediaSourceEngine.isResetMediaSourceNecessary(
1733
- mediaState.type, mediaState.stream);
1727
+ mediaState.type, mediaState.stream, mimeType, codecs);
1734
1728
  if (isResetMediaSourceNecessary) {
1735
1729
  let otherState = null;
1736
1730
  if (mediaState.type === ContentType.VIDEO) {
@@ -1739,9 +1733,16 @@ shaka.media.StreamingEngine = class {
1739
1733
  otherState = this.mediaStates_.get(ContentType.VIDEO);
1740
1734
  }
1741
1735
  if (otherState) {
1736
+ // First, abort all operations in progress on the other stream.
1737
+ await this.abortOperations_(otherState).catch(() => {});
1738
+ // Then clear our cache of the last init segment, since MSE will be
1739
+ // reloaded and no init segment will be there post-reload.
1742
1740
  otherState.lastInitSegmentReference = null;
1741
+ // Now force the existing buffer to be cleared. It is not necessary
1742
+ // to perform the MSE clear operation, but this has the side-effect
1743
+ // that our state for that stream will then match MSE's post-reload
1744
+ // state.
1743
1745
  this.forceClearBuffer_(otherState);
1744
- this.abortOperations_(otherState).catch(() => {});
1745
1746
  }
1746
1747
  }
1747
1748
 
@@ -1772,19 +1773,6 @@ shaka.media.StreamingEngine = class {
1772
1773
  appendWindowEnd, ignoreTimestampOffset,
1773
1774
  reference.mimeType || mediaState.stream.mimeType,
1774
1775
  reference.codecs || mediaState.stream.codecs, streamsByType);
1775
- if (isResetMediaSourceNecessary) {
1776
- let otherState = null;
1777
- if (mediaState.type === ContentType.VIDEO) {
1778
- otherState = this.mediaStates_.get(ContentType.AUDIO);
1779
- } else if (mediaState.type === ContentType.AUDIO) {
1780
- otherState = this.mediaStates_.get(ContentType.VIDEO);
1781
- }
1782
- if (otherState) {
1783
- otherState.waitingToClearBuffer = false;
1784
- otherState.performingUpdate = false;
1785
- this.forceClearBuffer_(otherState);
1786
- }
1787
- }
1788
1776
  } catch (error) {
1789
1777
  mediaState.lastAppendWindowStart = null;
1790
1778
  mediaState.lastAppendWindowEnd = null;
@@ -1879,9 +1867,7 @@ shaka.media.StreamingEngine = class {
1879
1867
  // Across discontinuity bounds, we should resync timestamps for
1880
1868
  // sequence mode playbacks. The next segment appended should
1881
1869
  // land at its theoretical timestamp from the segment index.
1882
- if (reference.discontinuitySequence != lastDiscontinuitySequence ||
1883
- mediaState.needsResync) {
1884
- mediaState.needsResync = false;
1870
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1885
1871
  operations.push(this.playerInterface_.mediaSourceEngine.resync(
1886
1872
  mediaState.type, reference.startTime));
1887
1873
  }
@@ -2591,7 +2577,6 @@ shaka.media.StreamingEngine.PlayerInterface;
2591
2577
  * clearBufferSafeMargin: number,
2592
2578
  * clearingBuffer: boolean,
2593
2579
  * seeked: boolean,
2594
- * needsResync: boolean,
2595
2580
  * adaptation: boolean,
2596
2581
  * recovering: boolean,
2597
2582
  * hasError: boolean,
@@ -2646,9 +2631,6 @@ shaka.media.StreamingEngine.PlayerInterface;
2646
2631
  * True indicates that the presentation just seeked.
2647
2632
  * @property {boolean} adaptation
2648
2633
  * True indicates that the presentation just automatically switched variants.
2649
- * @property {boolean} needsResync
2650
- * True indicates that the stream needs to be resynced in sequence mode,
2651
- * regardless of discontinuity sequence.
2652
2634
  * @property {boolean} recovering
2653
2635
  * True indicates that the last segment was not appended because it could not
2654
2636
  * fit in the buffer.
@@ -19,6 +19,7 @@ goog.require('shaka.net.NetworkingEngine');
19
19
  goog.require('shaka.util.Error');
20
20
  goog.require('shaka.util.LanguageUtils');
21
21
  goog.require('shaka.util.ManifestParserUtils');
22
+ goog.require('shaka.util.MimeUtils');
22
23
  goog.require('shaka.util.Mp4Generator');
23
24
  goog.require('shaka.util.OperationManager');
24
25
  goog.require('shaka.util.Timer');
@@ -556,6 +557,7 @@ shaka.mss.MssParser = class {
556
557
  accessibilityPurpose: null,
557
558
  external: false,
558
559
  fastSwitching: false,
560
+ fullMimeTypes: new Set(),
559
561
  };
560
562
 
561
563
  // This is specifically for text tracks.
@@ -634,6 +636,8 @@ shaka.mss.MssParser = class {
634
636
  }
635
637
  break;
636
638
  }
639
+ stream.fullMimeTypes.add(shaka.util.MimeUtils.getFullType(
640
+ stream.mimeType, stream.codecs));
637
641
 
638
642
  // Lazy-Load the segment index to avoid create all init segment at the
639
643
  // same time
@@ -211,6 +211,8 @@ shaka.offline.indexeddb.V1StorageCell = class
211
211
  appendWindowEnd,
212
212
  timestampOffset,
213
213
  tilesLayout: '',
214
+ mimeType: null,
215
+ codecs: null,
214
216
  };
215
217
  }
216
218
 
@@ -155,6 +155,8 @@ shaka.offline.indexeddb.V2StorageCell = class
155
155
  timestampOffset,
156
156
  dataKey: old.dataKey,
157
157
  tilesLayout: '',
158
+ mimeType: null,
159
+ codecs: null,
158
160
  };
159
161
  }
160
162
  };
@@ -14,6 +14,7 @@ goog.require('shaka.media.SegmentIndex');
14
14
  goog.require('shaka.media.SegmentReference');
15
15
  goog.require('shaka.offline.OfflineUri');
16
16
  goog.require('shaka.util.ManifestParserUtils');
17
+ goog.require('shaka.util.MimeUtils');
17
18
 
18
19
 
19
20
  /**
@@ -169,7 +170,7 @@ shaka.offline.ManifestConverter = class {
169
170
  fromStreamDB_(streamDB, timeline) {
170
171
  /** @type {!Array.<!shaka.media.SegmentReference>} */
171
172
  const segments = streamDB.segments.map(
172
- (segment, index) => this.fromSegmentDB_(index, segment));
173
+ (segment, index) => this.fromSegmentDB_(index, segment, streamDB));
173
174
 
174
175
  timeline.notifySegments(segments);
175
176
 
@@ -212,6 +213,8 @@ shaka.offline.ManifestConverter = class {
212
213
  accessibilityPurpose: null,
213
214
  external: streamDB.external,
214
215
  fastSwitching: streamDB.fastSwitching,
216
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
217
+ streamDB.mimeType, streamDB.codecs)]),
215
218
  };
216
219
 
217
220
  return stream;
@@ -220,10 +223,11 @@ shaka.offline.ManifestConverter = class {
220
223
  /**
221
224
  * @param {number} index
222
225
  * @param {shaka.extern.SegmentDB} segmentDB
226
+ * @param {shaka.extern.StreamDB} streamDB
223
227
  * @return {!shaka.media.SegmentReference}
224
228
  * @private
225
229
  */
226
- fromSegmentDB_(index, segmentDB) {
230
+ fromSegmentDB_(index, segmentDB, streamDB) {
227
231
  /** @type {!shaka.offline.OfflineUri} */
228
232
  const uri = shaka.offline.OfflineUri.segment(
229
233
  this.mechanism_, this.cell_, segmentDB.dataKey);
@@ -231,7 +235,7 @@ shaka.offline.ManifestConverter = class {
231
235
  const initSegmentReference = segmentDB.initSegmentKey != null ?
232
236
  this.fromInitSegmentDB_(segmentDB.initSegmentKey) : null;
233
237
 
234
- return new shaka.media.SegmentReference(
238
+ const ref = new shaka.media.SegmentReference(
235
239
  segmentDB.startTime,
236
240
  segmentDB.endTime,
237
241
  () => [uri.toString()],
@@ -243,6 +247,9 @@ shaka.offline.ManifestConverter = class {
243
247
  segmentDB.appendWindowEnd,
244
248
  /* partialReferences= */ [],
245
249
  segmentDB.tilesLayout || '');
250
+ ref.mimeType = segmentDB.mimeType || streamDB.mimeType || '';
251
+ ref.codecs = segmentDB.codecs || streamDB.codecs || '';
252
+ return ref;
246
253
  }
247
254
 
248
255
  /**
@@ -1389,6 +1389,8 @@ shaka.offline.Storage = class {
1389
1389
  tilesLayout: segment.tilesLayout,
1390
1390
  pendingSegmentRefId,
1391
1391
  dataKey: 0,
1392
+ mimeType: segment.mimeType,
1393
+ codecs: segment.codecs,
1392
1394
  };
1393
1395
  streamDb.segments.push(segmentDB);
1394
1396
  groupId = (groupId + 1) % numberOfParallelDownloads;
package/lib/player.js CHANGED
@@ -1977,31 +1977,49 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1977
1977
  this.config_.playRangeStart,
1978
1978
  this.config_.playRangeEnd);
1979
1979
 
1980
- this.playhead_ = this.createPlayhead(this.startTime_);
1981
- this.playheadObservers_ =
1982
- this.createPlayheadObserversForMSE_(startTimeOfLoad);
1983
1980
 
1984
- // We need to start the buffer management code near the end because it
1985
- // will set the initial buffering state and that depends on other
1986
- // components being initialized.
1987
- const rebufferThreshold = Math.max(
1988
- this.manifest_.minBufferTime, this.config_.streaming.rebufferingGoal);
1989
- this.startBufferManagement_(mediaElement, rebufferThreshold);
1981
+ const setupPlayhead = (startTime) => {
1982
+ this.playhead_ = this.createPlayhead(startTime);
1983
+ this.playheadObservers_ =
1984
+ this.createPlayheadObserversForMSE_(startTimeOfLoad);
1985
+
1986
+ // We need to start the buffer management code near the end because it
1987
+ // will set the initial buffering state and that depends on other
1988
+ // components being initialized.
1989
+ const rebufferThreshold = Math.max(
1990
+ this.manifest_.minBufferTime,
1991
+ this.config_.streaming.rebufferingGoal);
1992
+ this.startBufferManagement_(mediaElement, rebufferThreshold);
1993
+ };
1994
+
1995
+ if (!this.config_.streaming.startAtSegmentBoundary) {
1996
+ setupPlayhead(this.startTime_);
1997
+ }
1990
1998
 
1991
1999
  // Now we can switch to the initial variant.
1992
2000
  if (!activeVariant) {
1993
2001
  goog.asserts.assert(initialVariant,
1994
2002
  'Must have choosen an initial variant!');
1995
2003
 
1996
- this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
1997
- /* clearBuffer= */ false, /* safeMargin= */ 0);
1998
-
1999
2004
  // Now that we have initial streams, we may adjust the start time to
2000
2005
  // align to a segment boundary.
2001
2006
  if (this.config_.streaming.startAtSegmentBoundary) {
2002
- this.playhead_.setStartTime(await this.adjustStartTime_(
2003
- initialVariant, this.playhead_.getTime()));
2007
+ const timeline = this.manifest_.presentationTimeline;
2008
+ let initialTime = this.startTime_ || this.video_.currentTime;
2009
+ const seekRangeStart = timeline.getSeekRangeStart();
2010
+ const seekRangeEnd = timeline.getSeekRangeEnd();
2011
+ if (initialTime < seekRangeStart) {
2012
+ initialTime = seekRangeStart;
2013
+ } else if (initialTime > seekRangeEnd) {
2014
+ initialTime = seekRangeEnd;
2015
+ }
2016
+ const startTime = await this.adjustStartTime_(
2017
+ initialVariant, initialTime);
2018
+ setupPlayhead(startTime);
2004
2019
  }
2020
+
2021
+ this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
2022
+ /* clearBuffer= */ false, /* safeMargin= */ 0);
2005
2023
  }
2006
2024
 
2007
2025
  this.playhead_.ready();
@@ -2174,12 +2192,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2174
2192
  accessibilityPurpose: null,
2175
2193
  external: false,
2176
2194
  fastSwitching: false,
2195
+ fullMimeTypes: new Set(),
2177
2196
  },
2178
2197
  bandwidth: 100,
2179
2198
  allowedByApplication: true,
2180
2199
  allowedByKeySystem: true,
2181
2200
  decodingInfos: [],
2182
2201
  };
2202
+ variant.video.fullMimeTypes.add(shaka.util.MimeUtils.getFullType(
2203
+ variant.video.mimeType, variant.video.codecs));
2183
2204
 
2184
2205
  this.drmEngine_.setSrcEquals(/* srcEquals= */ true);
2185
2206
  await this.drmEngine_.initForPlayback(
@@ -2313,6 +2334,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2313
2334
  mediaElement.load();
2314
2335
  }
2315
2336
 
2337
+ // In Safari using HLS won't load anything unless you call load()
2338
+ // explicitly, no matter the value of the preload attribute.
2339
+ // Note: this only happens when there are not autoplay.
2340
+ if (mediaElement.preload != 'none' && !mediaElement.autoplay &&
2341
+ shaka.util.MimeUtils.isHlsType(mimeType) &&
2342
+ shaka.util.Platform.safariVersion()) {
2343
+ mediaElement.load();
2344
+ }
2345
+
2316
2346
  // Set the load mode last so that we know that all our components are
2317
2347
  // initialized.
2318
2348
  this.loadMode_ = shaka.Player.LoadMode.SRC_EQUALS;
@@ -3620,12 +3650,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3620
3650
  const currentTime = this.video_.currentTime;
3621
3651
  const seekRange = this.seekRange();
3622
3652
  const safeSeekOffset = this.config_.streaming.safeSeekOffset;
3653
+
3654
+ // Cancel trick play if we hit the beginning or end of the seekable
3655
+ // (Sub-second accuracy not required here)
3623
3656
  if (rate > 0) {
3624
- if (currentTime >= seekRange.end) {
3657
+ if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
3625
3658
  this.cancelTrickPlay();
3626
3659
  }
3627
3660
  } else {
3628
- if (currentTime <= (seekRange.start + safeSeekOffset)) {
3661
+ if (Math.floor(currentTime) <=
3662
+ Math.floor(seekRange.start + safeSeekOffset)) {
3629
3663
  this.cancelTrickPlay();
3630
3664
  }
3631
3665
  }
@@ -4804,6 +4838,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4804
4838
  accessibilityPurpose: null,
4805
4839
  external: true,
4806
4840
  fastSwitching: false,
4841
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
4842
+ mimeType || '', codec || '')]),
4807
4843
  };
4808
4844
 
4809
4845
  const fullMimeType = shaka.util.MimeUtils.getFullType(
@@ -4960,6 +4996,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4960
4996
  accessibilityPurpose: null,
4961
4997
  external: true,
4962
4998
  fastSwitching: false,
4999
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
5000
+ mimeType || '', '')]),
4963
5001
  };
4964
5002
 
4965
5003
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
@@ -5390,6 +5428,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5390
5428
  accessibilityPurpose: null,
5391
5429
  external: false,
5392
5430
  fastSwitching: false,
5431
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
5432
+ mimeType, '')]),
5393
5433
  };
5394
5434
  manifest.textStreams.push(textStream);
5395
5435
  closedCaptionsSet.add(id);
@@ -6947,7 +6987,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6947
6987
  * @export
6948
6988
  */
6949
6989
  // eslint-disable-next-line no-useless-concat
6950
- shaka.Player.version = 'v4.7.12' + '-uncompiled'; // x-release-please-version
6990
+ shaka.Player.version = 'v4.7.14' + '-uncompiled'; // x-release-please-version
6951
6991
 
6952
6992
  // Initialize the deprecation system using the version string we just set
6953
6993
  // on the player.
@@ -72,7 +72,9 @@ shaka.text.UITextDisplayer = class {
72
72
 
73
73
  /** @private {shaka.util.Timer} */
74
74
  this.captionsTimer_ = new shaka.util.Timer(() => {
75
- this.updateCaptions_();
75
+ if (!this.video_.paused) {
76
+ this.updateCaptions_();
77
+ }
76
78
  }).tickEvery(updatePeriod);
77
79
 
78
80
  /**
@@ -93,6 +95,10 @@ shaka.text.UITextDisplayer = class {
93
95
  this.updateCaptions_(/* forceUpdate= */ true);
94
96
  });
95
97
 
98
+ this.eventManager_.listen(this.video_, 'seeking', () => {
99
+ this.updateCaptions_(/* forceUpdate= */ true);
100
+ });
101
+
96
102
  // From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
97
103
  // Whenever the natural width or natural height of the video changes
98
104
  // (including, for example, because the selected video track was changed),
@@ -339,6 +345,9 @@ shaka.text.UITextDisplayer = class {
339
345
  const cueRegistry = this.currentCuesMap_.get(cue);
340
346
  goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
341
347
  if (cueRegistry.regionElement) {
348
+ if (cueRegistry.regionElement.contains(container)) {
349
+ cueRegistry.regionElement.removeChild(container);
350
+ }
342
351
  container.appendChild(cueRegistry.regionElement);
343
352
  cueRegistry.regionElement.appendChild(cueRegistry.cueElement);
344
353
  } else {
@@ -84,17 +84,22 @@ shaka.util.MimeUtils = class {
84
84
  * beyond the container and codec type, when available.
85
85
  *
86
86
  * @param {shaka.extern.Stream} stream
87
+ * @param {string} mimeType
88
+ * @param {string} codecs
87
89
  * @return {string}
88
90
  */
89
- static getExtendedType(stream) {
90
- const components = [stream.mimeType];
91
+ static getExtendedType(stream, mimeType, codecs) {
92
+ const components = [mimeType];
91
93
 
92
94
  const extendedMimeParams = shaka.util.MimeUtils.EXTENDED_MIME_PARAMETERS_;
93
95
  extendedMimeParams.forEach((mimeKey, streamKey) => {
94
96
  const value = stream[streamKey];
95
- if (streamKey == 'codecs' &&
96
- shaka.util.MimeUtils.RAW_FORMATS.includes(stream.mimeType)) {
97
- // Skip codecs for raw formats
97
+ if (streamKey == 'codecs') {
98
+ if (shaka.util.MimeUtils.RAW_FORMATS.includes(stream.mimeType)) {
99
+ // Skip codecs for raw formats
100
+ } else {
101
+ components.push('codecs="' + codecs + '"');
102
+ }
98
103
  } else if (value) {
99
104
  components.push(mimeKey + '="' + value + '"');
100
105
  }
@@ -599,14 +599,6 @@ shaka.util.PeriodCombiner = class {
599
599
  goog.asserts.assert(match.segmentIndex,
600
600
  'stream should have a segmentIndex.');
601
601
  if (match.segmentIndex) {
602
- match.segmentIndex.forEachTopLevelReference((ref) => {
603
- ref.codecs = match.codecs;
604
- ref.mimeType = match.mimeType;
605
- if (ref.initSegmentReference) {
606
- ref.initSegmentReference.codecs = match.codecs;
607
- ref.initSegmentReference.mimeType = match.mimeType;
608
- }
609
- });
610
602
  outputStream.segmentIndex.appendSegmentIndex(match.segmentIndex);
611
603
  }
612
604
  }
@@ -721,39 +713,14 @@ shaka.util.PeriodCombiner = class {
721
713
 
722
714
  if (used) {
723
715
  unusedStreamsPerPeriod[i].delete(match);
724
- // Add the codec of this stream to the output stream's codecs.
725
- const codecs = new Set(outputStream.codecs.split(','));
726
- for (const codec of match.codecs.split(',')) {
727
- if (codec) {
728
- codecs.add(codec);
716
+ // Add the full mimetypes to the stream.
717
+ if (match.fullMimeTypes) {
718
+ for (const fullMimeType of match.fullMimeTypes.values()) {
719
+ outputStream.fullMimeTypes.add(fullMimeType);
729
720
  }
730
721
  }
731
- const PeriodCombiner = shaka.util.PeriodCombiner;
732
- outputStream.codecs = PeriodCombiner.filterDuplicateCodecs_(
733
- Array.from(codecs)).join(',');
734
- }
735
- }
736
- }
737
-
738
- /**
739
- * @param {!Array.<string>} codecs
740
- * @return {!Array.<string>} codecs
741
- * @private
742
- */
743
- static filterDuplicateCodecs_(codecs) {
744
- // Filter out duplicate codecs.
745
- const seen = new Set();
746
- const ret = [];
747
- for (const codec of codecs) {
748
- const shortCodec = shaka.util.MimeUtils.getCodecBase(codec);
749
- if (!seen.has(shortCodec)) {
750
- ret.push(codec);
751
- seen.add(shortCodec);
752
- } else {
753
- shaka.log.debug('Ignoring duplicate codec');
754
722
  }
755
723
  }
756
- return ret;
757
724
  }
758
725
 
759
726
  /**
@@ -1578,6 +1545,7 @@ shaka.util.PeriodCombiner = class {
1578
1545
  accessibilityPurpose: null,
1579
1546
  external: false,
1580
1547
  fastSwitching: false,
1548
+ fullMimeTypes: new Set(),
1581
1549
  };
1582
1550
  }
1583
1551
 
@@ -149,10 +149,9 @@ shaka.util.Platform = class {
149
149
  * @return {boolean}
150
150
  */
151
151
  static isWebOS3() {
152
- // See: http://webostv.developer.lge.com/discover/specifications/web-engine/
153
- return shaka.util.Platform.userAgentContains_('Web0S') &&
154
- shaka.util.Platform.userAgentContains_(
155
- 'Chrome/38.0.2125.122 Safari/537.36');
152
+ // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
153
+ return shaka.util.Platform.isWebOS() &&
154
+ shaka.util.Platform.chromeVersion() === 38;
156
155
  }
157
156
 
158
157
  /**
@@ -161,18 +160,20 @@ shaka.util.Platform = class {
161
160
  * @return {boolean}
162
161
  */
163
162
  static isWebOS4() {
164
- // See: http://webostv.developer.lge.com/discover/specifications/web-engine/
165
- return !!navigator.userAgent.match(/webOS\/4/i);
163
+ // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
164
+ return shaka.util.Platform.isWebOS() &&
165
+ shaka.util.Platform.chromeVersion() === 53;
166
166
  }
167
167
 
168
168
  /**
169
- * Check if the current platform is a WebOS 4.
169
+ * Check if the current platform is a WebOS 5.
170
170
  *
171
171
  * @return {boolean}
172
172
  */
173
173
  static isWebOS5() {
174
- // See: http://webostv.developer.lge.com/discover/specifications/web-engine/
175
- return !!navigator.userAgent.match(/webOS\/5/i);
174
+ // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
175
+ return shaka.util.Platform.isWebOS() &&
176
+ shaka.util.Platform.chromeVersion() === 68;
176
177
  }
177
178
 
178
179
  /**
@@ -184,6 +185,17 @@ shaka.util.Platform = class {
184
185
  return shaka.util.Platform.userAgentContains_('CrKey');
185
186
  }
186
187
 
188
+ /**
189
+ * Check if the current platform is a Google Chromecast with Android
190
+ * (i.e. Chromecast with GoogleTV).
191
+ *
192
+ * @return {boolean}
193
+ */
194
+ static isAndroidCastDevice() {
195
+ const Platform = shaka.util.Platform;
196
+ return Platform.isChromecast() && Platform.isAndroid();
197
+ }
198
+
187
199
  /**
188
200
  * Returns a major version number for Chrome, or Chromium-based browsers.
189
201
  *
@@ -414,6 +426,9 @@ shaka.util.Platform = class {
414
426
  return navigator.userAgentData.platform.toLowerCase() == 'macos';
415
427
  }
416
428
  // Fall back to the old API, with less strict matching.
429
+ if (!navigator.platform) {
430
+ return false;
431
+ }
417
432
  return navigator.platform.toLowerCase().includes('mac');
418
433
  }
419
434
 
@@ -428,6 +443,9 @@ shaka.util.Platform = class {
428
443
  return navigator.userAgentData.platform.toLowerCase() == 'windows';
429
444
  }
430
445
  // Fall back to the old API, with less strict matching.
446
+ if (!navigator.platform) {
447
+ return false;
448
+ }
431
449
  return navigator.platform.toLowerCase().includes('windows');
432
450
  }
433
451
 
@@ -535,6 +553,10 @@ shaka.util.Platform = class {
535
553
  Platform.isWebOS4() || Platform.isWebOS5()) {
536
554
  return false;
537
555
  }
556
+ // Older chromecasts without GoogleTV seem to not support SMOOTH properly.
557
+ if (Platform.isChromecast() && !Platform.isAndroidCastDevice()) {
558
+ return false;
559
+ }
538
560
  // See: https://chromium-review.googlesource.com/c/chromium/src/+/4577759
539
561
  if (Platform.isWindows() && Platform.isEdge()) {
540
562
  return false;