shaka-player 4.3.5 → 4.3.7

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 (102) hide show
  1. package/.github/workflows/demo-version-index.yaml +3 -3
  2. package/.github/workflows/selenium-lab-tests.yaml +23 -1
  3. package/AUTHORS +2 -1
  4. package/CHANGELOG.md +53 -0
  5. package/CONTRIBUTORS +4 -1
  6. package/README.md +3 -0
  7. package/demo/demo.less +1 -0
  8. package/dist/controls.css +2 -2
  9. package/dist/controls.css.map +1 -1
  10. package/dist/demo.compiled.debug.js +167 -296
  11. package/dist/demo.compiled.debug.map +4 -4
  12. package/dist/demo.compiled.js +167 -296
  13. package/dist/demo.compiled.map +4 -4
  14. package/dist/demo.css +1 -1
  15. package/dist/demo.css.map +1 -1
  16. package/dist/deps.js +1 -1
  17. package/dist/locales.js +32 -32
  18. package/dist/receiver.compiled.debug.js +34 -63
  19. package/dist/receiver.compiled.debug.map +4 -4
  20. package/dist/receiver.compiled.js +34 -63
  21. package/dist/receiver.compiled.map +4 -4
  22. package/dist/shaka-player.compiled.d.ts +20 -3
  23. package/dist/shaka-player.compiled.debug.d.ts +20 -3
  24. package/dist/shaka-player.compiled.debug.externs.js +29 -14
  25. package/dist/shaka-player.compiled.debug.js +303 -292
  26. package/dist/shaka-player.compiled.debug.map +4 -4
  27. package/dist/shaka-player.compiled.externs.js +29 -14
  28. package/dist/shaka-player.compiled.js +886 -879
  29. package/dist/shaka-player.compiled.map +3 -3
  30. package/dist/shaka-player.ui.d.ts +20 -3
  31. package/dist/shaka-player.ui.debug.d.ts +20 -3
  32. package/dist/shaka-player.ui.debug.externs.js +17 -2
  33. package/dist/shaka-player.ui.debug.js +321 -309
  34. package/dist/shaka-player.ui.debug.map +3 -3
  35. package/dist/shaka-player.ui.externs.js +17 -2
  36. package/dist/shaka-player.ui.js +1027 -1021
  37. package/dist/shaka-player.ui.map +3 -3
  38. package/docs/tutorials/a11y.md +0 -4
  39. package/docs/tutorials/ad_monetization.md +0 -4
  40. package/docs/tutorials/basic-usage.md +0 -5
  41. package/docs/tutorials/config.md +0 -5
  42. package/docs/tutorials/debugging.md +0 -5
  43. package/docs/tutorials/drm-config.md +0 -5
  44. package/docs/tutorials/fairplay.md +2 -2
  45. package/docs/tutorials/license-server-auth.md +0 -5
  46. package/docs/tutorials/license-wrapping.md +0 -5
  47. package/docs/tutorials/network-and-buffering-config.md +0 -5
  48. package/docs/tutorials/ui-customization.md +0 -5
  49. package/docs/tutorials/ui.md +0 -7
  50. package/docs/tutorials/welcome.md +1 -6
  51. package/externs/network_information.js +7 -2
  52. package/externs/shaka/abr_manager.js +6 -0
  53. package/externs/shaka/net.js +1 -0
  54. package/externs/shaka/player.js +13 -7
  55. package/lib/abr/simple_abr_manager.js +27 -3
  56. package/lib/ads/client_side_ad_manager.js +6 -3
  57. package/lib/cea/mp4_cea_parser.js +5 -2
  58. package/lib/dash/dash_parser.js +49 -48
  59. package/lib/debug/log.js +9 -13
  60. package/lib/hls/hls_parser.js +60 -7
  61. package/lib/hls/manifest_text_parser.js +10 -1
  62. package/lib/media/drm_engine.js +9 -6
  63. package/lib/media/media_source_engine.js +33 -13
  64. package/lib/media/presentation_timeline.js +9 -2
  65. package/lib/media/segment_index.js +2 -0
  66. package/lib/media/segment_reference.js +25 -2
  67. package/lib/media/streaming_engine.js +40 -11
  68. package/lib/net/http_fetch_plugin.js +79 -57
  69. package/lib/net/networking_engine.js +1 -0
  70. package/lib/player.js +26 -3
  71. package/lib/polyfill/media_capabilities.js +13 -0
  72. package/lib/text/mp4_vtt_parser.js +4 -0
  73. package/lib/text/text_engine.js +1 -0
  74. package/lib/util/manifest_parser_utils.js +1 -2
  75. package/lib/util/mime_utils.js +11 -0
  76. package/lib/util/periods.js +5 -5
  77. package/lib/util/stream_utils.js +2 -2
  78. package/maintained-branches.md +2 -3
  79. package/package.json +8 -3
  80. package/test/cea/mp4_cea_parser_unit.js +19 -0
  81. package/test/dash/dash_parser_live_unit.js +3 -2
  82. package/test/hls/hls_live_unit.js +3 -1
  83. package/test/hls/hls_parser_unit.js +156 -0
  84. package/test/media/content_workarounds_unit.js +43 -0
  85. package/test/media/drm_engine_integration.js +4 -17
  86. package/test/media/media_source_engine_unit.js +30 -0
  87. package/test/media/streaming_engine_unit.js +6 -1
  88. package/test/net/networking_engine_unit.js +6 -1
  89. package/test/player_integration.js +39 -0
  90. package/test/player_unit.js +57 -0
  91. package/test/polyfill/media_capabilities_unit.js +1 -0
  92. package/test/test/assets/h265-cea-init.mp4 +0 -0
  93. package/test/test/assets/h265-cea-segment.mp4 +0 -0
  94. package/test/test/assets/text-clip-alt.vtt +63 -0
  95. package/test/test/util/fake_drm_engine.js +3 -0
  96. package/test/test/util/fake_media_source_engine.js +4 -0
  97. package/test/test/util/layout_tests.js +4 -3
  98. package/test/test/util/simple_fakes.js +6 -0
  99. package/test/test/util/test_scheme.js +9 -14
  100. package/test/text/mp4_vtt_parser_unit.js +8 -0
  101. package/test/util/mime_utils_unit.js +10 -0
  102. package/ui/less/containers.less +4 -0
