shaka-player 4.3.6 → 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 (39) hide show
  1. package/.github/workflows/demo-version-index.yaml +3 -3
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +3 -0
  4. package/demo/demo.less +1 -0
  5. package/dist/demo.css +1 -1
  6. package/dist/demo.css.map +1 -1
  7. package/dist/locales.js +31 -31
  8. package/dist/shaka-player.compiled.d.ts +8 -0
  9. package/dist/shaka-player.compiled.debug.d.ts +8 -0
  10. package/dist/shaka-player.compiled.debug.externs.js +9 -0
  11. package/dist/shaka-player.compiled.debug.js +130 -125
  12. package/dist/shaka-player.compiled.debug.map +3 -3
  13. package/dist/shaka-player.compiled.externs.js +9 -0
  14. package/dist/shaka-player.compiled.js +754 -751
  15. package/dist/shaka-player.compiled.map +3 -3
  16. package/dist/shaka-player.ui.d.ts +8 -0
  17. package/dist/shaka-player.ui.debug.d.ts +8 -0
  18. package/dist/shaka-player.ui.debug.externs.js +9 -0
  19. package/dist/shaka-player.ui.debug.js +236 -231
  20. package/dist/shaka-player.ui.debug.map +3 -3
  21. package/dist/shaka-player.ui.externs.js +9 -0
  22. package/dist/shaka-player.ui.js +578 -575
  23. package/dist/shaka-player.ui.map +3 -3
  24. package/docs/tutorials/welcome.md +1 -1
  25. package/lib/cea/mp4_cea_parser.js +5 -2
  26. package/lib/hls/hls_parser.js +43 -2
  27. package/lib/hls/manifest_text_parser.js +10 -1
  28. package/lib/media/drm_engine.js +9 -6
  29. package/lib/media/media_source_engine.js +6 -4
  30. package/lib/media/segment_reference.js +22 -0
  31. package/lib/media/streaming_engine.js +9 -4
  32. package/lib/player.js +5 -1
  33. package/package.json +8 -3
  34. package/test/cea/mp4_cea_parser_unit.js +19 -0
  35. package/test/hls/hls_live_unit.js +3 -1
  36. package/test/media/media_source_engine_unit.js +30 -0
  37. package/test/test/assets/h265-cea-init.mp4 +0 -0
  38. package/test/test/assets/h265-cea-segment.mp4 +0 -0
  39. package/test/test/util/layout_tests.js +4 -3
