shaka-player 4.6.16 → 4.6.18

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 (48) hide show
  1. package/.github/workflows/build-and-test.yaml +17 -4
  2. package/.github/workflows/release-please.yaml +2 -2
  3. package/AUTHORS +1 -0
  4. package/CHANGELOG.md +38 -0
  5. package/CONTRIBUTORS +1 -0
  6. package/dist/locales.js +34 -34
  7. package/dist/shaka-player.compiled.d.ts +1 -0
  8. package/dist/shaka-player.compiled.debug.d.ts +1 -0
  9. package/dist/shaka-player.compiled.debug.externs.js +4 -0
  10. package/dist/shaka-player.compiled.debug.js +206 -203
  11. package/dist/shaka-player.compiled.debug.map +3 -3
  12. package/dist/shaka-player.compiled.externs.js +4 -0
  13. package/dist/shaka-player.compiled.js +1070 -1066
  14. package/dist/shaka-player.compiled.map +3 -3
  15. package/dist/shaka-player.ui.d.ts +1 -0
  16. package/dist/shaka-player.ui.debug.d.ts +1 -0
  17. package/dist/shaka-player.ui.debug.externs.js +4 -0
  18. package/dist/shaka-player.ui.debug.js +222 -219
  19. package/dist/shaka-player.ui.debug.map +3 -3
  20. package/dist/shaka-player.ui.externs.js +4 -0
  21. package/dist/shaka-player.ui.js +1217 -1213
  22. package/dist/shaka-player.ui.map +3 -3
  23. package/docs/tutorials/faq.md +13 -0
  24. package/docs/tutorials/offline.md +3 -0
  25. package/docs/tutorials/ui.md +4 -4
  26. package/karma.conf.js +1 -0
  27. package/lib/ads/client_side_ad_manager.js +4 -2
  28. package/lib/dash/segment_template.js +24 -1
  29. package/lib/media/drm_engine.js +17 -3
  30. package/lib/media/media_source_engine.js +13 -3
  31. package/lib/media/segment_index.js +10 -0
  32. package/lib/media/segment_reference.js +12 -0
  33. package/lib/media/streaming_engine.js +9 -27
  34. package/lib/player.js +48 -17
  35. package/lib/text/ui_text_displayer.js +10 -1
  36. package/lib/util/platform.js +31 -9
  37. package/lib/util/stream_utils.js +8 -5
  38. package/maintained-branches.md +5 -0
  39. package/package.json +1 -1
  40. package/roadmap.md +22 -5
  41. package/test/codec_switching/codec_switching_integration.js +183 -13
  42. package/test/media/media_source_engine_unit.js +105 -148
  43. package/test/player_unit.js +1 -1
  44. package/test/test/assets/dash-multi-codec-ec3/ChID_voices_6ch_256kbps_ddp.mp4 +0 -0
  45. package/test/test/assets/dash-multi-codec-ec3/ChID_voices_6ch_640kbps_aac.mp4 +0 -0
  46. package/test/test/assets/dash-multi-codec-ec3/dash.mpd +32 -0
  47. package/test/util/platform_unit.js +54 -0
  48. package/ui/resolution_selection.js +1 -0
@@ -245,6 +245,19 @@ the following configuration:
245
245
  player.configure('manifest.hls.liveSegmentsDelay', 1);
