shaka-player 4.1.3 → 4.2.1

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 (68) hide show
  1. package/.github/workflows/selenium-lab-tests.yaml +158 -53
  2. package/CHANGELOG.md +39 -29
  3. package/demo/common/assets.js +25 -0
  4. package/demo/common/message_ids.js +2 -0
  5. package/demo/config.js +5 -1
  6. package/demo/index.html +1 -1
  7. package/demo/locales/en.json +2 -0
  8. package/demo/locales/source.json +8 -0
  9. package/dist/controls.css +1 -1
  10. package/dist/controls.css.map +1 -1
  11. package/dist/demo.compiled.debug.js +25 -23
  12. package/dist/demo.compiled.debug.map +3 -3
  13. package/dist/demo.compiled.js +25 -23
  14. package/dist/demo.compiled.map +3 -3
  15. package/dist/deps.js +3 -2
  16. package/dist/locales.js +32 -32
  17. package/dist/receiver.compiled.debug.js +24 -22
  18. package/dist/receiver.compiled.debug.map +3 -3
  19. package/dist/receiver.compiled.js +24 -22
  20. package/dist/receiver.compiled.map +3 -3
  21. package/dist/shaka-player.compiled.d.ts +57 -6
  22. package/dist/shaka-player.compiled.debug.d.ts +57 -6
  23. package/dist/shaka-player.compiled.debug.externs.js +62 -6
  24. package/dist/shaka-player.compiled.debug.js +305 -262
  25. package/dist/shaka-player.compiled.debug.map +4 -4
  26. package/dist/shaka-player.compiled.externs.js +62 -6
  27. package/dist/shaka-player.compiled.js +817 -801
  28. package/dist/shaka-player.compiled.map +4 -4
  29. package/dist/shaka-player.ui.d.ts +57 -6
  30. package/dist/shaka-player.ui.debug.d.ts +57 -6
  31. package/dist/shaka-player.ui.debug.externs.js +62 -6
  32. package/dist/shaka-player.ui.debug.js +308 -265
  33. package/dist/shaka-player.ui.debug.map +4 -4
  34. package/dist/shaka-player.ui.externs.js +62 -6
  35. package/dist/shaka-player.ui.js +942 -925
  36. package/dist/shaka-player.ui.map +4 -4
  37. package/docs/tutorials/fairplay.md +149 -0
  38. package/docs/tutorials/ui.md +6 -6
  39. package/externs/shaka/abr_manager.js +8 -0
  40. package/externs/shaka/manifest.js +43 -0
  41. package/externs/shaka/player.js +13 -2
  42. package/lib/abr/simple_abr_manager.js +72 -4
  43. package/lib/hls/hls_parser.js +138 -96
  44. package/lib/media/drm_engine.js +0 -2
  45. package/lib/media/segment_index.js +8 -0
  46. package/lib/media/segment_reference.js +7 -1
  47. package/lib/media/streaming_engine.js +38 -2
  48. package/lib/net/networking_engine.js +1 -0
  49. package/lib/player.js +20 -1
  50. package/lib/polyfill/patchedmediakeys_apple.js +729 -0
  51. package/lib/text/ui_text_displayer.js +8 -0
  52. package/lib/text/vtt_text_parser.js +61 -4
  53. package/lib/util/error.js +23 -11
  54. package/lib/util/fairplay_utils.js +100 -2
  55. package/lib/util/platform.js +12 -1
  56. package/lib/util/player_configuration.js +4 -10
  57. package/package.json +1 -1
  58. package/shaka-player.uncompiled.js +1 -0
  59. package/test/hls/hls_parser_unit.js +101 -34
  60. package/test/media/drm_engine_unit.js +29 -0
  61. package/test/media/streaming_engine_unit.js +79 -5
  62. package/test/player_src_equals_integration.js +2 -0
  63. package/test/player_unit.js +39 -1
  64. package/test/test/assets/small.mp4 +0 -0
  65. package/test/test/util/simple_fakes.js +4 -0
  66. package/test/test/util/streaming_engine_util.js +10 -2
  67. package/test/text/vtt_text_parser_unit.js +26 -0
  68. package/ui/less/ad_controls.less +4 -0
@@ -100,10 +100,13 @@ describe('StreamingEngine', () => {
100
100
  jasmine.clock().mockDate();
101
101
  });
102
102
 
