shaka-player 4.2.4 → 4.2.6

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 (70) hide show
  1. package/.github/workflows/build-and-test.yaml +13 -0
  2. package/AUTHORS +2 -0
  3. package/CHANGELOG.md +35 -0
  4. package/CONTRIBUTORS +2 -0
  5. package/demo/index.html +1 -1
  6. package/dist/demo.compiled.debug.js +64 -64
  7. package/dist/demo.compiled.debug.map +2 -2
  8. package/dist/demo.compiled.js +64 -64
  9. package/dist/demo.compiled.map +2 -2
  10. package/dist/deps.js +3 -2
  11. package/dist/locales.js +30 -30
  12. package/dist/receiver.compiled.debug.js +12 -12
  13. package/dist/receiver.compiled.debug.map +2 -2
  14. package/dist/receiver.compiled.js +12 -12
  15. package/dist/receiver.compiled.map +2 -2
  16. package/dist/shaka-player.compiled.d.ts +10 -0
  17. package/dist/shaka-player.compiled.debug.d.ts +10 -0
  18. package/dist/shaka-player.compiled.debug.externs.js +93 -79
  19. package/dist/shaka-player.compiled.debug.js +585 -577
  20. package/dist/shaka-player.compiled.debug.map +4 -4
  21. package/dist/shaka-player.compiled.externs.js +93 -79
  22. package/dist/shaka-player.compiled.js +816 -814
  23. package/dist/shaka-player.compiled.map +4 -4
  24. package/dist/shaka-player.ui.d.ts +10 -0
  25. package/dist/shaka-player.ui.debug.d.ts +10 -0
  26. package/dist/shaka-player.ui.debug.externs.js +93 -79
  27. package/dist/shaka-player.ui.debug.js +615 -606
  28. package/dist/shaka-player.ui.debug.map +4 -4
  29. package/dist/shaka-player.ui.externs.js +93 -79
  30. package/dist/shaka-player.ui.js +964 -962
  31. package/dist/shaka-player.ui.map +4 -4
  32. package/docs/tutorials/ui.md +7 -7
  33. package/docs/tutorials/welcome.md +2 -2
  34. package/externs/shaka/player.js +2 -1
  35. package/lib/ads/ad_manager.js +2 -2
  36. package/lib/cea/cea708_window.js +2 -2
  37. package/lib/dash/segment_template.js +1 -1
  38. package/lib/hls/hls_parser.js +24 -6
  39. package/lib/media/content_workarounds.js +12 -0
  40. package/lib/media/media_source_engine.js +16 -0
  41. package/lib/media/segment_reference.js +3 -0
  42. package/lib/media/streaming_engine.js +33 -10
  43. package/lib/player.js +6 -2
  44. package/lib/polyfill/abort_controller.js +143 -0
  45. package/lib/polyfill/media_capabilities.js +59 -9
  46. package/lib/text/ttml_text_parser.js +32 -7
  47. package/lib/text/ui_text_displayer.js +22 -5
  48. package/lib/util/platform.js +9 -1
  49. package/lib/util/player_configuration.js +10 -3
  50. package/lib/util/stream_utils.js +30 -10
  51. package/package.json +1 -1
  52. package/shaka-player.uncompiled.js +1 -0
  53. package/test/dash/dash_parser_manifest_unit.js +1 -0
  54. package/test/hls/hls_parser_unit.js +1 -0
  55. package/test/media/drm_engine_unit.js +11 -1
  56. package/test/media/media_source_engine_integration.js +78 -2
  57. package/test/player_integration.js +10 -3
  58. package/test/polyfill/media_capabilities_unit.js +261 -0
  59. package/test/test/boot.js +29 -11
  60. package/test/test/util/manifest_parser_util.js +4 -3
  61. package/test/test/util/stream_generator.js +212 -8
  62. package/test/test/util/test_scheme.js +5 -4
  63. package/test/test/util/util.js +3 -2
  64. package/test/text/mp4_ttml_parser_unit.js +6 -4
  65. package/test/text/ttml_text_parser_unit.js +119 -86
  66. package/test/text/ui_text_displayer_unit.js +76 -0
  67. package/test/ui/text_displayer_layout_unit.js +14 -0
  68. package/test/util/stream_utils_unit.js +18 -0
  69. package/test/util/xml_utils_unit.js +6 -1
  70. package/ui/controls.js +49 -36
