shaka-player 4.3.14 → 4.3.16

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 (75) hide show
  1. package/.github/workflows/appspot.yaml +4 -4
  2. package/.github/workflows/build-and-test.yaml +23 -10
  3. package/.github/workflows/custom-actions/set-commit-status/action.yaml +29 -7
  4. package/.github/workflows/demo-version-index.yaml +3 -3
  5. package/.github/workflows/nightly-demo.yaml +4 -4
  6. package/.github/workflows/release-please.yaml +25 -24
  7. package/.github/workflows/report-incremental-coverage.yaml +1 -1
  8. package/.github/workflows/selenium-lab-tests.yaml +30 -18
  9. package/.github/workflows/shaka-bot-commands/lib.sh +37 -12
  10. package/.github/workflows/shaka-bot-commands/main.sh +4 -2
  11. package/.github/workflows/sync-labels.yaml +1 -1
  12. package/.github/workflows/talk-to-shaka-bot.yaml +2 -1
  13. package/.github/workflows/update-issues.yaml +1 -1
  14. package/.github/workflows/update-screenshots.yaml +25 -38
  15. package/CHANGELOG.md +37 -0
  16. package/demo/demo.less +1 -5
  17. package/dist/controls.css +1 -1
  18. package/dist/controls.css.map +1 -1
  19. package/dist/demo.css +1 -1
  20. package/dist/demo.css.map +1 -1
  21. package/dist/deps.js +0 -1
  22. package/dist/locales.js +32 -32
  23. package/dist/shaka-player.compiled.d.ts +6 -10
  24. package/dist/shaka-player.compiled.debug.d.ts +6 -10
  25. package/dist/shaka-player.compiled.debug.externs.js +13 -11
  26. package/dist/shaka-player.compiled.debug.js +222 -217
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +13 -11
  29. package/dist/shaka-player.compiled.js +855 -853
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +6 -10
  32. package/dist/shaka-player.ui.debug.d.ts +6 -10
  33. package/dist/shaka-player.ui.debug.externs.js +13 -11
  34. package/dist/shaka-player.ui.debug.js +171 -167
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +13 -11
  37. package/dist/shaka-player.ui.js +950 -948
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/faq.md +25 -0
  40. package/docs/tutorials/offline.md +3 -0
  41. package/docs/tutorials/ui.md +4 -4
  42. package/externs/shaka/player.js +32 -19
  43. package/lib/cast/cast_proxy.js +1 -3
  44. package/lib/cast/cast_sender.js +8 -11
  45. package/lib/cea/cea608_memory.js +1 -1
  46. package/lib/dash/dash_parser.js +3 -0
  47. package/lib/dash/segment_template.js +5 -0
  48. package/lib/deprecate/deprecate.js +1 -1
  49. package/lib/hls/hls_parser.js +34 -17
  50. package/lib/media/drm_engine.js +17 -3
  51. package/lib/media/manifest_parser.js +2 -1
  52. package/lib/media/presentation_timeline.js +23 -0
  53. package/lib/media/segment_index.js +10 -0
  54. package/lib/media/streaming_engine.js +1 -13
  55. package/lib/player.js +19 -5
  56. package/lib/polyfill/mediasource.js +5 -36
  57. package/lib/text/ui_text_displayer.js +10 -1
  58. package/lib/text/vtt_text_parser.js +2 -1
  59. package/lib/util/error.js +1 -0
  60. package/lib/util/manifest_parser_utils.js +1 -0
  61. package/lib/util/stream_utils.js +8 -5
  62. package/package.json +2 -2
  63. package/test/cast/cast_proxy_unit.js +0 -46
  64. package/test/cast/cast_sender_unit.js +26 -26
  65. package/test/cea/cea608_memory_unit.js +3 -2
  66. package/test/hls/hls_live_unit.js +52 -0
  67. package/test/hls/hls_parser_unit.js +28 -0
  68. package/test/media/media_source_engine_integration.js +1 -1
  69. package/test/player_integration.js +9 -1
  70. package/test/player_load_graph_integration.js +13 -7
  71. package/test/player_src_equals_integration.js +2 -2
  72. package/test/player_unit.js +22 -4
  73. package/test/text/vtt_text_parser_unit.js +31 -0
  74. package/ui/less/containers.less +1 -0
  75. package/lib/polyfill/storage_estimate.js +0 -57
