shaka-player 4.3.0 → 4.3.2

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 (78) hide show
  1. package/.github/workflows/build-and-test.yaml +20 -0
  2. package/AUTHORS +2 -0
  3. package/CHANGELOG.md +44 -0
  4. package/CONTRIBUTORS +2 -0
  5. package/README.md +1 -1
  6. package/conditional/dummy_cast_proxy.js +120 -0
  7. package/dist/demo.compiled.debug.js +104 -110
  8. package/dist/demo.compiled.debug.map +3 -3
  9. package/dist/demo.compiled.js +104 -110
  10. package/dist/demo.compiled.map +3 -3
  11. package/dist/deps.js +8 -6
  12. package/dist/locales.js +32 -32
  13. package/dist/receiver.compiled.debug.js +54 -60
  14. package/dist/receiver.compiled.debug.map +3 -3
  15. package/dist/receiver.compiled.js +54 -60
  16. package/dist/receiver.compiled.map +3 -3
  17. package/dist/shaka-player.compiled.d.ts +19 -2
  18. package/dist/shaka-player.compiled.debug.d.ts +19 -2
  19. package/dist/shaka-player.compiled.debug.externs.js +102 -80
  20. package/dist/shaka-player.compiled.debug.js +657 -652
  21. package/dist/shaka-player.compiled.debug.map +4 -4
  22. package/dist/shaka-player.compiled.externs.js +102 -80
  23. package/dist/shaka-player.compiled.js +904 -901
  24. package/dist/shaka-player.compiled.map +4 -4
  25. package/dist/shaka-player.ui.d.ts +19 -2
  26. package/dist/shaka-player.ui.debug.d.ts +19 -2
  27. package/dist/shaka-player.ui.debug.externs.js +102 -80
  28. package/dist/shaka-player.ui.debug.js +703 -698
  29. package/dist/shaka-player.ui.debug.map +4 -4
  30. package/dist/shaka-player.ui.externs.js +102 -80
  31. package/dist/shaka-player.ui.js +1029 -1026
  32. package/dist/shaka-player.ui.map +4 -4
  33. package/docs/tutorials/welcome.md +2 -2
  34. package/externs/shaka/mp4_parser.js +3 -0
  35. package/externs/shaka/player.js +2 -1
  36. package/lib/ads/ad_manager.js +2 -2
  37. package/lib/cea/cea708_window.js +2 -2
  38. package/lib/cea/mp4_cea_parser.js +37 -16
  39. package/lib/dash/segment_template.js +1 -1
  40. package/lib/hls/hls_parser.js +49 -23
  41. package/lib/media/content_workarounds.js +12 -0
  42. package/lib/media/media_source_capabilities.js +36 -0
  43. package/lib/media/media_source_engine.js +28 -7
  44. package/lib/media/segment_reference.js +3 -0
  45. package/lib/media/streaming_engine.js +33 -10
  46. package/lib/media/transmuxer.js +6 -4
  47. package/lib/player.js +10 -6
  48. package/lib/polyfill/abort_controller.js +143 -0
  49. package/lib/polyfill/media_capabilities.js +65 -11
  50. package/lib/polyfill/patchedmediakeys_apple.js +33 -28
  51. package/lib/text/ttml_text_parser.js +32 -7
  52. package/lib/text/ui_text_displayer.js +17 -0
  53. package/lib/util/mp4_box_parsers.js +24 -0
  54. package/lib/util/mp4_parser.js +35 -0
  55. package/lib/util/player_configuration.js +10 -3
  56. package/lib/util/stream_utils.js +35 -13
  57. package/package.json +4 -4
  58. package/shaka-player.uncompiled.js +1 -0
  59. package/test/ads/ad_manager_unit.js +3 -1
  60. package/test/dash/dash_parser_manifest_unit.js +1 -0
  61. package/test/hls/hls_parser_unit.js +132 -0
  62. package/test/media/drm_engine_unit.js +11 -1
  63. package/test/media/media_source_engine_integration.js +78 -2
  64. package/test/player_integration.js +10 -3
  65. package/test/polyfill/media_capabilities_unit.js +259 -0
  66. package/test/polyfill/patchedmediakeys_apple_unit.js +6 -0
  67. package/test/test/boot.js +29 -11
  68. package/test/test/util/manifest_parser_util.js +4 -3
  69. package/test/test/util/stream_generator.js +212 -8
  70. package/test/test/util/test_scheme.js +6 -5
  71. package/test/test/util/util.js +3 -2
  72. package/test/text/mp4_ttml_parser_unit.js +6 -4
  73. package/test/text/ttml_text_parser_unit.js +119 -86
  74. package/test/text/ui_text_displayer_unit.js +76 -0
  75. package/test/util/stream_utils_unit.js +18 -0
  76. package/test/util/xml_utils_unit.js +6 -1
  77. package/third_party/closure-uri/uri.js +1 -1
  78. package/ui/controls.js +49 -36