@@ -30,7 +30,7 @@ Set up controls with HTML data attributes:
30
30
  The data-shaka-player-cast-receiver-id tag allows you to provide a Cast Application ID that
31
31
  the cast button will cast to; the value provided here is the sample cast receiver. -->
32
32
  <div data-shaka-player-container style="max-width:40em"
33
- data-shaka-player-cast-receiver-id="07AEE832">
33
+ data-shaka-player-cast-receiver-id="A67E4E9A">
34
34
  <!-- The data-shaka-player tag will make the UI library use this video element.
35
35
  If no video is provided, the UI will automatically make one inside the container div. -->
36
36
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%"></video>
@@ -111,7 +111,7 @@ set up a listener for the 'caststatuschanged' events.
111
111
  <!-- Add a data-shaka-player-cast-receiver-id tag to provide a Cast Application ID that
112
112
  the cast button will cast to; the value provided here is the sample cast receiver. -->
113
113
  <div data-shaka-player-container style="max-width:40em"
114
- data-shaka-player-cast-receiver-id="07AEE832">
114
+ data-shaka-player-cast-receiver-id="A67E4E9A">
115
115
  </div>
116
116
  ```
117
117
 
@@ -139,7 +139,7 @@ casting to an Android receiver app.
139
139
 
140
140
  ```html
141
141
  <div data-shaka-player-container style="max-width:40em"
142
- data-shaka-player-cast-receiver-id="E7271BEC"
142
+ data-shaka-player-cast-receiver-id="A67E4E9A"
143
143
  data-shaka-player-cast-android-receiver-compatible="true">
144
144
  <!-- The manifest url in the src attribute will be automatically loaded -->
145
145
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%"
@@ -154,7 +154,7 @@ or a `<source>` tag inside it to enable auto loading of the specified content.
154
154
 
155
155
  ```html
156
156
  <div data-shaka-player-container style="max-width:40em"
157
- data-shaka-player-cast-receiver-id="07AEE832">
157
+ data-shaka-player-cast-receiver-id="A67E4E9A">
158
158
  <!-- The manifest url in the src attribute will be automatically loaded -->
159
159
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%"
160
160
  src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"></video>
@@ -165,7 +165,7 @@ or
165
165
 
166
166
  ```html
167
167
  <div data-shaka-player-container style="max-width:40em"
168
- data-shaka-player-cast-receiver-id="07AEE832">
168
+ data-shaka-player-cast-receiver-id="A67E4E9A">
169
169
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%">
170
170
  <!-- The manifest url in the src attribute will be auto loaded -->
171
171
  <source src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"/>
@@ -178,7 +178,7 @@ call to the first one fails.
178
178
 
