shaka-player 4.3.7 → 4.3.9

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 (86) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
  2. package/.github/workflows/appspot.yaml +39 -0
  3. package/.github/workflows/build-and-test.yaml +8 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +3 -2
  5. package/.github/workflows/update-issues.yaml +1 -0
  6. package/CHANGELOG.md +44 -0
  7. package/CONTRIBUTORS +1 -0
  8. package/demo/custom.js +1 -1
  9. package/dist/demo.compiled.debug.js +2 -2
  10. package/dist/demo.compiled.debug.map +2 -2
  11. package/dist/demo.compiled.js +2 -2
  12. package/dist/demo.compiled.map +2 -2
  13. package/dist/deps.js +2 -2
  14. package/dist/locales.js +31 -31
  15. package/dist/shaka-player.compiled.d.ts +24 -2
  16. package/dist/shaka-player.compiled.debug.d.ts +24 -2
  17. package/dist/shaka-player.compiled.debug.js +251 -247
  18. package/dist/shaka-player.compiled.debug.map +3 -3
  19. package/dist/shaka-player.compiled.js +796 -793
  20. package/dist/shaka-player.compiled.map +4 -4
  21. package/dist/shaka-player.ui.d.ts +24 -2
  22. package/dist/shaka-player.ui.debug.d.ts +24 -2
  23. package/dist/shaka-player.ui.debug.js +289 -286
  24. package/dist/shaka-player.ui.debug.map +3 -3
  25. package/dist/shaka-player.ui.js +831 -827
  26. package/dist/shaka-player.ui.map +4 -4
  27. package/docs/tutorials/drm-config.md +9 -6
  28. package/docs/tutorials/selenium-grid-config.md +60 -8
  29. package/externs/ima.js +34 -0
  30. package/externs/shaka/manifest.js +5 -1
  31. package/externs/shaka/offline.js +5 -1
  32. package/karma.conf.js +50 -7
  33. package/lib/abr/simple_abr_manager.js +6 -6
  34. package/lib/cea/mp4_cea_parser.js +24 -18
  35. package/lib/dash/dash_parser.js +19 -3
  36. package/lib/hls/hls_parser.js +24 -4
  37. package/lib/media/drm_engine.js +14 -8
  38. package/lib/media/gap_jumping_controller.js +24 -2
  39. package/lib/media/media_source_engine.js +11 -8
  40. package/lib/media/playhead.js +11 -0
  41. package/lib/media/region_observer.js +19 -1
  42. package/lib/media/streaming_engine.js +41 -21
  43. package/lib/media/video_wrapper.js +6 -1
  44. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  45. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  46. package/lib/offline/manifest_converter.js +1 -0
  47. package/lib/offline/storage.js +1 -0
  48. package/lib/player.js +20 -9
  49. package/lib/polyfill/abort_controller.js +7 -0
  50. package/lib/polyfill/orientation.js +34 -7
  51. package/lib/text/vtt_text_parser.js +3 -1
  52. package/lib/util/manifest_parser_utils.js +5 -3
  53. package/lib/util/periods.js +2 -0
  54. package/lib/util/platform.js +14 -0
  55. package/lib/util/stream_utils.js +70 -26
  56. package/package.json +2 -2
  57. package/test/cast/cast_utils_unit.js +4 -1
  58. package/test/cea/mp4_cea_parser_unit.js +28 -5
  59. package/test/dash/dash_parser_manifest_unit.js +120 -0
  60. package/test/hls/hls_parser_unit.js +216 -0
  61. package/test/media/adaptation_set_unit.js +1 -0
  62. package/test/media/drm_engine_integration.js +2 -2
  63. package/test/media/media_source_engine_unit.js +1 -1
  64. package/test/media/region_observer_unit.js +2 -1
  65. package/test/media/segment_index_unit.js +3 -3
  66. package/test/media/streaming_engine_integration.js +14 -11
  67. package/test/media/streaming_engine_unit.js +26 -2
  68. package/test/offline/manifest_convert_unit.js +5 -0
  69. package/test/offline/offline_integration.js +6 -3
  70. package/test/player_external.js +19 -16
  71. package/test/player_integration.js +35 -48
  72. package/test/player_src_equals_integration.js +9 -5
  73. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  74. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  75. package/test/test/util/fake_demo_main.js +7 -3
  76. package/test/test/util/layout_tests.js +2 -2
  77. package/test/test/util/manifest_generator.js +2 -0
  78. package/test/test/util/offline_utils.js +1 -0
  79. package/test/test/util/streaming_engine_util.js +3 -0
  80. package/test/test/util/ui_utils.js +9 -2
  81. package/test/test/util/util.js +6 -27
  82. package/test/test/util/waiter.js +73 -3
  83. package/test/ui/ad_ui_unit.js +9 -9
  84. package/test/util/stream_utils_unit.js +46 -0
  85. package/ui/range_element.js +5 -0
  86. package/ui/resolution_selection.js +7 -1