@@ -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._
@@ -11,6 +11,7 @@
11
11
 
12
12
  /**
13
13
  * @typedef {{
14
+ * name: string,
14
15
  * parser: !shaka.util.Mp4Parser,
15
16
  * partialOkay: boolean,
16
17
  * start: number,
@@ -21,6 +22,8 @@
21
22
  * has64BitSize: boolean
22
23
  * }}
23
24
  *
25
+ * @property {string} name
26
+ * The box name, a 4-character string (fourcc).
24
27
  * @property {!shaka.util.Mp4Parser} parser
25
28
  * The parser that parsed this box. The parser can be used to parse child
26
29
  * boxes where the configuration of the current parser is needed to parsed
@@ -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;
@@ -66,14 +66,7 @@ shaka.cea.Mp4CeaParser = class {
66
66
  const trackIds = [];
67
67
  const timescales = [];
68
68
 
69
- // This generates a box-parser callback that simply sets a particular
70
- // bitstream format. It makes the list of codec-signalling boxes below
71
- // more compact and readable.
72
- const bitstreamFormatSetter = (format) => {
73
- return (box) => {
74
- this.bitstreamFormat_ = format;
75
- };
76
- };
69
+ const codecBoxParser = (box) => this.setBitstreamFormat_(box.name);
77
70
 
78
71
  new Mp4Parser()
79
72
  .box('moov', Mp4Parser.children)
@@ -108,14 +101,21 @@ shaka.cea.Mp4CeaParser = class {
108
101
  .fullBox('stsd', Mp4Parser.sampleDescription)
109
102
 
110
103
  // These are the various boxes that signal a codec.
111
- // Use these to set bistreamFormat_.
112
- .box('avc1', bitstreamFormatSetter(BitstreamFormat.H264))
113
- .box('avc3', bitstreamFormatSetter(BitstreamFormat.H264))
114
- .box('hev1', bitstreamFormatSetter(BitstreamFormat.H265))
115
- .box('hvc1', bitstreamFormatSetter(BitstreamFormat.H265))
116
- // Dobly vision is also H265.
117
- .box('dvh1', bitstreamFormatSetter(BitstreamFormat.H265))
118
- .box('dvhe', bitstreamFormatSetter(BitstreamFormat.H265))
104
+ .box('avc1', codecBoxParser)
105
+ .box('avc3', codecBoxParser)
106
+ .box('hev1', codecBoxParser)
107
+ .box('hvc1', codecBoxParser)
108
+ .box('dvh1', codecBoxParser)
109
+ .box('dvhe', codecBoxParser)
110
+
111
+ // This signals an encrypted sample, which we can go inside of to find
112
+ // the codec used.
113
+ .box('encv', Mp4Parser.visualSampleEntry)
114
+ .box('sinf', Mp4Parser.children)
115
+ .box('frma', (box) => {
116
+ const {codec} = shaka.util.Mp4BoxParsers.parseFRMA(box.reader);
117
+ this.setBitstreamFormat_(codec);
118
+ })
119
119
 
120
120
  .parse(initSegment, /* partialOkay= */ true);
121
121
 
@@ -338,6 +338,16 @@ shaka.cea.Mp4CeaParser = class {
338
338
  }
339
339
  }
340
340
  }
341
+
342
+ /**
343
+ * @param {string} codec A fourcc for a codec.
344
+ * @private
345
+ */
346
+ setBitstreamFormat_(codec) {
347
+ if (codec in shaka.cea.Mp4CeaParser.CodecBitstreamMap_) {
348
+ this.bitstreamFormat_ = shaka.cea.Mp4CeaParser.CodecBitstreamMap_[codec];
349
+ }
350
+ }
341
351
  };