179
179
  ```html
180
180
  <div data-shaka-player-container style="max-width:40em"
181
- data-shaka-player-cast-receiver-id="07AEE832">
181
+ data-shaka-player-cast-receiver-id="A67E4E9A">
182
182
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%">
183
183
  <!-- Try this first -->
184
184
  <source src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"/>
@@ -222,7 +222,7 @@ const video = controls.getVideo();
222
222
  // Receiver Compatability.
223
223
  ui.configure({
224
224
  // Set the castReceiverAppId
225
- 'castReceiverAppId': '07AEE832',
225
+ 'castReceiverAppId': 'A67E4E9A',
226
226
  // Enable casting to native Android Apps (e.g. Android TV Apps)
227
227
  'castAndroidReceiverCompatible': true,
228
228
  });
@@ -29,8 +29,8 @@ You can build Shaka on Linux, Windows, or Mac.
29
29
  To get the sources and compile the library, you will need:
30
30
  * {@link https://git-scm.com/downloads Git v1.9+}
31
31
  * {@link https://www.python.org/downloads/ Python v2.7 or v3.5+}
32
- * {@link https://java.com/en/download/ Java Runtime Environment v8+}
33
- * {@link https://nodejs.org/en/download/ NodeJS v10+}
32
+ * {@link https://java.com/en/download/ Java Runtime Environment v14+}
33
+ * {@link https://nodejs.org/en/download/ NodeJS v14+}
34
34
  * A local web server, such as {@link https://httpd.apache.org/ Apache}
35
35
  * _NOTE: A local web server is required because browsers place restrictions
36
36
  on applications from file:/// URLs._
@@ -131,7 +131,8 @@ shaka.extern.StateChange;
131
131
  * @property {number} manifestTimeSeconds
132
132
  * The amount of time it took to download and parse the manifest.
133
133
  * @property {number} drmTimeSeconds
134
- * The amount of time it took to download the first drm key.
134
+ * The amount of time it took to download the first drm key, and load that key
135
+ * into the drm system.
135
136
  * @property {number} playTime
136
137
  * The total time spent in a playing state in seconds.
137
138
  * @property {number} pauseTime
@@ -19,8 +19,8 @@ goog.require('shaka.util.IReleasable');
19
19
 
20
20
 
21
21
  /**
22
- * @event shaka.ads.AdManager.ADS_LOADED
23
- * @description Fired when an ad has started playing.
22
+ * @event shaka.ads.AdManager.AdsLoadedEvent
23
+ * @description Fired when a sequence of ads has been loaded.
24
24
  * @property {string} type
25
25
  * 'ads-loaded'
26
26
  * @property {number} loadTime
@@ -420,7 +420,7 @@ shaka.cea.Cea708Window.TextJustification = {
420
420
  shaka.cea.Cea708Window.MAX_COLS = 42;
421
421
 
422
422
  /**
423
- * Maximum of 15 rows that can be indexed from 0 to 14.
423
+ * Maximum of 16 rows that can be indexed from 0 to 15.
424
424
  * @private @const {number}
425
425
  */
426
- shaka.cea.Cea708Window.MAX_ROWS = 15;
426
+ shaka.cea.Cea708Window.MAX_ROWS = 16;
@@ -67,7 +67,7 @@ shaka.dash.SegmentTemplate = class {
67
67
  },
68
68
  };
69
69
  } else if (info.segmentDuration) {
70
- if (!isUpdate) {
70
+ if (!isUpdate && context.adaptationSet.contentType !== 'image') {
71
71
  context.presentationTimeline.notifyMaxSegmentDuration(
72
72
  info.segmentDuration);
73
73
  context.presentationTimeline.notifyMinSegmentStartTime(
@@ -1049,7 +1049,7 @@ shaka.hls.HlsParser = class {
1049
1049
  shaka.log.debug('Guessing audio-only.');
1050
1050
  type = ContentType.AUDIO;
1051
1051
  ignoreStream = true;
1052
- } else if (res.video.length) {
1052
+ } else if (res.video.length && !res.audio.length) {
1053
1053
  // There are associated video streams. Assume this is audio.
1054
1054
  shaka.log.debug('Guessing audio-only.');
1055
1055
  type = ContentType.AUDIO;
@@ -1665,9 +1665,16 @@ shaka.hls.HlsParser = class {
1665
1665
  /** @type {!Map.<number, number>} */
1666
1666
  const mediaSequenceToStartTime = new Map();
1667
1667
 
1668
+ const prevLowLatencyMode = this.lowLatencyMode_;
1668
1669
  const segments = this.createSegments_(verbatimMediaPlaylistUri, playlist,
1669
1670
  type, mimeType, mediaSequenceToStartTime, mediaVariables, codecs);
1670
1671
 
1672
+ // This happens when autoLowLatencyMode is true, so we need set the
1673
+ // correct lowLatencyPresentationDelay_
1674
+ if (prevLowLatencyMode != this.lowLatencyMode_) {
1675
+ this.determinePresentationType_(playlist);
1676
+ }
1677
+
1671
1678
  const lastEndTime = segments[segments.length - 1].endTime;
1672
1679
  /** @type {!shaka.media.SegmentIndex} */
1673
1680
  const segmentIndex = new shaka.media.SegmentIndex(segments);
@@ -1796,8 +1803,11 @@ shaka.hls.HlsParser = class {
1796
1803
  shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
1797
1804
  }
1798
1805
 
1806
+ const algorithm = {
1807
+ name: 'AES-CBC',
1808
+ };
1799
1809
  keyInfo.cryptoKey = await window.crypto.subtle.importKey(
1800
- 'raw', keyResponse.data, 'AES-CBC', true, ['decrypt']);
1810
+ 'raw', keyResponse.data, algorithm, true, ['decrypt']);
1801
1811
  keyInfo.fetchKey = undefined; // No longer needed.
1802
1812
  };
1803
1813
 
@@ -2233,12 +2243,12 @@ shaka.hls.HlsParser = class {
2233
2243
  /** @type {shaka.extern.HlsAes128Key|undefined} */
2234
2244
  let hlsAes128Key = undefined;
2235
2245
 
2236
- // We may need to look at the media itself to determine a segment start
2237
- // time.
2246
+ let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2247
+ playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
2238
2248
  const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2239
2249
  playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
2240
- const skipTag = shaka.hls.Utils.getFirstTagWithName(playlist.tags,
2241
- 'EXT-X-SKIP');
2250
+ const skipTag = shaka.hls.Utils.getFirstTagWithName(
2251
+ playlist.tags, 'EXT-X-SKIP');
2242
2252
  const skippedSegments =
2243
2253
  skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
2244
2254
  let position = mediaSequenceNumber + skippedSegments;
@@ -2261,6 +2271,12 @@ shaka.hls.HlsParser = class {
2261
2271
  (i == 0) ? firstStartTime : previousReference.endTime;
2262
2272
  position = mediaSequenceNumber + skippedSegments + i;
2263
2273
 
2274
+ const discontinuityTag = shaka.hls.Utils.getFirstTagWithName(
2275
+ playlist.tags, 'EXT-X-DISCONTINUITY');
2276
+ if (discontinuityTag) {
2277
+ discontinuitySequence++;
2278
+ }
2279
+
2264
2280
  // Apply new AES-128 tags as you see them, keeping a running total.
2265
2281
  for (const drmTag of item.tags) {
2266
2282
  if (drmTag.name == 'EXT-X-KEY' &&
@@ -2298,6 +2314,8 @@ shaka.hls.HlsParser = class {
2298
2314
  previousReference = reference;
2299
2315
 
2300
2316
  if (reference) {
2317
+ reference.discontinuitySequence = discontinuitySequence;
2318
+
2301
2319
  if (this.config_.hls.ignoreManifestProgramDateTime &&
2302
2320
  this.minSequenceNumber_ != null &&
2303
2321
  position < this.minSequenceNumber_) {
@@ -69,6 +69,18 @@ shaka.media.ContentWorkarounds = class {
69
69
  })
70
70
  .fullBox('encv', onEncryptionMetadataBox)
71
71
  .fullBox('enca', onEncryptionMetadataBox)
72
+ .fullBox('hev1', (box) => {
73
+ boxesToModify.push({
74
+ box,
75
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
76
+ });
77
+ })
78
+ .fullBox('hvc1', (box) => {
79
+ boxesToModify.push({
80
+ box,
81
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
82
+ });
83
+ })
72
84
  .fullBox('avc1', (box) => {
73
85
  boxesToModify.push({
74
86
  box,
@@ -856,6 +856,22 @@ shaka.media.MediaSourceEngine = class {
856
856
  ]);
857
857
  }
858
858
 
859
+ /**
860
+ * Adjust timestamp offset to maintain AV sync across discontinuities.
861
+ * Only used in sequence mode.
862
+ *
863
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
864
+ * @param {number} timestampOffset
865
+ * @return {!Promise}
866
+ */
867
+ async resync(contentType, timestampOffset) {
868
+ goog.asserts.assert(this.sequenceMode_,
869
+ 'resyncAudio only used with sequence mode!');
870
+ await this.enqueueOperation_(
871
+ contentType,
872
+ () => this.setTimestampOffset_(contentType, timestampOffset));
873
+ }
874
+
859
875
  /**
860
876
  * @param {string=} reason Valid reasons are 'network' and 'decode'.
861
877
  * @return {!Promise}
@@ -239,6 +239,9 @@ shaka.media.SegmentReference = class {
239
239
 
240
240
  /** @type {?shaka.extern.HlsAes128Key} */
241
241
  this.hlsAes128Key = hlsAes128Key;
242
+
243
+ /** @type {number} */
244
+ this.discontinuitySequence = 0;
242
245
  }
243
246
 
244
247
  /**
@@ -618,6 +618,11 @@ shaka.media.StreamingEngine = class {
618
618
  * within the presentation timeline.
619
619
  */
620
620
  seeked() {
621
+ if (!this.playerInterface_) {
622
+ // Already destroyed.
623
+ return;
624
+ }
625
+
621
626
  const presentationTime = this.playerInterface_.getPresentationTime();
622
627
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
623
628
  const newTimeIsBuffered = (type) => {
@@ -675,7 +680,7 @@ shaka.media.StreamingEngine = class {
675
680
 
676
681
  /**
677
682
  * Clear the buffer for a given stream. Unlike clearBuffer_, this will handle
678
- * cases where a MediaState is performing an update. After this runs, every
683
+ * cases where a MediaState is performing an update. After this runs, the
679
684
  * MediaState will have a pending update.
680
685
  * @param {!shaka.media.StreamingEngine.MediaState_} mediaState
681
686
  * @private
@@ -791,9 +796,9 @@ shaka.media.StreamingEngine = class {
791
796
  for (const type of streamsByType.keys()) {
792
797
  const stream = streamsByType.get(type);
793
798
  if (!this.mediaStates_.has(type)) {
794
- const state = this.createMediaState_(stream);
795
- this.mediaStates_.set(type, state);
796
- this.scheduleUpdate_(state, 0);
799
+ const mediaState = this.createMediaState_(stream);
800
+ this.mediaStates_.set(type, mediaState);
801
+ this.scheduleUpdate_(mediaState, 0);
797
802
  }
798
803
  }
799
804
  }
@@ -905,7 +910,7 @@ shaka.media.StreamingEngine = class {
905
910
  'mediastate.stream should not have segmentIndex yet.');
906
911
  thisStream.closeSegmentIndex();
907
912
  }
908
- if (mediaState.updateTimer == null) {
913
+ if (!mediaState.performingUpdate && !mediaState.updateTimer) {
909
914
  this.scheduleUpdate_(mediaState, 0);
910
915
  }
911
916
  return;
@@ -1389,7 +1394,7 @@ shaka.media.StreamingEngine = class {
1389
1394
  // If the network slows down, abort the current fetch request and start
1390
1395
  // a new one, and ignore the error message.
1391
1396
  mediaState.performingUpdate = false;
1392
- mediaState.updateTimer = null;
1397
+ this.cancelUpdate_(mediaState);
1393
1398
  this.scheduleUpdate_(mediaState, 0);
1394
1399
  } else if (mediaState.type == ContentType.TEXT &&
1395
1400
  this.config_.ignoreTextStreamFailures) {
@@ -1462,7 +1467,10 @@ shaka.media.StreamingEngine = class {
1462
1467
 
1463
1468
  for (const mediaState of this.mediaStates_.values()) {
1464
1469
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1465
- if (mediaState.hasError) {
1470
+ // Only schedule an update if it has an error, but it's not mid-update
1471
+ // and there is not already an update scheduled.
1472
+ if (mediaState.hasError && !mediaState.performingUpdate &&
1473
+ !mediaState.updateTimer) {
1466
1474
  shaka.log.info(logPrefix, 'Retrying after failure...');
1467
1475
  mediaState.hasError = false;
1468
1476
  this.scheduleUpdate_(mediaState, delaySeconds);
@@ -1645,6 +1653,19 @@ shaka.media.StreamingEngine = class {
1645
1653
  }
1646
1654
  }
1647
1655
 
1656
+ if (this.manifest_.sequenceMode) {
1657
+ // Across discontinuity bounds, we should resync timestamps for
1658
+ // sequence mode playbacks. The next segment appended should
1659
+ // land at its theoretical timestamp from the segment index.
1660
+ const lastDiscontinuitySequence =
1661
+ mediaState.lastSegmentReference ?
1662
+ mediaState.lastSegmentReference.discontinuitySequence : null;
1663
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1664
+ operations.push(this.playerInterface_.mediaSourceEngine.resync(
1665
+ mediaState.type, reference.startTime));
1666
+ }
1667
+ }
1668
+
1648
1669
  await Promise.all(operations);
1649
1670
  }
1650
1671
 
@@ -1678,8 +1699,6 @@ shaka.media.StreamingEngine = class {
1678
1699
 
1679
1700
  await this.evict_(mediaState, presentationTime);
1680
1701
  this.destroyer_.ensureNotDestroyed();
1681
- shaka.log.v1(logPrefix, 'appending media segment at',
1682
- (reference.syncTime == null ? 'unknown' : reference.syncTime));
1683
1702
 
1684
1703
  // 'seeked' or 'adaptation' triggered logic applies only to this
1685
1704
  // appendBuffer() call.
@@ -1956,7 +1975,11 @@ shaka.media.StreamingEngine = class {
1956
1975
  shaka.log.debug(logPrefix, 'cleared buffer');
1957
1976
  mediaState.clearingBuffer = false;
1958
1977
  mediaState.endOfStream = false;
1959
- this.scheduleUpdate_(mediaState, 0);
1978
+ // Since the clear operation was async, check to make sure we're not doing
1979
+ // another update and we don't have one scheduled yet.
1980
+ if (!mediaState.performingUpdate && !mediaState.updateTimer) {
1981
+ this.scheduleUpdate_(mediaState, 0);
1982
+ }
1960
1983
  }
1961
1984
 
1962
1985
 
package/lib/player.js CHANGED
@@ -4140,6 +4140,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4140
4140
  return [];
4141
4141
  }
4142
4142
  const chapters = [];
4143
+ const uniqueChapters = new Set();
4143
4144
  for (const chaptersTrack of chaptersTracksWithLanguage) {
4144
4145
  if (chaptersTrack && chaptersTrack.cues) {
4145
4146
  for (const cue of chaptersTrack.cues) {
@@ -4154,7 +4155,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4154
4155
  startTime: cue.startTime,
4155
4156
  endTime: cue.endTime,
4156
4157
  };
4157
- chapters.push(chapter);
4158
+ if (!uniqueChapters.has(id)) {
4159
+ chapters.push(chapter);
4160
+ uniqueChapters.add(id);
4161
+ }
4158
4162
  }
4159
4163
  }
4160
4164
  }
@@ -6595,7 +6599,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6595
6599
  * @define {string} A version number taken from git at compile time.
6596
6600
  * @export
6597
6601
  */
6598
- shaka.Player.version = 'v4.2.4-uncompiled';
6602
+ shaka.Player.version = 'v4.2.6-uncompiled';
6599
6603
 
6600
6604
  // Initialize the deprecation system using the version string we just set
6601
6605
  // on the player.
@@ -0,0 +1,143 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.polyfill.AbortController');
8
+
9
+ goog.require('shaka.polyfill');
10
+ goog.require('shaka.util.FakeEvent');
11
+ goog.require('shaka.util.FakeEventTarget');
12
+
13
+ /**
14
+ * @summary A polyfill for systems that do not implement AbortController.
15
+ * This is used both with the fetch API for HTTP requests and inside the HLS
16
+ * parser.
17
+ * @export
18
+ * @extends AbortController
19
+ */
20
+ shaka.polyfill.AbortController = class {
21
+ /**
22
+ * Install the polyfill if needed.
23
+ * @export
24
+ */
25
+ static install() {
26
+ if (window.AbortController) {
27
+ // Not needed.
28
+ return;
29
+ }
30
+
31
+ window.AbortController = shaka.polyfill.AbortController;
32
+ window.AbortSignal = shaka.polyfill.AbortController.AbortSignal;
33
+ }
34
+
35
+ /** */
36
+ constructor() {
37
+ /** @private {!shaka.polyfill.AbortController.AbortSignal} */
38
+ this.signal_ = new shaka.polyfill.AbortController.AbortSignal();
39
+ }
40
+
41
+ /**
42
+ * @override
43
+ * @suppress {const|duplicate} Since the original is defined as "const", we
44
+ * need this suppression to override it.
45
+ */
46
+ get signal() {
47
+ return this.signal_;
48
+ }
49
+
50
+ /**
51
+ * @param {*=} reason
52
+ * @override
53
+ */
54
+ abort(reason) {
55
+ this.signal_.doAbort_(reason);
56
+ }
57
+ };
58
+
59
+ /**
60
+ * @summary A polyfill for AbortSignal, part of the AbortController API.
61
+ * @implements {AbortSignal}
62
+ */
63
+ shaka.polyfill.AbortController.AbortSignal =
64
+ class extends shaka.util.FakeEventTarget {
65
+ /** */
66
+ constructor() {
67
+ super();
68
+
69
+ /** @private {boolean} */
70
+ this.aborted_ = false;
71
+
72
+ /** @private {*} */
73
+ this.reason_ = undefined;
74
+
75
+ /** @type {?function(!Event)} */
76
+ this.onabort = null;
77
+ }
78
+
79
+ /** @override */
80
+ get aborted() {
81
+ return this.aborted_;
82
+ }
83
+
84
+ /** @return {*} */
85
+ get reason() {
86
+ return this.reason_;
87
+ }
88
+
89
+ /**
90
+ * @param {*} reason
91
+ * @private
92
+ */
93
+ doAbort_(reason) {
94
+ if (this.aborted_) {
95
+ return;
96
+ }
97
+
98
+ this.aborted_ = true;
99
+ this.reason_ = reason;
100
+ if (this.reason_ === undefined) {
101
+ // This is equivalent to a native implementation.
102
+ this.reason_ = new DOMException(
103
+ 'signal is aborted without reason', 'AbortError');
104
+ }
105
+
106
+ // According to MDN:
107
+ // "Event type - A generic Event with no added properties."
108
+ const event = new shaka.util.FakeEvent('abort');
109
+ if (this.onabort) {
110
+ this.onabort(event);
111
+ }
112
+ this.dispatchEvent(event);
113
+ }
114
+
115
+
116
+ /**
117
+ * @param {*=} reason
118
+ * @return {!AbortSignal}
119
+ */
120
+ static abort(reason) {
121
+ const signal = new shaka.polyfill.AbortController.AbortSignal();
122
+ signal.doAbort_(reason);
123
+ return signal;
124
+ }
125
+
126
+ /**
127
+ * @param {number} timeMs
128
+ * @return {!AbortSignal}
129
+ */
130
+ static timeout(timeMs) {
131
+ const signal = new shaka.polyfill.AbortController.AbortSignal();
132
+
133
+ window.setTimeout(() => {
134
+ // This is equivalent to a native implementation.
135
+ signal.doAbort_(new DOMException('signal timed out', 'TimeoutError'));
136
+ }, timeMs);
137
+
138
+ return signal;
139
+ }
140
+ };
141
+
142
+
143
+ shaka.polyfill.register(shaka.polyfill.AbortController.install);
@@ -8,6 +8,7 @@ goog.provide('shaka.polyfill.MediaCapabilities');
8
8
 
9
9
  goog.require('shaka.log');
10
10
  goog.require('shaka.polyfill');
11
+ goog.require('shaka.util.Error');
11
12
  goog.require('shaka.util.Platform');
12
13
 
13
14
 
@@ -96,37 +97,48 @@ shaka.polyfill.MediaCapabilities = class {
96
97
  return res;
97
98
  }
98
99
 
100
+ const videoConfig = mediaDecodingConfig['video'];
101
+ const audioConfig = mediaDecodingConfig['audio'];
102
+
99
103
  if (mediaDecodingConfig.type == 'media-source') {
100
104
  if (!shaka.util.Platform.supportsMediaSource()) {
101
105
  return res;
102
106
  }
103
107
  // Use 'MediaSource.isTypeSupported' to check if the stream is supported.
104
- if (mediaDecodingConfig['video']) {
105
- const contentType = mediaDecodingConfig['video'].contentType;
106
- const isSupported = MediaSource.isTypeSupported(contentType);
108
+ // Cast platforms will additionally check canDisplayType(), which
109
+ // accepts extended MIME type parameters.
110
+ // See: https://github.com/shaka-project/shaka-player/issues/4726
111
+ if (videoConfig) {
112
+ let isSupported;
113
+ if (shaka.util.Platform.isChromecast()) {
114
+ isSupported =
115
+ shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
116
+ } else {
117
+ isSupported = MediaSource.isTypeSupported(videoConfig.contentType);
118
+ }
107
119
  if (!isSupported) {
108
120
  return res;
109
121
  }
110
122
  }
111
123
 
112
- if (mediaDecodingConfig['audio']) {
113
- const contentType = mediaDecodingConfig['audio'].contentType;
124
+ if (audioConfig) {
125
+ const contentType = audioConfig.contentType;
114
126
  const isSupported = MediaSource.isTypeSupported(contentType);
115
127
  if (!isSupported) {
116
128
  return res;
117
129
  }
118
130
  }
119
131
  } else if (mediaDecodingConfig.type == 'file') {
120
- if (mediaDecodingConfig['video']) {
121
- const contentType = mediaDecodingConfig['video'].contentType;
132
+ if (videoConfig) {
133
+ const contentType = videoConfig.contentType;
122
134
  const isSupported = shaka.util.Platform.supportsMediaType(contentType);
123
135
  if (!isSupported) {
124
136
  return res;
125
137
  }
126
138
  }
127
139
 
128
- if (mediaDecodingConfig['audio']) {
129
- const contentType = mediaDecodingConfig['audio'].contentType;
140
+ if (audioConfig) {
141
+ const contentType = audioConfig.contentType;
130
142
  const isSupported = shaka.util.Platform.supportsMediaType(contentType);
131
143
  if (!isSupported) {
132
144
  return res;
@@ -201,6 +213,44 @@ shaka.polyfill.MediaCapabilities = class {
201
213
 
202
214
  return res;
203
215
  }
216
+
217
+ /**
218
+ * Checks if the given media parameters of the video or audio streams are
219
+ * supported by the Cast platform.
220
+ * @param {!VideoConfiguration} videoConfig The 'video' field of the
221
+ * MediaDecodingConfiguration.
222
+ * @return {boolean} `true` when the stream can be displayed on a Cast device.
223
+ * @private
224
+ */
225
+ static canCastDisplayType_(videoConfig) {
226
+ if (!(window.cast)) {
227
+ shaka.log.error('Expected cast namespace to be available!');
228
+ throw new shaka.util.Error(
229
+ shaka.util.Error.Severity.CRITICAL,
230
+ shaka.util.Error.Category.CAST,
231
+ shaka.util.Error.Code.CAST_API_UNAVAILABLE);
232
+ } else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
233
+ shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
234
+ 'MediaSource.isTypeSupported() for type support.');
235
+ return MediaSource.isTypeSupported(videoConfig.contentType);
236
+ }
237
+
238
+ let displayType = videoConfig.contentType;
239
+ if (videoConfig.width && videoConfig.height) {
240
+ displayType +=
241
+ `; width=${videoConfig.width}; height=${videoConfig.height}`;
242
+ }
243
+ if (videoConfig.framerate) {
244
+ displayType += `; framerate=${videoConfig.framerate}`;
245
+ }
246
+ if (videoConfig.transferFunction === 'pq') {
247
+ // A "PQ" transfer function indicates this is an HDR-capable stream;
248
+ // "smpte2084" is the published standard. We need to inform the platform
249
+ // this query is specifically for HDR.
250
+ displayType += '; eotf=smpte2084';
251
+ }
252
+ return cast.__platform__.canDisplayType(displayType);
253
+ }
204
254
  };
205
255
 
206
256
  /**