shaka-player 4.9.34 → 4.9.36

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 (79) hide show
  1. package/AUTHORS +1 -0
  2. package/CONTRIBUTORS +1 -0
  3. package/README.md +9 -3
  4. package/dist/controls.css +1 -1
  5. package/dist/controls.css.map +1 -1
  6. package/dist/deps.js +6 -6
  7. package/dist/locales.js +31 -31
  8. package/dist/shaka-player.compiled.d.ts +1 -0
  9. package/dist/shaka-player.compiled.debug.d.ts +1 -0
  10. package/dist/shaka-player.compiled.debug.externs.js +4 -3
  11. package/dist/shaka-player.compiled.debug.js +445 -427
  12. package/dist/shaka-player.compiled.debug.map +3 -3
  13. package/dist/shaka-player.compiled.externs.js +4 -3
  14. package/dist/shaka-player.compiled.js +1254 -1240
  15. package/dist/shaka-player.compiled.map +3 -3
  16. package/dist/shaka-player.dash.d.ts +4099 -0
  17. package/dist/shaka-player.dash.debug.d.ts +4099 -0
  18. package/dist/shaka-player.dash.debug.externs.js +4188 -0
  19. package/dist/shaka-player.dash.debug.js +2161 -0
  20. package/dist/shaka-player.dash.debug.map +8 -0
  21. package/dist/shaka-player.dash.externs.js +4188 -0
  22. package/dist/shaka-player.dash.js +1007 -0
  23. package/dist/shaka-player.dash.map +8 -0
  24. package/dist/shaka-player.hls.d.ts +4196 -0
  25. package/dist/shaka-player.hls.debug.d.ts +4196 -0
  26. package/dist/shaka-player.hls.debug.externs.js +4403 -0
  27. package/dist/shaka-player.hls.debug.js +2243 -0
  28. package/dist/shaka-player.hls.debug.map +8 -0
  29. package/dist/shaka-player.hls.externs.js +4403 -0
  30. package/dist/shaka-player.hls.js +1076 -0
  31. package/dist/shaka-player.hls.map +8 -0
  32. package/dist/shaka-player.ui.d.ts +1 -0
  33. package/dist/shaka-player.ui.debug.d.ts +1 -0
  34. package/dist/shaka-player.ui.debug.externs.js +4 -3
  35. package/dist/shaka-player.ui.debug.js +472 -454
  36. package/dist/shaka-player.ui.debug.map +3 -3
  37. package/dist/shaka-player.ui.externs.js +4 -3
  38. package/dist/shaka-player.ui.js +1495 -1484
  39. package/dist/shaka-player.ui.map +3 -3
  40. package/externs/shaka/player.js +8 -4
  41. package/lib/cast/cast_proxy.js +48 -10
  42. package/lib/cea/cea708_service.js +20 -2
  43. package/lib/cea/dtvcc_packet_builder.js +15 -0
  44. package/lib/dash/dash_parser.js +24 -13
  45. package/lib/dash/mpd_utils.js +7 -4
  46. package/lib/dash/segment_template.js +74 -56
  47. package/lib/hls/hls_parser.js +5 -4
  48. package/lib/hls/manifest_text_parser.js +1 -0
  49. package/lib/media/content_workarounds.js +110 -72
  50. package/lib/media/drm_engine.js +72 -11
  51. package/lib/media/gap_jumping_controller.js +14 -1
  52. package/lib/media/media_source_engine.js +29 -11
  53. package/lib/media/region_timeline.js +4 -3
  54. package/lib/media/segment_utils.js +28 -16
  55. package/lib/media/streaming_engine.js +38 -12
  56. package/lib/net/http_fetch_plugin.js +1 -1
  57. package/lib/net/http_plugin_utils.js +4 -1
  58. package/lib/net/networking_engine.js +8 -0
  59. package/lib/net/networking_utils.js +1 -0
  60. package/lib/player.js +109 -37
  61. package/lib/polyfill/patchedmediakeys_apple.js +21 -0
  62. package/lib/text/cue.js +6 -2
  63. package/lib/text/mp4_ttml_parser.js +94 -6
  64. package/lib/text/ui_text_displayer.js +35 -4
  65. package/lib/transmuxer/ac3_transmuxer.js +6 -1
  66. package/lib/util/config_utils.js +13 -3
  67. package/lib/util/drm_utils.js +8 -0
  68. package/lib/util/error.js +6 -0
  69. package/lib/util/mp4_generator.js +19 -4
  70. package/lib/util/periods.js +4 -2
  71. package/lib/util/platform.js +60 -12
  72. package/lib/util/player_configuration.js +13 -2
  73. package/lib/util/stream_utils.js +18 -0
  74. package/lib/util/tXml.js +3 -2
  75. package/package.json +3 -3
  76. package/ui/controls.js +2 -6
  77. package/ui/less/thumbnails.less +1 -0
  78. package/ui/statistics_button.js +3 -1
  79. package/ui/ui.js +44 -35