246
246
  ```
247
247
 
248
+ <hr>
249
+
250
+ **Q:** Why can I not find the new version of Shaka Player on
251
+ ajax.googleapis.com?
252
+
253
+ **A:** When a new version of Shaka Player is released on GitHub, it has to
254
+ undergo a brief manual review process before it can be uploaded to Google's
255
+ servers. After approval, the upload process then usually takes several hours.
256
+ Therefore it is not unusual for a day or two to pass before a new version of
257
+ Shaka Player is hosted on Google's ajax.googleapis.com website. We ask for your
258
+ patience in these matters.
259
+
260
+
248
261
  [386]: https://github.com/shaka-project/shaka-player/issues/386#issuecomment-227898001
249
262
  [489]: https://github.com/shaka-project/shaka-player/issues/489#issuecomment-240466224
250
263
  [743]: https://github.com/shaka-project/shaka-player/issues/743
@@ -340,6 +340,9 @@ you want.
340
340
  At this point, the content is now stored offline and it's ready to be played.
341
341
  Next we will add functionality to play offline content.
342
342
 
343
+ Note: If you call `storage.store` twice with the same manifestUri as input,
344
+ you'll download the same manifestUri twice.
345
+
343
346
  ## Playing Offline Content
344
347
 
345
348
  Now that we have stored some content, we want to play it. To do this, resolve
@@ -16,9 +16,9 @@ Set up controls with HTML data attributes:
16
16
  <!DOCTYPE html>
17
17
  <html>
18
18
  <head>
19
- <!-- Shaka Player ui compiled library: -->
19
+ <!-- Shaka Player UI compiled library: -->
20
20
  <script src="dist/shaka-player.ui.js"></script>
21
- <!-- Shaka Player ui compiled library default CSS: -->
21
+ <!-- Shaka Player UI compiled library default CSS: -->
22
22
  <link rel="stylesheet" type="text/css" href="dist/controls.css">
23
23
  <!-- Chromecast SDK (if you want Chromecast support for your app): -->
24
24
  <script defer src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
@@ -52,7 +52,7 @@ async function init() {
52
52
  const controls = ui.getControls();
53
53
  const player = controls.getPlayer();
54
54
 
55
- // Attach player and ui to the window to make it easy to access in the JS console.
55
+ // Attach player and UI to the window to make it easy to access in the JS console.
56
56
  window.player = player;
57
57
  window.ui = ui;
58
58
 
@@ -145,7 +145,7 @@ casting to an Android receiver app.
145
145
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%"
146
146
  src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"></video>
147
147
  </div
148
-
148
+ ```
149
149
 
150
150
  #### Providing source(s) for auto load.
151
151
 
package/karma.conf.js CHANGED
@@ -234,6 +234,7 @@ module.exports = (config) => {
234
234
  {pattern: 'test/**/*.js', included: false},
235
235
  {pattern: 'test/test/assets/*', included: false},
236
236
  {pattern: 'test/test/assets/dash-multi-codec/*', included: false},
237
+ {pattern: 'test/test/assets/dash-multi-codec-ec3/*', included: false},
237
238
  {pattern: 'test/test/assets/3675/*', included: false},
238
239
  {pattern: 'test/test/assets/6339/*', included: false},
239
240
  {pattern: 'test/test/assets/dash-aes-128/*', included: false},
@@ -533,7 +533,9 @@ shaka.ads.ClientSideAdManager = class {
533
533
  shaka.ads.AdManager.AD_STARTED, data));