@@ -58,6 +58,9 @@ shaka.media.GapJumpingController = class {
58
58
  /** @private {number} */
59
59
  this.prevReadyState_ = video.readyState;
60
60
 
61
+ /** @private {number} */
62
+ this.startTime_ = 0;
63
+
61
64
  /** @private {number} */
62
65
  this.gapsJumped_ = 0;
63
66
 
@@ -123,6 +126,18 @@ shaka.media.GapJumpingController = class {
123
126
  this.onPollGapJump_();
124
127
  }
125
128
 
129
+ /**
130
+ * Called when playback has started and the video element is
131
+ * listening for seeks.
132
+ *
133
+ * @param {number} startTime
134
+ */
135
+ onStarted(startTime) {
136
+ if (this.video_.seeking && !this.seekingEventReceived_) {
137
+ this.seekingEventReceived_ = true;
138
+ this.startTime_ = startTime;
139
+ }
140
+ }
126
141
 
127
142
  /** Called when a seek has started. */
128
143
  onSeeking() {
@@ -164,8 +179,8 @@ shaka.media.GapJumpingController = class {
164
179
  // while paused on a livestream. We make an exception for time 0, since we
165
180
  // may be _required_ to seek on startup before play can begin, but only if
166
181
  // autoplay is enabled.
167
- if (this.video_.paused && (this.video_.currentTime != 0 ||
168
- (!this.video_.autoplay && this.video_.currentTime == 0))) {
182
+ if (this.video_.paused && (this.video_.currentTime != this.startTime_ ||
183
+ (!this.video_.autoplay && this.video_.currentTime == this.startTime_))) {
169
184
  return;
170
185
  }
171
186
 
@@ -228,6 +243,13 @@ shaka.media.GapJumpingController = class {
228
243
  }
229
244
 
230
245
  this.video_.currentTime = jumpTo;
246
+ // This accounts for the possibility that we jump a gap at the start
247
+ // position but we jump _into_ another gap. By setting the start
248
+ // position to the new jumpTo we ensure that the check above will
249
+ // pass even though the video is still paused.
250
+ if (currentTime == this.startTime_) {
251
+ this.startTime_ = jumpTo;
252
+ }
231
253
  this.gapsJumped_++;
232
254
  this.onEvent_(
233
255
  new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped));
@@ -369,7 +369,7 @@ shaka.media.MediaSourceEngine = class {
369
369
  let mimeType = shaka.util.MimeUtils.getFullType(
370
370
  stream.mimeType, stream.codecs);
371
371
  if (contentType == ContentType.TEXT) {
372
- this.reinitText(mimeType, sequenceMode);
372
+ this.reinitText(mimeType, sequenceMode, stream.external);
373
373
  } else {
374
374
  if ((forceTransmux ||
375
375
  !shaka.media.Capabilities.isTypeSupported(mimeType)) &&
@@ -425,13 +425,14 @@ shaka.media.MediaSourceEngine = class {
425
425
  * Reinitialize the TextEngine for a new text type.
426
426
  * @param {string} mimeType
427
427
  * @param {boolean} sequenceMode
428
+ * @param {boolean} external
428
429
  */
429
- reinitText(mimeType, sequenceMode) {
430
+ reinitText(mimeType, sequenceMode, external) {
430
431
  if (!this.textEngine_) {
431
432
  this.textEngine_ = new shaka.text.TextEngine(this.textDisplayer_);
432
433
  }
433
434
  this.textEngine_.initParser(mimeType, sequenceMode,
434
- this.segmentRelativeVttTiming_);
435
+ external || this.segmentRelativeVttTiming_);
435
436
  }
436
437
 
437
438
  /**
@@ -582,11 +583,13 @@ shaka.media.MediaSourceEngine = class {
582
583
  * @param {boolean=} seeked True if we just seeked
583
584
  * @param {boolean=} adaptation True if we just automatically switched active
584
585
  * variant(s).
586
+ * @param {boolean=} isChunkedData True if we add to the buffer from the
587
+ * partial read of the segment.
585
588
  * @return {!Promise}
586
589
  */
587
590
  async appendBuffer(
588
591
  contentType, data, reference, hasClosedCaptions, seeked = false,
589
- adaptation = false) {
592
+ adaptation = false, isChunkedData = false) {
590
593
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
591
594
 
592
595
  if (contentType == ContentType.TEXT) {
@@ -649,7 +652,7 @@ shaka.media.MediaSourceEngine = class {
649
652
  // the video stream, so textEngine may not have been initialized.
650
653
  if (!this.textEngine_) {
651
654
  this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
652
- this.sequenceMode_);
655
+ this.sequenceMode_, /* external= */ false);
653
656
  }
654
657
  // This doesn't work for native TS support (ex. Edge/Chromecast),
655
658
  // since no transmuxing is needed for native TS.
@@ -669,7 +672,7 @@ shaka.media.MediaSourceEngine = class {
669
672
  } else if (hasClosedCaptions && contentType == ContentType.VIDEO) {
670
673
  if (!this.textEngine_) {
671
674
  this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
672
- this.sequenceMode_);
675
+ this.sequenceMode_, /* external= */ false);
673
676
  }
674
677
  if (!this.captionParser_) {
675
678
  this.captionParser_ = this.getCaptionParser(mimeType);
@@ -787,13 +790,13 @@ shaka.media.MediaSourceEngine = class {
787
790
  let bufferedBefore = null;
788
791
 
789
792
  await this.enqueueOperation_(contentType, () => {
790
- if (goog.DEBUG && reference) {
793
+ if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
791
794
  bufferedBefore = this.getBuffered_(contentType);
792
795
  }
793
796
  this.append_(contentType, data);
794
797
  });
795
798
 
796
- if (goog.DEBUG && reference && !reference.isPreload()) {
799
+ if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
797
800
  const bufferedAfter = this.getBuffered_(contentType);
798
801
  const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
799
802
  bufferedBefore, bufferedAfter);
@@ -251,6 +251,7 @@ shaka.media.MediaSourcePlayhead = class {
251
251
  this.videoWrapper_ = new shaka.media.VideoWrapper(
252
252
  mediaElement,
253
253
  () => this.onSeeking_(),
254
+ (realStartTime) => this.onStarted_(realStartTime),
254
255
  this.getStartTime_(startTime));
255
256
 
256
257
  /** @type {shaka.util.Timer} */
@@ -390,6 +391,16 @@ shaka.media.MediaSourcePlayhead = class {
390
391
  }
391
392
  }
392
393
 
394
+ /**
395
+ * Called when the video element has started up and is listening for new seeks
396
+ *
397
+ * @param {number} startTime
398
+ * @private
399
+ */
400
+ onStarted_(startTime) {
401
+ this.gapController_.onStarted(startTime);
402
+ }
403
+
393
404
  /**
394
405
  * Handles when a seek happens on the video.
395
406
  *
@@ -27,13 +27,24 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
27
27
  * destroy the timeline.
28
28
  *
29
29
  * @param {!shaka.media.RegionTimeline} timeline
30
+ * @param {boolean} startsPastZero
30
31
  */
31
- constructor(timeline) {
32
+ constructor(timeline, startsPastZero) {
32
33
  super();
33
34
 
34
35
  /** @private {shaka.media.RegionTimeline} */
35
36
  this.timeline_ = timeline;
36
37
 
38
+ /**
39
+ * Whether the asset is expected to start at a time beyond 0 seconds.
40
+ * For example, if the asset is a live stream.
41
+ * If true, we will not start polling for regions until the playhead has
42
+ * moved past 0 seconds, to avoid bad behaviors where the current time is
43
+ * briefly 0 before we have enough data to play.
44
+ * @private {boolean}
45
+ */
46
+ this.startsPastZero_ = startsPastZero;
47
+
37
48
  /**
38
49
  * A mapping between a region and where we previously were relative to it.
39
50
  * When the value here differs from what we calculate, it means we moved and
@@ -121,6 +132,13 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
121
132
  /** @override */
122
133
  poll(positionInSeconds, wasSeeking) {
123
134
  const RegionObserver = shaka.media.RegionObserver;
135
+ if (this.startsPastZero_ && positionInSeconds == 0) {
136
+ // Don't start checking regions until the timeline has begun moving.
137
+ return;
138
+ }
139
+ // Now that we have seen the playhead go past 0, it's okay if it goes
140
+ // back there (e.g. seeking back to the start).
141
+ this.startsPastZero_ = false;
124
142
 
125
143
  for (const region of this.timeline_.regions()) {
126
144
  const previousPosition = this.oldPosition_.get(region);
@@ -244,7 +244,7 @@ shaka.media.StreamingEngine = class {
244
244
  const mimeType = shaka.util.MimeUtils.getFullType(
245
245
  stream.mimeType, stream.codecs);
246
246
  this.playerInterface_.mediaSourceEngine.reinitText(
247
- mimeType, this.manifest_.sequenceMode);
247
+ mimeType, this.manifest_.sequenceMode, stream.external);
248
248
 
249
249
  const textDisplayer =
250
250
  this.playerInterface_.mediaSourceEngine.getTextDisplayer();
@@ -458,7 +458,7 @@ shaka.media.StreamingEngine = class {
458
458
  const fullMimeType = shaka.util.MimeUtils.getFullType(
459
459
  stream.mimeType, stream.codecs);
460
460
  this.playerInterface_.mediaSourceEngine.reinitText(
461
- fullMimeType, this.manifest_.sequenceMode);
461
+ fullMimeType, this.manifest_.sequenceMode, stream.external);
462
462
  }
463
463
 
464
464
  // Releases the segmentIndex of the old stream.
@@ -1319,7 +1319,8 @@ shaka.media.StreamingEngine = class {
1319
1319
  const dataToAppend = remaining.subarray(0, offset);
1320
1320
  remaining = remaining.subarray(offset);
1321
1321
  await this.append_(
1322
- mediaState, presentationTime, stream, reference, dataToAppend);
1322
+ mediaState, presentationTime, stream, reference, dataToAppend,
1323
+ /* isChunkedData= */ true);
1323
1324
  }
1324
1325
  };
1325
1326
 
@@ -1731,10 +1732,12 @@ shaka.media.StreamingEngine = class {
1731
1732
  * @param {shaka.extern.Stream} stream
1732
1733
  * @param {!shaka.media.SegmentReference} reference
1733
1734
  * @param {BufferSource} segment
1735
+ * @param {boolean=} isChunkedData
1734
1736
  * @return {!Promise}
1735
1737
  * @private
1736
1738
  */
1737
- async append_(mediaState, presentationTime, stream, reference, segment) {
1739
+ async append_(mediaState, presentationTime, stream, reference, segment,
1740
+ isChunkedData = false) {
1738
1741
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1739
1742
 
1740
1743
  const hasClosedCaptions = stream.closedCaptions &&
@@ -1788,7 +1791,8 @@ shaka.media.StreamingEngine = class {
1788
1791
  reference,
1789
1792
  hasClosedCaptions,
1790
1793
  seeked,
1791
- adaptation);
1794
+ adaptation,
1795
+ isChunkedData);
1792
1796
  this.destroyer_.ensureNotDestroyed();
1793
1797
  shaka.log.v2(logPrefix, 'appended media segment');
1794
1798
  }
@@ -1852,7 +1856,6 @@ shaka.media.StreamingEngine = class {
1852
1856
  const messageData = box.reader.readBytes(
1853
1857
  box.reader.getLength() - box.reader.getPosition());
1854
1858
 
1855
-
1856
1859
  // See DASH sec. 5.10.3.3.1
1857
1860
  // If a DASH client detects an event message box with a scheme that is not
1858
1861
  // defined in MPD, the client is expected to ignore it.
@@ -1862,22 +1865,8 @@ shaka.media.StreamingEngine = class {
1862
1865
  // A special scheme in DASH used to signal manifest updates.
1863
1866
  if (schemeId == 'urn:mpeg:dash:event:2012') {
1864
1867
  this.playerInterface_.onManifestUpdate();
1865
- } else if (schemeId == 'https://aomedia.org/emsg/ID3') {
1866
- // See https://aomediacodec.github.io/id3-emsg/
1867
- const frames = shaka.util.Id3Utils.getID3Frames(messageData);
1868
- if (frames.length && reference) {
1869
- /** @private {shaka.extern.ID3Metadata} */
1870
- const metadata = {
1871
- cueTime: reference.startTime,
1872
- data: messageData,
1873
- frames: frames,
1874
- dts: reference.startTime,
1875
- pts: reference.startTime,
1876
- };
1877
- this.playerInterface_.onMetadata(
1878
- [metadata], /* offset= */ 0, reference.endTime);
1879
- }
1880
1868
  } else {
1869
+ // All other schemes are dispatched as a general 'emsg' event.
1881
1870
  /** @type {shaka.extern.EmsgInfo} */
1882
1871
  const emsg = {
1883
1872
  startTime: startTime,
@@ -1895,7 +1884,38 @@ shaka.media.StreamingEngine = class {
1895
1884
  const eventName = shaka.util.FakeEvent.EventName.Emsg;
1896
1885
  const data = (new Map()).set('detail', emsg);
1897
1886
  const event = new shaka.util.FakeEvent(eventName, data);
1887
+ // A user can call preventDefault() on a cancelable event.
1888
+ event.cancelable = true;
1889
+
1898
1890
  this.playerInterface_.onEvent(event);
1891
+
1892
+ if (event.defaultPrevented) {
1893
+ // If the caller uses preventDefault() on the 'emsg' event, don't
1894
+ // process any further, and don't generate an ID3 'metadata' event
1895
+ // for the same data.
1896
+ return;
1897
+ }
1898
+
1899
+ // Additionally, ID3 events generate a 'metadata' event. This is a
1900
+ // pre-parsed version of the metadata blob already dispatched in the
1901
+ // 'emsg' event.
1902
+ if (schemeId == 'https://aomedia.org/emsg/ID3' ||
1903
+ schemeId == 'https://developer.apple.com/streaming/emsg-id3') {
1904
+ // See https://aomediacodec.github.io/id3-emsg/
1905
+ const frames = shaka.util.Id3Utils.getID3Frames(messageData);
1906
+ if (frames.length && reference) {
1907
+ /** @private {shaka.extern.ID3Metadata} */
1908
+ const metadata = {
1909
+ cueTime: reference.startTime,
1910
+ data: messageData,
1911
+ frames: frames,
1912
+ dts: reference.startTime,
1913
+ pts: reference.startTime,
1914
+ };
1915
+ this.playerInterface_.onMetadata(
1916
+ [metadata], /* offset= */ 0, reference.endTime);
1917
+ }
1918
+ }
1899
1919
  }
1900
1920
  }
1901
1921
  }
@@ -27,15 +27,19 @@ shaka.media.VideoWrapper = class {
27
27
  /**
28
28
  * @param {!HTMLMediaElement} video
29
29
  * @param {function()} onSeek Called when the video seeks.
30
+ * @param {function(number)} onStarted Called when the video has started.
30
31
  * @param {number} startTime The time to start at.
31
32
  */
32
- constructor(video, onSeek, startTime) {
33
+ constructor(video, onSeek, onStarted, startTime) {
33
34
  /** @private {HTMLMediaElement} */
34
35
  this.video_ = video;
35
36
 
36
37
  /** @private {function()} */
37
38
  this.onSeek_ = onSeek;
38
39
 
40
+ /** @private {function(number)} */
41
+ this.onStarted_ = onStarted;
42
+
39
43
  /** @private {number} */
40
44
  this.startTime_ = startTime;
41
45
 
@@ -165,6 +169,7 @@ shaka.media.VideoWrapper = class {
165
169
  this.started_ = true;
166
170
 
167
171
  this.eventManager_.listen(this.video_, 'seeking', () => this.onSeek_());
172
+ this.onStarted_(this.video_.currentTime);
168
173
  }
169
174
  };
170
175
 
@@ -176,6 +176,7 @@ shaka.offline.indexeddb.V1StorageCell = class
176
176
  spatialAudio: false,
177
177
  closedCaptions: null,
178
178
  tilesLayout: undefined,
179
+ external: false,
179
180
  };
180
181
  }
181
182
 
@@ -125,6 +125,7 @@ shaka.offline.indexeddb.V2StorageCell = class
125
125
  spatialAudio: false,
126
126
  closedCaptions: null,
127
127
  tilesLayout: undefined,
128
+ external: false,
128
129
  };
129
130
  }
130
131
 
@@ -202,6 +202,7 @@ shaka.offline.ManifestConverter = class {
202
202
  spatialAudio: streamDB.spatialAudio,
203
203
  closedCaptions: streamDB.closedCaptions,
204
204
  tilesLayout: streamDB.tilesLayout,
205
+ external: streamDB.external,
205
206
  };
206
207
 
207
208
  return stream;
@@ -1325,6 +1325,7 @@ shaka.offline.Storage = class {
1325
1325
  spatialAudio: stream.spatialAudio,
1326
1326
  closedCaptions: stream.closedCaptions,
1327
1327
  tilesLayout: stream.tilesLayout,
1328
+ external: stream.external,
1328
1329
  };
1329
1330
 
1330
1331
  const startTime =
package/lib/player.js CHANGED
@@ -99,7 +99,9 @@ goog.requireType('shaka.routing.Payload');
99
99
 
100
100
  /**
101
101
  * @event shaka.Player.EmsgEvent
102
- * @description Fired when a non-typical emsg is found in a segment.
102
+ * @description Fired when an emsg box is found in a segment.
103
+ * If the application calls preventDefault() on this event, further parsing
104
+ * will not happen, and no 'metadata' event will be raised for ID3 payloads.
103
105
  * @property {string} type
104
106
  * 'emsg'
105
107
  * @property {shaka.extern.EmsgInfo} detail
@@ -2190,8 +2192,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2190
2192
  await Promise.all(createSegmentIndexPromises);
2191
2193
  }
2192
2194
 
2195
+ shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
2196
+ this.config_.playRangeStart,
2197
+ this.config_.playRangeEnd);
2198
+
2193
2199
  this.playhead_ = this.createPlayhead(has.startTime);
2194
- this.playheadObservers_ = this.createPlayheadObserversForMSE_();
2200
+ this.playheadObservers_ =
2201
+ this.createPlayheadObserversForMSE_(has.startTimeOfLoad);
2195
2202
 
2196
2203
  // We need to start the buffer management code near the end because it will
2197
2204
  // set the initial buffering state and that depends on other components
@@ -2217,10 +2224,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2217
2224
 
2218
2225
  this.playhead_.setStartTime(adjustedTime);
2219
2226
  }
2220
-
2221
- // Since the first streams just became active, send an adaptation event.
2222
- this.onAdaptation_(null,
2223
- shaka.util.StreamUtils.variantToTrack(initialVariant));
2224
2227
  }
2225
2228
 
2226
2229
  this.playhead_.ready();
@@ -2387,6 +2390,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2387
2390
  audioSamplingRate: null,
2388
2391
  spatialAudio: false,
2389
2392
  closedCaptions: null,
2393
+ external: false,
2390
2394
  },
2391
2395
  bandwidth: 100,
2392
2396
  allowedByApplication: true,
@@ -2918,17 +2922,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2918
2922
  * Create the observers for MSE playback. These observers are responsible for
2919
2923
  * notifying the app and player of specific events during MSE playback.
2920
2924
  *
2925
+ * @param {number} startTimeOfLoad
2921
2926
  * @return {!shaka.media.PlayheadObserverManager}
2922
2927
  * @private
2923
2928
  */
2924
- createPlayheadObserversForMSE_() {
2929
+ createPlayheadObserversForMSE_(startTimeOfLoad) {
2925
2930
  goog.asserts.assert(this.manifest_, 'Must have manifest');
2926
2931
  goog.asserts.assert(this.regionTimeline_, 'Must have region timeline');
2927
2932
  goog.asserts.assert(this.video_, 'Must have video element');
2928
2933
 
2934
+ const startsPastZero = this.isLive() || startTimeOfLoad > 0;
2935
+
2929
2936
  // Create the region observer. This will allow us to notify the app when we
2930
2937
  // move in and out of timeline regions.
2931
- const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
2938
+ const regionObserver = new shaka.media.RegionObserver(
2939
+ this.regionTimeline_, startsPastZero);
2932
2940
 
2933
2941
  regionObserver.addEventListener('enter', (event) => {
2934
2942
  /** @type {shaka.extern.TimelineRegionInfo} */
@@ -4816,6 +4824,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4816
4824
  audioSamplingRate: null,
4817
4825
  spatialAudio: false,
4818
4826
  closedCaptions: null,
4827
+ external: true,
4819
4828
  };
4820
4829
 
4821
4830
  const fullMimeType = shaka.util.MimeUtils.getFullType(
@@ -4967,6 +4976,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4967
4976
  spatialAudio: false,
4968
4977
  closedCaptions: null,
4969
4978
  tilesLayout: '1x1',
4979
+ external: true,
4970
4980
  };
4971
4981
 
4972
4982
  this.manifest_.imageStreams.push(stream);
@@ -5386,6 +5396,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5386
5396
  audioSamplingRate: null,
5387
5397
  spatialAudio: false,
5388
5398
  closedCaptions: null,
5399
+ external: false,
5389
5400
  };
5390
5401
  manifest.textStreams.push(textStream);
5391
5402
  closedCaptionsSet.add(id);
@@ -7009,7 +7020,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7009
7020
  * @define {string} A version number taken from git at compile time.
7010
7021
  * @export
7011
7022
  */
7012
- shaka.Player.version = 'v4.3.7-uncompiled';
7023
+ shaka.Player.version = 'v4.3.9-uncompiled';
7013
7024
 
7014
7025
  // Initialize the deprecation system using the version string we just set
7015
7026
  // on the player.
@@ -86,6 +86,13 @@ class extends shaka.util.FakeEventTarget {
86
86
  return this.reason_;
87
87
  }
88
88
 
89
+ /** @return {void} */
90
+ throwIfAborted() {
91
+ if (this.aborted_) {
92
+ throw this.reason_;
93
+ }
94
+ }
95
+
89
96
  /**
90
97
  * @param {*} reason
91
98
  * @private
@@ -6,6 +6,7 @@
6
6
 
7
7
  goog.provide('shaka.polyfill.Orientation');
8
8
 
9
+ goog.require('shaka.log');
9
10
  goog.require('shaka.util.FakeEvent');
10
11
  goog.require('shaka.util.FakeEventTarget');
11
12
  goog.require('shaka.polyfill');
@@ -23,21 +24,47 @@ shaka.polyfill.Orientation = class {
23
24
  * @export
24
25
  */
25
26
  static install() {
26
- if (screen.orientation) {
27
+ if (screen.orientation && screen.orientation.unlock) {
27
28
  // Not needed.
29
+ shaka.log.info('Using native screen.orientation');
28
30
  return;
29
31
  }
30
32
 
31
- // There is no way to check to see if the 'orientationchange' event exists
32
- // on window, which could theoretically lead to this making a
33
- // screen.orientation object that doesn't actually work.
34
- // However, it looks like all platforms that support the deprecated
35
- // window.orientation feature also support that event.
36
- if (window.orientation != undefined) {
33
+ if (screen.orientation != undefined) {
34
+ // There are some platforms where screen.orientation is defined but
35
+ // incomplete (e.g. Safari).
36
+ // Install a very simple polyfill in that case.
37
+ shaka.polyfill.Orientation.installBasedOnScreenMethods_();
38
+ } else if (window.orientation != undefined) {
39
+ // There is no way to check to see if the 'orientationchange' event exists
40
+ // on window, which could theoretically lead to this making a
41
+ // screen.orientation object that doesn't actually work.
42
+ // However, it looks like all platforms that support the deprecated
43
+ // window.orientation feature also support that event.
37
44
  shaka.polyfill.Orientation.installBasedOnWindowMethods_();
38
45
  }
39
46
  }
40
47
 
48
+ /**
49
+ * Modifies screen.orientation to add no-ops for missing methods.
50
+ * Meant for cases where screen.orientation is defined, but has missing
51
+ * methods that cannot be properly polyfilled.
52
+ * @private
53
+ */
54
+ static installBasedOnScreenMethods_() {
55
+ if (screen.orientation.lock === undefined) {
56
+ screen.orientation.lock = (orientation) => {
57
+ shaka.log.info('screen.orientation.lock is a no-op');
58
+ return Promise.resolve();
59
+ };
60
+ }
61
+ if (screen.orientation.unlock === undefined) {
62
+ screen.orientation.unlock = () => {
63
+ shaka.log.info('screen.orientation.unlock is a no-op');
64
+ };
65
+ }
66
+ }
67
+
41
68
  /**
42
69
  * Makes a polyfill for orientation, based on window methods.
43
70
  * Note that some of the features this is based on are deprecated, so this
@@ -674,6 +674,7 @@ shaka.text.VttTextParser = class {
674
674
  cue.fontWeight = refCue.fontWeight;
675
675
  cue.fontStyle = refCue.fontStyle;
676
676
  cue.opacity = refCue.opacity;
677
+ cue.textShadow = refCue.textShadow;
677
678
  cue.wrapLine = refCue.wrapLine;
678
679
  }
679
680
 
@@ -785,7 +786,8 @@ shaka.text.VttTextParser = class {
785
786
  } else if ((results = /^size:([\d.]+)%$/.exec(word))) {
786
787
  cue.size = Number(results[1]);
787
788
  } else if ((results =
788
- /^position:([\d.]+)%(?:,(line-left|line-right|center|start|end))?$/
789
+ // eslint-disable-next-line max-len
790
+ /^position:([\d.]+)%(?:,(line-left|line-right|middle|center|start|end))?$/
789
791
  .exec(word))) {
790
792
  cue.position = Number(results[1]);
791
793
  if (results[2]) {
@@ -168,7 +168,7 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
168
168
  /^hvc/,
169
169
  /^vp0?[89]/,
170
170
  /^av01/,
171
- /^dvhe/,
171
+ /^dvh/,
172
172
  ];
173
173
 
174
174
 
@@ -180,8 +180,10 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
180
180
  */
181
181
  shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
182
182
  /^vorbis$/,
183
- /^opus$/,
184
- /^flac$/,
183
+ /^Opus$/, // correct codec string according to RFC 6381 section 3.3
184
+ /^opus$/, // some manifests wrongfully use this
185
+ /^fLaC$/, // correct codec string according to RFC 6381 section 3.3
186
+ /^flac$/, // some manifests wrongfully use this
185
187
  /^mp4a/,
186
188
  /^[ae]c-3$/,
187
189
  /^ac-4$/,
@@ -1491,6 +1491,7 @@ shaka.util.PeriodCombiner = class {
1491
1491
  audioSamplingRate: null,
1492
1492
  spatialAudio: false,
1493
1493
  closedCaptions: null,
1494
+ external: false,
1494
1495
  };
1495
1496
  }
1496
1497
 
@@ -1527,6 +1528,7 @@ shaka.util.PeriodCombiner = class {
1527
1528
  audioSamplingRate: null,
1528
1529
  spatialAudio: false,
1529
1530
  closedCaptions: null,
1531
+ external: false,
1530
1532
  };
1531
1533
  }
1532
1534
 
@@ -346,6 +346,20 @@ shaka.util.Platform = class {
346
346
  return shaka.util.Platform.isApple() && navigator.maxTouchPoints > 1;
347
347
  }
348
348
 
349
+ /**
350
+ * Return true if the platform is a Mac, regardless of the browser.
351
+ *
352
+ * @return {boolean}
353
+ */
354
+ static isMac() {
355
+ // Try the newer standard first.
356
+ if (navigator.userAgentData && navigator.userAgentData.platform) {
357
+ return navigator.userAgentData.platform.toLowerCase() == 'macos';
358
+ }
359
+ // Fall back to the old API, with less strict matching.
360
+ return navigator.platform.toLowerCase().includes('mac');
361
+ }
362
+
349
363
  /**
350
364
  * Check if the user agent contains a key. This is the best way we know of
351
365
  * right now to detect platforms. If there is a better way, please send a