@@ -72,7 +72,9 @@ shaka.text.UITextDisplayer = class {
72
72
 
73
73
  /** @private {shaka.util.Timer} */
74
74
  this.captionsTimer_ = new shaka.util.Timer(() => {
75
- this.updateCaptions_();
75
+ if (!this.video_.paused) {
76
+ this.updateCaptions_();
77
+ }
76
78
  }).tickEvery(updatePeriod);
77
79
 
78
80
  /**
@@ -93,6 +95,10 @@ shaka.text.UITextDisplayer = class {
93
95
  this.updateCaptions_(/* forceUpdate= */ true);
94
96
  });
95
97
 
98
+ this.eventManager_.listen(this.video_, 'seeking', () => {
99
+ this.updateCaptions_(/* forceUpdate= */ true);
100
+ });
101
+
96
102
  // From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
97
103
  // Whenever the natural width or natural height of the video changes
98
104
  // (including, for example, because the selected video track was changed),
@@ -339,6 +345,9 @@ shaka.text.UITextDisplayer = class {
339
345
  const cueRegistry = this.currentCuesMap_.get(cue);
340
346
  goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
341
347
  if (cueRegistry.regionElement) {
348
+ if (cueRegistry.regionElement.contains(container)) {
349
+ cueRegistry.regionElement.removeChild(container);
350
+ }
342
351
  container.appendChild(cueRegistry.regionElement);
343
352
  cueRegistry.regionElement.appendChild(cueRegistry.cueElement);
344
353
  } else {
@@ -673,13 +673,14 @@ shaka.text.VttTextParser = class {
673
673
  refCue.fontFamily, cue.fontFamily);
674
674
  cue.fontSize = VttTextParser.getOrDefault_(
675
675
  refCue.fontSize, cue.fontSize);
676
+ cue.textShadow = VttTextParser.getOrDefault_(
677
+ refCue.textShadow, cue.textShadow);
676
678
 
677
679
  // Overwrite with new values as unable to determine
678
680
  // if new value is set or not
679
681
  cue.fontWeight = refCue.fontWeight;
680
682
  cue.fontStyle = refCue.fontStyle;
681
683
  cue.opacity = refCue.opacity;
682
- cue.textShadow = refCue.textShadow;
683
684
  cue.wrapLine = refCue.wrapLine;
684
685
  }
685
686
 
package/lib/util/error.js CHANGED
@@ -506,6 +506,7 @@ shaka.util.Error.Code = {
506
506
  * <li>Configure the server to accept a HEAD request for the manifest.
507
507
  * </ul>
508
508
  * <br> error.data[0] is the manifest URI.
509
+ * <br> error.data[1] is the detected or specified MIME type.
509
510
  */
510
511
  'UNABLE_TO_GUESS_MANIFEST_TYPE': 4000,
511
512
 
@@ -200,6 +200,7 @@ shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
200
200
  /^[ae]c-3$/,
201
201
  /^ac-4$/,
202
202
  /^dts[cex]$/, // DTS Digital Surround (dtsc), DTS Express (dtse), DTS:X (dtsx)
203
+ /^iamf/,
203
204
  ];
204
205
 
205
206
 
@@ -806,8 +806,12 @@ shaka.util.StreamUtils = class {
806
806
  // currently don't support 'fLaC', while 'flac' is supported by most
807
807
  // major browsers.
808
808
  // See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
809
- if (codecs === 'fLaC' && !shaka.util.Platform.isSafari()) {
810
- return 'flac';
809
+ if (codecs.toLowerCase() == 'flac') {
810
+ if (!shaka.util.Platform.isSafari()) {
811
+ return 'flac';
812
+ } else {
813
+ return 'fLaC';
814
+ }
811
815
  }
812
816
 
813
817
  // The same is true for 'Opus'.
@@ -1273,8 +1277,6 @@ shaka.util.StreamUtils = class {
1273
1277
  * @return {shaka.extern.Track}
1274
1278
  */
1275
1279
  static html5TextTrackToTrack(textTrack) {
1276
- const CLOSED_CAPTION_MIMETYPE =
1277
- shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE;
1278
1280
  const StreamUtils = shaka.util.StreamUtils;
1279
1281
 
1280
1282
  /** @type {shaka.extern.Track} */
@@ -1283,7 +1285,8 @@ shaka.util.StreamUtils = class {
1283
1285
  track.type = 'text';
1284
1286
  track.originalTextId = textTrack.id;
1285
1287
  if (textTrack.kind == 'captions') {
1286
- track.mimeType = CLOSED_CAPTION_MIMETYPE;
1288
+ // See: https://github.com/shaka-project/shaka-player/issues/6233
1289
+ track.mimeType = 'unknown';
1287
1290
  }
1288
1291
  if (textTrack.kind == 'subtitles') {
1289
1292
  track.mimeType = 'text/vtt';
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.14",
4
+ "version": "4.3.16",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -48,7 +48,7 @@
48
48
  "karma-spec-reporter": "^0.0.34",
49
49
  "karma-webdriver-launcher": "^1.0.8",
50
50
  "lcevc_dil.js": "^1.1.20",
51
- "less": "https://gitpkg.now.sh/joeyparrish/less.js/packages/less?28c63a43",
51
+ "less": "^4.2.0",
52
52
  "less-plugin-clean-css": "github:austingardner/less-plugin-clean-css#4e9e77bf",
53
53
  "material-design-lite": "^1.3.0",
54
54
  "mux.js": "^6.2.0",
@@ -114,52 +114,6 @@ describe('CastProxy', () => {
114
114
  });
115
115
 
116
116
  describe('cast', () => {
117
- it('pauses the local video', () => {
118
- proxy.cast();
119
- expect(mockVideo.pause).toHaveBeenCalled();
120
- });
121
-
122
- it('passes initial state to sender', () => {
123
- mockVideo.loop = true;
124
- mockVideo.playbackRate = 3;
125
- mockVideo.currentTime = 12;
126
- const fakeConfig = {key: 'value'};
127
- mockPlayer.getConfiguration.and.returnValue(fakeConfig);
128
- mockPlayer.isTextTrackVisible.and.returnValue(false);
129
- const fakeManifestUri = 'foo://bar';
130
- mockPlayer.getAssetUri.and.returnValue(fakeManifestUri);
131
-
132
- proxy.cast();
133
- const calls = mockSender.cast.calls;
134
- expect(calls.count()).toBe(1);
135
- if (calls.count()) {
136
- const state = calls.argsFor(0)[0];
137
- // Video state goes directly:
138
- expect(state.video.loop).toBe(mockVideo.loop);
139
- expect(state.video.playbackRate).toBe(mockVideo.playbackRate);
140
- // Player state uses corresponding setter names:
141
- expect(state.player.configure).toEqual(fakeConfig);
142
- expect(state['playerAfterLoad'].setTextTrackVisibility).toBe(false);
143
- // Manifest URI:
144
- expect(state.manifest).toBe(fakeManifestUri);
145
- // Start time:
146
- expect(state.startTime).toBe(mockVideo.currentTime);
147
- }
148
- });
149
-
150
- it('does not provide a start time if the video has ended', () => {
151
- mockVideo.ended = true;
152
- mockVideo.currentTime = 12;
153
-
154
- proxy.cast();
155
- const calls = mockSender.cast.calls;
156
- expect(calls.count()).toBe(1);
157
- if (calls.count()) {
158
- const state = calls.argsFor(0)[0];
159
- expect(state.startTime).toBe(null);
160
- }
161
- });
162
-
163
117
  it('unloads the local player after casting is complete', async () => {
164
118
  /** @type {!shaka.util.PublicPromise} */
165
119
  const p = new shaka.util.PublicPromise();
@@ -157,7 +157,7 @@ describe('CastSender', () => {
157
157
  shaka.util.Error.Severity.RECOVERABLE,
158
158
  shaka.util.Error.Category.CAST,
159
159
  shaka.util.Error.Code.CAST_API_UNAVAILABLE));
160
- await expectAsync(sender.cast(fakeInitState)).toBeRejectedWith(expected);
160
+ await expectAsync(sender.cast()).toBeRejectedWith(expected);
161
161
  });
162
162
 
163
163
  it('fails when there are no receivers', async () => {
@@ -168,7 +168,7 @@ describe('CastSender', () => {
168
168
  shaka.util.Error.Severity.RECOVERABLE,
169
169
  shaka.util.Error.Category.CAST,
170
170
  shaka.util.Error.Code.NO_CAST_RECEIVERS));
171
- await expectAsync(sender.cast(fakeInitState)).toBeRejectedWith(expected);
171
+ await expectAsync(sender.cast()).toBeRejectedWith(expected);
172
172
  });
173
173
 
174
174
  it('creates a session and sends an "init" message', async () => {
@@ -177,7 +177,7 @@ describe('CastSender', () => {
177
177
  fakeReceiverAvailability(true);
178
178
  expect(sender.hasReceivers()).toBe(true);
179
179
 
180
- const p = sender.cast(fakeInitState);
180
+ const p = sender.cast();
181
181
  fakeSessionConnection();
182
182
 
183
183
  await p;
@@ -220,7 +220,7 @@ describe('CastSender', () => {
220
220
  sender.init();
221
221
  fakeReceiverAvailability(true);
222
222
 
223
- const p = sender.cast(fakeInitState);
223
+ const p = sender.cast();
224
224
  fakeSessionConnectionFailure(metadata.castErrorCode);
225
225
 
226
226
  const expected = Util.jasmineError(new shaka.util.Error(
@@ -236,7 +236,7 @@ describe('CastSender', () => {
236
236
  sender.init();
237
237
  fakeReceiverAvailability(true);
238
238
 
239
- const p = sender.cast(fakeInitState);
239
+ const p = sender.cast();
240
240
  fakeSessionConnection();
241
241
 
242
242
  await p;
@@ -244,14 +244,14 @@ describe('CastSender', () => {
244
244
  shaka.util.Error.Severity.RECOVERABLE,
245
245
  shaka.util.Error.Category.CAST,
246
246
  shaka.util.Error.Code.ALREADY_CASTING));
247
- await expectAsync(sender.cast(fakeInitState)).toBeRejectedWith(expected);
247
+ await expectAsync(sender.cast()).toBeRejectedWith(expected);
248
248
  });
249
249
  });
250
250
 
251
251
  it('re-uses old sessions', async () => {
252
252
  sender.init();
253
253
  fakeReceiverAvailability(true);
254
- const p = sender.cast(fakeInitState);
254
+ const p = sender.cast();
255
255
  fakeSessionConnection();
256
256
  const oldMockSession = mockSession;
257
257
  await p;
@@ -289,7 +289,7 @@ describe('CastSender', () => {
289
289
  it('doesn\'t re-use stopped sessions', async () => {
290
290
  sender.init();
291
291
  fakeReceiverAvailability(true);
292
- const p = sender.cast(fakeInitState);
292
+ const p = sender.cast();
293
293
  fakeSessionConnection();
294
294
  await p;
295
295
  await sender.destroy();
@@ -334,7 +334,7 @@ describe('CastSender', () => {
334
334
  sender.init();
335
335
  fakeReceiverAvailability(true);
336
336
  sender.setAppData(fakeAppData);
337
- const p = sender.cast(fakeInitState);
337
+ const p = sender.cast();
338
338
  fakeSessionConnection();
339
339
 
340
340
  await p;
@@ -347,7 +347,7 @@ describe('CastSender', () => {
347
347
  it('sends a special "appData" message if casting', async () => {
348
348
  sender.init();
349
349
  fakeReceiverAvailability(true);
350
- const p = sender.cast(fakeInitState);
350
+ const p = sender.cast();
351
351
  fakeSessionConnection();
352
352
  await p;
353
353
 
@@ -390,7 +390,7 @@ describe('CastSender', () => {
390
390
  it('is not triggered if making a new session', async () => {
391
391
  sender.init();
392
392
  fakeReceiverAvailability(true);
393
- const p = sender.cast(fakeInitState);
393
+ const p = sender.cast();
394
394
  fakeSessionConnection();
395
395
  await p;
396
396
 
@@ -437,7 +437,7 @@ describe('CastSender', () => {
437
437
  it('is triggered by an "event" message', async () => {
438
438
  sender.init();
439
439
  fakeReceiverAvailability(true);
440
- const p = sender.cast(fakeInitState);
440
+ const p = sender.cast();
441
441
  fakeSessionConnection();
442
442
  await p;
443
443
 
@@ -460,7 +460,7 @@ describe('CastSender', () => {
460
460
  it('is triggered when casting ends', async () => {
461
461
  sender.init();
462
462
  fakeReceiverAvailability(true);
463
- const p = sender.cast(fakeInitState);
463
+ const p = sender.cast();
464
464
  fakeSessionConnection();
465
465
  await p;
466
466
 
@@ -477,7 +477,7 @@ describe('CastSender', () => {
477
477
  it('opens the dialog if we are casting', async () => {
478
478
  sender.init();
479
479
  fakeReceiverAvailability(true);
480
- const p = sender.cast(fakeInitState);
480
+ const p = sender.cast();
481
481
  fakeSessionConnection();
482
482
  await p;
483
483
 
@@ -502,7 +502,7 @@ describe('CastSender', () => {
502
502
  it('returns most recent properties from "update" messages', async () => {
503
503
  sender.init();
504
504
  fakeReceiverAvailability(true);
505
- const p = sender.cast(fakeInitState);
505
+ const p = sender.cast();
506
506
  fakeSessionConnection();
507
507
  await p;
508
508
 
@@ -545,7 +545,7 @@ describe('CastSender', () => {
545
545
  it('simple methods trigger "call" messages', async () => {
546
546
  sender.init();
547
547
  fakeReceiverAvailability(true);
548
- const p = sender.cast(fakeInitState);
548
+ const p = sender.cast();
549
549
  fakeSessionConnection();
550
550
  await p;
551
551
 
@@ -568,7 +568,7 @@ describe('CastSender', () => {
568
568
  method = null;
569
569
  sender.init();
570
570
  fakeReceiverAvailability(true);
571
- const p = sender.cast(fakeInitState);
571
+ const p = sender.cast();
572
572
  fakeSessionConnection();
573
573
  await p;
574
574
 
@@ -655,7 +655,7 @@ describe('CastSender', () => {
655
655
  it('overrides any cached properties', async () => {
656
656
  sender.init();
657
657
  fakeReceiverAvailability(true);
658
- const p = sender.cast(fakeInitState);
658
+ const p = sender.cast();
659
659
  fakeSessionConnection();
660
660
  await p;
661
661
 
@@ -675,7 +675,7 @@ describe('CastSender', () => {
675
675
  it('causes a "set" message to be sent', async () => {
676
676
  sender.init();
677
677
  fakeReceiverAvailability(true);
678
- const p = sender.cast(fakeInitState);
678
+ const p = sender.cast();
679
679
  fakeSessionConnection();
680
680
  await p;
681
681
 
@@ -691,7 +691,7 @@ describe('CastSender', () => {
691
691
  it('can be used before we have an "update" message', async () => {
692
692
  sender.init();
693
693
  fakeReceiverAvailability(true);
694
- const p = sender.cast(fakeInitState);
694
+ const p = sender.cast();
695
695
  fakeSessionConnection();
696
696
  await p;
697
697
 
@@ -705,7 +705,7 @@ describe('CastSender', () => {
705
705
  it('is true only after we have an "update" message', async () => {
706
706
  sender.init();
707
707
  fakeReceiverAvailability(true);
708
- const p = sender.cast(fakeInitState);
708
+ const p = sender.cast();
709
709
  fakeSessionConnection();
710
710
  await p;
711
711
 
@@ -723,7 +723,7 @@ describe('CastSender', () => {
723
723
  it('disconnects and cancels all async operations', async () => {
724
724
  sender.init();
725
725
  fakeReceiverAvailability(true);
726
- const cast = sender.cast(fakeInitState);
726
+ const cast = sender.cast();
727
727
  fakeSessionConnection();
728
728
  await cast;
729
729
 
@@ -756,7 +756,7 @@ describe('CastSender', () => {
756
756
  it('transfers playback to local device', async () => {
757
757
  sender.init();
758
758
  fakeReceiverAvailability(true);
759
- const cast = sender.cast(fakeInitState);
759
+ const cast = sender.cast();
760
760
  fakeSessionConnection();
761
761
  await cast;
762
762
 
@@ -772,7 +772,7 @@ describe('CastSender', () => {
772
772
  it('succeeds even if session.stop() throws', async () => {
773
773
  sender.init();
774
774
  fakeReceiverAvailability(true);
775
- const cast = sender.cast(fakeInitState);
775
+ const cast = sender.cast();
776
776
  fakeSessionConnection();
777
777
  await cast;
778
778
 
@@ -792,7 +792,7 @@ describe('CastSender', () => {
792
792
  beforeEach(async () => {
793
793
  sender.init();
794
794
  fakeReceiverAvailability(true);
795
- const cast = sender.cast(fakeInitState);
795
+ const cast = sender.cast();
796
796
  fakeSessionConnection();
797
797
  await cast;
798
798
 
@@ -837,7 +837,7 @@ describe('CastSender', () => {
837
837
  it('cancels all async operations', async () => {
838
838
  sender.init();
839
839
  fakeReceiverAvailability(true);
840
- const cast = sender.cast(fakeInitState);
840
+ const cast = sender.cast();
841
841
  fakeSessionConnection();
842
842
  await cast;
843
843
 
@@ -46,11 +46,12 @@ describe('Cea608Memory', () => {
46
46
  it('adds and emits a series of special characters from the buffer', () => {
47
47
  const startTime = 1;
48
48
  const endTime = 2;
49
- const expectedText = '½¿èôÇ©ë»ö{ß│';
49
+ const expectedText = '½¿ èôÇ©ë»ö{ß│';
50
50
  const charGroups = [
51
51
  {
52
52
  set: CharSet.SPECIAL_NORTH_AMERICAN,
53
- chars: [0x32, 0x33, 0x3a, 0x3e], // ½, ¿, è, ô
53
+ // Note TS is not at either end to avoid side effect of trim()
54
+ chars: [0x32, 0x33, 0x39, 0x3a, 0x3e], // ½, ¿, TS, è, ô
54
55
  },
55
56
 
56
57
  {
@@ -776,6 +776,58 @@ describe('HlsParser live', () => {
776
776
  manifest.variants[1].video, [ref4]);
777
777
  });
778
778
 
779
+ describe('when ignoreManifestProgramDateTime is set', () => {
780
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
781
+ config.hls.ignoreManifestProgramDateTime = true;
782
+
783
+ it('does not reset segment times when switching', async () => {
784
+ parser.configure(config);
785
+
786
+ const ref1 = makeReference(
787
+ 'test:/main.mp4', 0, 2, /* syncTime= */ null);
788
+ const ref2 = makeReference(
789
+ 'test:/main2.mp4', 2, 4, /* syncTime= */ null);
790
+
791
+ const secondVariant = [
792
+ '#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1",',
793
+ 'RESOLUTION=1200x940,FRAME-RATE=60\n',
794
+ 'video2',
795
+ ].join('');
796
+ const masterWithTwoVariants = master + secondVariant;
797
+ configureNetEngineForInitialManifest(masterWithTwoVariants,
798
+ mediaWithAdditionalSegment, mediaWithAdditionalSegment2);
799
+
800
+ const manifest = await parser.start('test:/master', playerInterface);
801
+ await manifest.variants[0].video.createSegmentIndex();
802
+ ManifestParser.verifySegmentIndex(
803
+ manifest.variants[0].video, [ref1, ref2]);
804
+ expect(manifest.variants[1].video.segmentIndex).toBeNull();
805
+
806
+ // In the initial playlist, we know the earliest start time is 0, at
807
+ // EXT-X-MEDIA-SEQUENCE of 0.
808
+ expect(
809
+ manifest.variants[0].video.segmentIndex.earliestReference()
810
+ .getStartTime())
811
+ .toBe(0);
812
+
813
+ // Update.
814
+ fakeNetEngine
815
+ .setResponseText('test:/video', mediaWithRemovedSegment)
816
+ .setResponseText('test:/video2', mediaWithRemovedSegment2);
817
+ await delayForUpdatePeriod();
818
+
819
+ // Switch. The new variant starts at EXT-X-MEDIA-SEQUENCE of 1.
820
+ await manifest.variants[0].video.closeSegmentIndex();
821
+ await manifest.variants[1].video.createSegmentIndex();
822
+
823
+ // The earliest start time of the new segmentIndex should therefore be
824
+ // 2.
825
+ expect(manifest.variants[0].video.segmentIndex).toBeNull();
826
+ const segIdx = manifest.variants[1].video.segmentIndex;
827
+ expect(segIdx.earliestReference().getStartTime()).toBe(2);
828
+ });
829
+ });
830
+
779
831
  it('handles switching during update', async () => {
780
832
  const ref1 = makeReference(
781
833
  'test:/main.mp4', 0, 2, /* syncTime= */ null);
@@ -3743,6 +3743,34 @@ describe('HlsParser', () => {
3743
3743
  await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
3744
3744
  });
3745
3745
 
3746
+ it('if SAMPLE-AES encryption with MSE and mp2t content', async () => {
3747
+ const master = [
3748
+ '#EXTM3U\n',
3749
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3750
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3751
+ 'video\n',
3752
+ ].join('');
3753
+
3754
+ const media = [
3755
+ '#EXTM3U\n',
3756
+ '#EXT-X-TARGETDURATION:6\n',
3757
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
3758
+ '#EXT-X-KEY:METHOD=SAMPLE-AES,',
3759
+ 'URI="fake",\n',
3760
+ '#EXTINF:5,\n',
3761
+ '#EXT-X-BYTERANGE:121090@616\n',
3762
+ 'main.ts',
3763
+ ].join('');
3764
+
3765
+ const error = new shaka.util.Error(
3766
+ shaka.util.Error.Severity.CRITICAL,
3767
+ shaka.util.Error.Category.MANIFEST,
3768
+ Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
3769
+
3770
+ await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
3771
+ });
3772
+
3773
+
3746
3774
  describe('if required tags are missing', () => {
3747
3775
  /**
3748
3776
  * @param {string} master
@@ -28,7 +28,7 @@ describe('MediaSourceEngine', () => {
28
28
  jasmine.objectContaining({
29
29
  startTime: Util.closeTo(0.067, 0.001),
30
30
  endTime: Util.closeTo(1, 0.001),
31
- payload: 'eng:⠀00:00:00:00',
31
+ payload: 'eng: 00:00:00:00',
32
32
  textAlign: Cue.textAlign.CENTER,
33
33
  }),
34
34
  ],
@@ -1197,13 +1197,21 @@ describe('Player', () => {
1197
1197
  'com.widevine.alpha': bogusUrl,
1198
1198
  'com.microsoft.playready': bogusUrl,
1199
1199
  });
1200
- await player.load('test:sintel-enc_compiled');
1200
+
1201
+ // This load may be interrupted, so ignore errors and don't wait.
1202
+ const loadPromise =
1203
+ player.load('test:sintel-enc_compiled').catch(() => {});
1201
1204
 
1202
1205
  await errorPromise;
1203
1206
  expect(unloadPromise).not.toBeNull();
1207
+
1204
1208
  if (unloadPromise) {
1205
1209
  await unloadPromise;
1206
1210
  }
1211
+
1212
+ // This should be done, and errors ignored. But don't leave any Promise
1213
+ // unresolved.
1214
+ await loadPromise;
1207
1215
  });
1208
1216
  }); // describe('unloading')
1209
1217
 
@@ -464,11 +464,11 @@ describe('Player Load Graph', () => {
464
464
 
465
465
  // Make the two requests one-after-another so that we don't have any idle
466
466
  // time between them.
467
- const attachRequest = player.attach(video);
468
- const loadRequest = player.load('test:sintel');
467
+ const attach = player.attach(video);
468
+ const load = player.load('test:sintel');
469
469
 
470
- await attachRequest;
471
- await expectAsync(loadRequest).toBeRejected();
470
+ await attach;
471
+ await expectAsync(load).toBeRejected();
472
472
 
473
473
  // Wait a couple interrupter cycles to allow the player to enter idle
474
474
  // state.
@@ -506,10 +506,12 @@ describe('Player Load Graph', () => {
506
506
  // Normally the player would initialize media source after attaching to
507
507
  // the media element, however since we don't support media source, it
508
508
  // should stop at the attach state.
509
- player.attach(video, /* initMediaSource= */ true);
509
+ const attach = player.attach(video, /* initMediaSource= */ true);
510
510
 
511
511
  const event = await spyIsCalled(stateIdleSpy);
512
512
  expect(event.state).toBe('attach');
513
+
514
+ await attach;
513
515
  });
514
516
 
515
517
  it('loading ignores media source path', async () => {
@@ -518,10 +520,12 @@ describe('Player Load Graph', () => {
518
520
  // Normally the player would load content like this with the media source
519
521
  // path, but since we don't have media source support, it should use the
520
522
  // src= path.
521
- player.load(SMALL_MP4_CONTENT_URI);
523
+ const load = player.load(SMALL_MP4_CONTENT_URI);
522
524
 
523
525
  const event = await spyIsCalled(stateIdleSpy);
524
526
  expect(event.state).toBe('src-equals');
527
+
528
+ await load;
525
529
  });
526
530
 
527
531
  it('unloading ignores init media source flag', async () => {
@@ -531,10 +535,12 @@ describe('Player Load Graph', () => {
531
535
  // Normally the player would try to go to the media source state because
532
536
  // we are saying to initialize media source after unloading, but since we
533
537
  // don't have media source, it should stop at the attach state.
534
- player.unload(/* initMediaSource= */ true);
538
+ const unload = player.unload(/* initMediaSource= */ true);
535
539
 
536
540
  const event = await spyIsCalled(stateIdleSpy);
537
541
  expect(event.state).toBe('attach');
542
+
543
+ await unload;
538
544
  });
539
545
  });
540
546
 
@@ -114,8 +114,8 @@ describe('Player Src Equals', () => {
114
114
  expect(video.currentTime).toBeLessThan(2.0);
115
115
 
116
116
  // Trigger a seek and then wait for the seek to take effect.
117
- // This seek target is very close to the duration of the video.
118
- video.currentTime = 10;
117
+ // This seek target is very close to the duration of the video (10.01s).
118
+ video.currentTime = 9.5;
119
119
  await waiter.waitForMovementOrFailOnTimeout(video, /* timeout= */10);
120
120
 
121
121
  // Make sure the playhead is roughly where we expect it to be after
@@ -559,15 +559,21 @@ describe('Player', () => {
559
559
  });
560
560
 
561
561
  describe('when config.streaming.preferNativeHls is set to true', () => {
562
- beforeEach(() => {
562
+ beforeAll(() => {
563
563
  shaka.media.ManifestParser.registerParserByMime(
564
564
  'application/x-mpegurl',
565
565
  () => new shaka.test.FakeManifestParser(manifest));
566
566
  });
567
567
 
568
+ afterAll(() => {
569
+ // IMPORTANT: restore the ORIGINAL parser. DO NOT just unregister the
570
+ // fake!
571
+ shaka.media.ManifestParser.registerParserByMime(
572
+ 'application/x-mpegurl',
573
+ () => new shaka.hls.HlsParser());
574
+ });
575
+
568
576
  afterEach(() => {
569
- shaka.media.ManifestParser.unregisterParserByMime(
570
- 'application/x-mpegurl');
571
577
  video.canPlayType.calls.reset();
572
578
  });
573
579
 
@@ -3513,11 +3519,14 @@ describe('Player', () => {
3513
3519
  });
3514
3520
 
3515
3521
  describe('getPlayheadTimeAsDate()', () => {
3522
+ /** @type {?shaka.media.PresentationTimeline} */
3523
+ let timeline;
3516
3524
  beforeEach(async () => {
3517
- const timeline = new shaka.media.PresentationTimeline(300, 0);
3525
+ timeline = new shaka.media.PresentationTimeline(300, 0);
3518
3526
  timeline.setStatic(false);
3519
3527
 
3520
3528
  manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3529
+ goog.asserts.assert(timeline, 'timeline must be non-null');
3521
3530
  manifest.presentationTimeline = timeline;
3522
3531
  manifest.addVariant(0, (variant) => {
3523
3532
  variant.addVideo(1);
@@ -3535,6 +3544,15 @@ describe('Player', () => {
3535
3544
  // (300 (presentation start time) + 20 (playhead time)) * 1000 (ms/sec)
3536
3545
  expect(liveTimeUtc).toEqual(new Date(320 * 1000));
3537
3546
  });
3547
+
3548
+ it('uses program date time', () => {
3549
+ timeline.setInitialProgramDateTime(100);
3550
+ playhead.getTime.and.returnValue(20);
3551
+
3552
+ const liveTimeUtc = player.getPlayheadTimeAsDate();
3553
+ // (100 (program date time) + 20 (playhead time)) * 1000 (ms/sec)
3554
+ expect(liveTimeUtc).toEqual(new Date(120 * 1000));
3555
+ });
3538
3556
  });
3539
3557
 
3540
3558
  it('rejects empty manifests', async () => {