534
534
  if (this.ad_.isLinear()) {
535
535
  this.adContainer_.setAttribute('ad-active', 'true');
536
- this.video_.pause();
536
+ if (!this.config_.customPlayheadTracker) {
537
+ this.video_.pause();
538
+ }
537
539
  if (this.video_.muted) {
538
540
  this.ad_.setInitialMuted(this.video_.volume);
539
541
  } else {
@@ -551,7 +553,7 @@ shaka.ads.ClientSideAdManager = class {
551
553
  (new Map()).set('originalEvent', e)));
552
554
  if (this.ad_ && this.ad_.isLinear()) {
553
555
  this.adContainer_.removeAttribute('ad-active');
554
- if (!this.video_.ended) {
556
+ if (!this.config_.customPlayheadTracker && !this.video_.ended) {
555
557
  this.video_.play();
556
558
  }
557
559
  }
@@ -392,6 +392,11 @@ shaka.dash.SegmentTemplate = class {
392
392
  Math.ceil(availablePeriodTimes[1] / segmentDuration) - 1,
393
393
  ];
394
394
 
395
+ // For Low Latency we can request the partial current position.
396
+ if (context.representation.availabilityTimeOffset) {
397
+ availablePeriodPositions[1]++;
398
+ }
399
+
395
400
  // In absolute positions.
396
401
  const availablePresentationPositions =
397
402
  availablePeriodPositions.map((x) => x + startNumber);
@@ -689,7 +694,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
689
694
  */
690
695
  appendTemplateInfo(info, periodStart, periodEnd, shouldFit,
691
696
  initSegmentReference) {
692
- this.initSegmentReference_ = initSegmentReference;
697
+ this.updateInitSegmentReference(initSegmentReference);
693
698
  if (!this.templateInfo_) {
694
699
  this.templateInfo_ = info;
695
700
  this.periodStart_ = periodStart;
@@ -720,6 +725,24 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
720
725
  }
721
726
  }
722
727
 
728
+ /**
729
+ * Updates the init segment reference and propagates the update to all
730
+ * references.
731
+ * @param {shaka.media.InitSegmentReference} initSegmentReference
732
+ */
733
+ updateInitSegmentReference(initSegmentReference) {
734
+ if (this.initSegmentReference_ === initSegmentReference) {
735
+ return;
736
+ }
737
+
738
+ this.initSegmentReference_ = initSegmentReference;
739
+ for (const reference of this.references) {
740
+ if (reference) {
741
+ reference.updateInitSegmentReference(initSegmentReference);
742
+ }
743
+ }
744
+ }
745
+
723
746
  /**
724
747
  *
725
748
  * @param {number} time
@@ -1551,6 +1551,9 @@ shaka.media.DrmEngine = class {
1551
1551
  const req = this.playerInterface_.netEngine.request(requestType, request);
1552
1552
  response = await req.promise;
1553
1553
  } catch (error) {
1554
+ if (this.destroyer_.destroyed()) {
1555
+ return;
1556
+ }
1554
1557
  // Request failed!
1555
1558
  goog.asserts.assert(error instanceof shaka.util.Error,
1556
1559
  'Wrong NetworkingEngine error type!');
@@ -1559,9 +1562,20 @@ shaka.media.DrmEngine = class {
1559
1562
  shaka.util.Error.Category.DRM,
1560
1563
  shaka.util.Error.Code.LICENSE_REQUEST_FAILED,
1561
1564
  error);
1562
- this.onError_(shakaErr);
1563
- if (metadata && metadata.updatePromise) {
1564
- metadata.updatePromise.reject(shakaErr);
1565
+ if (this.activeSessions_.size == 1) {
1566
+ this.onError_(shakaErr);
1567
+ if (metadata && metadata.updatePromise) {
1568
+ metadata.updatePromise.reject(shakaErr);
1569
+ }
1570
+ } else {
1571
+ if (metadata && metadata.updatePromise) {
1572
+ metadata.updatePromise.reject(shakaErr);
1573
+ }
1574
+ this.activeSessions_.delete(session);
1575
+ if (this.areAllSessionsLoaded_()) {
1576
+ this.allSessionsLoaded_.resolve();
1577
+ this.keyStatusTimer_.tickAfter(/* seconds= */ 0.1);
1578
+ }
1565
1579
  }
1566
1580
  return;
1567
1581
  }
