shaka-player 4.3.5 → 4.3.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 (88) hide show
  1. package/.github/workflows/selenium-lab-tests.yaml +23 -1
  2. package/AUTHORS +2 -1
  3. package/CHANGELOG.md +32 -0
  4. package/CONTRIBUTORS +4 -1
  5. package/dist/controls.css +2 -2
  6. package/dist/controls.css.map +1 -1
  7. package/dist/demo.compiled.debug.js +167 -296
  8. package/dist/demo.compiled.debug.map +4 -4
  9. package/dist/demo.compiled.js +167 -296
  10. package/dist/demo.compiled.map +4 -4
  11. package/dist/deps.js +1 -1
  12. package/dist/locales.js +32 -32
  13. package/dist/receiver.compiled.debug.js +34 -63
  14. package/dist/receiver.compiled.debug.map +4 -4
  15. package/dist/receiver.compiled.js +34 -63
  16. package/dist/receiver.compiled.map +4 -4
  17. package/dist/shaka-player.compiled.d.ts +12 -3
  18. package/dist/shaka-player.compiled.debug.d.ts +12 -3
  19. package/dist/shaka-player.compiled.debug.externs.js +20 -14
  20. package/dist/shaka-player.compiled.debug.js +261 -255
  21. package/dist/shaka-player.compiled.debug.map +4 -4
  22. package/dist/shaka-player.compiled.externs.js +20 -14
  23. package/dist/shaka-player.compiled.js +862 -858
  24. package/dist/shaka-player.compiled.map +3 -3
  25. package/dist/shaka-player.ui.d.ts +12 -3
  26. package/dist/shaka-player.ui.debug.d.ts +12 -3
  27. package/dist/shaka-player.ui.debug.externs.js +8 -2
  28. package/dist/shaka-player.ui.debug.js +271 -264
  29. package/dist/shaka-player.ui.debug.map +3 -3
  30. package/dist/shaka-player.ui.externs.js +8 -2
  31. package/dist/shaka-player.ui.js +1005 -1002
  32. package/dist/shaka-player.ui.map +3 -3
  33. package/docs/tutorials/a11y.md +0 -4
  34. package/docs/tutorials/ad_monetization.md +0 -4
  35. package/docs/tutorials/basic-usage.md +0 -5
  36. package/docs/tutorials/config.md +0 -5
  37. package/docs/tutorials/debugging.md +0 -5
  38. package/docs/tutorials/drm-config.md +0 -5
  39. package/docs/tutorials/fairplay.md +2 -2
  40. package/docs/tutorials/license-server-auth.md +0 -5
  41. package/docs/tutorials/license-wrapping.md +0 -5
  42. package/docs/tutorials/network-and-buffering-config.md +0 -5
  43. package/docs/tutorials/ui-customization.md +0 -5
  44. package/docs/tutorials/ui.md +0 -7
  45. package/docs/tutorials/welcome.md +0 -5
  46. package/externs/network_information.js +7 -2
  47. package/externs/shaka/abr_manager.js +6 -0
  48. package/externs/shaka/net.js +1 -0
  49. package/externs/shaka/player.js +13 -7
  50. package/lib/abr/simple_abr_manager.js +27 -3
  51. package/lib/ads/client_side_ad_manager.js +6 -3
  52. package/lib/dash/dash_parser.js +49 -48
  53. package/lib/debug/log.js +9 -13
  54. package/lib/hls/hls_parser.js +17 -5
  55. package/lib/media/media_source_engine.js +27 -9
  56. package/lib/media/presentation_timeline.js +9 -2
  57. package/lib/media/segment_index.js +2 -0
  58. package/lib/media/segment_reference.js +3 -2
  59. package/lib/media/streaming_engine.js +31 -7
  60. package/lib/net/http_fetch_plugin.js +79 -57
  61. package/lib/net/networking_engine.js +1 -0
  62. package/lib/player.js +22 -3
  63. package/lib/polyfill/media_capabilities.js +13 -0
  64. package/lib/text/mp4_vtt_parser.js +4 -0
  65. package/lib/text/text_engine.js +1 -0
  66. package/lib/util/manifest_parser_utils.js +1 -2
  67. package/lib/util/mime_utils.js +11 -0
  68. package/lib/util/periods.js +5 -5
  69. package/lib/util/stream_utils.js +2 -2
  70. package/maintained-branches.md +2 -3
  71. package/package.json +1 -1
  72. package/test/dash/dash_parser_live_unit.js +3 -2
  73. package/test/hls/hls_parser_unit.js +156 -0
  74. package/test/media/content_workarounds_unit.js +43 -0
  75. package/test/media/drm_engine_integration.js +4 -17
  76. package/test/media/streaming_engine_unit.js +6 -1
  77. package/test/net/networking_engine_unit.js +6 -1
  78. package/test/player_integration.js +39 -0
  79. package/test/player_unit.js +57 -0
  80. package/test/polyfill/media_capabilities_unit.js +1 -0
  81. package/test/test/assets/text-clip-alt.vtt +63 -0
  82. package/test/test/util/fake_drm_engine.js +3 -0
  83. package/test/test/util/fake_media_source_engine.js +4 -0
  84. package/test/test/util/simple_fakes.js +6 -0
  85. package/test/test/util/test_scheme.js +9 -14
  86. package/test/text/mp4_vtt_parser_unit.js +8 -0
  87. package/test/util/mime_utils_unit.js +10 -0
  88. package/ui/less/containers.less +4 -0
