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
|
@@ -493,6 +493,14 @@ shaka.text.UITextDisplayer = class {
|
|
|
493
493
|
style.backgroundRepeat = 'no-repeat';
|
|
494
494
|
style.backgroundSize = 'contain';
|
|
495
495
|
style.backgroundPosition = 'center';
|
|
496
|
+
|
|
497
|
+
// Quoting https://www.w3.org/TR/ttml-imsc1.2/:
|
|
498
|
+
// "The width and height (in pixels) of the image resource referenced by
|
|
499
|
+
// smpte:backgroundImage SHALL be equal to the width and height expressed
|
|
500
|
+
// by the tts:extent attribute of the region in which the div element is
|
|
501
|
+
// presented".
|
|
502
|
+
style.width = '100%';
|
|
503
|
+
style.height = '100%';
|
|
496
504
|
} else {
|
|
497
505
|
// If we have both text and nested cues, then style everything; otherwise
|
|
498
506
|
// place the text in its own <span> so the background doesn't fill the
|
|
@@ -412,6 +412,7 @@ shaka.text.VttTextParser = class {
|
|
|
412
412
|
VttTextParser.addDefaultTextColor_(styles);
|
|
413
413
|
}
|
|
414
414
|
payload = VttTextParser.replaceColorPayload_(payload);
|
|
415
|
+
payload = VttTextParser.replaceKaraokeStylePayload_(payload);
|
|
415
416
|
const xmlPayload = '<span>' + payload + '</span>';
|
|
416
417
|
const element = shaka.util.XmlUtils.parseXmlString(xmlPayload, 'span');
|
|
417
418
|
if (element) {
|
|
@@ -437,6 +438,44 @@ shaka.text.VttTextParser = class {
|
|
|
437
438
|
}
|
|
438
439
|
}
|
|
439
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Converts karaoke style tag to be valid for xml parsing
|
|
443
|
+
* For example,
|
|
444
|
+
* input: Text <00:00:00.450> time <00:00:01.450> 1
|
|
445
|
+
* output: Text <div time="00:00:00.450"> time
|
|
446
|
+
* <div time="00:00:01.450"> 1</div></div>
|
|
447
|
+
*
|
|
448
|
+
* @param {string} payload
|
|
449
|
+
* @return {string} processed payload
|
|
450
|
+
* @private
|
|
451
|
+
*/
|
|
452
|
+
static replaceKaraokeStylePayload_(payload) {
|
|
453
|
+
const names = [];
|
|
454
|
+
let nameStart = -1;
|
|
455
|
+
for (let i = 0; i < payload.length; i++) {
|
|
456
|
+
if (payload[i] === '<') {
|
|
457
|
+
nameStart = i + 1;
|
|
458
|
+
} else if (payload[i] === '>') {
|
|
459
|
+
if (nameStart > 0) {
|
|
460
|
+
const name = payload.substr(nameStart, i - nameStart);
|
|
461
|
+
if (name.match(shaka.text.VttTextParser.timeFormat_)) {
|
|
462
|
+
names.push(name);
|
|
463
|
+
}
|
|
464
|
+
nameStart = -1;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
let newPayload = payload;
|
|
469
|
+
for (const name of names) {
|
|
470
|
+
const replaceTag = '<' + name + '>';
|
|
471
|
+
const startTag = '<div time="' + name + '">';
|
|
472
|
+
const endTag = '</div>';
|
|
473
|
+
newPayload = newPayload.replace(replaceTag, startTag);
|
|
474
|
+
newPayload += endTag;
|
|
475
|
+
}
|
|
476
|
+
return newPayload;
|
|
477
|
+
}
|
|
478
|
+
|
|
440
479
|
/**
|
|
441
480
|
* Converts color end tag to be valid for xml parsing
|
|
442
481
|
* For example,
|
|
@@ -560,6 +599,18 @@ shaka.text.VttTextParser = class {
|
|
|
560
599
|
case 'u':
|
|
561
600
|
nestedCue.textDecoration.push(underline);
|
|
562
601
|
break;
|
|
602
|
+
case 'div': {
|
|
603
|
+
const time = /** @type {!Element} */(element).getAttribute('time');
|
|
604
|
+
if (!time) {
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
const parser = new shaka.util.TextParser(time);
|
|
608
|
+
const cueTime = shaka.text.VttTextParser.parseTime_(parser);
|
|
609
|
+
if (cueTime) {
|
|
610
|
+
nestedCue.startTime = cueTime;
|
|
611
|
+
}
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
563
614
|
default:
|
|
564
615
|
break;
|
|
565
616
|
}
|
|
@@ -776,10 +827,7 @@ shaka.text.VttTextParser = class {
|
|
|
776
827
|
* @private
|
|
777
828
|
*/
|
|
778
829
|
static parseTime_(parser) {
|
|
779
|
-
|
|
780
|
-
// 00:00.00 or 00:00:00.00 or 0:00:00.00
|
|
781
|
-
const regexExpresion = /(?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{2,3})/g;
|
|
782
|
-
const results = parser.readRegex(regexExpresion);
|
|
830
|
+
const results = parser.readRegex(shaka.text.VttTextParser.timeFormat_);
|
|
783
831
|
if (results == null) {
|
|
784
832
|
return null;
|
|
785
833
|
}
|
|
@@ -810,6 +858,15 @@ shaka.text.VttTextParser.MPEG_TIMESCALE_ = 90000;
|
|
|
810
858
|
*/
|
|
811
859
|
shaka.text.VttTextParser.TS_ROLLOVER_ = 0x200000000;
|
|
812
860
|
|
|
861
|
+
/**
|
|
862
|
+
* @const
|
|
863
|
+
* @private {!RegExp}
|
|
864
|
+
* @example 00:00.000 or 00:00:00.000 or 0:00:00.000 or
|
|
865
|
+
* 00:00.00 or 00:00:00.00 or 0:00:00.00
|
|
866
|
+
*/
|
|
867
|
+
shaka.text.VttTextParser.timeFormat_ =
|
|
868
|
+
/(?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{2,3})/g;
|
|
869
|
+
|
|
813
870
|
shaka.text.TextEngine.registerParser(
|
|
814
871
|
'text/vtt', () => new shaka.text.VttTextParser());
|
|
815
872
|
|
package/lib/util/error.js
CHANGED
|
@@ -656,16 +656,8 @@ shaka.util.Error.Code = {
|
|
|
656
656
|
*/
|
|
657
657
|
'CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM': 4033,
|
|
658
658
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
*/
|
|
662
|
-
'HLS_AES_128_ENCRYPTION_NOT_SUPPORTED': 4034,
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* An internal error code that should never be seen by applications, thrown
|
|
666
|
-
* to force the HLS parser to skip an unsupported stream.
|
|
667
|
-
*/
|
|
668
|
-
'HLS_INTERNAL_SKIP_STREAM': 4035,
|
|
659
|
+
// RETIRED: 'HLS_AES_128_ENCRYPTION_NOT_SUPPORTED': 4034,
|
|
660
|
+
// RETIRED: 'HLS_INTERNAL_SKIP_STREAM': 4035,
|
|
669
661
|
|
|
670
662
|
/** The Manifest contained no Variants. */
|
|
671
663
|
'NO_VARIANTS': 4036,
|
|
@@ -693,7 +685,27 @@ shaka.util.Error.Code = {
|
|
|
693
685
|
*/
|
|
694
686
|
'HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED': 4040,
|
|
695
687
|
|
|
696
|
-
|
|
688
|
+
/**
|
|
689
|
+
* We do not support playing encrypted content (different than mp2t) with MSE
|
|
690
|
+
* and legacy Apple MediaKeys API.
|
|
691
|
+
*/
|
|
692
|
+
'HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED': 4041,
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Web Crypto API is not available (to decrypt AES-128 streams). Web Crypto
|
|
696
|
+
* only exists in secure origins like https.
|
|
697
|
+
*/
|
|
698
|
+
'NO_WEB_CRYPTO_API': 4042,
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* AES-128 iv length should be 16 bytes.
|
|
702
|
+
*/
|
|
703
|
+
'HLS_AES_128_INVALID_IV_LENGTH': 4043,
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* AES-128 encryption key length should be 16 bytes.
|
|
707
|
+
*/
|
|
708
|
+
'HLS_AES_128_INVALID_KEY_LENGTH': 4044,
|
|
697
709
|
|
|
698
710
|
|
|
699
711
|
// RETIRED: 'INCONSISTENT_BUFFER_STATE': 5000,
|
|
@@ -133,13 +133,83 @@ shaka.util.FairPlayUtils = class {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
136
|
+
* Verimatrix initDataTransform configuration.
|
|
137
|
+
*
|
|
138
|
+
* @param {!Uint8Array} initData
|
|
139
|
+
* @param {string} initDataType
|
|
140
|
+
* @param {?shaka.extern.DrmInfo} drmInfo
|
|
141
|
+
* @export
|
|
142
|
+
*/
|
|
143
|
+
static verimatrixInitDataTransform(initData, initDataType, drmInfo) {
|
|
144
|
+
if (initDataType !== 'skd') {
|
|
145
|
+
return initData;
|
|
146
|
+
}
|
|
147
|
+
const StringUtils = shaka.util.StringUtils;
|
|
148
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
149
|
+
const cert = drmInfo.serverCertificate;
|
|
150
|
+
const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
|
|
151
|
+
const contentId = initDataAsString.split('skd://').pop();
|
|
152
|
+
return FairPlayUtils.initDataTransform(initData, contentId, cert);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* EZDRM initDataTransform configuration.
|
|
157
|
+
*
|
|
158
|
+
* @param {!Uint8Array} initData
|
|
159
|
+
* @param {string} initDataType
|
|
160
|
+
* @param {?shaka.extern.DrmInfo} drmInfo
|
|
161
|
+
* @export
|
|
162
|
+
*/
|
|
163
|
+
static ezdrmInitDataTransform(initData, initDataType, drmInfo) {
|
|
164
|
+
if (initDataType !== 'skd') {
|
|
165
|
+
return initData;
|
|
166
|
+
}
|
|
167
|
+
const StringUtils = shaka.util.StringUtils;
|
|
168
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
169
|
+
const cert = drmInfo.serverCertificate;
|
|
170
|
+
const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
|
|
171
|
+
const contentId = initDataAsString.split(';').pop();
|
|
172
|
+
return FairPlayUtils.initDataTransform(initData, contentId, cert);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Conax initDataTransform configuration.
|
|
177
|
+
*
|
|
178
|
+
* @param {!Uint8Array} initData
|
|
179
|
+
* @param {string} initDataType
|
|
180
|
+
* @param {?shaka.extern.DrmInfo} drmInfo
|
|
181
|
+
* @export
|
|
182
|
+
*/
|
|
183
|
+
static conaxInitDataTransform(initData, initDataType, drmInfo) {
|
|
184
|
+
if (initDataType !== 'skd') {
|
|
185
|
+
return initData;
|
|
186
|
+
}
|
|
187
|
+
const StringUtils = shaka.util.StringUtils;
|
|
188
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
189
|
+
const cert = drmInfo.serverCertificate;
|
|
190
|
+
const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
|
|
191
|
+
const skdValue = initDataAsString.split('skd://').pop().split('?').shift();
|
|
192
|
+
const stringToArray = (string) => {
|
|
193
|
+
// 2 bytes for each char
|
|
194
|
+
const buffer = new ArrayBuffer(string.length * 2);
|
|
195
|
+
const array = new Uint16Array(buffer);
|
|
196
|
+
for (let i = 0, strLen = string.length; i < strLen; i++) {
|
|
197
|
+
array[i] = string.charCodeAt(i);
|
|
198
|
+
}
|
|
199
|
+
return array;
|
|
200
|
+
};
|
|
201
|
+
const contentId = stringToArray(window.atob(skdValue));
|
|
202
|
+
return FairPlayUtils.initDataTransform(initData, contentId, cert);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Verimatrix FairPlay request.
|
|
137
207
|
*
|
|
138
208
|
* @param {shaka.net.NetworkingEngine.RequestType} type
|
|
139
209
|
* @param {shaka.extern.Request} request
|
|
140
210
|
* @export
|
|
141
211
|
*/
|
|
142
|
-
static
|
|
212
|
+
static verimatrixFairPlayRequest(type, request) {
|
|
143
213
|
if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
|
|
144
214
|
return;
|
|
145
215
|
}
|
|
@@ -150,6 +220,34 @@ shaka.util.FairPlayUtils = class {
|
|
|
150
220
|
request.body = shaka.util.StringUtils.toUTF8('spc=' + base64Payload);
|
|
151
221
|
}
|
|
152
222
|
|
|
223
|
+
/**
|
|
224
|
+
* EZDRM FairPlay request.
|
|
225
|
+
*
|
|
226
|
+
* @param {shaka.net.NetworkingEngine.RequestType} type
|
|
227
|
+
* @param {shaka.extern.Request} request
|
|
228
|
+
* @export
|
|
229
|
+
*/
|
|
230
|
+
static ezdrmFairPlayRequest(type, request) {
|
|
231
|
+
if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
request.headers['Content-Type'] = 'application/octet-stream';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Conax FairPlay request.
|
|
239
|
+
*
|
|
240
|
+
* @param {shaka.net.NetworkingEngine.RequestType} type
|
|
241
|
+
* @param {shaka.extern.Request} request
|
|
242
|
+
* @export
|
|
243
|
+
*/
|
|
244
|
+
static conaxFairPlayRequest(type, request) {
|
|
245
|
+
if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
request.headers['Content-Type'] = 'application/octet-stream';
|
|
249
|
+
}
|
|
250
|
+
|
|
153
251
|
/**
|
|
154
252
|
* Common FairPlay response transform for some DRMs providers.
|
|
155
253
|
*
|
package/lib/util/platform.js
CHANGED
|
@@ -199,7 +199,8 @@ shaka.util.Platform = class {
|
|
|
199
199
|
return !!navigator.vendor && navigator.vendor.includes('Apple') &&
|
|
200
200
|
!shaka.util.Platform.isTizen() &&
|
|
201
201
|
!shaka.util.Platform.isEOS() &&
|
|
202
|
-
!shaka.util.Platform.isPS4()
|
|
202
|
+
!shaka.util.Platform.isPS4() &&
|
|
203
|
+
!shaka.util.Platform.isAmazonFireTV();
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
/**
|
|
@@ -222,6 +223,16 @@ shaka.util.Platform = class {
|
|
|
222
223
|
return shaka.util.Platform.userAgentContains_('PlayStation 4');
|
|
223
224
|
}
|
|
224
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Check if the current platform is Amazon Fire TV.
|
|
228
|
+
* https://developer.amazon.com/docs/fire-tv/identify-amazon-fire-tv-devices.html
|
|
229
|
+
*
|
|
230
|
+
* @return {boolean}
|
|
231
|
+
*/
|
|
232
|
+
static isAmazonFireTV() {
|
|
233
|
+
return shaka.util.Platform.userAgentContains_('AFT');
|
|
234
|
+
}
|
|
235
|
+
|
|
225
236
|
/**
|
|
226
237
|
* Returns a major version number for Safari, or Safari-based iOS browsers.
|
|
227
238
|
*
|
|
@@ -147,7 +147,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
147
147
|
ignoreTextStreamFailures: false,
|
|
148
148
|
alwaysStreamText: false,
|
|
149
149
|
startAtSegmentBoundary: false,
|
|
150
|
-
gapDetectionThreshold: 0.
|
|
150
|
+
gapDetectionThreshold: 0.5,
|
|
151
151
|
durationBackoff: 1,
|
|
152
152
|
forceTransmuxTS: false,
|
|
153
153
|
// Offset by 5 seconds since Chromecast takes a few seconds to start
|
|
@@ -173,15 +173,6 @@ shaka.util.PlayerConfiguration = class {
|
|
|
173
173
|
maxDisabledTime: 30,
|
|
174
174
|
};
|
|
175
175
|
|
|
176
|
-
// Some browsers will stop earlier than others before a gap (e.g., Edge
|
|
177
|
-
// stops 0.5 seconds before a gap). So for some browsers we need to use a
|
|
178
|
-
// larger threshold. See: https://bit.ly/2K5xmJO
|
|
179
|
-
if (shaka.util.Platform.isLegacyEdge() ||
|
|
180
|
-
shaka.util.Platform.isTizen() ||
|
|
181
|
-
shaka.util.Platform.isChromecast()) {
|
|
182
|
-
streaming.gapDetectionThreshold = 0.5;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
176
|
// WebOS, Tizen, and Chromecast have long hardware pipelines that respond
|
|
186
177
|
// slowly to seeking. Therefore we should not seek when we detect a stall
|
|
187
178
|
// on one of these platforms. Instead, default stallSkip to 0 to force the
|
|
@@ -253,6 +244,8 @@ shaka.util.PlayerConfiguration = class {
|
|
|
253
244
|
fastHalfLife: 2,
|
|
254
245
|
slowHalfLife: 5,
|
|
255
246
|
},
|
|
247
|
+
restrictToElementSize: false,
|
|
248
|
+
ignoreDevicePixelRatio: false,
|
|
256
249
|
};
|
|
257
250
|
|
|
258
251
|
const cmcd = {
|
|
@@ -321,6 +314,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
321
314
|
*/
|
|
322
315
|
static mergeConfigObjects(destination, updates, template) {
|
|
323
316
|
const overrides = {
|
|
317
|
+
'.drm.keySystemsMapping': '',
|
|
324
318
|
'.drm.servers': '',
|
|
325
319
|
'.drm.clearKeys': '',
|
|
326
320
|
'.drm.advanced': {
|
package/package.json
CHANGED
|
@@ -38,6 +38,7 @@ goog.require('shaka.polyfill.Fullscreen');
|
|
|
38
38
|
goog.require('shaka.polyfill.MediaSource');
|
|
39
39
|
goog.require('shaka.polyfill.MediaCapabilities');
|
|
40
40
|
goog.require('shaka.polyfill.Orientation');
|
|
41
|
+
goog.require('shaka.polyfill.PatchedMediaKeysApple');
|
|
41
42
|
goog.require('shaka.polyfill.PatchedMediaKeysNop');
|
|
42
43
|
goog.require('shaka.polyfill.PatchedMediaKeysWebkit');
|
|
43
44
|
goog.require('shaka.polyfill.PiPWebkit');
|
|
@@ -33,6 +33,8 @@ describe('HlsParser', () => {
|
|
|
33
33
|
let segmentData;
|
|
34
34
|
/** @type {!Uint8Array} */
|
|
35
35
|
let selfInitializingSegmentData;
|
|
36
|
+
/** @type {!Uint8Array} */
|
|
37
|
+
let aes128Key;
|
|
36
38
|
|
|
37
39
|
afterEach(() => {
|
|
38
40
|
shaka.log.alwaysWarn = originalAlwaysWarn;
|
|
@@ -75,6 +77,11 @@ describe('HlsParser', () => {
|
|
|
75
77
|
selfInitializingSegmentData =
|
|
76
78
|
shaka.util.Uint8ArrayUtils.concat(initSegmentData, segmentData);
|
|
77
79
|
|
|
80
|
+
aes128Key = new Uint8Array([
|
|
81
|
+
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
82
|
+
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
83
|
+
]);
|
|
84
|
+
|
|
78
85
|
fakeNetEngine = new shaka.test.FakeNetworkingEngine();
|
|
79
86
|
|
|
80
87
|
config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
@@ -2432,7 +2439,7 @@ describe('HlsParser', () => {
|
|
|
2432
2439
|
expect(initSegments[1].getUris()[0]).toBe('test:/init2.mp4');
|
|
2433
2440
|
});
|
|
2434
2441
|
|
|
2435
|
-
it('
|
|
2442
|
+
it('parses variants encrypted with AES-128', async () => {
|
|
2436
2443
|
const master = [
|
|
2437
2444
|
'#EXTM3U\n',
|
|
2438
2445
|
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
@@ -2441,10 +2448,15 @@ describe('HlsParser', () => {
|
|
|
2441
2448
|
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
|
|
2442
2449
|
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
|
|
2443
2450
|
'video2\n',
|
|
2451
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
|
|
2452
|
+
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud3"\n',
|
|
2453
|
+
'video3\n',
|
|
2444
2454
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
|
2445
2455
|
'URI="audio"\n',
|
|
2446
2456
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
|
|
2447
2457
|
'URI="audio2"\n',
|
|
2458
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud3",LANGUAGE="de",',
|
|
2459
|
+
'URI="audio3"\n',
|
|
2448
2460
|
].join('');
|
|
2449
2461
|
|
|
2450
2462
|
const media = [
|
|
@@ -2456,17 +2468,26 @@ describe('HlsParser', () => {
|
|
|
2456
2468
|
'main.mp4',
|
|
2457
2469
|
].join('');
|
|
2458
2470
|
|
|
2459
|
-
const
|
|
2471
|
+
const mediaWithMp4AesEncryption = [
|
|
2460
2472
|
'#EXTM3U\n',
|
|
2461
2473
|
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
2462
2474
|
'#EXT-X-KEY:METHOD=AES-128,',
|
|
2463
|
-
'URI="800k.key\n',
|
|
2475
|
+
'URI="800k.key"\n',
|
|
2464
2476
|
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
2465
2477
|
'#EXTINF:5,\n',
|
|
2466
2478
|
'#EXT-X-BYTERANGE:121090@616\n',
|
|
2467
2479
|
'main.mp4',
|
|
2468
2480
|
].join('');
|
|
2469
2481
|
|
|
2482
|
+
const mediaWithTSAesEncryption = [
|
|
2483
|
+
'#EXTM3U\n',
|
|
2484
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
2485
|
+
'#EXT-X-KEY:METHOD=AES-128,',
|
|
2486
|
+
'URI="800k.key"\n',
|
|
2487
|
+
'#EXTINF:5,\n',
|
|
2488
|
+
'main.ts',
|
|
2489
|
+
].join('');
|
|
2490
|
+
|
|
2470
2491
|
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
2471
2492
|
manifest.anyTimeline();
|
|
2472
2493
|
manifest.addPartialVariant((variant) => {
|
|
@@ -2478,6 +2499,24 @@ describe('HlsParser', () => {
|
|
|
2478
2499
|
stream.language = 'en';
|
|
2479
2500
|
});
|
|
2480
2501
|
});
|
|
2502
|
+
manifest.addPartialVariant((variant) => {
|
|
2503
|
+
variant.bandwidth = 300;
|
|
2504
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
2505
|
+
stream.size(960, 540);
|
|
2506
|
+
});
|
|
2507
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
2508
|
+
stream.language = 'fr';
|
|
2509
|
+
});
|
|
2510
|
+
});
|
|
2511
|
+
manifest.addPartialVariant((variant) => {
|
|
2512
|
+
variant.bandwidth = 300;
|
|
2513
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
2514
|
+
stream.size(960, 540);
|
|
2515
|
+
});
|
|
2516
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
2517
|
+
stream.language = 'de';
|
|
2518
|
+
});
|
|
2519
|
+
});
|
|
2481
2520
|
manifest.sequenceMode = true;
|
|
2482
2521
|
});
|
|
2483
2522
|
|
|
@@ -2485,19 +2524,73 @@ describe('HlsParser', () => {
|
|
|
2485
2524
|
.setResponseText('test:/master', master)
|
|
2486
2525
|
.setResponseText('test:/audio', media)
|
|
2487
2526
|
.setResponseText('test:/audio2', media)
|
|
2527
|
+
.setResponseText('test:/audio3', media)
|
|
2488
2528
|
.setResponseText('test:/video', media)
|
|
2489
|
-
.setResponseText('test:/video2',
|
|
2529
|
+
.setResponseText('test:/video2', mediaWithMp4AesEncryption)
|
|
2530
|
+
.setResponseText('test:/video3', mediaWithTSAesEncryption)
|
|
2490
2531
|
.setResponseText('test:/main.vtt', vttText)
|
|
2491
2532
|
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
2492
2533
|
.setResponseValue('test:/main.mp4', segmentData)
|
|
2493
2534
|
.setResponseValue('test:/main.test', segmentData)
|
|
2535
|
+
.setResponseValue('test:/800k.key', aes128Key)
|
|
2494
2536
|
.setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
|
|
2495
2537
|
|
|
2496
2538
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2497
2539
|
expect(actual).toEqual(manifest);
|
|
2498
|
-
return actual;
|
|
2499
2540
|
});
|
|
2500
2541
|
|
|
2542
|
+
it('fails on AES-128 if WebCrypto APIs are not available', async () => {
|
|
2543
|
+
const master = [
|
|
2544
|
+
'#EXTM3U\n',
|
|
2545
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
2546
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
|
2547
|
+
'video\n',
|
|
2548
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
|
2549
|
+
'URI="audio"\n',
|
|
2550
|
+
].join('');
|
|
2551
|
+
|
|
2552
|
+
const mediaWithMp4AesEncryption = [
|
|
2553
|
+
'#EXTM3U\n',
|
|
2554
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
2555
|
+
'#EXT-X-KEY:METHOD=AES-128,',
|
|
2556
|
+
'URI="800k.key"\n',
|
|
2557
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
2558
|
+
'#EXTINF:5,\n',
|
|
2559
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
2560
|
+
'main.mp4',
|
|
2561
|
+
].join('');
|
|
2562
|
+
|
|
2563
|
+
fakeNetEngine
|
|
2564
|
+
.setResponseText('test:/master', master)
|
|
2565
|
+
.setResponseText('test:/audio', mediaWithMp4AesEncryption)
|
|
2566
|
+
.setResponseText('test:/video', mediaWithMp4AesEncryption)
|
|
2567
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
2568
|
+
.setResponseValue('test:/main.mp4', segmentData)
|
|
2569
|
+
.setResponseValue('test:/800k.key', aes128Key);
|
|
2570
|
+
|
|
2571
|
+
let originalWebCrypto = null;
|
|
2572
|
+
try {
|
|
2573
|
+
originalWebCrypto = window.crypto;
|
|
2574
|
+
Object.defineProperty(window, 'crypto', {
|
|
2575
|
+
configurable: true,
|
|
2576
|
+
value: null,
|
|
2577
|
+
});
|
|
2578
|
+
|
|
2579
|
+
const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
|
|
2580
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2581
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2582
|
+
shaka.util.Error.Code.NO_WEB_CRYPTO_API));
|
|
2583
|
+
await expectAsync(parser.start('test:/master', playerInterface))
|
|
2584
|
+
.toBeRejectedWith(expectedError);
|
|
2585
|
+
} finally {
|
|
2586
|
+
Object.defineProperty(window, 'crypto', {
|
|
2587
|
+
configurable: true,
|
|
2588
|
+
value: originalWebCrypto,
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
});
|
|
2592
|
+
|
|
2593
|
+
|
|
2501
2594
|
it('constructs DrmInfo for Widevine', async () => {
|
|
2502
2595
|
const master = [
|
|
2503
2596
|
'#EXTM3U\n',
|
|
@@ -2674,7 +2767,9 @@ describe('HlsParser', () => {
|
|
|
2674
2767
|
.setResponseText('test:/video', media)
|
|
2675
2768
|
.setResponseValue('test:/main.exe', segmentData)
|
|
2676
2769
|
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
2677
|
-
.setResponseValue('test:/main.mp4', segmentData)
|
|
2770
|
+
.setResponseValue('test:/main.mp4', segmentData)
|
|
2771
|
+
.setResponseValue('data:text/plain;base64,AAECAwQFBgcICQoLDA0ODw==',
|
|
2772
|
+
aes128Key);
|
|
2678
2773
|
|
|
2679
2774
|
await expectAsync(parser.start('test:/master', playerInterface))
|
|
2680
2775
|
.toBeRejectedWith(Util.jasmineError(error));
|
|
@@ -2708,34 +2803,6 @@ describe('HlsParser', () => {
|
|
|
2708
2803
|
await verifyError(master, media, error);
|
|
2709
2804
|
});
|
|
2710
2805
|
|
|
2711
|
-
it('if all variants are encrypted with AES-128', async () => {
|
|
2712
|
-
const master = [
|
|
2713
|
-
'#EXTM3U\n',
|
|
2714
|
-
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
2715
|
-
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
2716
|
-
'video\n',
|
|
2717
|
-
].join('');
|
|
2718
|
-
|
|
2719
|
-
const media = [
|
|
2720
|
-
'#EXTM3U\n',
|
|
2721
|
-
'#EXT-X-TARGETDURATION:6\n',
|
|
2722
|
-
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
2723
|
-
'#EXT-X-KEY:METHOD=AES-128,',
|
|
2724
|
-
'URI="data:text/plain;base64\n',
|
|
2725
|
-
'#EXT-X-MAP:URI="init.mp4"\n',
|
|
2726
|
-
'#EXTINF:5,\n',
|
|
2727
|
-
'#EXT-X-BYTERANGE:121090@616\n',
|
|
2728
|
-
'main.mp4',
|
|
2729
|
-
].join('');
|
|
2730
|
-
|
|
2731
|
-
const error = new shaka.util.Error(
|
|
2732
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2733
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2734
|
-
Code.HLS_AES_128_ENCRYPTION_NOT_SUPPORTED);
|
|
2735
|
-
|
|
2736
|
-
await verifyError(master, media, error);
|
|
2737
|
-
});
|
|
2738
|
-
|
|
2739
2806
|
describe('if required attributes are missing', () => {
|
|
2740
2807
|
/**
|
|
2741
2808
|
* @param {string} master
|
|
@@ -677,6 +677,35 @@ describe('DrmEngine', () => {
|
|
|
677
677
|
.toBeRejectedWith(expected);
|
|
678
678
|
});
|
|
679
679
|
|
|
680
|
+
it('uses key system IDs from keySystemsMapping config', async () => {
|
|
681
|
+
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
682
|
+
manifest.addVariant(0, (variant) => {
|
|
683
|
+
variant.addVideo(1, (stream) => {
|
|
684
|
+
stream.encrypted = true;
|
|
685
|
+
stream.addDrmInfo('drm.abc');
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
setDecodingInfoSpy([]);
|
|
691
|
+
|
|
692
|
+
config.keySystemsMapping['drm.abc'] = 'drm.def';
|
|
693
|
+
drmEngine.configure(config);
|
|
694
|
+
|
|
695
|
+
const variants = manifest.variants;
|
|
696
|
+
await expectAsync(
|
|
697
|
+
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
|
|
698
|
+
.toBeRejected();
|
|
699
|
+
|
|
700
|
+
expect(drmEngine.initialized()).toBe(false);
|
|
701
|
+
expect(decodingInfoSpy).toHaveBeenCalledTimes(1);
|
|
702
|
+
expect(decodingInfoSpy).toHaveBeenCalledWith(containing({
|
|
703
|
+
keySystemConfiguration: containing({
|
|
704
|
+
keySystem: 'drm.def',
|
|
705
|
+
}),
|
|
706
|
+
}));
|
|
707
|
+
});
|
|
708
|
+
|
|
680
709
|
it('maps TS MIME types through the transmuxer', async () => {
|
|
681
710
|
const originalIsSupported = shaka.media.Transmuxer.isSupported;
|
|
682
711
|
|