@@ -58,6 +58,20 @@ shaka.media.ContentWorkarounds = class {
58
58
  /** @type {!Array.<{box: shaka.extern.ParsedBox, newType: number}>} */
59
59
  const boxesToModify = [];
60
60
 
61
+ const pushEncv = (box) => {
62
+ boxesToModify.push({
63
+ box,
64
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
65
+ });
66
+ };
67
+
68
+ const pushEnca = (box) => {
69
+ boxesToModify.push({
70
+ box,
71
+ newType: ContentWorkarounds.BOX_TYPE_ENCA_,
72
+ });
73
+ };
74
+
61
75
  new shaka.util.Mp4Parser()
62
76
  .box('moov', onSimpleAncestorBox)
63
77
  .box('trak', onSimpleAncestorBox)
@@ -71,78 +85,23 @@ shaka.media.ContentWorkarounds = class {
71
85
  })
72
86
  .fullBox('encv', onEncryptionMetadataBox)
73
87
  .fullBox('enca', onEncryptionMetadataBox)
74
- .fullBox('dvav', (box) => {
75
- boxesToModify.push({
76
- box,
77
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
78
- });
79
- })
80
- .fullBox('dva1', (box) => {
81
- boxesToModify.push({
82
- box,
83
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
84
- });
85
- })
86
- .fullBox('dvh1', (box) => {
87
- boxesToModify.push({
88
- box,
89
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
90
- });
91
- })
92
- .fullBox('dvhe', (box) => {
93
- boxesToModify.push({
94
- box,
95
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
96
- });
97
- })
98
- .fullBox('hev1', (box) => {
99
- boxesToModify.push({
100
- box,
101
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
102
- });
103
- })
104
- .fullBox('hvc1', (box) => {
105
- boxesToModify.push({
106
- box,
107
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
108
- });
109
- })
110
- .fullBox('avc1', (box) => {
111
- boxesToModify.push({
112
- box,
113
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
114
- });
115
- })
116
- .fullBox('avc3', (box) => {
117
- boxesToModify.push({
118
- box,
119
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
120
- });
121
- })
122
- .fullBox('ac-3', (box) => {
123
- boxesToModify.push({
124
- box,
125
- newType: ContentWorkarounds.BOX_TYPE_ENCA_,
126
- });
127
- })
128
- .fullBox('ec-3', (box) => {
129
- boxesToModify.push({
130
- box,
131
- newType: ContentWorkarounds.BOX_TYPE_ENCA_,
132
- });
133
- })
134
- .fullBox('ac-4', (box) => {
135
- boxesToModify.push({
136
- box,
137
- newType: ContentWorkarounds.BOX_TYPE_ENCA_,
138
- });
139
- })
140
- .fullBox('mp4a', (box) => {
141
- boxesToModify.push({
142
- box,
143
- newType: ContentWorkarounds.BOX_TYPE_ENCA_,
144
- });
145
- }).parse(initSegment);
88
+ .fullBox('dvav', pushEncv)
89
+ .fullBox('dva1', pushEncv)
90
+ .fullBox('dvh1', pushEncv)
91
+ .fullBox('dvhe', pushEncv)
92
+ .fullBox('dvc1', pushEncv)
93
+ .fullBox('dvi1', pushEncv)
94
+ .fullBox('hev1', pushEncv)
95
+ .fullBox('hvc1', pushEncv)
96
+ .fullBox('avc1', pushEncv)
97
+ .fullBox('avc3', pushEncv)
98
+ .fullBox('ac-3', pushEnca)
99
+ .fullBox('ec-3', pushEnca)
100
+ .fullBox('ac-4', pushEnca)
101
+ .fullBox('Opus', pushEnca)
102
+ .fullBox('fLaC', pushEnca)
103
+ .fullBox('mp4a', pushEnca)
104
+ .parse(initSegment);
146
105
 
147
106
  if (isEncrypted) {
148
107
  shaka.log.debug('Init segment already indicates encryption.');
@@ -335,6 +294,53 @@ shaka.media.ContentWorkarounds = class {
335
294
  boxView.setUint32(ContentWorkarounds.BOX_SIZE_OFFSET_, newBoxSize);
336
295
  }
337
296
  }