@@ -29,7 +29,7 @@ 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 v14+}
32
+ * {@link https://learn.microsoft.com/en-us/java/openjdk/download Java Runtime Environment v14+}
33
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
@@ -275,6 +275,7 @@ shaka.cea.Mp4CeaParser = class {
275
275
  const naluHeader = reader.readUint8();
276
276
  let naluType = null;
277
277
  let isSeiMessage = false;
278
+ let naluHeaderSize = 1;
278
279
 
279
280
  goog.asserts.assert(this.bitstreamFormat_ != BitstreamFormat.UNKNOWN,
280
281
  'Bitstream format should have been checked before now!');
@@ -285,6 +286,8 @@ shaka.cea.Mp4CeaParser = class {
285
286
  break;
286
287
 
287
288
  case BitstreamFormat.H265:
289
+ naluHeaderSize = 2;
290
+ reader.skip(1);
288
291
  naluType = (naluHeader >> 1) & 0x3f;
289
292
  isSeiMessage =
290
293
  naluType == ICeaParser.H265_PREFIX_NALU_TYPE_SEI ||
@@ -304,7 +307,7 @@ shaka.cea.Mp4CeaParser = class {
304
307
 
305
308
  const pts = (time + timeOffset)/timescale;
306
309
  for (const packet of this.seiProcessor_
307
- .process(reader.readBytes(naluSize - 1))) {
310
+ .process(reader.readBytes(naluSize - naluHeaderSize))) {
308
311
  captionPackets.push({
309
312
  packet,
310
313
  pts,
@@ -312,7 +315,7 @@ shaka.cea.Mp4CeaParser = class {
312
315
  }
313
316
  } else {
314
317
  try {
315
- reader.skip(naluSize - 1);
318
+ reader.skip(naluSize - naluHeaderSize);
316
319
  } catch (e) {
317
320
  // It is necessary to ignore this error because it can break the start
318
321
  // of playback even if the user does not want to see the subtitles.
@@ -319,6 +319,9 @@ shaka.hls.HlsParser = class {
319
319
  this.presentationTimeline_.setDuration(Math.min(...maxTimestamps));
320
320
  this.playerInterface_.updateDuration();
321
321
  }
322
+ if (stillLive) {
323
+ this.determineDuration_();
324
+ }
322
325
  }
323
326
 
324
327
  /**
@@ -400,9 +403,16 @@ shaka.hls.HlsParser = class {
400
403
  mediaSequenceNumber);
401
404
  stream.segmentIndex.evict(playlistStartTime);
402
405
  }
406
+ const oldSegment = segments[0];
407
+ goog.asserts.assert(oldSegment, 'Should have segments!');
408
+
409
+ streamInfo.minTimestamp = oldSegment.startTime;
410
+
403
411
  const newestSegment = segments[segments.length - 1];
404
412
  goog.asserts.assert(newestSegment, 'Should have segments!');
405
413
 
414
+ streamInfo.maxTimestamp = newestSegment.endTime;
415
+
406
416
  // Once the last segment has been added to the playlist,
407
417
  // #EXT-X-ENDLIST tag will be appended.
408
418
  // If that happened, treat the rest of the EVENT presentation as VOD.
@@ -413,7 +423,6 @@ shaka.hls.HlsParser = class {
413
423
  // Flag this for later. We don't convert the whole presentation into VOD
414
424
  // until we've seen the ENDLIST tag for all active playlists.
415
425
  streamInfo.hasEndList = true;
416
- streamInfo.maxTimestamp = newestSegment.endTime;
417
426
  }
418
427
  }
419
428
 
@@ -576,6 +585,9 @@ shaka.hls.HlsParser = class {
576
585
  * @private
577
586
  */
578
587
  offsetStreamInfo_(streamInfo, offset) {
588
+ // Adjust our accounting of the minimum timestamp.
589
+ streamInfo.minTimestamp += offset;
590
+
579
591
  // Adjust our accounting of the maximum timestamp.
580
592
  streamInfo.maxTimestamp += offset;
581
593
  goog.asserts.assert(streamInfo.maxTimestamp >= 0,
@@ -804,7 +816,7 @@ shaka.hls.HlsParser = class {
804
816
  const PresentationType = shaka.hls.HlsParser.PresentationType_;
805
817
 
806
818
  if (this.presentationType_ == PresentationType.LIVE) {
807
- let segmentAvailabilityDuration = this.getMinDuration_();
819
+ let segmentAvailabilityDuration = this.getLiveDuration_();
808
820
 
809
821
  // This defaults to the presentation delay, which has the effect of
810
822
  // making the live stream unseekable. This is consistent with Apple's
@@ -1655,6 +1667,7 @@ shaka.hls.HlsParser = class {
1655
1667
  verbatimMediaPlaylistUri,
1656
1668
  // These values are filled out or updated after lazy-loading:
1657
1669
  absoluteMediaPlaylistUri: initialMediaPlaylistUri,
1670
+ minTimestamp: 0,
1658
1671
  maxTimestamp: 0,
1659
1672
  mediaSequenceToStartTime: new Map(),
1660
1673
  canSkipSegments: false,
@@ -1700,6 +1713,7 @@ shaka.hls.HlsParser = class {
1700
1713
 
1701
1714
  // Copy values from the real stream info to our initial one.
1702
1715
  streamInfo.absoluteMediaPlaylistUri = absoluteMediaPlaylistUri;
1716
+ streamInfo.minTimestamp = realStreamInfo.minTimestamp;
1703
1717
  streamInfo.maxTimestamp = realStreamInfo.maxTimestamp;
1704
1718
  streamInfo.canSkipSegments = realStreamInfo.canSkipSegments;
1705
1719
  streamInfo.hasEndList = realStreamInfo.hasEndList;
@@ -1833,6 +1847,22 @@ shaka.hls.HlsParser = class {
1833
1847
  return minDuration;
1834
1848
  }
1835
1849
 
1850
+ /**
1851
+ * @return {number}
1852
+ * @private
1853
+ */
1854
+ getLiveDuration_() {
1855
+ let maxTimestamp = Infinity;
1856
+ let minTimestamp = Infinity;
1857
+ for (const streamInfo of this.uriToStreamInfosMap_.values()) {
1858
+ if (streamInfo.stream.segmentIndex && streamInfo.stream.type != 'text') {
1859
+ maxTimestamp = Math.min(maxTimestamp, streamInfo.maxTimestamp);
1860
+ minTimestamp = Math.min(minTimestamp, streamInfo.minTimestamp);
1861
+ }
1862
+ }
1863
+ return maxTimestamp - minTimestamp;
1864
+ }
1865
+
1836
1866
  /**
1837
1867
  * @param {!Array.<!shaka.extern.Stream>} streams
1838
1868
  * @private
@@ -1978,6 +2008,7 @@ shaka.hls.HlsParser = class {
1978
2008
  this.determinePresentationType_(playlist);
1979
2009
  }
1980
2010
 
2011
+ const firstStartTime = segments[0].startTime;
1981
2012
  const lastEndTime = segments[segments.length - 1].endTime;
1982
2013
  /** @type {!shaka.media.SegmentIndex} */
1983
2014
  const segmentIndex = new shaka.media.SegmentIndex(segments);
@@ -2000,6 +2031,7 @@ shaka.hls.HlsParser = class {
2000
2031
  type,
2001
2032
  verbatimMediaPlaylistUri,
2002
2033
  absoluteMediaPlaylistUri,
2034
+ minTimestamp: firstStartTime,
2003
2035
  maxTimestamp: lastEndTime,
2004
2036
  canSkipSegments,
2005
2037
  hasEndList: false,
@@ -2498,6 +2530,9 @@ shaka.hls.HlsParser = class {
2498
2530
  /* tileDuration= */ null,
2499
2531
  /* syncTime= */ null,
2500
2532
  partialStatus);
2533
+ if (item.name == 'EXT-X-PRELOAD-HINT') {
2534
+ partial.markAsPreload();
2535
+ }
2501
2536
  partialSegmentRefs.push(partial);
2502
2537
  } // for-loop of hlsSegment.partialSegments
2503
2538
  }
@@ -2807,6 +2842,9 @@ shaka.hls.HlsParser = class {
2807
2842
  * @private
2808
2843
  */
2809
2844
  variableSubstitution_(uri, variables) {
2845
+ if (!variables.size) {
2846
+ return uri;
2847
+ }
2810
2848
  let newUri = String(uri).replace(/%7B/g, '{').replace(/%7D/g, '}');
2811
2849
 
2812
2850
  const uriVariables = newUri.match(/{\$\w*}/g);
@@ -3252,6 +3290,7 @@ shaka.hls.HlsParser = class {
3252
3290
  * type: string,
3253
3291
  * verbatimMediaPlaylistUri: string,
3254
3292
  * absoluteMediaPlaylistUri: string,
3293
+ * minTimestamp: number,
3255
3294
  * maxTimestamp: number,
3256
3295
  * mediaSequenceToStartTime: !Map.<number, number>,
3257
3296
  * canSkipSegments: boolean,
@@ -3275,6 +3314,8 @@ shaka.hls.HlsParser = class {
3275
3314
  * @property {string} absoluteMediaPlaylistUri
3276
3315
  * The absolute media playlist URI, resolved relative to the master playlist
3277
3316
  * and updated to reflect any redirects.
3317
+ * @property {number} minTimestamp
3318
+ * The minimum timestamp found in the stream.
3278
3319
  * @property {number} maxTimestamp
3279
3320
  * The maximum timestamp found in the stream.
3280
3321
  * @property {!Map.<number, number>} mediaSequenceToStartTime
@@ -158,7 +158,15 @@ shaka.hls.ManifestTextParser = class {
158
158
  partialSegmentTags.push(tag);
159
159
  } else if (tag.name == 'EXT-X-PRELOAD-HINT') {
160
160
  if (tag.getAttributeValue('TYPE') == 'PART') {
161
- partialSegmentTags.push(tag);
161
+ // Note: BYTERANGE-START without BYTERANGE-LENGTH is being
162
+ // ignored.
163
+ if (tag.getAttributeValue('BYTERANGE-START') != null) {
164
+ if (tag.getAttributeValue('BYTERANGE-LENGTH') != null) {
165
+ partialSegmentTags.push(tag);
166
+ }
167
+ } else {
168
+ partialSegmentTags.push(tag);
169
+ }
162
170
  } else if (tag.getAttributeValue('TYPE') == 'MAP') {
163
171
  // Rename the Preload Hint tag to be a Map tag.
164
172
  tag.setName('EXT-X-MAP');
@@ -300,6 +308,7 @@ shaka.hls.ManifestTextParser.MEDIA_PLAYLIST_TAGS = [
300
308
  'EXT-X-ENDLIST',
301
309
  'EXT-X-SERVER-CONTROL',
302
310
  'EXT-X-SKIP',
311
+ 'EXT-X-PART-INF',
303
312
  ];
304
313
 
305
314
 
@@ -133,6 +133,9 @@ shaka.media.DrmEngine = class {
133
133
 
134
134
  /** @private {Promise} */
135
135
  this.mediaKeysAttached_ = null;
136
+
137
+ /** @private {?shaka.extern.InitDataOverride} */
138
+ this.manifestInitData_ = null;
136
139
  }
137
140
 
138
141
  /** @override */
@@ -480,10 +483,10 @@ shaka.media.DrmEngine = class {
480
483
  () => this.closeOpenSessions_());
481
484
  }
482
485
 
483
- const manifestInitData = this.currentDrmInfo_ ?
484
- this.currentDrmInfo_.initData.find(
486
+ this.manifestInitData_ = this.currentDrmInfo_ ?
487
+ (this.currentDrmInfo_.initData.find(
485
488
  (initDataOverride) => initDataOverride.initData.length > 0,
486
- ) : null;
489
+ ) || null) : null;
487
490
 
488
491
  /**
489
492
  * We can attach media keys before the playback actually begins when:
@@ -491,7 +494,7 @@ shaka.media.DrmEngine = class {
491
494
  * - Some initData already has been generated (through the manifest)
492
495
  * - In case of an offline session
493
496
  */
494
- if (manifestInitData ||
497
+ if (this.manifestInitData_ ||
495
498
  this.currentDrmInfo_.keySystem !== 'com.apple.fps' ||
496
499
  this.offlineSessionIds_.length) {
497
500
  await this.attachMediaKeys_();
@@ -504,7 +507,7 @@ shaka.media.DrmEngine = class {
504
507
  // Also suppress 'encrypted' events when parsing in-band ppsh
505
508
  // from media segments because that serves the same purpose as the
506
509
  // 'encrypted' events.
507
- if (!manifestInitData && !this.offlineSessionIds_.length &&
510
+ if (!this.manifestInitData_ && !this.offlineSessionIds_.length &&
508
511
  !this.config_.parseInbandPsshEnabled) {
509
512
  this.eventManager_.listen(
510
513
  this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e));
@@ -2361,7 +2364,7 @@ shaka.media.DrmEngine = class {
2361
2364
  * @return {!Promise<void>}
2362
2365
  */
2363
2366
  parseInbandPssh(contentType, mediaSegment) {
2364
- if (!this.config_.parseInbandPsshEnabled) {
2367
+ if (!this.config_.parseInbandPsshEnabled || this.manifestInitData_) {
2365
2368
  return Promise.resolve();
2366
2369
  }
2367
2370
 
@@ -109,15 +109,15 @@ shaka.media.MediaSourceEngine = class {
109
109
  /** @private {!shaka.util.PublicPromise} */
110
110
  this.mediaSourceOpen_ = new shaka.util.PublicPromise();
111
111
 
112
+ /** @private {string} */
113
+ this.url_ = '';
114
+
112
115
  /** @private {MediaSource} */
113
116
  this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
114
117
 
115
118
  /** @type {!shaka.util.Destroyer} */
116
119
  this.destroyer_ = new shaka.util.Destroyer(() => this.doDestroy_());
117
120
 
118
- /** @private {string} */
119
- this.url_ = '';
120
-
121
121
  /** @private {boolean} */
122
122
  this.sequenceMode_ = false;
123
123
 
@@ -154,6 +154,8 @@ shaka.media.MediaSourceEngine = class {
154
154
  * @private
155
155
  */
156
156
  onSourceOpen_(p) {
157
+ goog.asserts.assert(this.url_, 'Must have object URL');
158
+
157
159
  // Release the object URL that was previously created, to prevent memory
158
160
  // leak.
159
161
  // createObjectURL creates a strong reference to the MediaSource object
@@ -791,7 +793,7 @@ shaka.media.MediaSourceEngine = class {
791
793
  this.append_(contentType, data);
792
794
  });
793
795
 
794
- if (goog.DEBUG && reference) {
796
+ if (goog.DEBUG && reference && !reference.isPreload()) {
795
797
  const bufferedAfter = this.getBuffered_(contentType);
796
798
  const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
797
799
  bufferedBefore, bufferedAfter);
@@ -243,6 +243,9 @@ shaka.media.SegmentReference = class {
243
243
  /** @type {shaka.media.SegmentReference.Status} */
244
244
  this.status = status;
245
245
 
246
+ /** @type {boolean} */
247
+ this.preload = false;
248
+
246
249
  /** @type {?shaka.extern.HlsAes128Key} */
247
250
  this.hlsAes128Key = hlsAes128Key;
248
251
 
@@ -366,6 +369,25 @@ shaka.media.SegmentReference = class {
366
369
  this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
367
370
  }
368
371
 
372
+ /**
373
+ * Mark the reference as preload.
374
+ *
375
+ * @export
376
+ */
377
+ markAsPreload() {
378
+ this.preload = true;
379
+ }
380
+
381
+ /**
382
+ * Returns true if the segment is preloaded.
383
+ *
384
+ * @return {boolean}
385
+ * @export
386
+ */
387
+ isPreload() {
388
+ return this.preload;
389
+ }
390
+
369
391
  /**
370
392
  * Set the segment's thumbnail sprite.
371
393
  *
@@ -651,10 +651,15 @@ shaka.media.StreamingEngine = class {
651
651
  const mediaState = this.mediaStates_.get(type);
652
652
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
653
653
 
654
- // Always clear the iterator since we need to start streaming from the
655
- // new time. This also happens in clearBuffer_, but if we don't clear,
656
- // we still want to reset the iterator.
657
- mediaState.segmentIterator = null;
654
+ let segment = null;
655
+ if (mediaState.segmentIterator) {
656
+ segment = mediaState.segmentIterator.current();
657
+ }
658
+ // Only reset the iterator if we seek outside the current segment.
659
+ if (!segment || segment.startTime > presentationTime ||
660
+ segment.endTime < presentationTime) {
661
+ mediaState.segmentIterator = null;
662
+ }
658
663
 
659
664
  if (!newTimeIsBuffered(type)) {
660
665
  const bufferEnd =
package/lib/player.js CHANGED
@@ -6066,6 +6066,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6066
6066
  error.category != shaka.util.Error.Category.NETWORK) {
6067
6067
  return false;
6068
6068
  }
6069
+ if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE) {
6070
+ return false;
6071
+ }
6072
+ goog.asserts.assert(this.manifest_, 'Should have manifest!');
6069
6073
 
6070
6074
  if (!navigator.onLine) {
6071
6075
  // Don't restrict variants if we're completely offline, or else we end up
@@ -7005,7 +7009,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7005
7009
  * @define {string} A version number taken from git at compile time.
7006
7010
  * @export
7007
7011
  */
7008
- shaka.Player.version = 'v4.3.6-uncompiled';
7012
+ shaka.Player.version = 'v4.3.7-uncompiled';
7009
7013
 
7010
7014
  // Initialize the deprecation system using the version string we just set
7011
7015
  // on the player.
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.6",
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
 
@@ -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(
@@ -171,9 +171,12 @@ describe('MediaSourceEngine', () => {
171
171
  describe('constructor', () => {
172
172
  const originalCreateObjectURL =
173
173
  shaka.media.MediaSourceEngine.createObjectURL;
174
+ const originalRevokeObjectURL = window.URL.revokeObjectURL;
174
175
  const originalMediaSource = window.MediaSource;
175
176
  /** @type {jasmine.Spy} */
176
177
  let createObjectURLSpy;
178
+ /** @type {jasmine.Spy} */
179
+ let revokeObjectURLSpy;
177
180
 
178
181
  beforeEach(async () => {
179
182
  // Mock out MediaSource so we can test the production version of
@@ -188,6 +191,9 @@ describe('MediaSourceEngine', () => {
188
191
  shaka.media.MediaSourceEngine.createObjectURL =
189
192
  Util.spyFunc(createObjectURLSpy);
190
193
 
194
+ revokeObjectURLSpy = jasmine.createSpy('revokeObjectURL');
195
+ window.URL.revokeObjectURL = Util.spyFunc(revokeObjectURLSpy);
196
+
191
197
  const mediaSourceSpy = jasmine.createSpy('MediaSource');
192
198
  // Because this is a fake constructor, it must be callable with "new".
193
199
  // This will cause jasmine to invoke the callback with "new" as well, so
@@ -205,6 +211,7 @@ describe('MediaSourceEngine', () => {
205
211
  afterAll(() => {
206
212
  shaka.media.MediaSourceEngine.createObjectURL = originalCreateObjectURL;
207
213
  window.MediaSource = originalMediaSource;
214
+ window.URL.revokeObjectURL = originalRevokeObjectURL;
208
215
  });
209
216
 
210
217
  it('creates a MediaSource object and sets video.src', () => {
@@ -216,6 +223,29 @@ describe('MediaSourceEngine', () => {
216
223
  expect(createObjectURLSpy).toHaveBeenCalled();
217
224
  expect(mockVideo.src).toBe('blob:foo');
218
225
  });
226
+
227
+ it('revokes object URL after MediaSource opens', () => {
228
+ let onSourceOpenListener;
229
+
230
+ mockMediaSource.addEventListener.and.callFake((event, callback, _) => {
231
+ if (event == 'sourceopen') {
232
+ onSourceOpenListener = callback;
233
+ }
234
+ });
235
+
236
+ mediaSourceEngine = new shaka.media.MediaSourceEngine(
237
+ video,
238
+ new shaka.test.FakeTextDisplayer());
239
+
240
+ expect(mockMediaSource.addEventListener).toHaveBeenCalledTimes(1);
241
+ expect(mockMediaSource.addEventListener.calls.mostRecent().args[0])
242
+ .toBe('sourceopen');
243
+ expect(typeof onSourceOpenListener).toBe(typeof Function);
244
+
245
+ onSourceOpenListener();
246
+
247
+ expect(revokeObjectURLSpy).toHaveBeenCalledWith('blob:foo');
248
+ });
219
249
  });
220
250
 
221
251
  describe('init', () => {
@@ -43,9 +43,10 @@ shaka.test.LayoutTests = class {
43
43
  });
44
44
  });
45
45
 
46
- // Wait one extra tick to make sure the font rendering on the page has been
47
- // updated. Without this, we saw some rare test flake in Firefox on Mac.
48
- await shaka.test.Util.shortDelay();
46
+ // Wait one extra second to make sure the font rendering on the page has
47
+ // been updated. Without this, we saw some rare test flake in Firefox on
48
+ // Mac.
49
+ await shaka.test.Util.delay(1);
49
50
  }
50
51
 
51
52
  /**