103
- /** @param {boolean=} trickMode
103
+ /**
104
+ * @param {boolean=} trickMode
104
105
  * @param {number=} mediaOffset The offset from 0 for the segment start times
106
+ * @param {shaka.extern.HlsAes128Key=} hlsAes128Key The AES-128 key to put in
107
+ * the manifest, if one should exist
105
108
  */
106
- function setupVod(trickMode, mediaOffset) {
109
+ function setupVod(trickMode, mediaOffset, hlsAes128Key) {
107
110
  // For VOD, we fake a presentation that has 2 Periods of equal duration
108
111
  // (20 seconds), where each Period has 1 Variant and 1 text stream.
109
112
  //
@@ -209,7 +212,8 @@ describe('StreamingEngine', () => {
209
212
  setupManifest(
210
213
  /* firstPeriodStartTime= */ 0,
211
214
  /* secondPeriodStartTime= */ 20,
212
- /* presentationDuration= */ 40);
215
+ /* presentationDuration= */ 40,
216
+ hlsAes128Key);
213
217
  }
214
218
 
215
219
  function setupLive() {
@@ -356,8 +360,15 @@ describe('StreamingEngine', () => {
356
360
  /* delays= */ netEngineDelays);
357
361
  }
358
362
 
363
+ /**
364
+ * @param {number} firstPeriodStartTime
365
+ * @param {number} secondPeriodStartTime
366
+ * @param {number} presentationDuration
367
+ * @param {shaka.extern.HlsAes128Key=} hlsAes128Key
368
+ */
359
369
  function setupManifest(
360
- firstPeriodStartTime, secondPeriodStartTime, presentationDuration) {
370
+ firstPeriodStartTime, secondPeriodStartTime, presentationDuration,
371
+ hlsAes128Key) {
361
372
  const segmentDurations = {
362
373
  audio: segmentData[ContentType.AUDIO].segmentDuration,
363
374
  video: segmentData[ContentType.VIDEO].segmentDuration,
@@ -380,7 +391,7 @@ describe('StreamingEngine', () => {
380
391
  /** @type {!shaka.media.PresentationTimeline} */(timeline),
381
392
  [firstPeriodStartTime, secondPeriodStartTime],
382
393
  presentationDuration, segmentDurations, initSegmentRanges,
383
- timestampOffsets);
394
+ timestampOffsets, hlsAes128Key);
384
395
 
385
396
  audioStream = manifest.variants[0].audio;
386
397
  videoStream = manifest.variants[0].video;
@@ -3430,6 +3441,69 @@ describe('StreamingEngine', () => {
3430
3441
  expect(alternateVideoStream.createSegmentIndex).not.toHaveBeenCalled();
3431
3442
  });
3432
3443
 
3444
+ describe('AES-128', () => {
3445
+ let key;
3446
+ /** @type {!shaka.extern.HlsAes128Key} */
3447
+ let hlsAes128Key;
3448
+
3449
+ beforeEach(async () => {
3450
+ // Get a key.
3451
+ const keyData = new ArrayBuffer(16);
3452
+ const keyDataView = new DataView(keyData);
3453
+ keyDataView.setInt16(0, 31710); // 0111 1011 1101 1110
3454
+ key = await window.crypto.subtle.importKey(
3455
+ 'raw', keyData, 'AES-CBC', true, ['decrypt']);
3456
+
3457
+ // Set up a manifest with AES-128 key info.
3458
+ // We don't actually provide the imported key OR the key fetching function
3459
+ // here, though, so that the individual tests can choose what the starting
3460
+ // state of the hlsAes128Key object is.
3461
+ hlsAes128Key = {method: 'AES-128', firstMediaSequenceNumber: 0};
3462
+
3463
+ setupVod(false, 0, hlsAes128Key);
3464
+ mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
3465
+ presentationTimeInSeconds = 0;
3466
+ createStreamingEngine();
3467
+ streamingEngine.switchVariant(variant);
3468
+ });
3469
+
3470
+ afterEach(async () => {
3471
+ await streamingEngine.destroy();
3472
+ });
3473
+
3474
+ async function runTest() {
3475
+ spyOn(window.crypto.subtle, 'decrypt').and.callThrough();
3476
+
3477
+ await streamingEngine.start();
3478
+ playing = true;
3479
+ await Util.fakeEventLoop(10);
3480
+
3481
+ expect(mediaSourceEngine.appendBuffer).toHaveBeenCalledTimes(2);
3482
+ expect(window.crypto.subtle.decrypt).toHaveBeenCalledTimes(2);
3483
+ expect(window.crypto.subtle.decrypt).toHaveBeenCalledWith(
3484
+ {name: 'AES-CBC', iv: jasmine.any(Object)}, key, jasmine.any(Object));
3485
+ }
3486
+
3487
+ it('decrypts segments', async () => {
3488
+ hlsAes128Key.cryptoKey = key;
3489
+ await runTest();
3490
+ });
3491
+
3492
+ it('downloads key if not pre-filled', async () => {
3493
+ hlsAes128Key.fetchKey = () => {
3494
+ hlsAes128Key.cryptoKey = key;
3495
+ hlsAes128Key.fetchKey = undefined;
3496
+ return Promise.resolve();
3497
+ };
3498
+
3499
+ await runTest();
3500
+
3501
+ // The key should have been fetched.
3502
+ expect(hlsAes128Key.cryptoKey).not.toBeUndefined();
3503
+ expect(hlsAes128Key.fetchKey).toBeUndefined();
3504
+ });
3505
+ });
3506
+
3433
3507
  describe('destroy', () => {
3434
3508
  it('aborts pending network operations', async () => {
3435
3509
  setupVod();
@@ -305,6 +305,8 @@ describe('Player Src Equals', () => {
305
305
  expect(stats.loadLatency).toBeGreaterThan(0);
306
306
  expect(stats.manifestTimeSeconds).toBeNaN(); // There's no manifest.
307
307
  expect(stats.drmTimeSeconds).toBeNaN(); // There's no DRM.
308
+ expect(stats.height).toBe(110);
309
+ expect(stats.width).toBe(256);
308
310
  });
309
311
 
310
312
  it('plays with external text tracks', async () => {
@@ -422,7 +422,6 @@ describe('Player', () => {
422
422
  dispatchEventSpy.calls.reset();
423
423
  player.configure({streaming: {maxDisabledTime}});
424
424
  player.setMaxHardwareResolution(123, 456);
425
- onErrorCallback(httpError);
426
425
  });
427
426
 
428
427
  afterEach(() => {
@@ -434,14 +433,18 @@ describe('Player', () => {
434
433
  });
435
434
 
436
435
  it('handles HTTP_ERROR', () => {
436
+ onErrorCallback(httpError);
437
437
  expect(httpError.handled).toBeTruthy();
438
438
  });
439
439
 
440
440
  it('does not dispatch any error', () => {
441
+ onErrorCallback(httpError);
441
442
  expect(dispatchEventSpy).not.toHaveBeenCalled();
442
443
  });
443
444
 
444
445
  it('disables the current variant and applies restrictions', () => {
446
+ onErrorCallback(httpError);
447
+
445
448
  const foundDisabledVariant =
446
449
  manifest.variants.some(({disabledUntilTime}) =>
447
450
  disabledUntilTime == currentTime + maxDisabledTime);
@@ -453,11 +456,46 @@ describe('Player', () => {
453
456
  });
454
457
 
455
458
  it('switches the variant', () => {
459
+ onErrorCallback(httpError);
460
+
456
461
  expect(chooseVariantSpy).toHaveBeenCalled();
457
462
  expect(getBufferedInfoSpy).toHaveBeenCalled();
458
463
  expect(switchVariantSpy)
459
464
  .toHaveBeenCalledWith(chosenVariant, false, true, 14);
460
465
  });
466
+
467
+ describe('but browser is truly offline', () => {
468
+ /** @type {!Object} */
469
+ let navigatorOnLineDescriptor;
470
+
471
+ // eslint-disable-next-line no-restricted-syntax
472
+ const navigatorPrototype = Navigator.prototype;
473
+
474
+ beforeAll(() => {
475
+ navigatorOnLineDescriptor =
476
+ /** @type {!Object} */(Object.getOwnPropertyDescriptor(
477
+ navigatorPrototype, 'onLine'));
478
+ });
479
+
480
+ beforeEach(() => {
481
+ // Redefine the property, replacing only the getter.
482
+ Object.defineProperty(navigatorPrototype, 'onLine',
483
+ Object.assign(navigatorOnLineDescriptor, {
484
+ get: () => false,
485
+ }));
486
+ });
487
+
488
+ afterEach(() => {
489
+ // Restore the original property definition.
490
+ Object.defineProperty(
491
+ navigatorPrototype, 'onLine', navigatorOnLineDescriptor);
492
+ });
493
+
494
+ it('does not handle HTTP_ERROR', () => {
495
+ onErrorCallback(httpError);
496
+ expect(httpError.handled).toBe(false);
497
+ });
498
+ });
461
499
  });
462
500
  });
463
501
  });
Binary file
@@ -481,6 +481,10 @@ shaka.test.FakeSegmentIndex = class {
481
481
  return this.get(nextPosition - 1);
482
482
  },
483
483
 
484
+ currentPosition: () => {
485
+ return nextPosition;
486
+ },
487
+
484
488
  seek: (time) => {
485
489
  nextPosition = this.find(time);
486
490
  return this.get(nextPosition++);
@@ -149,11 +149,13 @@ shaka.test.StreamingEngineUtil = class {
149
149
  * ranges for each type of init segment.
150
150
  * @param {!Object.<string,number>=} timestampOffsets The timestamp offset
151
151
  * for each type of segment
152
+ * @param {shaka.extern.HlsAes128Key=} hlsAes128Key The AES-128 key to provide
153
+ * to streams, if desired.
152
154
  * @return {shaka.extern.Manifest}
153
155
  */
154
156
  static createManifest(
155
157
  presentationTimeline, periodStartTimes, presentationDuration,
156
- segmentDurations, initSegmentRanges, timestampOffsets) {
158
+ segmentDurations, initSegmentRanges, timestampOffsets, hlsAes128Key) {
157
159
  const Util = shaka.test.Util;
158
160
 
159
161
  /**
@@ -255,7 +257,7 @@ shaka.test.StreamingEngineUtil = class {
255
257
  const appendWindowEnd = periodIndex == periodStartTimes.length - 1?
256
258
  presentationDuration : periodStartTimes[periodIndex + 1];
257
259
 
258
- return new shaka.media.SegmentReference(
260
+ const ref = new shaka.media.SegmentReference(
259
261
  /* startTime= */ periodStart + positionWithinPeriod * d,
260
262
  /* endTime= */ periodStart + (positionWithinPeriod + 1) * d,
261
263
  getUris,
@@ -265,6 +267,12 @@ shaka.test.StreamingEngineUtil = class {
265
267
  timestampOffset,
266
268
  appendWindowStart,
267
269
  appendWindowEnd);
270
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
271
+ if (hlsAes128Key &&
272
+ (type == ContentType.AUDIO || type == ContentType.VIDEO)) {
273
+ ref.hlsAes128Key = hlsAes128Key;
274
+ }
275
+ return ref;
268
276
  };
269
277
 
270
278
  /** @type {shaka.extern.Manifest} */
@@ -1008,6 +1008,32 @@ describe('VttTextParser', () => {
1008
1008
  {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
1009
1009
  });
1010
1010
 
1011
+ it('supports karaoke style text', () => {
1012
+ verifyHelper(
1013
+ [
1014
+ {
1015
+ startTime: 20, endTime: 40,
1016
+ payload: '',
1017
+ nestedCues: [
1018
+ {
1019
+ startTime: 20,
1020
+ endTime: 40,
1021
+ payload: 'Test',
1022
+ },
1023
+ {
1024
+ startTime: 25,
1025
+ endTime: 40,
1026
+ payload: ' 1',
1027
+ },
1028
+ ],
1029
+ },
1030
+ ],
1031
+ 'WEBVTT\n\n' +
1032
+ '00:00:20.00 --> 00:00:40.00\n' +
1033
+ 'Test<00:00:25.00> 1',
1034
+ {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
1035
+ });
1036
+
1011
1037
  it('supports default color overriding', () => {
1012
1038
  verifyHelper(
1013
1039
  [
@@ -71,6 +71,10 @@
71
71
  .ad-text-shadow();
72
72
  }
73
73
 
74
+ .shaka-ad-controls .shaka-ad-position:not(.shaka-hidden) + .shaka-ad-counter {
75
+ margin-left: 0.75em;
76
+ }
77
+
74
78
  .shaka-skip-ad-container {
75
79
  /* Skip button is positioned at the very right edge of the
76
80
  * video container unlike the rest of the bottom controls. */