@@ -1784,12 +1784,17 @@ shaka.media.MediaSourceEngine = class {
1784
1784
 
1785
1785
  const totalOfBuffers = streamsByType.size;
1786
1786
  let numberOfSourceBufferAdded = 0;
1787
- this.eventManager_.listen(sourceBuffers, 'addsourcebuffer', (event) => {
1787
+ const onSourceBufferAdded = () => {
1788
1788
  numberOfSourceBufferAdded++;
1789
1789
  if (numberOfSourceBufferAdded === totalOfBuffers) {
1790
1790
  sourceBufferAdded.resolve();
1791
+ this.eventManager_.unlisten(sourceBuffers, 'addsourcebuffer',
1792
+ onSourceBufferAdded);
1791
1793
  }
1792
- });
1794
+ };
1795
+
1796
+ this.eventManager_.listen(sourceBuffers, 'addsourcebuffer',
1797
+ onSourceBufferAdded);
1793
1798
 
1794
1799
  for (const contentType of streamsByType.keys()) {
1795
1800
  const stream = streamsByType.get(contentType);
@@ -1812,7 +1817,12 @@ shaka.media.MediaSourceEngine = class {
1812
1817
  this.destroyer_.ensureNotDestroyed();
1813
1818
 
1814
1819
  this.eventManager_.listenOnce(this.video_, 'canplaythrough', () => {
1815
- this.destroyer_.ensureNotDestroyed();
1820
+ // Don't use ensureNotDestroyed() from this event listener, because
1821
+ // that results in an uncaught exception. Instead, just check the
1822
+ // flag.
1823
+ if (this.destroyer_.destroyed()) {
1824
+ return;
1825
+ }
1816
1826
 
1817
1827
  this.video_.autoplay = previousAutoPlayState;
1818
1828
  if (!previousPausedState) {
@@ -693,6 +693,16 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
693
693
  this.indexes_ = [];
694
694
  }
695
695
 
696
+ /**
697
+ * @override
698
+ * @export
699
+ */
700
+ forEachTopLevelReference(fn) {
701
+ for (const index of this.indexes_) {
702
+ index.forEachTopLevelReference(fn);
703
+ }
704
+ }
705
+
696
706
  /**
697
707
  * @override
698
708
  * @export
@@ -586,6 +586,18 @@ shaka.media.SegmentReference = class {
586
586
  this.offset(offset);
587
587
  }
588
588
  }
589
+
590
+ /**
591
+ * Updates the init segment reference and propagates the update to all partial
592
+ * references.
593
+ * @param {shaka.media.InitSegmentReference} initSegmentReference
594
+ */
595
+ updateInitSegmentReference(initSegmentReference) {
596
+ this.initSegmentReference = initSegmentReference;
597
+ for (const partialReference of this.partialReferences) {
598
+ partialReference.updateInitSegmentReference(initSegmentReference);
599
+ }
600
+ }
589
601
  };
590
602
 
591
603
 
@@ -878,7 +878,6 @@ shaka.media.StreamingEngine = class {
878
878
  // The playhead might be seeking on startup, if a start time is set, so
879
879
  // start "seeked" as true.
880
880
  seeked: true,
881
- needsResync: false,
882
881
  recovering: false,
883
882
  hasError: false,
884
883
  operation: null,
@@ -1245,11 +1244,6 @@ shaka.media.StreamingEngine = class {
1245
1244
  shaka.log.v1(
1246
1245
  logPrefix, 'looking up segment from new stream endTime:', time);
1247
1246
 
1248
- // Using a new iterator means we need to resync the stream in sequence
1249
- // mode. The buffered range might not align perfectly with the last
1250
- // segment end time, so we may end up repeating a segment. Resyncing
1251
- // makes this safe to do.
1252
- mediaState.needsResync = true;
1253
1247
  mediaState.segmentIterator =
1254
1248
  mediaState.stream.segmentIndex.getIteratorForTime(time);
1255
1249
  const ref = mediaState.segmentIterator &&
@@ -1730,9 +1724,16 @@ shaka.media.StreamingEngine = class {
1730
1724
  otherState = this.mediaStates_.get(ContentType.VIDEO);
1731
1725
  }
1732
1726
  if (otherState) {
1727
+ // First, abort all operations in progress on the other stream.
1728
+ await this.abortOperations_(otherState).catch(() => {});
1729
+ // Then clear our cache of the last init segment, since MSE will be
1730
+ // reloaded and no init segment will be there post-reload.
1733
1731
  otherState.lastInitSegmentReference = null;
1732
+ // Now force the existing buffer to be cleared. It is not necessary
1733
+ // to perform the MSE clear operation, but this has the side-effect
1734
+ // that our state for that stream will then match MSE's post-reload
1735
+ // state.
1734
1736
  this.forceClearBuffer_(otherState);
1735
- this.abortOperations_(otherState).catch(() => {});
1736
1737
  }
1737
1738
  }
1738
1739
 
@@ -1762,19 +1763,6 @@ shaka.media.StreamingEngine = class {
1762
1763
  mediaState.type, timestampOffset, appendWindowStart,
1763
1764
  appendWindowEnd, ignoreTimestampOffset,
1764
1765
  mediaState.stream, streamsByType);
1765
- if (isResetMediaSourceNecessary) {
1766
- let otherState = null;
1767
- if (mediaState.type === ContentType.VIDEO) {
1768
- otherState = this.mediaStates_.get(ContentType.AUDIO);
1769
- } else if (mediaState.type === ContentType.AUDIO) {
1770
- otherState = this.mediaStates_.get(ContentType.VIDEO);
1771
- }
1772
- if (otherState) {
1773
- otherState.waitingToClearBuffer = false;
1774
- otherState.performingUpdate = false;
1775
- this.forceClearBuffer_(otherState);
1776
- }
1777
- }
1778
1766
  } catch (error) {
1779
1767
  mediaState.lastAppendWindowStart = null;
1780
1768
  mediaState.lastAppendWindowEnd = null;
@@ -1869,9 +1857,7 @@ shaka.media.StreamingEngine = class {
1869
1857
  // Across discontinuity bounds, we should resync timestamps for
1870
1858
  // sequence mode playbacks. The next segment appended should
1871
1859
  // land at its theoretical timestamp from the segment index.
1872
- if (reference.discontinuitySequence != lastDiscontinuitySequence ||
1873
- mediaState.needsResync) {
1874
- mediaState.needsResync = false;
1860
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1875
1861
  operations.push(this.playerInterface_.mediaSourceEngine.resync(
1876
1862
  mediaState.type, reference.startTime));
1877
1863
  }
@@ -2541,7 +2527,6 @@ shaka.media.StreamingEngine.PlayerInterface;
2541
2527
  * clearBufferSafeMargin: number,
2542
2528
  * clearingBuffer: boolean,
2543
2529
  * seeked: boolean,
2544
- * needsResync: boolean,
2545
2530
  * adaptation: boolean,
2546
2531
  * recovering: boolean,
2547
2532
  * hasError: boolean,
@@ -2596,9 +2581,6 @@ shaka.media.StreamingEngine.PlayerInterface;
2596
2581
  * True indicates that the presentation just seeked.
2597
2582
  * @property {boolean} adaptation
2598
2583
  * True indicates that the presentation just automatically switched variants.
2599
- * @property {boolean} needsResync
2600
- * True indicates that the stream needs to be resynced in sequence mode,
2601
- * regardless of discontinuity sequence.
2602
2584
  * @property {boolean} recovering
2603
2585
  * True indicates that the last segment was not appended because it could not
2604
2586
  * fit in the buffer.
package/lib/player.js CHANGED
@@ -1946,31 +1946,49 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1946
1946
  this.config_.playRangeStart,
1947
1947
  this.config_.playRangeEnd);
1948
1948
 
1949
- this.playhead_ = this.createPlayhead(this.startTime_);
1950
- this.playheadObservers_ =
1951
- this.createPlayheadObserversForMSE_(startTimeOfLoad);
1952
1949
 
1953
- // We need to start the buffer management code near the end because it
1954
- // will set the initial buffering state and that depends on other
1955
- // components being initialized.
1956
- const rebufferThreshold = Math.max(
1957
- this.manifest_.minBufferTime, this.config_.streaming.rebufferingGoal);
1958
- this.startBufferManagement_(mediaElement, rebufferThreshold);
1950
+ const setupPlayhead = (startTime) => {
1951
+ this.playhead_ = this.createPlayhead(startTime);
1952
+ this.playheadObservers_ =
1953
+ this.createPlayheadObserversForMSE_(startTimeOfLoad);
1954
+
1955
+ // We need to start the buffer management code near the end because it
1956
+ // will set the initial buffering state and that depends on other
1957
+ // components being initialized.
1958
+ const rebufferThreshold = Math.max(
1959
+ this.manifest_.minBufferTime,
1960
+ this.config_.streaming.rebufferingGoal);
1961
+ this.startBufferManagement_(mediaElement, rebufferThreshold);
1962
+ };
1963
+
1964
+ if (!this.config_.streaming.startAtSegmentBoundary) {
1965
+ setupPlayhead(this.startTime_);
1966
+ }
1959
1967
 
1960
1968
  // Now we can switch to the initial variant.
1961
1969
  if (!activeVariant) {
1962
1970
  goog.asserts.assert(initialVariant,
1963
1971
  'Must have choosen an initial variant!');
1964
1972
 
1965
- this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
1966
- /* clearBuffer= */ false, /* safeMargin= */ 0);
1967
-
1968
1973
  // Now that we have initial streams, we may adjust the start time to
1969
1974
  // align to a segment boundary.
1970
1975
  if (this.config_.streaming.startAtSegmentBoundary) {
1971
- this.playhead_.setStartTime(await this.adjustStartTime_(
1972
- initialVariant, this.playhead_.getTime()));
1976
+ const timeline = this.manifest_.presentationTimeline;
1977
+ let initialTime = this.startTime_ || this.video_.currentTime;
1978
+ const seekRangeStart = timeline.getSeekRangeStart();
1979
+ const seekRangeEnd = timeline.getSeekRangeEnd();
1980
+ if (initialTime < seekRangeStart) {
1981
+ initialTime = seekRangeStart;
1982
+ } else if (initialTime > seekRangeEnd) {
1983
+ initialTime = seekRangeEnd;
1984
+ }
1985
+ const startTime = await this.adjustStartTime_(
1986
+ initialVariant, initialTime);
1987
+ setupPlayhead(startTime);
1973
1988
  }
1989
+
1990
+ this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
1991
+ /* clearBuffer= */ false, /* safeMargin= */ 0);
1974
1992
  }
1975
1993
 
1976
1994
  this.playhead_.ready();
@@ -2282,6 +2300,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2282
2300
  mediaElement.load();
2283
2301
  }
2284
2302
 
2303
+ // In Safari using HLS won't load anything unless you call load()
2304
+ // explicitly, no matter the value of the preload attribute.
2305
+ // Note: this only happens when there are not autoplay.
2306
+ if (mediaElement.preload != 'none' && !mediaElement.autoplay &&
2307
+ shaka.util.MimeUtils.isHlsType(mimeType) &&
2308
+ shaka.util.Platform.safariVersion()) {
2309
+ mediaElement.load();
2310
+ }
2311
+
2285
2312
  // Set the load mode last so that we know that all our components are
2286
2313
  // initialized.
2287
2314
  this.loadMode_ = shaka.Player.LoadMode.SRC_EQUALS;
@@ -3544,12 +3571,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3544
3571
  const currentTime = this.video_.currentTime;
3545
3572
  const seekRange = this.seekRange();
3546
3573
  const safeSeekOffset = this.config_.streaming.safeSeekOffset;
3574
+
3575
+ // Cancel trick play if we hit the beginning or end of the seekable
3576
+ // (Sub-second accuracy not required here)
3547
3577
  if (rate > 0) {
3548
- if (currentTime >= seekRange.end) {
3578
+ if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
3549
3579
  this.cancelTrickPlay();
3550
3580
  }
3551
3581
  } else {
3552
- if (currentTime <= (seekRange.start + safeSeekOffset)) {
3582
+ if (Math.floor(currentTime) <=
3583
+ Math.floor(seekRange.start + safeSeekOffset)) {
3553
3584
  this.cancelTrickPlay();
3554
3585
  }
3555
3586
  }
@@ -6850,7 +6881,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6850
6881
  * @export
6851
6882
  */
6852
6883
  // eslint-disable-next-line no-useless-concat
6853
- shaka.Player.version = 'v4.6.16' + '-uncompiled'; // x-release-please-version
6884
+ shaka.Player.version = 'v4.6.18' + '-uncompiled'; // x-release-please-version
6854
6885
 
6855
6886
  // Initialize the deprecation system using the version string we just set
6856
6887
  // 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 {
@@ -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;
@@ -839,8 +839,12 @@ shaka.util.StreamUtils = class {
839
839
  // currently don't support 'fLaC', while 'flac' is supported by most
840
840
  // major browsers.
841
841
  // See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
842
- if (codecs === 'fLaC' && !shaka.util.Platform.isSafari()) {
843
- return 'flac';
842
+ if (codecs.toLowerCase() == 'flac') {
843
+ if (!shaka.util.Platform.isSafari()) {
844
+ return 'flac';
845
+ } else {
846
+ return 'fLaC';
847
+ }
844
848
  }
845
849
 
846
850
  // The same is true for 'Opus'.
@@ -1321,8 +1325,6 @@ shaka.util.StreamUtils = class {
1321
1325
  * @return {shaka.extern.Track}
1322
1326
  */
1323
1327
  static html5TextTrackToTrack(textTrack) {
1324
- const CLOSED_CAPTION_MIMETYPE =
1325
- shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE;
1326
1328
  const StreamUtils = shaka.util.StreamUtils;
1327
1329
 
1328
1330
  /** @type {shaka.extern.Track} */
@@ -1331,7 +1333,8 @@ shaka.util.StreamUtils = class {
1331
1333
  track.type = 'text';
1332
1334
  track.originalTextId = textTrack.id;
1333
1335
  if (textTrack.kind == 'captions') {
1334
- track.mimeType = CLOSED_CAPTION_MIMETYPE;
1336
+ // See: https://github.com/shaka-project/shaka-player/issues/6233
1337
+ track.mimeType = 'unknown';
1335
1338
  }
1336
1339
  if (textTrack.kind == 'subtitles') {
1337
1340
  track.mimeType = 'text/vtt';
@@ -2,8 +2,13 @@
2
2
 
3
3
  We are currently maintaining the following branches of Shaka Player:
4
4
 
5
+ <<<<<<< HEAD
5
6
  - v4.6 (latest)
6
7
  - v4.5 (previous)
8
+ =======
9
+ - v4.8 (latest)
10
+ - v4.7 (previous)
11
+ >>>>>>> 99204977e (chore: Update maintained branches doc (#6504))
7
12
  - v4.3 (in use by the Cast Application Framework)
8
13
  - No active LTS branches at this time
9
14
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.6.16",
4
+ "version": "4.6.18",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
package/roadmap.md CHANGED
@@ -9,8 +9,6 @@ The goals of future milestones are fluid until we begin that development cycle,
9
9
  so the exact milestone for future features is not pre-determined.
10
10
 
11
11
  Candidate features for future release cycles:
12
- - Official TypeScript defs, generated from source
13
- https://github.com/shaka-project/shaka-player/issues/1030
14
12
  - Background fetch for offline storage
15
13
  https://github.com/shaka-project/shaka-player/issues/879
16
14
  - A method to stitch together clips
@@ -19,13 +17,32 @@ Candidate features for future release cycles:
19
17
  https://github.com/shaka-project/shaka-player/issues/898
20
18
  - Smaller, more modular binary
21
19
 
22
- v5.0 - 2024 Q1
20
+ v5.0 - 2024 Q4
21
+ - Conversion to Typescript
22
+
23
+ v4.9 - 2024 Q3
24
+ - DASH patch manifests
25
+ https://github.com/shaka-project/shaka-player/issues/2228
26
+
27
+ =====
28
+
29
+ v4.8 - 2024 Q2
23
30
  - Preload API
24
31
  https://github.com/shaka-project/shaka-player/issues/880
32
+ - AES-256 and AES-256-CTR (HLS)
33
+ https://github.com/shaka-project/shaka-player/issues/6001
34
+ - Detect maximum HW resolution automatically on some platforms
35
+
36
+ v4.7 - 2023 Q4
25
37
  - Common Media Server Data (CMSD)
26
38
  https://github.com/shaka-project/shaka-player/issues/5890
27
-
28
- =====
39
+ - DASH: Handle mixed-codec variants
40
+ https://github.com/shaka-project/shaka-player/issues/5961
41
+ - Allow Media Source Recoveries
42
+ - UI: Add double tap to forward/rewind in the video
43
+ https://github.com/shaka-project/shaka-player/issues/3357
44
+ - Improve npm package size
45
+ https://github.com/shaka-project/shaka-player/issues/2172
29
46
 
30
47
  v4.6 - 2023 Q4
31
48
  - Content Steering