shaka-player 3.3.16 → 3.3.18
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/build-and-test.yaml +4 -4
- package/.github/workflows/demo-version-index.yaml +6 -0
- package/.github/workflows/selenium-lab-tests.yaml +72 -11
- package/.github/workflows/update-screenshots.yaml +142 -0
- package/CHANGELOG.md +38 -0
- package/demo/common/asset.js +15 -0
- package/demo/common/assets.js +4 -2
- package/demo/common/message_ids.js +1 -0
- package/demo/custom.js +19 -0
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +14 -0
- package/dist/demo.compiled.debug.js +146 -144
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +146 -144
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +1 -1
- package/dist/locales.js +28 -28
- package/dist/receiver.compiled.debug.js +15 -15
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +15 -15
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +12 -0
- package/dist/shaka-player.compiled.debug.d.ts +12 -0
- package/dist/shaka-player.compiled.debug.externs.js +4 -0
- package/dist/shaka-player.compiled.debug.js +105 -100
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +4 -0
- package/dist/shaka-player.compiled.js +296 -292
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +12 -0
- package/dist/shaka-player.ui.debug.d.ts +12 -0
- package/dist/shaka-player.ui.debug.externs.js +4 -0
- package/dist/shaka-player.ui.debug.js +119 -114
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +4 -0
- package/dist/shaka-player.ui.js +311 -307
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/errors.md +94 -0
- package/docs/tutorials/index.json +2 -0
- package/docs/tutorials/widevine-service-certs.md +45 -0
- package/externs/ima.js +9 -0
- package/externs/shaka/text.js +7 -0
- package/lib/ads/client_side_ad_manager.js +6 -3
- package/lib/dash/segment_template.js +17 -5
- package/lib/hls/hls_parser.js +3 -1
- package/lib/media/transmuxer.js +13 -7
- package/lib/media/video_wrapper.js +3 -3
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/player.js +3 -1
- package/lib/polyfill/media_capabilities.js +3 -8
- package/lib/polyfill/mediasource.js +7 -2
- package/lib/text/cue.js +6 -0
- package/lib/text/ui_text_displayer.js +2 -5
- package/lib/text/vtt_text_parser.js +257 -93
- package/lib/util/mime_utils.js +1 -1
- package/lib/util/platform.js +8 -0
- package/lib/util/stream_utils.js +26 -14
- package/package.json +1 -1
- package/support.html +1 -0
- package/test/dash/dash_parser_manifest_unit.js +0 -36
- package/test/polyfill/media_capabilities_unit.js +20 -22
- package/test/test/util/layout_tests.js +433 -0
- package/test/test/util/util.js +0 -120
- package/test/{ui → text}/text_displayer_layout_unit.js +59 -235
- package/test/text/ui_text_displayer_unit.js +2 -4
- package/test/text/vtt_text_parser_unit.js +151 -6
- package/test/text/web_vtt_layout_integration.js +525 -0
|
@@ -132,14 +132,14 @@ shaka.text.VttTextParser = class {
|
|
|
132
132
|
for (const [key, value] of Object.entries(textColor)) {
|
|
133
133
|
const cue = new shaka.text.Cue(0, 0, '');
|
|
134
134
|
cue.color = value;
|
|
135
|
-
styles.set(key, cue);
|
|
135
|
+
styles.set('.' + key, cue);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
const bgColor = shaka.text.Cue.defaultTextBackgroundColor;
|
|
139
139
|
for (const [key, value] of Object.entries(bgColor)) {
|
|
140
140
|
const cue = new shaka.text.Cue(0, 0, '');
|
|
141
141
|
cue.backgroundColor = value;
|
|
142
|
-
styles.set(key, cue);
|
|
142
|
+
styles.set('.' + key, cue);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -199,92 +199,121 @@ shaka.text.VttTextParser = class {
|
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
let
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
/** @type {!Array.<!Array.<string>>} */
|
|
203
|
+
const styleBlocks = [];
|
|
204
|
+
let lastBlockIndex = -1;
|
|
205
|
+
for (let i = 1; i < text.length; i++) {
|
|
206
|
+
if (text[i].includes('::cue')) {
|
|
207
|
+
styleBlocks.push([]);
|
|
208
|
+
lastBlockIndex = styleBlocks.length - 1;
|
|
209
|
+
}
|
|
210
|
+
if (lastBlockIndex == -1) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
styleBlocks[lastBlockIndex].push(text[i]);
|
|
214
|
+
if (text[i].includes('}')) {
|
|
215
|
+
lastBlockIndex = -1;
|
|
216
|
+
}
|
|
211
217
|
}
|
|
212
218
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
+
for (const styleBlock of styleBlocks) {
|
|
220
|
+
let styleSelector = 'global';
|
|
221
|
+
// Look for what is within parentheses. For example:
|
|
222
|
+
// <code>:: cue (b) {</code>, what we are looking for is <code>b</code>
|
|
223
|
+
const selector = styleBlock[0].match(/\((.*)\)/);
|
|
224
|
+
if (selector) {
|
|
225
|
+
styleSelector = selector.pop();
|
|
219
226
|
}
|
|
220
|
-
}
|
|
221
227
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
228
|
+
// We start at 1 to avoid '::cue' and end earlier to avoid '}'
|
|
229
|
+
let propertyLines = styleBlock.slice(1, -1);
|
|
230
|
+
if (styleBlock[0].includes('}')) {
|
|
231
|
+
const payload = /\{(.*?)\}/.exec(styleBlock[0]);
|
|
232
|
+
if (payload) {
|
|
233
|
+
propertyLines = payload[1].split(';');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Continue styles over multiple selectors if necessary.
|
|
238
|
+
// For example,
|
|
239
|
+
// ::cue(b) { background: white; } ::cue(b) { color: blue; }
|
|
240
|
+
// should set both the background and foreground of bold tags.
|
|
241
|
+
let cue = styles.get(styleSelector);
|
|
242
|
+
if (!cue) {
|
|
243
|
+
cue = new shaka.text.Cue(0, 0, '');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let validStyle = false;
|
|
247
|
+
for (let i = 0; i < propertyLines.length; i++) {
|
|
248
|
+
// We look for CSS properties. As a general rule they are separated by
|
|
249
|
+
// <code>:</code>. Eg: <code>color: red;</code>
|
|
250
|
+
const lineParts = /^\s*([^:]+):\s*(.*)/.exec(propertyLines[i]);
|
|
251
|
+
if (lineParts) {
|
|
252
|
+
const name = lineParts[1].trim();
|
|
253
|
+
const value = lineParts[2].trim().replace(';', '');
|
|
254
|
+
switch (name) {
|
|
255
|
+
case 'background-color':
|
|
256
|
+
case 'background':
|
|
250
257
|
validStyle = true;
|
|
251
|
-
cue.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
258
|
+
cue.backgroundColor = value;
|
|
259
|
+
break;
|
|
260
|
+
case 'color':
|
|
261
|
+
validStyle = true;
|
|
262
|
+
cue.color = value;
|
|
263
|
+
break;
|
|
264
|
+
case 'font-family':
|
|
265
|
+
validStyle = true;
|
|
266
|
+
cue.fontFamily = value;
|
|
267
|
+
break;
|
|
268
|
+
case 'font-size':
|
|
269
|
+
validStyle = true;
|
|
270
|
+
cue.fontSize = value;
|
|
271
|
+
break;
|
|
272
|
+
case 'font-weight':
|
|
273
|
+
if (parseInt(value, 10) >= 700 || value == 'bold') {
|
|
265
274
|
validStyle = true;
|
|
266
|
-
cue.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
275
|
+
cue.fontWeight = shaka.text.Cue.fontWeight.BOLD;
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
case 'font-style':
|
|
279
|
+
switch (value) {
|
|
280
|
+
case 'normal':
|
|
281
|
+
validStyle = true;
|
|
282
|
+
cue.fontStyle = shaka.text.Cue.fontStyle.NORMAL;
|
|
283
|
+
break;
|
|
284
|
+
case 'italic':
|
|
285
|
+
validStyle = true;
|
|
286
|
+
cue.fontStyle = shaka.text.Cue.fontStyle.ITALIC;
|
|
287
|
+
break;
|
|
288
|
+
case 'oblique':
|
|
289
|
+
validStyle = true;
|
|
290
|
+
cue.fontStyle = shaka.text.Cue.fontStyle.OBLIQUE;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
case 'opacity':
|
|
295
|
+
validStyle = true;
|
|
296
|
+
cue.opacity = parseFloat(value);
|
|
297
|
+
break;
|
|
298
|
+
case 'text-shadow':
|
|
299
|
+
validStyle = true;
|
|
300
|
+
cue.textShadow = value;
|
|
301
|
+
break;
|
|
302
|
+
case 'white-space':
|
|
303
|
+
validStyle = true;
|
|
304
|
+
cue.wrapLine = value != 'noWrap';
|
|
305
|
+
break;
|
|
306
|
+
default:
|
|
307
|
+
shaka.log.warning('VTT parser encountered an unsupported style: ',
|
|
308
|
+
lineParts);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
282
311
|
}
|
|
283
312
|
}
|
|
284
|
-
}
|
|
285
313
|
|
|
286
|
-
|
|
287
|
-
|
|
314
|
+
if (validStyle) {
|
|
315
|
+
styles.set(styleSelector, cue);
|
|
316
|
+
}
|
|
288
317
|
}
|
|
289
318
|
}
|
|
290
319
|
|
|
@@ -311,8 +340,8 @@ shaka.text.VttTextParser = class {
|
|
|
311
340
|
return null;
|
|
312
341
|
}
|
|
313
342
|
|
|
314
|
-
// Skip style blocks.
|
|
315
|
-
if (text[0] == 'STYLE') {
|
|
343
|
+
// Skip style and region blocks.
|
|
344
|
+
if (text[0] == 'STYLE' || text[0] == 'REGION') {
|
|
316
345
|
return null;
|
|
317
346
|
}
|
|
318
347
|
|
|
@@ -386,6 +415,7 @@ shaka.text.VttTextParser = class {
|
|
|
386
415
|
VttTextParser.addDefaultTextColor_(styles);
|
|
387
416
|
}
|
|
388
417
|
payload = VttTextParser.replaceColorPayload_(payload);
|
|
418
|
+
payload = VttTextParser.replaceVoiceStylePayload_(payload);
|
|
389
419
|
const xmlPayload = '<span>' + payload + '</span>';
|
|
390
420
|
const element = shaka.util.XmlUtils.parseXmlString(xmlPayload, 'span');
|
|
391
421
|
if (element) {
|
|
@@ -396,7 +426,7 @@ shaka.text.VttTextParser = class {
|
|
|
396
426
|
const childNode = childNodes[0];
|
|
397
427
|
if (childNode.nodeType == Node.TEXT_NODE ||
|
|
398
428
|
childNode.nodeType == Node.CDATA_SECTION_NODE) {
|
|
399
|
-
rootCue.payload = payload;
|
|
429
|
+
rootCue.payload = VttTextParser.htmlUnescape_(payload);
|
|
400
430
|
return;
|
|
401
431
|
}
|
|
402
432
|
}
|
|
@@ -407,8 +437,81 @@ shaka.text.VttTextParser = class {
|
|
|
407
437
|
rootCue.nestedCues = cues;
|
|
408
438
|
} else {
|
|
409
439
|
shaka.log.warning('The cue\'s markup could not be parsed: ', payload);
|
|
410
|
-
rootCue.payload = payload;
|
|
440
|
+
rootCue.payload = VttTextParser.htmlUnescape_(payload);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Converts voice style tag to be valid for xml parsing
|
|
446
|
+
* For example,
|
|
447
|
+
* input: <v Shaka>Test
|
|
448
|
+
* output: <v.voice-Shaka>Test</v.voice-Shaka>
|
|
449
|
+
*
|
|
450
|
+
* @param {string} payload
|
|
451
|
+
* @return {string} processed payload
|
|
452
|
+
* @private
|
|
453
|
+
*/
|
|
454
|
+
static replaceVoiceStylePayload_(payload) {
|
|
455
|
+
const voiceTag = 'v';
|
|
456
|
+
const names = [];
|
|
457
|
+
let nameStart = -1;
|
|
458
|
+
let newPayload = '';
|
|
459
|
+
let hasVoiceEndTag = false;
|
|
460
|
+
for (let i = 0; i < payload.length; i++) {
|
|
461
|
+
// This condition is used to manage tags that have end tags.
|
|
462
|
+
if (payload[i] === '/') {
|
|
463
|
+
const end = payload.indexOf('>', i);
|
|
464
|
+
if (end === -1) {
|
|
465
|
+
return payload;
|
|
466
|
+
}
|
|
467
|
+
const tagEnd = payload.substring(i + 1, end);
|
|
468
|
+
if (!tagEnd || tagEnd != voiceTag) {
|
|
469
|
+
newPayload += payload[i];
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
hasVoiceEndTag = true;
|
|
473
|
+
let tagStart = null;
|
|
474
|
+
if (names.length) {
|
|
475
|
+
tagStart = names[names.length -1];
|
|
476
|
+
}
|
|
477
|
+
if (!tagStart) {
|
|
478
|
+
newPayload += payload[i];
|
|
479
|
+
} else if (tagStart === tagEnd) {
|
|
480
|
+
newPayload += '/' + tagEnd + '>';
|
|
481
|
+
i += tagEnd.length + 1;
|
|
482
|
+
} else {
|
|
483
|
+
if (!tagStart.startsWith(voiceTag)) {
|
|
484
|
+
newPayload += payload[i];
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
newPayload += '/' + tagStart + '>';
|
|
488
|
+
i += tagEnd.length + 1;
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
// Here we only want the tag name, not any other payload.
|
|
492
|
+
if (payload[i] === '<') {
|
|
493
|
+
nameStart = i + 1;
|
|
494
|
+
if (payload[nameStart] != voiceTag) {
|
|
495
|
+
nameStart = -1;
|
|
496
|
+
}
|
|
497
|
+
} else if (payload[i] === '>') {
|
|
498
|
+
if (nameStart > 0) {
|
|
499
|
+
names.push(payload.substr(nameStart, i - nameStart));
|
|
500
|
+
nameStart = -1;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
newPayload += payload[i];
|
|
504
|
+
}
|
|
411
505
|
}
|
|
506
|
+
for (const name of names) {
|
|
507
|
+
const newName = name.replace(' ', '.voice-');
|
|
508
|
+
newPayload = newPayload.replace(`<${name}>`, `<${newName}>`);
|
|
509
|
+
newPayload = newPayload.replace(`</${name}>`, `</${newName}>`);
|
|
510
|
+
if (!hasVoiceEndTag) {
|
|
511
|
+
newPayload += `</${newName}>`;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return newPayload;
|
|
412
515
|
}
|
|
413
516
|
|
|
414
517
|
/**
|
|
@@ -429,28 +532,36 @@ shaka.text.VttTextParser = class {
|
|
|
429
532
|
let nameStart = -1;
|
|
430
533
|
let newPayload = '';
|
|
431
534
|
for (let i = 0; i < payload.length; i++) {
|
|
432
|
-
if (payload[i] === '/') {
|
|
535
|
+
if (payload[i] === '/' && i > 0 && payload[i - 1] === '<') {
|
|
433
536
|
const end = payload.indexOf('>', i);
|
|
434
537
|
if (end <= i) {
|
|
435
538
|
return payload;
|
|
436
539
|
}
|
|
437
540
|
const tagEnd = payload.substring(i + 1, end);
|
|
541
|
+
if (!tagEnd || tagEnd !== 'c') {
|
|
542
|
+
newPayload += payload[i];
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
438
545
|
const tagStart = names.pop();
|
|
439
|
-
if (!
|
|
440
|
-
|
|
546
|
+
if (!tagStart) {
|
|
547
|
+
newPayload += payload[i];
|
|
441
548
|
} else if (tagStart === tagEnd) {
|
|
442
549
|
newPayload += '/' + tagEnd + '>';
|
|
443
550
|
i += tagEnd.length + 1;
|
|
444
551
|
} else {
|
|
445
|
-
if (!tagStart.startsWith('c.')
|
|
446
|
-
|
|
552
|
+
if (!tagStart.startsWith('c.')) {
|
|
553
|
+
newPayload += payload[i];
|
|
554
|
+
continue;
|
|
447
555
|
}
|
|
448
|
-
newPayload += '/' + tagStart + '>';
|
|
449
556
|
i += tagEnd.length + 1;
|
|
557
|
+
newPayload += '/' + tagStart + '>';
|
|
450
558
|
}
|
|
451
559
|
} else {
|
|
452
560
|
if (payload[i] === '<') {
|
|
453
561
|
nameStart = i + 1;
|
|
562
|
+
if (payload[nameStart] != 'c') {
|
|
563
|
+
nameStart = -1;
|
|
564
|
+
}
|
|
454
565
|
} else if (payload[i] === '>') {
|
|
455
566
|
if (nameStart > 0) {
|
|
456
567
|
names.push(payload.substr(nameStart, i - nameStart));
|
|
@@ -519,12 +630,29 @@ shaka.text.VttTextParser = class {
|
|
|
519
630
|
const bold = shaka.text.Cue.fontWeight.BOLD;
|
|
520
631
|
const italic = shaka.text.Cue.fontStyle.ITALIC;
|
|
521
632
|
const underline = shaka.text.Cue.textDecoration.UNDERLINE;
|
|
522
|
-
const tags = element.nodeName.split(/[ .]+/);
|
|
633
|
+
const tags = element.nodeName.split(/(?=[ .])+/g);
|
|
523
634
|
for (const tag of tags) {
|
|
524
|
-
|
|
525
|
-
|
|
635
|
+
let styleTag = tag;
|
|
636
|
+
// White blanks at start indicate that the style is a voice
|
|
637
|
+
if (styleTag.startsWith('.voice-')) {
|
|
638
|
+
const voice = styleTag.split('-').pop();
|
|
639
|
+
styleTag = `v[voice="${voice}"]`;
|
|
640
|
+
// The specification allows to have quotes and not, so we check to
|
|
641
|
+
// see which one is being used.
|
|
642
|
+
if (!styles.has(styleTag)) {
|
|
643
|
+
styleTag = `v[voice=${voice}]`;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (styles.has(styleTag)) {
|
|
647
|
+
VttTextParser.mergeStyle_(nestedCue, styles.get(styleTag));
|
|
526
648
|
}
|
|
527
649
|
switch (tag) {
|
|
650
|
+
case 'br': {
|
|
651
|
+
const lineBreakCue = rootCue.clone();
|
|
652
|
+
lineBreakCue.lineBreak = true;
|
|
653
|
+
cues.push(lineBreakCue);
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
528
656
|
case 'b':
|
|
529
657
|
nestedCue.fontWeight = bold;
|
|
530
658
|
break;
|
|
@@ -553,7 +681,7 @@ shaka.text.VttTextParser = class {
|
|
|
553
681
|
}
|
|
554
682
|
if (text.length > 0) {
|
|
555
683
|
const textCue = nestedCue.clone();
|
|
556
|
-
textCue.payload = text;
|
|
684
|
+
textCue.payload = VttTextParser.htmlUnescape_(text);
|
|
557
685
|
cues.push(textCue);
|
|
558
686
|
}
|
|
559
687
|
isFirst = false;
|
|
@@ -769,6 +897,42 @@ shaka.text.VttTextParser = class {
|
|
|
769
897
|
|
|
770
898
|
return (milliseconds / 1000) + seconds + (minutes * 60) + (hours * 3600);
|
|
771
899
|
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* This method converts the HTML entities &, <, >, ", ',
|
|
903
|
+
* , ‎ and ‏ in string to their corresponding characters.
|
|
904
|
+
*
|
|
905
|
+
* @param {!string} input
|
|
906
|
+
* @return {string}
|
|
907
|
+
* @private
|
|
908
|
+
*/
|
|
909
|
+
static htmlUnescape_(input) {
|
|
910
|
+
// Used to map HTML entities to characters.
|
|
911
|
+
const htmlUnescapes = {
|
|
912
|
+
'&': '&',
|
|
913
|
+
'<': '<',
|
|
914
|
+
'>': '>',
|
|
915
|
+
'"': '"',
|
|
916
|
+
''': '\'',
|
|
917
|
+
' ': '\u{a0}',
|
|
918
|
+
'‎': '\u{200e}',
|
|
919
|
+
'‏': '\u{200f}',
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
// Used to match HTML entities and HTML characters.
|
|
923
|
+
const reEscapedHtml = /&(?:amp|lt|gt|quot|#(0+)?39|nbsp|lrm|rlm);/g;
|
|
924
|
+
const reHasEscapedHtml = RegExp(reEscapedHtml.source);
|
|
925
|
+
// This check is an optimization, since replace always makes a copy
|
|
926
|
+
if (input && reHasEscapedHtml.test(input)) {
|
|
927
|
+
return input.replace(reEscapedHtml, (entity) => {
|
|
928
|
+
// The only thing that might not match the dictionary above is the
|
|
929
|
+
// single quote, which can be matched by many strings in the regex, but
|
|
930
|
+
// only has a single entry in the dictionary.
|
|
931
|
+
return htmlUnescapes[entity] || '\'';
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
return input || '';
|
|
935
|
+
}
|
|
772
936
|
};
|
|
773
937
|
|
|
774
938
|
/**
|
package/lib/util/mime_utils.js
CHANGED
|
@@ -50,7 +50,7 @@ shaka.util.MimeUtils = class {
|
|
|
50
50
|
return shaka.media.Transmuxer.convertTsCodecs(
|
|
51
51
|
contentType, fullMimeType);
|
|
52
52
|
}
|
|
53
|
-
} else if (contentType == ContentType.AUDIO) {
|
|
53
|
+
} else if (mimeType != 'video/mp2t' && contentType == ContentType.AUDIO) {
|
|
54
54
|
// video/mp2t is the correct mime type for TS audio, so only replace the
|
|
55
55
|
// word "video" with "audio" for non-TS audio content.
|
|
56
56
|
return fullMimeType.replace('video', 'audio');
|
package/lib/util/platform.js
CHANGED
|
@@ -248,6 +248,14 @@ shaka.util.Platform = class {
|
|
|
248
248
|
return shaka.util.Platform.userAgentContains_('PlayStation 4');
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Check if the current platform is Hisense.
|
|
253
|
+
*/
|
|
254
|
+
static isHisense() {
|
|
255
|
+
return shaka.util.Platform.userAgentContains_('Hisense') ||
|
|
256
|
+
shaka.util.Platform.userAgentContains_('VIDAA');
|
|
257
|
+
}
|
|
258
|
+
|
|
251
259
|
/**
|
|
252
260
|
* Check if the current platform is Virgin Media device.
|
|
253
261
|
*/
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -703,26 +703,38 @@ shaka.util.StreamUtils = class {
|
|
|
703
703
|
}
|
|
704
704
|
|
|
705
705
|
if (audio) {
|
|
706
|
-
|
|
706
|
+
// See: https://github.com/shaka-project/shaka-player/issues/4659
|
|
707
|
+
if (info.audioRobustness != '') {
|
|
708
|
+
if (!keySystemConfig.audio) {
|
|
709
|
+
// KeySystemTrackConfiguration
|
|
710
|
+
keySystemConfig.audio = {
|
|
711
|
+
robustness: info.audioRobustness,
|
|
712
|
+
};
|
|
713
|
+
} else {
|
|
714
|
+
keySystemConfig.audio.robustness =
|
|
715
|
+
keySystemConfig.audio.robustness || info.audioRobustness;
|
|
716
|
+
}
|
|
717
|
+
} else if (!keySystemConfig.audio) {
|
|
707
718
|
// KeySystemTrackConfiguration
|
|
708
|
-
keySystemConfig.audio = {
|
|
709
|
-
robustness: info.audioRobustness,
|
|
710
|
-
};
|
|
711
|
-
} else {
|
|
712
|
-
keySystemConfig.audio.robustness =
|
|
713
|
-
keySystemConfig.audio.robustness || info.audioRobustness;
|
|
719
|
+
keySystemConfig.audio = {};
|
|
714
720
|
}
|
|
715
721
|
}
|
|
716
722
|
|
|
717
723
|
if (video) {
|
|
718
|
-
|
|
724
|
+
// See: https://github.com/shaka-project/shaka-player/issues/4659
|
|
725
|
+
if (info.videoRobustness != '') {
|
|
726
|
+
if (!keySystemConfig.video) {
|
|
727
|
+
// KeySystemTrackConfiguration
|
|
728
|
+
keySystemConfig.video = {
|
|
729
|
+
robustness: info.videoRobustness,
|
|
730
|
+
};
|
|
731
|
+
} else {
|
|
732
|
+
keySystemConfig.video.robustness =
|
|
733
|
+
keySystemConfig.video.robustness || info.videoRobustness;
|
|
734
|
+
}
|
|
735
|
+
} else if (!keySystemConfig.video) {
|
|
719
736
|
// KeySystemTrackConfiguration
|
|
720
|
-
keySystemConfig.video = {
|
|
721
|
-
robustness: info.videoRobustness,
|
|
722
|
-
};
|
|
723
|
-
} else {
|
|
724
|
-
keySystemConfig.video.robustness =
|
|
725
|
-
keySystemConfig.video.robustness || info.videoRobustness;
|
|
737
|
+
keySystemConfig.video = {};
|
|
726
738
|
}
|
|
727
739
|
}
|
|
728
740
|
}
|
package/package.json
CHANGED
package/support.html
CHANGED
|
@@ -194,42 +194,6 @@ describe('DashParser Manifest', () => {
|
|
|
194
194
|
}));
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
-
it('rejects periods after one without duration', async () => {
|
|
198
|
-
const periodContents = [
|
|
199
|
-
' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
|
|
200
|
-
' <Representation bandwidth="100">',
|
|
201
|
-
' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
|
|
202
|
-
' <SegmentTimeline>',
|
|
203
|
-
' <S t="0" d="10" />',
|
|
204
|
-
' </SegmentTimeline>',
|
|
205
|
-
' </SegmentTemplate>',
|
|
206
|
-
' </Representation>',
|
|
207
|
-
' </AdaptationSet>',
|
|
208
|
-
].join('\n');
|
|
209
|
-
const template = [
|
|
210
|
-
'<MPD mediaPresentationDuration="PT75S">',
|
|
211
|
-
' <Period id="1">',
|
|
212
|
-
'%(periodContents)s',
|
|
213
|
-
' </Period>',
|
|
214
|
-
' <Period id="2">',
|
|
215
|
-
'%(periodContents)s',
|
|
216
|
-
' </Period>',
|
|
217
|
-
'</MPD>',
|
|
218
|
-
].join('\n');
|
|
219
|
-
const source = sprintf(template, {periodContents: periodContents});
|
|
220
|
-
|
|
221
|
-
fakeNetEngine.setResponseText('dummy://foo', source);
|
|
222
|
-
/** @type {shaka.extern.Manifest} */
|
|
223
|
-
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
224
|
-
const video = manifest.variants[0].video;
|
|
225
|
-
await video.createSegmentIndex();
|
|
226
|
-
|
|
227
|
-
// The first period has a segment from 0-10.
|
|
228
|
-
// With the second period skipping, we should fail to find a segment at 10.
|
|
229
|
-
expect(video.segmentIndex.find(0)).not.toBe(null);
|
|
230
|
-
expect(video.segmentIndex.find(10)).toBe(null);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
197
|
it('calculates Period times when missing', async () => {
|
|
234
198
|
const periodContents = [
|
|
235
199
|
' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
describe('MediaCapabilities', () => {
|
|
8
|
-
const Util = shaka.test.Util;
|
|
9
8
|
const originalCast = window['cast'];
|
|
10
9
|
const originalVendor = navigator.vendor;
|
|
11
10
|
const originalUserAgent = navigator.userAgent;
|
|
@@ -168,30 +167,29 @@ describe('MediaCapabilities', () => {
|
|
|
168
167
|
expect(result.keySystemAccess).toEqual(mockResult);
|
|
169
168
|
});
|
|
170
169
|
|
|
171
|
-
it('
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
it('falls back to isTypeSupported() when cast namespace is not available',
|
|
171
|
+
async () => {
|
|
172
|
+
// Temporarily remove window.cast to trigger error. It's restored
|
|
173
|
+
// after every test.
|
|
174
|
+
delete window['cast'];
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
shaka.util.Error.Code.CAST_API_UNAVAILABLE));
|
|
183
|
-
const isTypeSupportedSpy =
|
|
184
|
-
spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
|
|
176
|
+
const isChromecastSpy =
|
|
177
|
+
spyOn(shaka['util']['Platform'],
|
|
178
|
+
'isChromecast').and.returnValue(true);
|
|
179
|
+
const isTypeSupportedSpy =
|
|
180
|
+
spyOn(window['MediaSource'], 'isTypeSupported')
|
|
181
|
+
.and.returnValue(true);
|
|
185
182
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
navigator.mediaCapabilities.decodingInfo(mockDecodingConfig))
|
|
189
|
-
.toBeRejectedWith(expected);
|
|
183
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
184
|
+
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
186
|
+
expect(mockCanDisplayType).not.toHaveBeenCalled();
|
|
187
|
+
// 1 (during install()) + 1 (for video config check).
|
|
188
|
+
expect(isChromecastSpy).toHaveBeenCalledTimes(2);
|
|
189
|
+
// 1 (fallback in canCastDisplayType()) +
|
|
190
|
+
// 1 (mockDecodingConfig.audio).
|
|
191
|
+
expect(isTypeSupportedSpy).toHaveBeenCalledTimes(2);
|
|
192
|
+
});
|
|
195
193
|
|
|
196
194
|
it('falls back to isTypeSupported() when canDisplayType() missing',
|
|
197
195
|
async () => {
|