@@ -78,6 +78,9 @@ shaka.media.StreamingEngine = class {
78
78
  /** @private {?shaka.extern.Stream} */
79
79
  this.currentTextStream_ = null;
80
80
 
81
+ /** @private {number} */
82
+ this.textStreamSequenceId_ = 0;
83
+
81
84
  /** @private {boolean} */
82
85
  this.parsedPrftEventRaised_ = false;
83
86
 
@@ -224,6 +227,8 @@ shaka.media.StreamingEngine = class {
224
227
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
225
228
  goog.asserts.assert(!this.mediaStates_.has(ContentType.TEXT),
226
229
  'Should not call loadNewTextStream_ while streaming text!');
230
+ this.textStreamSequenceId_++;
231
+ const currentSequenceId = this.textStreamSequenceId_;
227
232
 
228
233
  try {
229
234
  // Clear MediaSource's buffered text, so that the new text stream will
@@ -246,7 +251,7 @@ shaka.media.StreamingEngine = class {
246
251
  const streamText =
247
252
  textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
248
253
 
249
- if (streamText) {
254
+ if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
250
255
  const state = this.createMediaState_(stream);
251
256
  this.mediaStates_.set(ContentType.TEXT, state);
252
257
  this.scheduleUpdate_(state, 0);
@@ -357,7 +362,7 @@ shaka.media.StreamingEngine = class {
357
362
  /**
358
363
  * @param {shaka.extern.Stream} textStream
359
364
  */
360
- switchTextStream(textStream) {
365
+ async switchTextStream(textStream) {
361
366
  this.currentTextStream_ = textStream;
362
367
 
363
368
  if (!this.startupComplete_) {
@@ -369,6 +374,13 @@ shaka.media.StreamingEngine = class {
369
374
  goog.asserts.assert(textStream && textStream.type == ContentType.TEXT,
370
375
  'Wrong stream type passed to switchTextStream!');
371
376
 
377
+ // In HLS it is possible that the mimetype changes when the media
378
+ // playlist is downloaded, so it is necessary to have the updated data
379
+ // here.
380
+ if (!textStream.segmentIndex) {
381
+ await textStream.createSegmentIndex();
382
+ }
383
+
372
384
  this.switchInternal_(
373
385
  textStream, /* clearBuffer= */ true,
374
386
  /* safeMargin= */ 0, /* force= */ false);
@@ -1564,10 +1576,10 @@ shaka.media.StreamingEngine = class {
1564
1576
  'waiting for another stream to recover...');
1565
1577
  }
1566
1578
 
1567
- // QuotaExceededError gets thrown if evication didn't help to make room
1579
+ // QuotaExceededError gets thrown if eviction didn't help to make room
1568
1580
  // for a segment. We want to wait for a while (4 seconds is just an
1569
1581
  // arbitrary number) before updating to give the playhead a chance to
1570
- // advance, so we don't immidiately throw again.
1582
+ // advance, so we don't immediately throw again.
1571
1583
  this.scheduleUpdate_(mediaState, 4);
1572
1584
  }
1573
1585
 
@@ -1675,19 +1687,31 @@ shaka.media.StreamingEngine = class {
1675
1687
  }
1676
1688
  }
1677
1689
 
1690
+ const lastDiscontinuitySequence =
1691
+ mediaState.lastSegmentReference ?
1692
+ mediaState.lastSegmentReference.discontinuitySequence : null;
1678
1693
  if (this.manifest_.sequenceMode) {
1679
1694
  // Across discontinuity bounds, we should resync timestamps for
1680
1695
  // sequence mode playbacks. The next segment appended should
1681
1696
  // land at its theoretical timestamp from the segment index.
1682
- const lastDiscontinuitySequence =
1683
- mediaState.lastSegmentReference ?
1684
- mediaState.lastSegmentReference.discontinuitySequence : null;
1685
1697
  if (reference.discontinuitySequence != lastDiscontinuitySequence ||
1686
1698
  mediaState.needsResync) {
1687
1699
  mediaState.needsResync = false;
1688
1700
  operations.push(this.playerInterface_.mediaSourceEngine.resync(
1689
1701
  mediaState.type, reference.startTime));
1690
1702
  }
1703
+ } else {
1704
+ // In segments mode, we need to resync to set the timestampOffset
1705
+ // to the start of the current discontinuity sequence. This is
1706
+ // because individual discontinuity sequences may have internal
1707
+ // timestamps that overlap, so we adjust the timestampOffset to avoid
1708
+ // having the SourceBuffer get overwritten.
1709
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1710
+ operations.push(
1711
+ this.playerInterface_.mediaSourceEngine.resync(
1712
+ mediaState.type,
1713
+ reference.timestampOffset));
1714
+ }
1691
1715
  }
1692
1716
 
1693
1717
  await Promise.all(operations);
@@ -120,65 +120,77 @@ shaka.net.HttpFetchPlugin = class {
120
120
  // not the body yet.
121
121
  headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
122
122
  response.headers));
123
- // Getting the reader in this way allows us to observe the process of
124
- // downloading the body, instead of just waiting for an opaque promise to
125
- // resolve.
126
- // We first clone the response because calling getReader locks the body
127
- // stream; if we didn't clone it here, we would be unable to get the
128
- // response's arrayBuffer later.
129
- const reader = response.clone().body.getReader();
130
-
131
- const contentLengthRaw = response.headers.get('Content-Length');
132
- const contentLength =
133
- contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;
134
-
135
- const start = (controller) => {
136
- const push = async () => {
137
- let readObj;
138
- try {
139
- readObj = await reader.read();
140
- } catch (e) {
141
- // If we abort the request, we'll get an error here. Just ignore it
142
- // since real errors will be reported when we read the buffer below.
143
- shaka.log.v1('error reading from stream', e.message);
144
- return;
145
- }
146
-
147
- if (!readObj.done) {
148
- loaded += readObj.value.byteLength;
149
- if (streamDataCallback) {
150
- await streamDataCallback(readObj.value);
123
+
124
+ // In new versions of Chromium, HEAD requests now have a response body
125
+ // that is null.
126
+ // So just don't try to download the body at all, if it's a HEAD request,
127
+ // to avoid null reference errors.
128
+ // See: https://crbug.com/1297060
129
+ if (init.method != 'HEAD') {
130
+ goog.asserts.assert(response.body,
131
+ 'non-HEAD responses should have a body');
132
+
133
+ // Getting the reader in this way allows us to observe the process of
134
+ // downloading the body, instead of just waiting for an opaque promise
135
+ // to resolve.
136
+ // We first clone the response because calling getReader locks the body
137
+ // stream; if we didn't clone it here, we would be unable to get the
138
+ // response's arrayBuffer later.
139
+ const reader = response.clone().body.getReader();
140
+
141
+ const contentLengthRaw = response.headers.get('Content-Length');
142
+ const contentLength =
143
+ contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;
144
+
145
+ const start = (controller) => {
146
+ const push = async () => {
147
+ let readObj;
148
+ try {
149
+ readObj = await reader.read();
150
+ } catch (e) {
151
+ // If we abort the request, we'll get an error here. Just ignore
152
+ // it since real errors will be reported when we read the buffer
153
+ // below.
154
+ shaka.log.v1('error reading from stream', e.message);
155
+ return;
156
+ }
157
+
158
+ if (!readObj.done) {
159
+ loaded += readObj.value.byteLength;
160
+ if (streamDataCallback) {
161
+ await streamDataCallback(readObj.value);
162
+ }
163
+ }
164
+
165
+ const currentTime = Date.now();
166
+ // If the time between last time and this time we got progress event
167
+ // is long enough, or if a whole segment is downloaded, call
168
+ // progressUpdated().
169
+ if (currentTime - lastTime > 100 || readObj.done) {
170
+ progressUpdated(currentTime - lastTime, loaded - lastLoaded,
171
+ contentLength - loaded);
172
+ lastLoaded = loaded;
173
+ lastTime = currentTime;
174
+ }
175
+
176
+ if (readObj.done) {
177
+ goog.asserts.assert(!readObj.value,
178
+ 'readObj should be unset when "done" is true.');
179
+ controller.close();
180
+ } else {
181
+ controller.enqueue(readObj.value);
182
+ push();
151
183
  }
152
- }
153
-
154
- const currentTime = Date.now();
155
- // If the time between last time and this time we got progress event
156
- // is long enough, or if a whole segment is downloaded, call
157
- // progressUpdated().
158
- if (currentTime - lastTime > 100 || readObj.done) {
159
- progressUpdated(currentTime - lastTime, loaded - lastLoaded,
160
- contentLength - loaded);
161
- lastLoaded = loaded;
162
- lastTime = currentTime;
163
- }
164
-
165
- if (readObj.done) {
166
- goog.asserts.assert(!readObj.value,
167
- 'readObj should be unset when "done" is true.');
168
- controller.close();
169
- } else {
170
- controller.enqueue(readObj.value);
171
- push();
172
- }
184
+ };
185
+ push();
173
186
  };
174
- push();
175
- };
176
- // Create a ReadableStream to use the reader. We don't need to use the
177
- // actual stream for anything, though, as we are using the response's
178
- // arrayBuffer method to get the body, so we don't store the
179
- // ReadableStream.
180
- new ReadableStream({start}); // eslint-disable-line no-new
181
- arrayBuffer = await response.arrayBuffer();
187
+ // Create a ReadableStream to use the reader. We don't need to use the
188
+ // actual stream for anything, though, as we are using the response's
189
+ // arrayBuffer method to get the body, so we don't store the
190
+ // ReadableStream.
191
+ new ReadableStream({start}); // eslint-disable-line no-new
192
+ arrayBuffer = await response.arrayBuffer();
193
+ }
182
194
  } catch (error) {
183
195
  if (abortStatus.canceled) {
184
196
  throw new shaka.util.Error(
@@ -243,6 +255,16 @@ shaka.net.HttpFetchPlugin = class {
243
255
  } else {
244
256
  return false;
245
257
  }
258
+ // Old fetch implementations hasn't body and ReadableStream implementation
259
+ // See: https://github.com/shaka-project/shaka-player/issues/5088
260
+ if (window.Response) {
261
+ const response = new Response('');
262
+ if (!response.body) {
263
+ return false;
264
+ }
265
+ } else {
266
+ return false;
267
+ }
246
268
  return !!(window.fetch && window.AbortController);
247
269
  }
248
270
  };
@@ -312,6 +312,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
312
312
  if (!responseAndGotProgress.gotProgress &&
313
313
  this.onProgressUpdated_ &&
314
314
  !response.fromCache &&
315
+ request.method != 'HEAD' &&
315
316
  type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
316
317
  this.onProgressUpdated_(response.timeMs, response.data.byteLength);
317
318
  }
package/lib/player.js CHANGED
@@ -897,7 +897,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
897
897
  }
898
898
 
899
899
  this.abrManagerFactory_ = null;
900
- this.abrManager_ = null;
901
900
  this.config_ = null;
902
901
  this.stats_ = null;
903
902
  this.videoContainer_ = null;
@@ -908,6 +907,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
908
907
  this.networkingEngine_ = null;
909
908
  }
910
909
 
910
+ if (this.abrManager_) {
911
+ this.abrManager_.release();
912
+ this.abrManager_ = null;
913
+ }
914
+
911
915
  // FakeEventTarget implements IReleasable
912
916
  super.release();
913
917
  }
@@ -1342,6 +1346,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1342
1346
  */
1343
1347
  shouldUseSrcEquals_(payload) {
1344
1348
  const Platform = shaka.util.Platform;
1349
+ const MimeUtils = shaka.util.MimeUtils;
1345
1350
 
1346
1351
  // If we are using a platform that does not support media source, we will
1347
1352
  // fall back to src= to handle all playback.
@@ -1396,7 +1401,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1396
1401
  // version there.
1397
1402
 
1398
1403
  // Native HLS can be preferred on any platform via this flag:
1399
- if (this.config_.streaming.preferNativeHls) {
1404
+ if (MimeUtils.isHlsType(mimeType) &&
1405
+ this.config_.streaming.preferNativeHls) {
1400
1406
  return true;
1401
1407
  }
1402
1408
 
@@ -4276,6 +4282,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4276
4282
  firstVariantWithLabel.language, '', 0, label);
4277
4283
 
4278
4284
  this.chooseVariantAndSwitch_();
4285
+ } else if (this.video_ && this.video_.audioTracks) {
4286
+ const audioTracks = Array.from(this.video_.audioTracks);
4287
+
4288
+ let trackMatch = null;
4289
+
4290
+ for (const audioTrack of audioTracks) {
4291
+ if (audioTrack.label == label) {
4292
+ trackMatch = audioTrack;
4293
+ }
4294
+ }
4295
+ if (trackMatch) {
4296
+ this.switchHtml5Track_(trackMatch);
4297
+ }
4279
4298
  }
4280
4299
  }
4281
4300
 
@@ -6986,7 +7005,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6986
7005
  * @define {string} A version number taken from git at compile time.
6987
7006
  * @export
6988
7007
  */
6989
- shaka.Player.version = 'v4.3.5-uncompiled';
7008
+ shaka.Player.version = 'v4.3.6-uncompiled';
6990
7009
 
6991
7010
  // Initialize the deprecation system using the version string we just set
6992
7011
  // on the player.
@@ -119,6 +119,19 @@ shaka.polyfill.MediaCapabilities = class {
119
119
  if (shaka.util.Platform.isChromecast()) {
120
120
  isSupported =
121
121
  shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
122
+ } else if (shaka.util.Platform.isTizen()) {
123
+ let extendedType = videoConfig.contentType;
124
+ if (videoConfig.width && videoConfig.height) {
125
+ extendedType += `; width=${videoConfig.width}`;
126
+ extendedType += `; height=${videoConfig.height}`;
127
+ }
128
+ if (videoConfig.framerate) {
129
+ extendedType += `; framerate=${videoConfig.framerate}`;
130
+ }
131
+ if (videoConfig.bitrate) {
132
+ extendedType += `; bitrate=${videoConfig.bitrate}`;
133
+ }
134
+ isSupported = Capabilities.isTypeSupported(extendedType);
122
135
  } else {
123
136
  isSupported = Capabilities.isTypeSupported(videoConfig.contentType);
124
137
  }
@@ -97,6 +97,10 @@ shaka.text.Mp4VttParser = class {
97
97
  * @export
98
98
  */
99
99
  parseMedia(data, time) {
100
+ if (!data.length) {
101
+ return [];
102
+ }
103
+
100
104
  if (!this.timescale_) {
101
105
  // Missing timescale for VTT content. We should have seen the init
102
106
  // segment.
@@ -139,6 +139,7 @@ shaka.text.TextEngine = class {
139
139
  // into TextEngine and TextDisplayer.
140
140
  if (mimeType == shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE ||
141
141
  mimeType == shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE) {
142
+ this.parser_ = null;
142
143
  return;
143
144
  }
144
145
 
@@ -185,8 +185,7 @@ shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
185
185
  /^mp4a/,
186
186
  /^[ae]c-3$/,
187
187
  /^ac-4$/,
188
- /^dts[cx]$/, // DTS Surround Sound
189
- /^[du]dts$/, // DTS Surround Sound
188
+ /^dts[cex]$/, // DTS Digital Surround (dtsc), DTS Express (dtse), DTS:X (dtsx)
190
189
  ];
191
190
 
192
191
 
@@ -193,6 +193,17 @@ shaka.util.MimeUtils = class {
193
193
  return value;
194
194
  }
195
195
 
196
+ /**
197
+ * Checks if the given MIME type is HLS MIME type.
198
+ *
199
+ * @param {string} mimeType
200
+ * @return {boolean}
201
+ */
202
+ static isHlsType(mimeType) {
203
+ return mimeType === 'application/x-mpegurl' ||
204
+ mimeType === 'application/vnd.apple.mpegurl';
205
+ }
206
+
196
207
  /**
197
208
  * Get the base and profile of a codec string. Where [0] will be the codec
198
209
  * base and [1] will be the profile.
@@ -671,11 +671,11 @@ shaka.util.PeriodCombiner = class {
671
671
  // Also checks if the segmentIndex is still valid after the async
672
672
  // operations, to make sure we stop if the active stream has changed.
673
673
  if (outputStream.segmentIndex instanceof shaka.media.MetaSegmentIndex) {
674
- for (let i = 0; i < streams.length; i++) {
674
+ for (let i = firstNewPeriodIndex; i < streams.length; i++) {
675
675
  const match = streams[i];
676
- if (match.segmentIndex && i >= firstNewPeriodIndex) {
677
- goog.asserts.assert(match.segmentIndex,
678
- 'stream should have a segmentIndex.');
676
+ goog.asserts.assert(match.segmentIndex,
677
+ 'stream should have a segmentIndex.');
678
+ if (match.segmentIndex) {
679
679
  outputStream.segmentIndex.appendSegmentIndex(match.segmentIndex);
680
680
  }
681
681
  }
@@ -728,7 +728,7 @@ shaka.util.PeriodCombiner = class {
728
728
  // in advance. concat() will add them to the output's MetaSegmentIndex.
729
729
  }
730
730
 
731
- if (!outputStream.matchedStreams) {
731
+ if (!outputStream.matchedStreams || !outputStream.matchedStreams.length) {
732
732
  // This is not a stream we can build output from, but it may become part
733
733
  // of another output based on another period's stream.
734
734
  return null;
@@ -830,7 +830,7 @@ shaka.util.StreamUtils = class {
830
830
  const video = variant.video;
831
831
  if (audio && currentVariant && currentVariant.audio) {
832
832
  if (!StreamUtils.areStreamsCompatible_(audio, currentVariant.audio)) {
833
- shaka.log.debug('Droping variant - not compatible with active audio',
833
+ shaka.log.debug('Dropping variant - not compatible with active audio',
834
834
  'active audio',
835
835
  StreamUtils.getStreamSummaryString_(currentVariant.audio),
836
836
  'variant.audio',
@@ -841,7 +841,7 @@ shaka.util.StreamUtils = class {
841
841
 
842
842
  if (video && currentVariant && currentVariant.video) {
843
843
  if (!StreamUtils.areStreamsCompatible_(video, currentVariant.video)) {
844
- shaka.log.debug('Droping variant - not compatible with active video',
844
+ shaka.log.debug('Dropping variant - not compatible with active video',
845
845
  'active video',
846
846
  StreamUtils.getStreamSummaryString_(currentVariant.video),
847
847
  'variant.video',
@@ -2,10 +2,9 @@
2
2
 
3
3
  We are currently maintaining the following branches of Shaka Player:
4
4
 
5
- - v4.3 (latest)
5
+ - v4.3 (latest, also in use by the Cast Application Framework)
6
6
  - v4.2 (previous)
7
- - v3.3 (LTS until April 30, 2023)
8
- - v3.2 (in use by the Cast Application Framework)
7
+ - No active LTS branches at this time
9
8
 
10
9
  Other branches are no longer receiving bug fixes, and we recommend you upgrade
11
10
  to a maintained branch. For details on upgrading, please see our
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.3.5",
4
+ "version": "4.3.6",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -194,7 +194,8 @@ describe('DashParser Live', () => {
194
194
  template, {updateTime: updateTime, contents: basicLines.join('\n')});
195
195
 
196
196
  fakeNetEngine.setResponseText('dummy://foo', text);
197
- Date.now = () => 0;
197
+ const baseTime = new Date(2015, 11, 30);
198
+ Date.now = () => baseTime.getTime();
198
199
  const manifest = await parser.start('dummy://foo', playerInterface);
199
200
 
200
201
  expect(manifest).toBeTruthy();
@@ -211,7 +212,7 @@ describe('DashParser Live', () => {
211
212
  // seconds long in all of these cases. So 11 seconds after the
212
213
  // manifest was parsed, the first segment should have fallen out of
213
214
  // the availability window.
214
- Date.now = () => 11 * 1000;
215
+ Date.now = () => baseTime.getTime() + (11 * 1000);
215
216
  await updateManifest();
216
217
  // The first reference should have been evicted.
217
218
  expect(stream.segmentIndex.find(0)).toBe(firstPosition + 1);
@@ -987,6 +987,118 @@ describe('HlsParser', () => {
987
987
  await testHlsParser(master, media, manifest);
988
988
  });
989
989
 
990
+ it('parses discontinuity tags', async () => {
991
+ const master = [
992
+ '#EXTM3U\n',
993
+ '#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1"\n',
994
+ 'video\n',
995
+ ].join('');
996
+
997
+ const media = [
998
+ '#EXTM3U\n',
999
+ '#EXT-X-VERSION:3\n',
1000
+ '#EXT-X-TARGETDURATION:5\n',
1001
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
1002
+ '#EXTINF:3,\n',
1003
+ 'clip0-video-0.ts\n',
1004
+ '#EXTINF:1,\n',
1005
+ 'clip0-video-1.ts\n',
1006
+ '#EXT-X-DISCONTINUITY\n',
1007
+ '#EXTINF:2,\n',
1008
+ 'clip1-video-1.ts\n',
1009
+ '#EXTINF:3,\n',
1010
+ 'clip1-video-2.ts\n',
1011
+ '#EXT-X-DISCONTINUITY\n',
1012
+ '#EXTINF:1,\n',
1013
+ 'media-clip2-video-0.ts\n',
1014
+ '#EXTINF:1,\n',
1015
+ 'media-clip2-video-1.ts\n',
1016
+ '#EXT-X-DISCONTINUITY\n',
1017
+ '#EXTINF:4,\n',
1018
+ 'media-clip3-video-1.ts\n',
1019
+ '#EXT-X-ENDLIST\n',
1020
+ ].join('');
1021
+
1022
+ fakeNetEngine
1023
+ .setResponseText('test:/master', master)
1024
+ .setResponseText('test:/video', media);
1025
+
1026
+ const manifest = await parser.start('test:/master', playerInterface);
1027
+ await manifest.variants[0].video.createSegmentIndex();
1028
+
1029
+ const segmentIndex = manifest.variants[0].video.segmentIndex;
1030
+ const references = [];
1031
+
1032
+ for (let i = 0; i < 7; i++) {
1033
+ references.push(segmentIndex.get(i));
1034
+ }
1035
+
1036
+ expect(references[0].discontinuitySequence).toBe(0);
1037
+ expect(references[1].discontinuitySequence).toBe(0);
1038
+ expect(references[2].discontinuitySequence).toBe(1);
1039
+ expect(references[3].discontinuitySequence).toBe(1);
1040
+ expect(references[4].discontinuitySequence).toBe(2);
1041
+ expect(references[5].discontinuitySequence).toBe(2);
1042
+ expect(references[6].discontinuitySequence).toBe(3);
1043
+ });
1044
+
1045
+ it('sets reference timetampOffset based on discontinuity start time',
1046
+ async () => {
1047
+ const master = [
1048
+ '#EXTM3U\n',
1049
+ '#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1"\n',
1050
+ 'video\n',
1051
+ ].join('');
1052
+
1053
+ const media = [
1054
+ '#EXTM3U\n',
1055
+ '#EXT-X-VERSION:3\n',
1056
+ '#EXT-X-TARGETDURATION:5\n',
1057
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
1058
+ '#EXTINF:3,\n',
1059
+ 'clip0-video-0.ts\n',
1060
+ '#EXTINF:1,\n',
1061
+ 'clip0-video-1.ts\n',
1062
+ '#EXT-X-DISCONTINUITY\n',
1063
+ '#EXTINF:2,\n',
1064
+ 'clip1-video-1.ts\n',
1065
+ '#EXTINF:3,\n',
1066
+ 'clip1-video-2.ts\n',
1067
+ '#EXT-X-DISCONTINUITY\n',
1068
+ '#EXTINF:1,\n',
1069
+ 'media-clip2-video-0.ts\n',
1070
+ '#EXTINF:1,\n',
1071
+ 'media-clip2-video-1.ts\n',
1072
+ '#EXT-X-DISCONTINUITY\n',
1073
+ '#EXTINF:4,\n',
1074
+ 'media-clip3-video-1.ts\n',
1075
+ '#EXT-X-ENDLIST\n',
1076
+ ].join('');
1077
+
1078
+ fakeNetEngine
1079
+ .setResponseText('test:/master', master)
1080
+ .setResponseText('test:/video', media);
1081
+
1082
+ const manifest = await parser.start('test:/master', playerInterface);
1083
+ await manifest.variants[0].video.createSegmentIndex();
1084
+
1085
+ const segmentIndex = manifest.variants[0].video.segmentIndex;
1086
+ const references = [];
1087
+
1088
+ for (let i = 0; i < 7; i++) {
1089
+ references.push(segmentIndex.get(i));
1090
+ }
1091
+
1092
+ expect(references[0].timestampOffset).toBe(0);
1093
+ expect(references[1].timestampOffset).toBe(0);
1094
+ expect(references[2].timestampOffset).toBe(4);
1095
+ expect(references[3].timestampOffset).toBe(4);
1096
+ expect(references[4].timestampOffset).toBe(9);
1097
+ expect(references[5].timestampOffset).toBe(9);
1098
+ expect(references[6].timestampOffset).toBe(11);
1099
+ },
1100
+ );
1101
+
990
1102
  it('parses characteristics from audio tags', async () => {
991
1103
  const master = [
992
1104
  '#EXTM3U\n',
@@ -2132,6 +2244,50 @@ describe('HlsParser', () => {
2132
2244
  expect(actual).toEqual(manifest);
2133
2245
  });
2134
2246
 
2247
+ it('parses manifest with MP4+WEBVTT streams', async () => {
2248
+ const master = [
2249
+ '#EXTM3U\n',
2250
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
2251
+ 'CODECS="wvtt",URI="text"\n',
2252
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2253
+ 'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
2254
+ 'video\n',
2255
+ ].join('');
2256
+
2257
+ const media = [
2258
+ '#EXTM3U\n',
2259
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2260
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2261
+ '#EXTINF:5,\n',
2262
+ '#EXT-X-BYTERANGE:121090@616\n',
2263
+ 'main.mp4',
2264
+ ].join('');
2265
+
2266
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2267
+ manifest.anyTimeline();
2268
+ manifest.addPartialVariant((variant) => {
2269
+ variant.addPartialStream(ContentType.VIDEO);
2270
+ });
2271
+ manifest.addPartialTextStream((stream) => {
2272
+ stream.language = 'en';
2273
+ stream.mime('application/mp4', 'wvtt');
2274
+ });
2275
+ manifest.sequenceMode = true;
2276
+ });
2277
+
2278
+ fakeNetEngine
2279
+ .setResponseText('test:/master', master)
2280
+ .setResponseText('test:/audio', media)
2281
+ .setResponseText('test:/video', media)
2282
+ .setResponseText('test:/text', media)
2283
+ .setResponseValue('test:/init.mp4', initSegmentData)
2284
+ .setResponseValue('test:/main.mp4', segmentData);
2285
+
2286
+ const actual = await parser.start('test:/master', playerInterface);
2287
+ await loadAllStreamsFor(actual);
2288
+ expect(actual).toEqual(manifest);
2289
+ });
2290
+
2135
2291
  it('detects VTT streams by codec', async () => {
2136
2292
  const master = [
2137
2293
  '#EXTM3U\n',