297
+
298
+ /**
299
+ * Transform the init segment into a new init segment buffer that indicates
300
+ * EC-3 as audio codec instead of AC-3. Even though any EC-3 decoder should
301
+ * be able to decode AC-3 streams, there are platforms that do not accept
302
+ * AC-3 as codec.
303
+ *
304
+ * Should only be called for MP4 init segments, and only on platforms that
305
+ * need this workaround. Returns a new buffer containing the modified init
306
+ * segment.
307
+ *
308
+ * @param {!BufferSource} initSegmentBuffer
309
+ * @return {!Uint8Array}
310
+ */
311
+ static fakeEC3(initSegmentBuffer) {
312
+ const ContentWorkarounds = shaka.media.ContentWorkarounds;
313
+ const initSegment = shaka.util.BufferUtils.toUint8(initSegmentBuffer);
314
+ const ancestorBoxes = [];
315
+
316
+ const onSimpleAncestorBox = (box) => {
317
+ ancestorBoxes.push({start: box.start, size: box.size});
318
+ shaka.util.Mp4Parser.children(box);
319
+ };
320
+
321
+ new shaka.util.Mp4Parser()
322
+ .box('moov', onSimpleAncestorBox)
323
+ .box('trak', onSimpleAncestorBox)
324
+ .box('mdia', onSimpleAncestorBox)
325
+ .box('minf', onSimpleAncestorBox)
326
+ .box('stbl', onSimpleAncestorBox)
327
+ .box('stsd', (box) => {
328
+ ancestorBoxes.push({start: box.start, size: box.size});
329
+ const stsdBoxView = shaka.util.BufferUtils.toDataView(
330
+ initSegment, box.start);
331
+ // "size - 3" is because we immediately read a uint32.
332
+ for (let i = 0; i < box.size -3; i++) {
333
+ const codecTag = stsdBoxView.getUint32(i);
334
+ if (codecTag == ContentWorkarounds.BOX_TYPE_AC_3_) {
335
+ stsdBoxView.setUint32(i, ContentWorkarounds.BOX_TYPE_EC_3_);
336
+ } else if (codecTag == ContentWorkarounds.BOX_TYPE_DAC3_) {
337
+ stsdBoxView.setUint32(i, ContentWorkarounds.BOX_TYPE_DEC3_);
338
+ }
339
+ }
340
+ }).parse(initSegment);
341
+
342
+ return initSegment;
343
+ }
338
344
  };
339
345
 
340
346
  /**
@@ -470,3 +476,35 @@ shaka.media.ContentWorkarounds.BOX_TYPE_ENCV_ = 0x656e6376;
470
476
  * @private
471
477
  */
472
478
  shaka.media.ContentWorkarounds.BOX_TYPE_ENCA_ = 0x656e6361;