@@ -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
 
@@ -6047,6 +6066,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6047
6066
  error.category != shaka.util.Error.Category.NETWORK) {
6048
6067
  return false;
6049
6068
  }
6069
+ if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE) {
6070
+ return false;
6071
+ }
6072
+ goog.asserts.assert(this.manifest_, 'Should have manifest!');
6050
6073
 
6051
6074
  if (!navigator.onLine) {
6052
6075
  // Don't restrict variants if we're completely offline, or else we end up
@@ -6986,7 +7009,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6986
7009
  * @define {string} A version number taken from git at compile time.
6987
7010
  * @export
6988
7011
  */
6989
- shaka.Player.version = 'v4.3.5-uncompiled';
7012
+ shaka.Player.version = 'v4.3.7-uncompiled';
6990
7013
 
6991
7014
  // Initialize the deprecation system using the version string we just set
6992
7015
  // 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.7",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -18,6 +18,7 @@
18
18
  "awesomplete": "^1.1.5",
19
19
  "babel-plugin-istanbul": "^6.1.1",
20
20
  "cajon": "^0.4.4",
21
+ "clean-package": "^2.2.0",
21
22
  "code-prettify": "^0.1.0",
22
23
  "color-themes-for-google-code-prettify": "^2.0.4",
23
24
  "core-js": "^3.21.1",
@@ -94,12 +95,16 @@
94
95
  },
95
96
  "license": "Apache-2.0",
96
97
  "scripts": {
98
+ "prepack": "clean-package",
99
+ "postpack": "clean-package restore",
97
100
  "prepublishOnly": "python build/checkversion.py && python build/all.py --force"
98
101
  },
99
102
  "dependencies": {
100
103
  "eme-encryption-scheme-polyfill": "^2.1.1"
101
104
  },
102
- "engines": {
103
- "node": ">=14"
105
+ "clean-package": {
106
+ "remove": [
107
+ "engines"
108
+ ]
104
109
  }
105
110
  }
@@ -7,20 +7,30 @@
7
7
  describe('Mp4CeaParser', () => {
8
8
  const ceaInitSegmentUri = '/base/test/test/assets/cea-init.mp4';
9
9
  const ceaSegmentUri = '/base/test/test/assets/cea-segment.mp4';
10
+ const h265ceaInitSegmentUri = '/base/test/test/assets/h265-cea-init.mp4';
11
+ const h265ceaSegmentUri = '/base/test/test/assets/h265-cea-segment.mp4';
10
12
  const Util = shaka.test.Util;
11
13
 
12
14
  /** @type {!ArrayBuffer} */
13
15
  let ceaInitSegment;
14
16
  /** @type {!ArrayBuffer} */
15
17
  let ceaSegment;
18
+ /** @type {!ArrayBuffer} */
19
+ let h265ceaInitSegment;
20
+ /** @type {!ArrayBuffer} */
21
+ let h265ceaSegment;
16
22
 
17
23
  beforeAll(async () => {
18
24
  const responses = await Promise.all([
19
25
  shaka.test.Util.fetch(ceaInitSegmentUri),
20
26
  shaka.test.Util.fetch(ceaSegmentUri),
27
+ shaka.test.Util.fetch(h265ceaInitSegmentUri),
28
+ shaka.test.Util.fetch(h265ceaSegmentUri),
21
29
  ]);
22
30
  ceaInitSegment = responses[0];
23
31
  ceaSegment = responses[1];
32
+ h265ceaInitSegment = responses[2];
33
+ h265ceaSegment = responses[3];
24
34
  });
25
35
 
26
36
  /**
@@ -70,6 +80,15 @@ describe('Mp4CeaParser', () => {
70
80
  .toEqual(expectedCea708Packet);
71
81
  });
72
82
 
83
+ it('parses cea data from an h265 mp4 stream', () => {
84
+ const ceaParser = new shaka.cea.Mp4CeaParser();
85
+
86
+ ceaParser.init(h265ceaInitSegment);
87
+ const ceaPackets = ceaParser.parse(h265ceaSegment);
88
+ expect(ceaPackets).toBeDefined();
89
+ expect(ceaPackets.length).toBe(60);
90
+ });
91
+
73
92
  it('parses an invalid init segment', () => {
74
93
  const cea708Parser = new shaka.cea.Mp4CeaParser();
75
94
 
@@ -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);
@@ -590,7 +590,8 @@ describe('HlsParser live', () => {
590
590
  // partialRef3
591
591
  '#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
592
592
  // preloadRef
593
- '#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n',
593
+ '#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210,',
594
+ 'BYTERANGE-LENGTH=210\n',
594
595
  ].join('');
595
596
 
596
597
  const partialRef = makeReference(
@@ -613,6 +614,7 @@ describe('HlsParser live', () => {
613
614
  const preloadRef = makeReference(
614
615
  'test:/partial.mp4', 6, 7.5, /* syncTime= */ null,
615
616
  /* baseUri= */ '', /* startByte= */ 210, /* endByte= */ null);
617
+ preloadRef.markAsPreload();
616
618
 
617
619
  // ref2 is not fully published yet, so it doesn't have a segment uri.
618
620
  const ref2 = makeReference(
@@ -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',