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.
- package/.github/workflows/selenium-lab-tests.yaml +158 -53
- package/CHANGELOG.md +39 -29
- package/demo/common/assets.js +25 -0
- package/demo/common/message_ids.js +2 -0
- package/demo/config.js +5 -1
- package/demo/index.html +1 -1
- package/demo/locales/en.json +2 -0
- package/demo/locales/source.json +8 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +25 -23
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +25 -23
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +3 -2
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +24 -22
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +24 -22
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +57 -6
- package/dist/shaka-player.compiled.debug.d.ts +57 -6
- package/dist/shaka-player.compiled.debug.externs.js +62 -6
- package/dist/shaka-player.compiled.debug.js +305 -262
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +62 -6
- package/dist/shaka-player.compiled.js +817 -801
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +57 -6
- package/dist/shaka-player.ui.debug.d.ts +57 -6
- package/dist/shaka-player.ui.debug.externs.js +62 -6
- package/dist/shaka-player.ui.debug.js +308 -265
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +62 -6
- package/dist/shaka-player.ui.js +942 -925
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +149 -0
- package/docs/tutorials/ui.md +6 -6
- package/externs/shaka/abr_manager.js +8 -0
- package/externs/shaka/manifest.js +43 -0
- package/externs/shaka/player.js +13 -2
- package/lib/abr/simple_abr_manager.js +72 -4
- package/lib/hls/hls_parser.js +138 -96
- package/lib/media/drm_engine.js +0 -2
- package/lib/media/segment_index.js +8 -0
- package/lib/media/segment_reference.js +7 -1
- package/lib/media/streaming_engine.js +38 -2
- package/lib/net/networking_engine.js +1 -0
- package/lib/player.js +20 -1
- package/lib/polyfill/patchedmediakeys_apple.js +729 -0
- package/lib/text/ui_text_displayer.js +8 -0
- package/lib/text/vtt_text_parser.js +61 -4
- package/lib/util/error.js +23 -11
- package/lib/util/fairplay_utils.js +100 -2
- package/lib/util/platform.js +12 -1
- package/lib/util/player_configuration.js +4 -10
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +1 -0
- package/test/hls/hls_parser_unit.js +101 -34
- package/test/media/drm_engine_unit.js +29 -0
- package/test/media/streaming_engine_unit.js +79 -5
- package/test/player_src_equals_integration.js +2 -0
- package/test/player_unit.js +39 -1
- package/test/test/assets/small.mp4 +0 -0
- package/test/test/util/simple_fakes.js +4 -0
- package/test/test/util/streaming_engine_util.js +10 -2
- package/test/text/vtt_text_parser_unit.js +26 -0
- package/ui/less/ad_controls.less +4 -0
|
@@ -100,10 +100,13 @@ describe('StreamingEngine', () => {
|
|
|
100
100
|
jasmine.clock().mockDate();
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
/**
|
|
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 () => {
|
package/test/player_unit.js
CHANGED
|
@@ -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
|
-
|
|
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
|
[
|
package/ui/less/ad_controls.less
CHANGED
|
@@ -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. */
|