479
+
480
+ /**
481
+ * Box type for "ac-3".
482
+ *
483
+ * @const {number}
484
+ * @private
485
+ */
486
+ shaka.media.ContentWorkarounds.BOX_TYPE_AC_3_ = 0x61632d33;
487
+
488
+ /**
489
+ * Box type for "dac3".
490
+ *
491
+ * @const {number}
492
+ * @private
493
+ */
494
+ shaka.media.ContentWorkarounds.BOX_TYPE_DAC3_ = 0x64616333;
495
+
496
+ /**
497
+ * Box type for "ec-3".
498
+ *
499
+ * @const {number}
500
+ * @private
501
+ */
502
+ shaka.media.ContentWorkarounds.BOX_TYPE_EC_3_ = 0x65632d33;
503
+
504
+ /**
505
+ * Box type for "dec3".
506
+ *
507
+ * @const {number}
508
+ * @private
509
+ */
510
+ shaka.media.ContentWorkarounds.BOX_TYPE_DEC3_ = 0x64656333;
@@ -1004,7 +1004,17 @@ shaka.media.DrmEngine = class {
1004
1004
 
1005
1005
  // If we have configured preferredKeySystems, choose a preferred keySystem
1006
1006
  // if available.
1007
- for (const preferredKeySystem of this.config_.preferredKeySystems) {
1007
+ let preferredKeySystems = this.config_.preferredKeySystems;
1008
+ if (!preferredKeySystems.length) {
1009
+ // If there is no preference set and we only have one license server, we
1010
+ // use this as preference. This is used to override manifests on those
1011
+ // that have the embedded license and the browser supports multiple DRMs.
1012
+ const servers = shaka.util.MapUtils.asMap(this.config_.servers);
1013
+ if (servers.size == 1) {
1014
+ preferredKeySystems = Array.from(servers.keys());
1015
+ }
1016
+ }
1017
+ for (const preferredKeySystem of preferredKeySystems) {
1008
1018
  for (const variant of variants) {
1009
1019
  const decodingInfo = variant.decodingInfos.find((decodingInfo) => {
1010
1020
  return decodingInfo.supported &&
@@ -1028,8 +1038,15 @@ shaka.media.DrmEngine = class {
1028
1038
  if (!decodingInfo.supported || !decodingInfo.keySystemAccess) {
1029
1039
  continue;
1030
1040
  }
1031
- const drmInfos =
1032
- drmInfosByKeySystem.get(decodingInfo.keySystemAccess.keySystem);
1041
+ const originalKeySystem = decodingInfo.keySystemAccess.keySystem;
1042
+ if (preferredKeySystems.includes(originalKeySystem)) {
1043
+ continue;
1044
+ }
1045
+ let drmInfos = drmInfosByKeySystem.get(originalKeySystem);
1046
+ if (!drmInfos && this.config_.keySystemsMapping[originalKeySystem]) {
1047
+ drmInfos = drmInfosByKeySystem.get(
1048
+ this.config_.keySystemsMapping[originalKeySystem]);
1049
+ }
1033
1050
  for (const info of drmInfos) {
1034
1051
  if (!!info.licenseServerUri == shouldHaveLicenseServer) {
1035
1052
  return decodingInfo.keySystemAccess;
@@ -1800,7 +1817,18 @@ shaka.media.DrmEngine = class {
1800
1817
  */
1801
1818
  const processMediaKeySystemAccess = async (keySystem, access) => {
1802
1819
  try {
1803
- await access.createMediaKeys();
1820
+ // Workaround: Our automated test lab runs Windows browsers under a
1821
+ // headless service. In this environment, Firefox's ClearKey CDM seems
1822
+ // to crash when we create the CDM here.
1823
+ if (goog.DEBUG && // not a production build
1824
+ shaka.util.Platform.isWindows() && // on Windows
1825
+ shaka.util.Platform.isFirefox()) { // with Firefox
1826
+ // Reject this, since it crashes our tests.
1827
+ throw new Error('Suppressing Firefox Windows ClearKey in testing!');
1828
+ } else {
1829
+ // Otherwise, create the CDM.
1830
+ await access.createMediaKeys();
1831
+ }
1804
1832
  } catch (error) {
1805
1833
  // In some cases, we can get a successful access object but fail to
1806
1834
  // create a MediaKeys instance. When this happens, don't update the
@@ -1947,17 +1975,47 @@ shaka.media.DrmEngine = class {
1947
1975
  support.set(keySystem, null);
1948
1976
  }
1949
1977
 
1978
+ const checkKeySystem = (keySystem) => {
1979
+ // Our Polyfill will reject anything apart com.apple.fps key systems.
1980
+ // It seems the Safari modern EME API will allow to request a
1981
+ // MediaKeySystemAccess for the ClearKey CDM, create and update a key
1982
+ // session but playback will never start
1983
+ // Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
1984
+ if (shaka.util.DrmUtils.isClearKeySystem(keySystem) &&
1985
+ shaka.util.Platform.isSafari()) {
1986
+ return false;
1987
+ }
1988
+ // FairPlay is a proprietary DRM from Apple and will never work on
1989
+ // Windows.
1990
+ if (shaka.util.DrmUtils.isFairPlayKeySystem(keySystem) &&
1991
+ shaka.util.Platform.isWindows()) {
1992
+ return false;
1993
+ }
1994
+ // PlayReady is a proprietary DRM from Microsoft and will never work on
1995
+ // Apple platforms
1996
+ if (shaka.util.DrmUtils.isPlayReadyKeySystem(keySystem) &&
1997
+ (shaka.util.Platform.isMac() || shaka.util.Platform.isApple())) {
1998
+ return false;
1999
+ }
2000
+ // Mozilla has no intention of supporting PlayReady according to
2001
+ // comments posted on Bugzilla.
2002
+ if (shaka.util.DrmUtils.isPlayReadyKeySystem(keySystem) &&
2003
+ shaka.util.Platform.isFirefox()) {
2004
+ return false;
2005
+ }
2006
+ // We are sure that WisePlay is not supported on Windows or macOS.
2007
+ if (shaka.util.DrmUtils.isWisePlayKeySystem(keySystem) &&
2008
+ (shaka.util.Platform.isWindows() || shaka.util.Platform.isMac())) {
2009
+ return false;
2010
+ }
2011
+ return true;
2012
+ };
2013
+
1950
2014
  // Test each key system and encryption scheme.
1951
2015
  const tests = [];
1952
2016
  for (const encryptionScheme of testEncryptionSchemes) {
1953
2017
  for (const keySystem of testKeySystems) {
1954
- // Our Polyfill will reject anything apart com.apple.fps key systems.
1955
- // It seems the Safari modern EME API will allow to request a
1956
- // MediaKeySystemAccess for the ClearKey CDM, create and update a key
1957
- // session but playback will never start
1958
- // Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
1959
- if (keySystem === 'org.w3.clearkey' &&
1960
- shaka.util.Platform.isSafari()) {
2018
+ if (!checkKeySystem(keySystem)) {
1961
2019
  continue;
1962
2020
  }
1963
2021
  tests.push(testSystemEme(keySystem, encryptionScheme, '', ''));
@@ -1967,6 +2025,9 @@ shaka.media.DrmEngine = class {
1967
2025
 
1968
2026
  for (const keySystem of testKeySystems) {
1969
2027
  for (const robustness of (testRobustness[keySystem] || [])) {
2028
+ if (!checkKeySystem(keySystem)) {
2029
+ continue;
2030
+ }
1970
2031
  tests.push(testSystemEme(keySystem, null, robustness, ''));
1971
2032
  tests.push(testSystemEme(keySystem, null, '', robustness));
1972
2033
  tests.push(testSystemMcap(keySystem, null, robustness, ''));
@@ -53,6 +53,9 @@ shaka.media.GapJumpingController = class {
53
53
  /** @private {shaka.util.EventManager} */
54
54
  this.eventManager_ = new shaka.util.EventManager();
55
55
 
56
+ /** @private {boolean} */
57
+ this.started_ = false;
58
+
56
59
  /** @private {boolean} */
57
60
  this.seekingEventReceived_ = false;
58
61
 
@@ -90,7 +93,7 @@ shaka.media.GapJumpingController = class {
90
93
  */
91
94
  this.gapJumpTimer_ = new shaka.util.Timer(() => {
92
95
  this.onPollGapJump_();
93
- }).tickEvery(this.config_.gapJumpTimerTime);
96
+ });
94
97
  }
95
98
 
96
99
 
@@ -134,16 +137,22 @@ shaka.media.GapJumpingController = class {
134
137
  * @param {number} startTime
135
138
  */
136
139
  onStarted(startTime) {
140
+ this.started_ = true;
137
141
  if (this.video_.seeking && !this.seekingEventReceived_) {
138
142
  this.seekingEventReceived_ = true;
139
143
  this.startTime_ = startTime;
140
144
  }
145
+ if (this.gapJumpTimer_) {
146
+ this.gapJumpTimer_.tickEvery(this.config_.gapJumpTimerTime);
147
+ }
148
+ this.onPollGapJump_();
141
149
  }
142
150
 
143
151
  /** Called when a seek has started. */
144
152
  onSeeking() {
145
153
  this.seekingEventReceived_ = true;
146
154
  this.hadSegmentAppended_ = false;
155
+ this.onPollGapJump_();
147
156
  }
148
157
 
149
158
 
@@ -167,6 +176,10 @@ shaka.media.GapJumpingController = class {
167
176
  if (this.video_.readyState == 0) {
168
177
  return;
169
178
  }
179
+ // Don't gap jump before playback started
180
+ if (!this.started_) {
181
+ return;
182
+ }
170
183
  // Do not gap jump if seeking has begun, but the seeking event has not
171
184
  // yet fired for this particular seek.
172
185
  if (this.video_.seeking) {
@@ -543,6 +543,11 @@ shaka.media.MediaSourceEngine = class {
543
543
  stream, contentType),
544
544
  'Type negotiation should happen before MediaSourceEngine.init!');
545
545
 
546
+ if (contentType == ContentType.AUDIO && codecs) {
547
+ codecs = shaka.util.StreamUtils.getCorrectAudioCodecs(
548
+ codecs, stream.mimeType);
549
+ }
550
+
546
551
  let mimeType = shaka.util.MimeUtils.getFullType(
547
552
  stream.mimeType, codecs);
548
553
  if (contentType == ContentType.TEXT) {
@@ -1157,7 +1162,7 @@ shaka.media.MediaSourceEngine = class {
1157
1162
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
1158
1163
  if (contentType == ContentType.TEXT) {
1159
1164
  await this.textEngine_.remove(startTime, endTime);
1160
- } else {
1165
+ } else if (endTime > startTime) {
1161
1166
  await this.enqueueOperation_(
1162
1167
  contentType,
1163
1168
  () => this.remove_(contentType, startTime, endTime),
@@ -1309,20 +1314,24 @@ shaka.media.MediaSourceEngine = class {
1309
1314
  null));
1310
1315
  }
1311
1316
  }
1312
- operations.push(this.enqueueOperation_(
1313
- contentType,
1314
- () => this.setAppendWindow_(
1315
- contentType, appendWindowStart, appendWindowEnd),
1316
- null));
1317
- if (this.needSplitMuxedContent_) {
1317
+ if (appendWindowStart != 0 || appendWindowEnd != Infinity) {
1318
1318
  operations.push(this.enqueueOperation_(
1319
- ContentType.AUDIO,
1319
+ contentType,
1320
1320
  () => this.setAppendWindow_(
1321
- ContentType.AUDIO, appendWindowStart, appendWindowEnd),
1321
+ contentType, appendWindowStart, appendWindowEnd),
1322
1322
  null));
1323
+ if (this.needSplitMuxedContent_) {
1324
+ operations.push(this.enqueueOperation_(
1325
+ ContentType.AUDIO,
1326
+ () => this.setAppendWindow_(
1327
+ ContentType.AUDIO, appendWindowStart, appendWindowEnd),
1328
+ null));
1329
+ }
1323
1330
  }
1324
1331
 
1325
- await Promise.all(operations);
1332
+ if (operations.length) {
1333
+ await Promise.all(operations);
1334
+ }
1326
1335
  }
1327
1336
 
1328
1337
  /**
@@ -1841,6 +1850,14 @@ shaka.media.MediaSourceEngine = class {
1841
1850
  segment = shaka.media.ContentWorkarounds.fakeEncryption(segment, uri);
1842
1851
  }
1843
1852
 
1853
+ if (isInitSegment &&
1854
+ Platform.requiresEC3InitSegments() &&
1855
+ shaka.util.MimeUtils.getContainerType(
1856
+ this.sourceBufferTypes_[contentType]) == 'mp4') {
1857
+ shaka.log.debug('Forcing fake EC-3 information in init segment.');
1858
+ segment = shaka.media.ContentWorkarounds.fakeEC3(segment);
1859
+ }
1860
+
1844
1861
  return segment;
1845
1862
  }
1846
1863
 
@@ -2169,7 +2186,8 @@ shaka.media.MediaSourceEngine = class {
2169
2186
  const newMimeType = realInfo.mimeType;
2170
2187
 
2171
2188
  let muxedContentCheck = true;
2172
- if (transmuxerMuxed) {
2189
+ if (transmuxerMuxed &&
2190
+ ContentType.AUDIO in this.sourceBufferTypes_) {
2173
2191
  const muxedRealInfo =
2174
2192
  this.getRealInfo_(ContentType.AUDIO, mimeType, codecs);
2175
2193
  const muxedCurrentCodec = MimeUtils.getNormalizedCodec(
@@ -102,13 +102,14 @@ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
102
102
  * @private
103
103
  */
104
104
  findSimilarRegion_(region) {
105
+ const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.1;
105
106
  for (const existing of this.regions_) {
106
107
  // The same scheme ID and time range means that it is similar-enough to
107
108
  // be the same region.
108
109
  const isSimilar = existing.schemeIdUri == region.schemeIdUri &&
109
- existing.id == region.id &&
110
- existing.startTime == region.startTime &&
111
- existing.endTime == region.endTime;
110
+ existing.id == region.id &&
111
+ isDiffNegligible(existing.startTime, region.startTime) &&
112
+ isDiffNegligible(existing.endTime, region.endTime);
112
113
 
113
114
  if (isSimilar) {
114
115
  return existing;
@@ -47,11 +47,12 @@ shaka.media.SegmentUtils = class {
47
47
 
48
48
  /**
49
49
  * @param {!BufferSource} data
50
- * @param {boolean=} disableAudio
51
- * @param {boolean=} disableVideo
50
+ * @param {boolean} disableAudio
51
+ * @param {boolean} disableVideo
52
+ * @param {boolean} disableText
52
53
  * @return {?shaka.media.SegmentUtils.BasicInfo}
53
54
  */
54
- static getBasicInfoFromTs(data, disableAudio = false, disableVideo = false) {
55
+ static getBasicInfoFromTs(data, disableAudio, disableVideo, disableText) {
55
56
  const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
56
57
  const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
57
58
  const tsCodecs = tsParser.getCodecs();
@@ -63,24 +64,34 @@ shaka.media.SegmentUtils = class {
63
64
  switch (tsCodecs.audio) {
64
65
  case 'aac':
65
66
  case 'aac-loas':
66
- codecs.push('mp4a.40.2');
67
- hasAudio = true;
67
+ if (tsParser.getAudioData().length) {
68
+ codecs.push('mp4a.40.2');
69
+ hasAudio = true;
70
+ }
68
71
  break;
69
72
  case 'mp3':
70
- codecs.push('mp4a.40.34');
71
- hasAudio = true;
73
+ if (tsParser.getAudioData().length) {
74
+ codecs.push('mp4a.40.34');
75
+ hasAudio = true;
76
+ }
72
77
  break;
73
78
  case 'ac3':
74
- codecs.push('ac-3');
75
- hasAudio = true;
79
+ if (tsParser.getAudioData().length) {
80
+ codecs.push('ac-3');
81
+ hasAudio = true;
82
+ }
76
83
  break;
77
84
  case 'ec3':
78
- codecs.push('ec-3');
79
- hasAudio = true;
85
+ if (tsParser.getAudioData().length) {
86
+ codecs.push('ec-3');
87
+ hasAudio = true;
88
+ }
80
89
  break;
81
90
  case 'opus':
82
- codecs.push('opus');
83
- hasAudio = true;
91
+ if (tsParser.getAudioData().length) {
92
+ codecs.push('opus');
93
+ hasAudio = true;
94
+ }
84
95
  break;
85
96
  }
86
97
  }
@@ -113,7 +124,7 @@ shaka.media.SegmentUtils = class {
113
124
  }
114
125
  const onlyAudio = hasAudio && !hasVideo;
115
126
  const closedCaptions = new Map();
116
- if (hasVideo) {
127
+ if (hasVideo && !disableText) {
117
128
  const captionParser = new shaka.media.ClosedCaptionParser('video/mp2t');
118
129
  captionParser.parseFrom(data);
119
130
  for (const stream of captionParser.getStreams()) {
@@ -139,9 +150,10 @@ shaka.media.SegmentUtils = class {
139
150
  /**
140
151
  * @param {?BufferSource} initData
141
152
  * @param {!BufferSource} data
153
+ * @param {boolean} disableText
142
154
  * @return {?shaka.media.SegmentUtils.BasicInfo}
143
155
  */
144
- static getBasicInfoFromMp4(initData, data) {
156
+ static getBasicInfoFromMp4(initData, data, disableText) {
145
157
  const Mp4Parser = shaka.util.Mp4Parser;
146
158
  const SegmentUtils = shaka.media.SegmentUtils;
147
159
 
@@ -382,7 +394,7 @@ shaka.media.SegmentUtils = class {
382
394
  }
383
395
  const onlyAudio = hasAudio && !hasVideo;
384
396
  const closedCaptions = new Map();
385
- if (hasVideo) {
397
+ if (hasVideo && !disableText) {
386
398
  const captionParser = new shaka.media.ClosedCaptionParser('video/mp4');
387
399
  if (initData) {
388
400
  captionParser.init(initData);
@@ -1314,12 +1314,17 @@ shaka.media.StreamingEngine = class {
1314
1314
  this.playerInterface_.mediaSourceEngine.clearSelectedClosedCaptionId();
1315
1315
  }
1316
1316
 
1317
+ // Update updateIntervalSeconds according to our current playbackrate,
1318
+ // and always considering a minimum of 1.
1319
+ const updateIntervalSeconds = this.config_.updateIntervalSeconds /
1320
+ Math.max(1, Math.abs(this.playerInterface_.getPlaybackRate()));
1321
+
1317
1322
  if (!this.playerInterface_.mediaSourceEngine.isStreamingAllowed() &&
1318
1323
  mediaState.type != ContentType.TEXT) {
1319
1324
  // It is not allowed to add segments yet, so we schedule an update to
1320
1325
  // check again later. So any prediction we make now could be terribly
1321
1326
  // invalid soon.
1322
- return this.config_.updateIntervalSeconds / 2;
1327
+ return updateIntervalSeconds / 2;
1323
1328
  }
1324
1329
 
1325
1330
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
@@ -1393,9 +1398,8 @@ shaka.media.StreamingEngine = class {
1393
1398
  shaka.log.v2(logPrefix, 'buffering goal met');
1394
1399
 
1395
1400
  // Do not try to predict the next update. Just poll according to
1396
- // configuration (seconds). The playback rate can change at any time, so
1397
- // any prediction we make now could be terribly invalid soon.
1398
- return this.config_.updateIntervalSeconds / 2;
1401
+ // configuration (seconds).
1402
+ return updateIntervalSeconds / 2;
1399
1403
  }
1400
1404
 
1401
1405
  // Lack of segment iterator is the best indicator stream has changed.
@@ -1407,7 +1411,7 @@ shaka.media.StreamingEngine = class {
1407
1411
  // In any case just try again... if the manifest is incomplete or is not
1408
1412
  // being updated then we'll idle forever; otherwise, we'll end up getting
1409
1413
  // a SegmentReference eventually.
1410
- return this.config_.updateIntervalSeconds;
1414
+ return updateIntervalSeconds;
1411
1415
  }
1412
1416
  // Get media state adaptation and reset this value. By guarding it during
1413
1417
  // actual stream change we ensure it won't be cleaned by accident on regular
@@ -1446,7 +1450,7 @@ shaka.media.StreamingEngine = class {
1446
1450
  // For example, let video buffering catch up to audio buffering before
1447
1451
  // fetching another audio segment.
1448
1452
  shaka.log.v2(logPrefix, 'waiting for other streams to buffer');
1449
- return this.config_.updateIntervalSeconds;
1453
+ return updateIntervalSeconds;
1450
1454
  }
1451
1455
 
1452
1456
  if (mediaState.segmentPrefetch && mediaState.segmentIterator &&
@@ -1509,7 +1513,18 @@ shaka.media.StreamingEngine = class {
1509
1513
  // Something is buffered from the same Stream. Use the current position
1510
1514
  // in the segment index. This is updated via next() after each segment is
1511
1515
  // appended.
1512
- return mediaState.segmentIterator.current();
1516
+ let ref = mediaState.segmentIterator.current();
1517
+ if (ref && mediaState.lastSegmentReference) {
1518
+ // In HLS sometimes the segment iterator adds or removes segments very
1519
+ // quickly, so we have to be sure that we do not add the last segment
1520
+ // again, tolerating a difference of 1ms.
1521
+ const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.001;
1522
+ const lastStartTime = mediaState.lastSegmentReference.startTime;
1523
+ if (isDiffNegligible(lastStartTime, ref.startTime)) {
1524
+ ref = mediaState.segmentIterator.next().value;
1525
+ }
1526
+ }
1527
+ return ref;
1513
1528
  } else if (mediaState.lastSegmentReference || bufferEnd) {
1514
1529
  // Something is buffered from another Stream.
1515
1530
  const time = mediaState.lastSegmentReference ?
@@ -2485,7 +2500,7 @@ shaka.media.StreamingEngine = class {
2485
2500
  const segmentIndex = mediaState.stream.segmentIndex;
2486
2501
  if (segmentIndex instanceof shaka.media.MetaSegmentIndex) {
2487
2502
  segmentIndex.evict(
2488
- this.manifest_.presentationTimeline.getSeekRangeStart());
2503
+ this.manifest_.presentationTimeline.getSegmentAvailabilityStart());
2489
2504
  }
2490
2505
 
2491
2506
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
@@ -2512,15 +2527,26 @@ shaka.media.StreamingEngine = class {
2512
2527
  }
2513
2528
  const bufferedBehind = presentationTime - startTime;
2514
2529
 
2515
- const overflow = bufferedBehind - bufferBehind;
2530
+ const evictionGoal = this.config_.evictionGoal;
2531
+
2532
+ const seekRangeStart =
2533
+ this.manifest_.presentationTimeline.getSeekRangeStart();
2534
+ const seekRangeEnd =
2535
+ this.manifest_.presentationTimeline.getSeekRangeEnd();
2536
+
2537
+ let overflow = bufferedBehind - bufferBehind;
2538
+ if (seekRangeEnd - seekRangeStart > evictionGoal) {
2539
+ overflow = Math.max(bufferedBehind - bufferBehind,
2540
+ seekRangeStart - evictionGoal - startTime);
2541
+ }
2516
2542
  // See: https://github.com/shaka-project/shaka-player/issues/6240
2517
- if (overflow <= this.config_.evictionGoal) {
2543
+ if (overflow <= evictionGoal) {
2518
2544
  shaka.log.v2(logPrefix,
2519
2545
  'buffer behind okay:',
2520
2546
  'presentationTime=' + presentationTime,
2521
2547
  'bufferedBehind=' + bufferedBehind,
2522
2548
  'bufferBehind=' + bufferBehind,
2523
- 'evictionGoal=' + this.config_.evictionGoal,
2549
+ 'evictionGoal=' + evictionGoal,
2524
2550
  'underflow=' + Math.abs(overflow));
2525
2551
  return;
2526
2552
  }
@@ -2530,7 +2556,7 @@ shaka.media.StreamingEngine = class {
2530
2556
  'presentationTime=' + presentationTime,
2531
2557
  'bufferedBehind=' + bufferedBehind,
2532
2558
  'bufferBehind=' + bufferBehind,
2533
- 'evictionGoal=' + this.config_.evictionGoal,
2559
+ 'evictionGoal=' + evictionGoal,
2534
2560
  'overflow=' + overflow);
2535
2561
 
2536
2562
  await this.playerInterface_.mediaSourceEngine.remove(mediaState.type,