342
352
 
343
353
  /** @enum {number} */
@@ -346,3 +356,14 @@ shaka.cea.Mp4CeaParser.BitstreamFormat = {
346
356
  H264: 1,
347
357
  H265: 2,
348
358
  };
359
+
360
+ /** @private {Object.<string, shaka.cea.Mp4CeaParser.BitstreamFormat>} */
361
+ shaka.cea.Mp4CeaParser.CodecBitstreamMap_ = {
362
+ 'avc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H264,
363
+ 'avc3': shaka.cea.Mp4CeaParser.BitstreamFormat.H264,
364
+ 'hev1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
365
+ 'hvc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
366
+ // Dobly vision is also H265.
367
+ 'dvh1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
368
+ 'dvhe': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
369
+ };
@@ -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(
@@ -618,9 +618,23 @@ shaka.hls.HlsParser = class {
618
618
  if (playlist.segments.length) {
619
619
  const parsedUri = new goog.Uri(playlist.segments[0].absoluteUri);
620
620
  const extension = parsedUri.getPath().split('.').pop();
621
- const mimeType = HlsParser.RAW_FORMATS_TO_MIME_TYPES_[extension];
621
+ let mimeType = HlsParser.RAW_FORMATS_TO_MIME_TYPES_[extension];
622
622
  if (mimeType) {
623
623
  fullMimeType = mimeType;
624
+ } else if (extension === 'ts') {
625
+ // TODO: Fetch one segment a use the TsParser to analize if there is
626
+ // video, audio or both.
627
+ } else if (extension === 'mp4') {
628
+ // TODO: Fetch one segment a use the Mp4Parser to analize if there is
629
+ // video, audio or both.
630
+ } else if (HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_[extension]) {
631
+ mimeType = HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_[extension];
632
+ const defaultAudioCodec = this.config_.hls.defaultAudioCodec;
633
+ fullMimeType = `${mimeType}; codecs="${defaultAudioCodec}"`;
634
+ } else if (HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_[extension]) {
635
+ mimeType = HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_[extension];
636
+ const defaultVideoCodec = this.config_.hls.defaultVideoCodec;
637
+ fullMimeType = `${mimeType}; codecs="${defaultVideoCodec}"`;
624
638
  }
625
639
  }
626
640
 
@@ -1123,7 +1137,7 @@ shaka.hls.HlsParser = class {
1123
1137
  shaka.log.debug('Guessing audio-only.');
1124
1138
  type = ContentType.AUDIO;
1125
1139
  ignoreStream = true;
1126
- } else if (res.video.length) {
1140
+ } else if (res.video.length && !res.audio.length) {
1127
1141
  // There are associated video streams. Assume this is audio.
1128
1142
  shaka.log.debug('Guessing audio-only.');
1129
1143
  type = ContentType.AUDIO;
@@ -1677,9 +1691,10 @@ shaka.hls.HlsParser = class {
1677
1691
  stream.encrypted = realStream.encrypted;
1678
1692
  stream.drmInfos = realStream.drmInfos;
1679
1693
  stream.keyIds = realStream.keyIds;
1680
- stream.kind = realStream.kind;
1681
- stream.roles = realStream.roles;
1682
1694
  stream.mimeType = realStream.mimeType;
1695
+ if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
1696
+ stream.codecs = '';
1697
+ }
1683
1698
 
1684
1699
  // Since we lazy-loaded this content, the player may need to create new
1685
1700
  // sessions for the DRM info in this stream.
@@ -1694,6 +1709,10 @@ shaka.hls.HlsParser = class {
1694
1709
  // To aid manifest filtering, assume before loading that all video
1695
1710
  // renditions have the same MIME type. (And likewise for audio.)
1696
1711
  otherStreamInfo.stream.mimeType = realStream.mimeType;
1712
+ if (shaka.media.MediaSourceEngine.RAW_FORMATS
1713
+ .includes(otherStreamInfo.stream.mimeType)) {
1714
+ otherStreamInfo.stream.codecs = '';
1715
+ }
1697
1716
  }
1698
1717
  }
1699
1718
  }
@@ -1923,23 +1942,20 @@ shaka.hls.HlsParser = class {
1923
1942
 
1924
1943
  const mediaSequenceToStartTime = this.isLive_() ?
1925
1944
  this.mediaSequenceToStartTimeByType_.get(type) : new Map();
1945
+ const prevLowLatencyMode = this.lowLatencyMode_;
1926
1946
  const segments = this.createSegments_(verbatimMediaPlaylistUri, playlist,
1927
1947
  type, mimeType, mediaSequenceToStartTime, mediaVariables);
1928
1948
 
1949
+ // This happens when autoLowLatencyMode is true, so we need set the
1950
+ // correct lowLatencyPresentationDelay_
1951
+ if (prevLowLatencyMode != this.lowLatencyMode_) {
1952
+ this.determinePresentationType_(playlist);
1953
+ }
1954
+
1929
1955
  const lastEndTime = segments[segments.length - 1].endTime;
1930
1956
  /** @type {!shaka.media.SegmentIndex} */
1931
1957
  const segmentIndex = new shaka.media.SegmentIndex(segments);
1932
1958
 
1933
- const kind = (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
1934
- shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined;
1935
-
1936
- const roles = [];
1937
- if (characteristics) {
1938
- for (const characteristic of characteristics.split(',')) {
1939
- roles.push(characteristic);
1940
- }
1941
- }
1942
-
1943
1959
  const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
1944
1960
  playlist.tags, 'EXT-X-SERVER-CONTROL');
1945
1961
  const canSkipSegments = serverControlTag ?
@@ -1951,8 +1967,6 @@ shaka.hls.HlsParser = class {
1951
1967
  stream.encrypted = encrypted;
1952
1968
  stream.drmInfos = drmInfos;
1953
1969
  stream.keyIds = keyIds;
1954
- stream.kind = kind;
1955
- stream.roles = roles;
1956
1970
  stream.mimeType = mimeType;
1957
1971
 
1958
1972
  return {
@@ -2002,7 +2016,8 @@ shaka.hls.HlsParser = class {
2002
2016
  segmentIndex: null,
2003
2017
  mimeType,
2004
2018
  codecs,
2005
- kind: undefined,
2019
+ kind: (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
2020
+ shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined,
2006
2021
  encrypted: false,
2007
2022
  drmInfos: [],
2008
2023
  keyIds: new Set(),
@@ -2018,7 +2033,7 @@ shaka.hls.HlsParser = class {
2018
2033
  width: undefined,
2019
2034
  height: undefined,
2020
2035
  bandwidth: undefined,
2021
- roles: [],
2036
+ roles: characteristics ? characteristics.split(',') : [],
2022
2037
  forced,
2023
2038
  channelsCount,
2024
2039
  audioSamplingRate: null,
@@ -2156,8 +2171,11 @@ shaka.hls.HlsParser = class {
2156
2171
  shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
2157
2172
  }
2158
2173
 
2174
+ const algorithm = {
2175
+ name: 'AES-CBC',
2176
+ };
2159
2177
  keyInfo.cryptoKey = await window.crypto.subtle.importKey(
2160
- 'raw', keyResponse.data, 'AES-CBC', true, ['decrypt']);
2178
+ 'raw', keyResponse.data, algorithm, true, ['decrypt']);
2161
2179
  keyInfo.fetchKey = undefined; // No longer needed.
2162
2180
  };
2163
2181
 
@@ -2574,12 +2592,12 @@ shaka.hls.HlsParser = class {
2574
2592
  /** @type {shaka.extern.HlsAes128Key|undefined} */
2575
2593
  let hlsAes128Key = undefined;
2576
2594
 
2577
- // We may need to look at the media itself to determine a segment start
2578
- // time.
2595
+ let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2596
+ playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
2579
2597
  const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2580
2598
  playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
2581
- const skipTag = shaka.hls.Utils.getFirstTagWithName(playlist.tags,
2582
- 'EXT-X-SKIP');
2599
+ const skipTag = shaka.hls.Utils.getFirstTagWithName(
2600
+ playlist.tags, 'EXT-X-SKIP');
2583
2601
  const skippedSegments =
2584
2602
  skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
2585
2603
  let position = mediaSequenceNumber + skippedSegments;
@@ -2602,6 +2620,12 @@ shaka.hls.HlsParser = class {
2602
2620
  (i == 0) ? firstStartTime : previousReference.endTime;
2603
2621
  position = mediaSequenceNumber + skippedSegments + i;
2604
2622
 
2623
+ const discontinuityTag = shaka.hls.Utils.getFirstTagWithName(
2624
+ playlist.tags, 'EXT-X-DISCONTINUITY');
2625
+ if (discontinuityTag) {
2626
+ discontinuitySequence++;
2627
+ }
2628
+
2605
2629
  // Apply new AES-128 tags as you see them, keeping a running total.
2606
2630
  for (const drmTag of item.tags) {
2607
2631
  if (drmTag.name == 'EXT-X-KEY' &&
@@ -2639,6 +2663,8 @@ shaka.hls.HlsParser = class {
2639
2663
  previousReference = reference;
2640
2664
 
2641
2665
  if (reference) {
2666
+ reference.discontinuitySequence = discontinuitySequence;
2667
+
2642
2668
  if (this.config_.hls.ignoreManifestProgramDateTime &&
2643
2669
  this.minSequenceNumber_ != null &&
2644
2670
  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,
@@ -0,0 +1,36 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.media.Capabilities');
8
+
9
+ /**
10
+ * @summary
11
+ * This is for capturing all media source capabilities on current platform.
12
+ * And this is for static check and can not be constructed.
13
+ */
14
+ shaka.media.Capabilities = class {
15
+ /**
16
+ * Cache browser engine call to improve performance on some poor platforms
17
+ *
18
+ * @param {string} type
19
+ * @return {boolean}
20
+ */
21
+ static isTypeSupported(type) {
22
+ const supportMap = shaka.media.Capabilities.MediaSourceTypeSupportMap;
23
+ if (supportMap.has(type)) {
24
+ return supportMap.get(type);
25
+ }
26
+ const currentSupport = MediaSource.isTypeSupported(type);
27
+ supportMap.set(type, currentSupport);
28
+ return currentSupport;
29
+ }
30
+ };
31
+
32
+ /**
33
+ * Public it for unit test, and developer could also check the support map.
34
+ * @type {!Map.<string, boolean>}
35
+ */
36
+ shaka.media.Capabilities.MediaSourceTypeSupportMap = new Map();
@@ -8,6 +8,7 @@ goog.provide('shaka.media.MediaSourceEngine');
8
8
 
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
+ goog.require('shaka.media.Capabilities');
11
12
  goog.require('shaka.media.ContentWorkarounds');
12
13
  goog.require('shaka.media.ClosedCaptionParser');
13
14
  goog.require('shaka.media.IClosedCaptionParser');
@@ -175,7 +176,7 @@ shaka.media.MediaSourceEngine = class {
175
176
  stream.mimeType, stream.codecs);
176
177
  const extendedMimeType = shaka.util.MimeUtils.getExtendedType(stream);
177
178
  return shaka.text.TextEngine.isTypeSupported(fullMimeType) ||
178
- MediaSource.isTypeSupported(extendedMimeType) ||
179
+ shaka.media.Capabilities.isTypeSupported(extendedMimeType) ||
179
180
  shaka.media.Transmuxer.isSupported(fullMimeType, stream.type);
180
181
  }
181
182
 
@@ -231,7 +232,7 @@ shaka.media.MediaSourceEngine = class {
231
232
  if (shaka.text.TextEngine.isTypeSupported(type)) {
232
233
  support[type] = true;
233
234
  } else {
234
- support[type] = MediaSource.isTypeSupported(type) ||
235
+ support[type] = shaka.media.Capabilities.isTypeSupported(type) ||
235
236
  shaka.media.Transmuxer.isSupported(type);
236
237
  }
237
238
  } else {
@@ -365,7 +366,8 @@ shaka.media.MediaSourceEngine = class {
365
366
  if (contentType == ContentType.TEXT) {
366
367
  this.reinitText(mimeType, sequenceMode);
367
368
  } else {
368
- if ((forceTransmux || !MediaSource.isTypeSupported(mimeType)) &&
369
+ if ((forceTransmux ||
370
+ !shaka.media.Capabilities.isTypeSupported(mimeType)) &&
369
371
  shaka.media.Transmuxer.isSupported(mimeType, contentType)) {
370
372
  this.transmuxers_[contentType] =
371
373
  new shaka.media.Transmuxer(mimeType);
@@ -586,11 +588,14 @@ shaka.media.MediaSourceEngine = class {
586
588
  mimeType = this.transmuxers_[contentType].getOrginalMimeType();
587
589
  }
588
590
  if (shaka.util.TsParser.probe(uint8ArrayData)) {
589
- const metadata = new shaka.util.TsParser().parse(uint8ArrayData)
590
- .getMetadata();
591
+ const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
592
+ // The SourceBuffer timestampOffset may or may not be set yet, so this is
593
+ // the timestamp offset that would eventually compute for this segment
594
+ // either way.
595
+ const timestampOffset =
596
+ reference.startTime - (tsParser.getStartTime()[contentType] || 0);
597
+ const metadata = tsParser.getMetadata();
591
598
  if (metadata.length) {
592
- const timestampOffset =
593
- this.sourceBuffers_[contentType].timestampOffset;
594
599
  this.onMetadata_(metadata, timestampOffset,
595
600
  reference ? reference.endTime : null);
596
601
  }
@@ -919,6 +924,22 @@ shaka.media.MediaSourceEngine = class {
919
924
  ]);
920
925
  }
921
926
 
927
+ /**
928
+ * Adjust timestamp offset to maintain AV sync across discontinuities.
929
+ * Only used in sequence mode.
930
+ *
931
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
932
+ * @param {number} timestampOffset
933
+ * @return {!Promise}
934
+ */
935
+ async resync(contentType, timestampOffset) {
936
+ goog.asserts.assert(this.sequenceMode_,
937
+ 'resyncAudio only used with sequence mode!');
938
+ await this.enqueueOperation_(
939
+ contentType,
940
+ () => this.setTimestampOffset_(contentType, timestampOffset));
941
+ }
942
+
922
943
  /**
923
944
  * @param {string=} reason Valid reasons are 'network' and 'decode'.
924
945
  * @return {!Promise}
@@ -246,6 +246,9 @@ shaka.media.SegmentReference = class {
246
246
 
247
247
  /** @type {?shaka.media.SegmentReference.ThumbnailSprite} */
248
248
  this.thumbnailSprite = null;
249
+
250
+ /** @type {number} */
251
+ this.discontinuitySequence = 0;
249
252
  }
250
253
 
251
254
  /**
@@ -622,6 +622,11 @@ shaka.media.StreamingEngine = class {
622
622
  * within the presentation timeline.
623
623
  */
624
624
  seeked() {
625
+ if (!this.playerInterface_) {
626
+ // Already destroyed.
627
+ return;
628
+ }
629
+
625
630
  const presentationTime = this.playerInterface_.getPresentationTime();
626
631
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
627
632
  const newTimeIsBuffered = (type) => {
@@ -679,7 +684,7 @@ shaka.media.StreamingEngine = class {
679
684
 
680
685
  /**
681
686
  * Clear the buffer for a given stream. Unlike clearBuffer_, this will handle
682
- * cases where a MediaState is performing an update. After this runs, every
687
+ * cases where a MediaState is performing an update. After this runs, the
683
688
  * MediaState will have a pending update.
684
689
  * @param {!shaka.media.StreamingEngine.MediaState_} mediaState
685
690
  * @private
@@ -795,9 +800,9 @@ shaka.media.StreamingEngine = class {
795
800
  for (const type of streamsByType.keys()) {
796
801
  const stream = streamsByType.get(type);
797
802
  if (!this.mediaStates_.has(type)) {
798
- const state = this.createMediaState_(stream);
799
- this.mediaStates_.set(type, state);
800
- this.scheduleUpdate_(state, 0);
803
+ const mediaState = this.createMediaState_(stream);
804
+ this.mediaStates_.set(type, mediaState);
805
+ this.scheduleUpdate_(mediaState, 0);
801
806
  }
802
807
  }
803
808
  }
@@ -909,7 +914,7 @@ shaka.media.StreamingEngine = class {
909
914
  'mediastate.stream should not have segmentIndex yet.');
910
915
  thisStream.closeSegmentIndex();
911
916
  }
912
- if (mediaState.updateTimer == null) {
917
+ if (!mediaState.performingUpdate && !mediaState.updateTimer) {
913
918
  this.scheduleUpdate_(mediaState, 0);
914
919
  }
915
920
  return;
@@ -1394,7 +1399,7 @@ shaka.media.StreamingEngine = class {
1394
1399
  // If the network slows down, abort the current fetch request and start
1395
1400
  // a new one, and ignore the error message.
1396
1401
  mediaState.performingUpdate = false;
1397
- mediaState.updateTimer = null;
1402
+ this.cancelUpdate_(mediaState);
1398
1403
  this.scheduleUpdate_(mediaState, 0);
1399
1404
  } else if (mediaState.type == ContentType.TEXT &&
1400
1405
  this.config_.ignoreTextStreamFailures) {
@@ -1467,7 +1472,10 @@ shaka.media.StreamingEngine = class {
1467
1472
 
1468
1473
  for (const mediaState of this.mediaStates_.values()) {
1469
1474
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1470
- if (mediaState.hasError) {
1475
+ // Only schedule an update if it has an error, but it's not mid-update
1476
+ // and there is not already an update scheduled.
1477
+ if (mediaState.hasError && !mediaState.performingUpdate &&
1478
+ !mediaState.updateTimer) {
1471
1479
  shaka.log.info(logPrefix, 'Retrying after failure...');
1472
1480
  mediaState.hasError = false;
1473
1481
  this.scheduleUpdate_(mediaState, delaySeconds);
@@ -1661,6 +1669,19 @@ shaka.media.StreamingEngine = class {
1661
1669
  }
1662
1670
  }
1663
1671
 
1672
+ if (this.manifest_.sequenceMode) {
1673
+ // Across discontinuity bounds, we should resync timestamps for
1674
+ // sequence mode playbacks. The next segment appended should
1675
+ // land at its theoretical timestamp from the segment index.
1676
+ const lastDiscontinuitySequence =
1677
+ mediaState.lastSegmentReference ?
1678
+ mediaState.lastSegmentReference.discontinuitySequence : null;
1679
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1680
+ operations.push(this.playerInterface_.mediaSourceEngine.resync(
1681
+ mediaState.type, reference.startTime));
1682
+ }
1683
+ }
1684
+
1664
1685
  await Promise.all(operations);
1665
1686
  }
1666
1687
 
@@ -1715,8 +1736,6 @@ shaka.media.StreamingEngine = class {
1715
1736
 
1716
1737
  await this.evict_(mediaState, presentationTime);
1717
1738
  this.destroyer_.ensureNotDestroyed();
1718
- shaka.log.v1(logPrefix, 'appending media segment at',
1719
- (reference.syncTime == null ? 'unknown' : reference.syncTime));
1720
1739
 
1721
1740
  // 'seeked' or 'adaptation' triggered logic applies only to this
1722
1741
  // appendBuffer() call.
@@ -2079,7 +2098,11 @@ shaka.media.StreamingEngine = class {
2079
2098
  shaka.log.debug(logPrefix, 'cleared buffer');
2080
2099
  mediaState.clearingBuffer = false;
2081
2100
  mediaState.endOfStream = false;
2082
- this.scheduleUpdate_(mediaState, 0);
2101
+ // Since the clear operation was async, check to make sure we're not doing
2102
+ // another update and we don't have one scheduled yet.
2103
+ if (!mediaState.performingUpdate && !mediaState.updateTimer) {
2104
+ this.scheduleUpdate_(mediaState, 0);
2105
+ }
2083
2106
  }
2084
2107
 
2085
2108
 
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.media.Transmuxer');
8
8
 
9
9
  goog.require('goog.asserts');
10
+ goog.require('shaka.media.Capabilities');
10
11
  goog.require('shaka.util.BufferUtils');
11
12
  goog.require('shaka.util.Error');
12
13
  goog.require('shaka.util.IDestroyable');
@@ -74,6 +75,7 @@ shaka.media.Transmuxer = class {
74
75
  */
75
76
  static isSupported(mimeType, contentType) {
76
77
  const Transmuxer = shaka.media.Transmuxer;
78
+ const Capabilities = shaka.media.Capabilities;
77
79
 
78
80
  const isTs = Transmuxer.isTsContainer_(mimeType);
79
81
  const isAac = Transmuxer.isAacContainer_(mimeType);
@@ -83,11 +85,11 @@ shaka.media.Transmuxer = class {
83
85
  }
84
86
 
85
87
  if (isAac) {
86
- return MediaSource.isTypeSupported(Transmuxer.convertAacCodecs_());
88
+ return Capabilities.isTypeSupported(Transmuxer.convertAacCodecs_());
87
89
  }
88
90
 
89
91
  if (contentType) {
90
- return MediaSource.isTypeSupported(
92
+ return Capabilities.isTypeSupported(
91
93
  Transmuxer.convertTsCodecs_(contentType, mimeType));
92
94
  }
93
95
 
@@ -95,8 +97,8 @@ shaka.media.Transmuxer = class {
95
97
 
96
98
  const audioMime = Transmuxer.convertTsCodecs_(ContentType.AUDIO, mimeType);
97
99
  const videoMime = Transmuxer.convertTsCodecs_(ContentType.VIDEO, mimeType);
98
- return MediaSource.isTypeSupported(audioMime) ||
99
- MediaSource.isTypeSupported(videoMime);
100
+ return Capabilities.isTypeSupported(audioMime) ||
101
+ Capabilities.isTypeSupported(videoMime);
100
102
  }
101
103
 
102
104
 
package/lib/player.js CHANGED
@@ -2160,15 +2160,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2160
2160
  // Unless the user has already picked a variant, anyway, by calling
2161
2161
  // selectVariantTrack before this loading stage.
2162
2162
  let initialVariant = null;
2163
- const activeVariantTrack = this.getVariantTracks().find((t) => t.active);
2164
- if (!activeVariantTrack) {
2163
+ const activeVariant = this.streamingEngine_.getCurrentVariant();
2164
+ if (!activeVariant) {
2165
2165
  initialVariant = this.chooseVariant_();
2166
2166
  goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
2167
2167
  }
2168
2168
 
2169
2169
  // Lazy-load the stream, so we will have enough info to make the playhead.
2170
2170
  const createSegmentIndexPromises = [];
2171
- const toLazyLoad = activeVariantTrack || initialVariant;
2171
+ const toLazyLoad = activeVariant || initialVariant;
2172
2172
  for (const stream of [toLazyLoad.video, toLazyLoad.audio]) {
2173
2173
  if (stream && !stream.segmentIndex) {
2174
2174
  createSegmentIndexPromises.push(stream.createSegmentIndex());
@@ -2189,7 +2189,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2189
2189
  this.startBufferManagement_(rebufferThreshold);
2190
2190
 
2191
2191
  // Now we can switch to the initial variant.
2192
- if (!activeVariantTrack) {
2192
+ if (!activeVariant) {
2193
2193
  goog.asserts.assert(initialVariant,
2194
2194
  'Must have choosen an initial variant!');
2195
2195
 
@@ -4330,6 +4330,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4330
4330
  return [];
4331
4331
  }
4332
4332
  const chapters = [];
4333
+ const uniqueChapters = new Set();
4333
4334
  for (const chaptersTrack of chaptersTracksWithLanguage) {
4334
4335
  if (chaptersTrack && chaptersTrack.cues) {
4335
4336
  for (const cue of chaptersTrack.cues) {
@@ -4344,7 +4345,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4344
4345
  startTime: cue.startTime,
4345
4346
  endTime: cue.endTime,
4346
4347
  };
4347
- chapters.push(chapter);
4348
+ if (!uniqueChapters.has(id)) {
4349
+ chapters.push(chapter);
4350
+ uniqueChapters.add(id);
4351
+ }
4348
4352
  }
4349
4353
  }
4350
4354
  }
@@ -6974,7 +6978,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6974
6978
  * @define {string} A version number taken from git at compile time.
6975
6979
  * @export
6976
6980
  */
6977
- shaka.Player.version = 'v4.3.0-uncompiled';
6981
+ shaka.Player.version = 'v4.3.2-uncompiled';
6978
6982
 
6979
6983
  // Initialize the deprecation system using the version string we just set
6980
6984
  // on the player.