shaka-player 4.3.2 → 4.3.4
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 +22 -0
- package/CHANGELOG.md +37 -0
- package/dist/deps.js +4 -3
- package/dist/locales.js +33 -33
- package/dist/shaka-player.compiled.d.ts +10 -0
- package/dist/shaka-player.compiled.debug.d.ts +10 -0
- package/dist/shaka-player.compiled.debug.externs.js +12 -0
- package/dist/shaka-player.compiled.debug.js +152 -144
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +12 -0
- package/dist/shaka-player.compiled.js +778 -772
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +10 -0
- package/dist/shaka-player.ui.debug.d.ts +10 -0
- package/dist/shaka-player.ui.debug.externs.js +12 -0
- package/dist/shaka-player.ui.debug.js +164 -157
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +12 -0
- package/dist/shaka-player.ui.js +851 -846
- package/dist/shaka-player.ui.map +4 -4
- package/lib/dash/segment_template.js +17 -5
- package/lib/hls/hls_parser.js +45 -16
- package/lib/media/media_source_engine.js +34 -4
- package/lib/media/segment_index.js +5 -10
- package/lib/media/segment_reference.js +39 -0
- package/lib/media/streaming_engine.js +15 -3
- package/lib/player.js +7 -1
- package/lib/polyfill/media_capabilities.js +4 -1
- package/lib/text/simple_text_displayer.js +2 -64
- package/lib/text/text_utils.js +88 -0
- package/lib/text/ui_text_displayer.js +24 -9
- package/lib/text/vtt_text_parser.js +224 -97
- package/lib/text/web_vtt_generator.js +2 -52
- package/lib/util/dom_utils.js +3 -1
- package/lib/util/platform.js +8 -0
- package/lib/util/stream_utils.js +6 -0
- package/lib/util/timer.js +17 -0
- package/package.json +1 -1
- package/support.html +1 -0
- package/test/dash/dash_parser_manifest_unit.js +0 -36
- package/test/hls/hls_live_unit.js +8 -30
- package/test/hls/hls_parser_unit.js +30 -0
- package/test/media/segment_index_unit.js +28 -0
- package/test/media/streaming_engine_unit.js +91 -119
- package/test/player_integration.js +27 -0
- package/test/player_unit.js +4 -2
- 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 +72 -4
- package/test/text/vtt_text_parser_unit.js +114 -8
- package/test/text/web_vtt_layout_integration.js +528 -0
|
@@ -154,14 +154,14 @@ shaka.text.VttTextParser = class {
|
|
|
154
154
|
for (const [key, value] of Object.entries(textColor)) {
|
|
155
155
|
const cue = new shaka.text.Cue(0, 0, '');
|
|
156
156
|
cue.color = value;
|
|
157
|
-
styles.set(key, cue);
|
|
157
|
+
styles.set('.' + key, cue);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const bgColor = shaka.text.Cue.defaultTextBackgroundColor;
|
|
161
161
|
for (const [key, value] of Object.entries(bgColor)) {
|
|
162
162
|
const cue = new shaka.text.Cue(0, 0, '');
|
|
163
163
|
cue.backgroundColor = value;
|
|
164
|
-
styles.set(key, cue);
|
|
164
|
+
styles.set('.' + key, cue);
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -221,96 +221,121 @@ shaka.text.VttTextParser = class {
|
|
|
221
221
|
return;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
let
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
/** @type {!Array.<!Array.<string>>} */
|
|
225
|
+
const styleBlocks = [];
|
|
226
|
+
let lastBlockIndex = -1;
|
|
227
|
+
for (let i = 1; i < text.length; i++) {
|
|
228
|
+
if (text[i].includes('::cue')) {
|
|
229
|
+
styleBlocks.push([]);
|
|
230
|
+
lastBlockIndex = styleBlocks.length - 1;
|
|
231
|
+
}
|
|
232
|
+
if (lastBlockIndex == -1) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
styleBlocks[lastBlockIndex].push(text[i]);
|
|
236
|
+
if (text[i].includes('}')) {
|
|
237
|
+
lastBlockIndex = -1;
|
|
238
|
+
}
|
|
233
239
|
}
|
|
234
240
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
+
for (const styleBlock of styleBlocks) {
|
|
242
|
+
let styleSelector = 'global';
|
|
243
|
+
// Look for what is within parentheses. For example:
|
|
244
|
+
// <code>:: cue (b) {</code>, what we are looking for is <code>b</code>
|
|
245
|
+
const selector = styleBlock[0].match(/\((.*)\)/);
|
|
246
|
+
if (selector) {
|
|
247
|
+
styleSelector = selector.pop();
|
|
241
248
|
}
|
|
242
|
-
}
|
|
243
249
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
250
|
+
// We start at 1 to avoid '::cue' and end earlier to avoid '}'
|
|
251
|
+
let propertyLines = styleBlock.slice(1, -1);
|
|
252
|
+
if (styleBlock[0].includes('}')) {
|
|
253
|
+
const payload = /\{(.*?)\}/.exec(styleBlock[0]);
|
|
254
|
+
if (payload) {
|
|
255
|
+
propertyLines = payload[1].split(';');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Continue styles over multiple selectors if necessary.
|
|
260
|
+
// For example,
|
|
261
|
+
// ::cue(b) { background: white; } ::cue(b) { color: blue; }
|
|
262
|
+
// should set both the background and foreground of bold tags.
|
|
263
|
+
let cue = styles.get(styleSelector);
|
|
264
|
+
if (!cue) {
|
|
265
|
+
cue = new shaka.text.Cue(0, 0, '');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let validStyle = false;
|
|
269
|
+
for (let i = 0; i < propertyLines.length; i++) {
|
|
270
|
+
// We look for CSS properties. As a general rule they are separated by
|
|
271
|
+
// <code>:</code>. Eg: <code>color: red;</code>
|
|
272
|
+
const lineParts = /^\s*([^:]+):\s*(.*)/.exec(propertyLines[i]);
|
|
273
|
+
if (lineParts) {
|
|
274
|
+
const name = lineParts[1].trim();
|
|
275
|
+
const value = lineParts[2].trim().replace(';', '');
|
|
276
|
+
switch (name) {
|
|
277
|
+
case 'background-color':
|
|
278
|
+
case 'background':
|
|
272
279
|
validStyle = true;
|
|
273
|
-
cue.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
280
|
+
cue.backgroundColor = value;
|
|
281
|
+
break;
|
|
282
|
+
case 'color':
|
|
283
|
+
validStyle = true;
|
|
284
|
+
cue.color = value;
|
|
285
|
+
break;
|
|
286
|
+
case 'font-family':
|
|
287
|
+
validStyle = true;
|
|
288
|
+
cue.fontFamily = value;
|
|
289
|
+
break;
|
|
290
|
+
case 'font-size':
|
|
291
|
+
validStyle = true;
|
|
292
|
+
cue.fontSize = value;
|
|
293
|
+
break;
|
|
294
|
+
case 'font-weight':
|
|
295
|
+
if (parseInt(value, 10) >= 700 || value == 'bold') {
|
|
287
296
|
validStyle = true;
|
|
288
|
-
cue.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
297
|
+
cue.fontWeight = shaka.text.Cue.fontWeight.BOLD;
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
case 'font-style':
|
|
301
|
+
switch (value) {
|
|
302
|
+
case 'normal':
|
|
303
|
+
validStyle = true;
|
|
304
|
+
cue.fontStyle = shaka.text.Cue.fontStyle.NORMAL;
|
|
305
|
+
break;
|
|
306
|
+
case 'italic':
|
|
307
|
+
validStyle = true;
|
|
308
|
+
cue.fontStyle = shaka.text.Cue.fontStyle.ITALIC;
|
|
309
|
+
break;
|
|
310
|
+
case 'oblique':
|
|
311
|
+
validStyle = true;
|
|
312
|
+
cue.fontStyle = shaka.text.Cue.fontStyle.OBLIQUE;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
case 'opacity':
|
|
317
|
+
validStyle = true;
|
|
318
|
+
cue.opacity = parseFloat(value);
|
|
319
|
+
break;
|
|
320
|
+
case 'text-shadow':
|
|
321
|
+
validStyle = true;
|
|
322
|
+
cue.textShadow = value;
|
|
323
|
+
break;
|
|
324
|
+
case 'white-space':
|
|
325
|
+
validStyle = true;
|
|
326
|
+
cue.wrapLine = value != 'noWrap';
|
|
327
|
+
break;
|
|
328
|
+
default:
|
|
329
|
+
shaka.log.warning('VTT parser encountered an unsupported style: ',
|
|
330
|
+
lineParts);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
308
333
|
}
|
|
309
334
|
}
|
|
310
|
-
}
|
|
311
335
|
|
|
312
|
-
|
|
313
|
-
|
|
336
|
+
if (validStyle) {
|
|
337
|
+
styles.set(styleSelector, cue);
|
|
338
|
+
}
|
|
314
339
|
}
|
|
315
340
|
}
|
|
316
341
|
|
|
@@ -337,8 +362,8 @@ shaka.text.VttTextParser = class {
|
|
|
337
362
|
return null;
|
|
338
363
|
}
|
|
339
364
|
|
|
340
|
-
// Skip style blocks.
|
|
341
|
-
if (text[0] == 'STYLE') {
|
|
365
|
+
// Skip style and region blocks.
|
|
366
|
+
if (text[0] == 'STYLE' || text[0] == 'REGION') {
|
|
342
367
|
return null;
|
|
343
368
|
}
|
|
344
369
|
|
|
@@ -414,6 +439,7 @@ shaka.text.VttTextParser = class {
|
|
|
414
439
|
}
|
|
415
440
|
payload = VttTextParser.replaceColorPayload_(payload);
|
|
416
441
|
payload = VttTextParser.replaceKaraokeStylePayload_(payload);
|
|
442
|
+
payload = VttTextParser.replaceVoiceStylePayload_(payload);
|
|
417
443
|
const xmlPayload = '<span>' + payload + '</span>';
|
|
418
444
|
const element = shaka.util.XmlUtils.parseXmlString(xmlPayload, 'span');
|
|
419
445
|
if (element) {
|
|
@@ -439,6 +465,79 @@ shaka.text.VttTextParser = class {
|
|
|
439
465
|
}
|
|
440
466
|
}
|
|
441
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Converts voice style tag to be valid for xml parsing
|
|
470
|
+
* For example,
|
|
471
|
+
* input: <v Shaka>Test
|
|
472
|
+
* output: <v.voice-Shaka>Test</v.voice-Shaka>
|
|
473
|
+
*
|
|
474
|
+
* @param {string} payload
|
|
475
|
+
* @return {string} processed payload
|
|
476
|
+
* @private
|
|
477
|
+
*/
|
|
478
|
+
static replaceVoiceStylePayload_(payload) {
|
|
479
|
+
const voiceTag = 'v';
|
|
480
|
+
const names = [];
|
|
481
|
+
let nameStart = -1;
|
|
482
|
+
let newPayload = '';
|
|
483
|
+
let hasVoiceEndTag = false;
|
|
484
|
+
for (let i = 0; i < payload.length; i++) {
|
|
485
|
+
// This condition is used to manage tags that have end tags.
|
|
486
|
+
if (payload[i] === '/') {
|
|
487
|
+
const end = payload.indexOf('>', i);
|
|
488
|
+
if (end === -1) {
|
|
489
|
+
return payload;
|
|
490
|
+
}
|
|
491
|
+
const tagEnd = payload.substring(i + 1, end);
|
|
492
|
+
if (!tagEnd || tagEnd != voiceTag) {
|
|
493
|
+
newPayload += payload[i];
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
hasVoiceEndTag = true;
|
|
497
|
+
let tagStart = null;
|
|
498
|
+
if (names.length) {
|
|
499
|
+
tagStart = names[names.length -1];
|
|
500
|
+
}
|
|
501
|
+
if (!tagStart) {
|
|
502
|
+
newPayload += payload[i];
|
|
503
|
+
} else if (tagStart === tagEnd) {
|
|
504
|
+
newPayload += '/' + tagEnd + '>';
|
|
505
|
+
i += tagEnd.length + 1;
|
|
506
|
+
} else {
|
|
507
|
+
if (!tagStart.startsWith(voiceTag)) {
|
|
508
|
+
newPayload += payload[i];
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
newPayload += '/' + tagStart + '>';
|
|
512
|
+
i += tagEnd.length + 1;
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
// Here we only want the tag name, not any other payload.
|
|
516
|
+
if (payload[i] === '<') {
|
|
517
|
+
nameStart = i + 1;
|
|
518
|
+
if (payload[nameStart] != voiceTag) {
|
|
519
|
+
nameStart = -1;
|
|
520
|
+
}
|
|
521
|
+
} else if (payload[i] === '>') {
|
|
522
|
+
if (nameStart > 0) {
|
|
523
|
+
names.push(payload.substr(nameStart, i - nameStart));
|
|
524
|
+
nameStart = -1;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
newPayload += payload[i];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
for (const name of names) {
|
|
531
|
+
const newName = name.replace(' ', '.voice-');
|
|
532
|
+
newPayload = newPayload.replace(`<${name}>`, `<${newName}>`);
|
|
533
|
+
newPayload = newPayload.replace(`</${name}>`, `</${newName}>`);
|
|
534
|
+
if (!hasVoiceEndTag) {
|
|
535
|
+
newPayload += `</${newName}>`;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return newPayload;
|
|
539
|
+
}
|
|
540
|
+
|
|
442
541
|
/**
|
|
443
542
|
* Converts karaoke style tag to be valid for xml parsing
|
|
444
543
|
* For example,
|
|
@@ -495,28 +594,36 @@ shaka.text.VttTextParser = class {
|
|
|
495
594
|
let nameStart = -1;
|
|
496
595
|
let newPayload = '';
|
|
497
596
|
for (let i = 0; i < payload.length; i++) {
|
|
498
|
-
if (payload[i] === '/') {
|
|
597
|
+
if (payload[i] === '/' && i > 0 && payload[i - 1] === '<') {
|
|
499
598
|
const end = payload.indexOf('>', i);
|
|
500
599
|
if (end <= i) {
|
|
501
600
|
return payload;
|
|
502
601
|
}
|
|
503
602
|
const tagEnd = payload.substring(i + 1, end);
|
|
603
|
+
if (!tagEnd || tagEnd !== 'c') {
|
|
604
|
+
newPayload += payload[i];
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
504
607
|
const tagStart = names.pop();
|
|
505
|
-
if (!
|
|
506
|
-
|
|
608
|
+
if (!tagStart) {
|
|
609
|
+
newPayload += payload[i];
|
|
507
610
|
} else if (tagStart === tagEnd) {
|
|
508
611
|
newPayload += '/' + tagEnd + '>';
|
|
509
612
|
i += tagEnd.length + 1;
|
|
510
613
|
} else {
|
|
511
|
-
if (!tagStart.startsWith('c.')
|
|
512
|
-
|
|
614
|
+
if (!tagStart.startsWith('c.')) {
|
|
615
|
+
newPayload += payload[i];
|
|
616
|
+
continue;
|
|
513
617
|
}
|
|
514
|
-
newPayload += '/' + tagStart + '>';
|
|
515
618
|
i += tagEnd.length + 1;
|
|
619
|
+
newPayload += '/' + tagStart + '>';
|
|
516
620
|
}
|
|
517
621
|
} else {
|
|
518
622
|
if (payload[i] === '<') {
|
|
519
623
|
nameStart = i + 1;
|
|
624
|
+
if (payload[nameStart] != 'c') {
|
|
625
|
+
nameStart = -1;
|
|
626
|
+
}
|
|
520
627
|
} else if (payload[i] === '>') {
|
|
521
628
|
if (nameStart > 0) {
|
|
522
629
|
names.push(payload.substr(nameStart, i - nameStart));
|
|
@@ -585,12 +692,29 @@ shaka.text.VttTextParser = class {
|
|
|
585
692
|
const bold = shaka.text.Cue.fontWeight.BOLD;
|
|
586
693
|
const italic = shaka.text.Cue.fontStyle.ITALIC;
|
|
587
694
|
const underline = shaka.text.Cue.textDecoration.UNDERLINE;
|
|
588
|
-
const tags = element.nodeName.split(/[ .]+/);
|
|
695
|
+
const tags = element.nodeName.split(/(?=[ .])+/g);
|
|
589
696
|
for (const tag of tags) {
|
|
590
|
-
|
|
591
|
-
|
|
697
|
+
let styleTag = tag;
|
|
698
|
+
// White blanks at start indicate that the style is a voice
|
|
699
|
+
if (styleTag.startsWith('.voice-')) {
|
|
700
|
+
const voice = styleTag.split('-').pop();
|
|
701
|
+
styleTag = `v[voice="${voice}"]`;
|
|
702
|
+
// The specification allows to have quotes and not, so we check to
|
|
703
|
+
// see which one is being used.
|
|
704
|
+
if (!styles.has(styleTag)) {
|
|
705
|
+
styleTag = `v[voice=${voice}]`;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (styles.has(styleTag)) {
|
|
709
|
+
VttTextParser.mergeStyle_(nestedCue, styles.get(styleTag));
|
|
592
710
|
}
|
|
593
711
|
switch (tag) {
|
|
712
|
+
case 'br': {
|
|
713
|
+
const lineBreakCue = rootCue.clone();
|
|
714
|
+
lineBreakCue.lineBreak = true;
|
|
715
|
+
cues.push(lineBreakCue);
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
594
718
|
case 'b':
|
|
595
719
|
nestedCue.fontWeight = bold;
|
|
596
720
|
break;
|
|
@@ -846,8 +970,8 @@ shaka.text.VttTextParser = class {
|
|
|
846
970
|
}
|
|
847
971
|
|
|
848
972
|
/**
|
|
849
|
-
* This method converts the HTML entities &, <, >, ",
|
|
850
|
-
*
|
|
973
|
+
* This method converts the HTML entities &, <, >, ", ',
|
|
974
|
+
* , ‎ and ‏ in string to their corresponding characters.
|
|
851
975
|
*
|
|
852
976
|
* @param {!string} input
|
|
853
977
|
* @return {string}
|
|
@@ -861,10 +985,13 @@ shaka.text.VttTextParser = class {
|
|
|
861
985
|
'>': '>',
|
|
862
986
|
'"': '"',
|
|
863
987
|
''': '\'',
|
|
988
|
+
' ': '\u{a0}',
|
|
989
|
+
'‎': '\u{200e}',
|
|
990
|
+
'‏': '\u{200f}',
|
|
864
991
|
};
|
|
865
992
|
|
|
866
993
|
// Used to match HTML entities and HTML characters.
|
|
867
|
-
const reEscapedHtml = /&(?:amp|lt|gt|quot|#(0+)?39);/g;
|
|
994
|
+
const reEscapedHtml = /&(?:amp|lt|gt|quot|#(0+)?39|nbsp|lrm|rlm);/g;
|
|
868
995
|
const reHasEscapedHtml = RegExp(reEscapedHtml.source);
|
|
869
996
|
// This check is an optimization, since replace always makes a copy
|
|
870
997
|
if (input && reHasEscapedHtml.test(input)) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
goog.provide('shaka.text.WebVttGenerator');
|
|
8
8
|
|
|
9
9
|
goog.require('shaka.text.Cue');
|
|
10
|
+
goog.require('shaka.text.Utils');
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -20,45 +21,6 @@ shaka.text.WebVttGenerator = class {
|
|
|
20
21
|
* @return {string}
|
|
21
22
|
*/
|
|
22
23
|
static convert(cues, adCuePoints) {
|
|
23
|
-
// Flatten nested cue payloads recursively. If a cue has nested cues,
|
|
24
|
-
// their contents should be combined and replace the payload of the parent.
|
|
25
|
-
const flattenPayload = (cue) => {
|
|
26
|
-
// Handle styles (currently bold/italics/underline).
|
|
27
|
-
// TODO: add support for color rendering.
|
|
28
|
-
const openStyleTags = [];
|
|
29
|
-
const bold = cue.fontWeight >= shaka.text.Cue.fontWeight.BOLD;
|
|
30
|
-
const italics = cue.fontStyle == shaka.text.Cue.fontStyle.ITALIC;
|
|
31
|
-
const underline = cue.textDecoration.includes(
|
|
32
|
-
shaka.text.Cue.textDecoration.UNDERLINE);
|
|
33
|
-
if (bold) {
|
|
34
|
-
openStyleTags.push('b');
|
|
35
|
-
}
|
|
36
|
-
if (italics) {
|
|
37
|
-
openStyleTags.push('i');
|
|
38
|
-
}
|
|
39
|
-
if (underline) {
|
|
40
|
-
openStyleTags.push('u');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Prefix opens tags, suffix closes tags in reverse order of opening.
|
|
44
|
-
const prefixStyleTags = openStyleTags.reduce((acc, tag) => {
|
|
45
|
-
return `${acc}<${tag}>`;
|
|
46
|
-
}, '');
|
|
47
|
-
const suffixStyleTags = openStyleTags.reduceRight((acc, tag) => {
|
|
48
|
-
return `${acc}</${tag}>`;
|
|
49
|
-
}, '');
|
|
50
|
-
|
|
51
|
-
if (cue.lineBreak) {
|
|
52
|
-
// This is a vertical lineBreak, so insert a newline.
|
|
53
|
-
return '\n';
|
|
54
|
-
} else if (cue.nestedCues.length) {
|
|
55
|
-
return cue.nestedCues.map(flattenPayload).join('');
|
|
56
|
-
} else {
|
|
57
|
-
// This is a real cue.
|
|
58
|
-
return prefixStyleTags + cue.payload + suffixStyleTags;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
24
|
const webvttTimeString = (time) => {
|
|
63
25
|
let newTime = time;
|
|
64
26
|
for (const adCuePoint of adCuePoints) {
|
|
@@ -78,19 +40,7 @@ shaka.text.WebVttGenerator = class {
|
|
|
78
40
|
milliseconds;
|
|
79
41
|
};
|
|
80
42
|
|
|
81
|
-
|
|
82
|
-
// technically own them. So we build a new array and replace certain items
|
|
83
|
-
// in it if they need to be flattened.
|
|
84
|
-
const flattenedCues = cues.map((cue) => {
|
|
85
|
-
if (cue.nestedCues.length) {
|
|
86
|
-
const flatCue = cue.clone();
|
|
87
|
-
flatCue.nestedCues = [];
|
|
88
|
-
flatCue.payload = flattenPayload(cue);
|
|
89
|
-
return flatCue;
|
|
90
|
-
} else {
|
|
91
|
-
return cue;
|
|
92
|
-
}
|
|
93
|
-
});
|
|
43
|
+
const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues, []);
|
|
94
44
|
|
|
95
45
|
let webvttString = 'WEBVTT\n\n';
|
|
96
46
|
for (const cue of flattenedCues) {
|
package/lib/util/dom_utils.js
CHANGED
|
@@ -36,7 +36,9 @@ shaka.util.Dom = class {
|
|
|
36
36
|
* @return {!HTMLButtonElement}
|
|
37
37
|
*/
|
|
38
38
|
static createButton() {
|
|
39
|
-
|
|
39
|
+
const button = document.createElement('button');
|
|
40
|
+
button.setAttribute('type', 'button');
|
|
41
|
+
return /** @type {!HTMLButtonElement} */ (button);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
|
package/lib/util/platform.js
CHANGED
|
@@ -238,6 +238,14 @@ shaka.util.Platform = class {
|
|
|
238
238
|
return shaka.util.Platform.userAgentContains_('PlayStation 4');
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Check if the current platform is Hisense.
|
|
243
|
+
*/
|
|
244
|
+
static isHisense() {
|
|
245
|
+
return shaka.util.Platform.userAgentContains_('Hisense') ||
|
|
246
|
+
shaka.util.Platform.userAgentContains_('VIDAA');
|
|
247
|
+
}
|
|
248
|
+
|
|
241
249
|
/**
|
|
242
250
|
* Check if the current platform is Virgin Media device.
|
|
243
251
|
*/
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -459,12 +459,16 @@ shaka.util.StreamUtils = class {
|
|
|
459
459
|
if (!Capabilities.isTypeSupported(audioFullType)) {
|
|
460
460
|
return false;
|
|
461
461
|
}
|
|
462
|
+
// Update the codec string with the (possibly) converted codecs.
|
|
463
|
+
videoCodecs = [videoCodecs, audioCodecs].join(',');
|
|
462
464
|
}
|
|
463
465
|
const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
464
466
|
video.mimeType, videoCodecs, ContentType.VIDEO);
|
|
465
467
|
if (!Capabilities.isTypeSupported(fullType)) {
|
|
466
468
|
return false;
|
|
467
469
|
}
|
|
470
|
+
// Update the codec string with the (possibly) converted codecs.
|
|
471
|
+
video.codecs = videoCodecs;
|
|
468
472
|
}
|
|
469
473
|
const audio = variant.audio;
|
|
470
474
|
if (audio) {
|
|
@@ -475,6 +479,8 @@ shaka.util.StreamUtils = class {
|
|
|
475
479
|
if (!Capabilities.isTypeSupported(fullType)) {
|
|
476
480
|
return false;
|
|
477
481
|
}
|
|
482
|
+
// Update the codec string with the (possibly) converted codecs.
|
|
483
|
+
audio.codecs = codecs;
|
|
478
484
|
}
|
|
479
485
|
|
|
480
486
|
// See: https://github.com/shaka-project/shaka-player/issues/3380
|
package/lib/util/timer.js
CHANGED
|
@@ -78,6 +78,11 @@ shaka.util.Timer = class {
|
|
|
78
78
|
tickEvery(seconds) {
|
|
79
79
|
this.stop();
|
|
80
80
|
|
|
81
|
+
if (goog.DEBUG) {
|
|
82
|
+
// Capture the stack trace by making a fake error.
|
|
83
|
+
const stackTrace = Error('Timer created').stack;
|
|
84
|
+
shaka.util.Timer.activeTimers.set(this, stackTrace);
|
|
85
|
+
}
|
|
81
86
|
this.ticker_ = new shaka.util.DelayedTick(() => {
|
|
82
87
|
// Schedule the timer again first. |onTick_| could cancel the timer and
|
|
83
88
|
// rescheduling first simplifies the implementation.
|
|
@@ -99,5 +104,17 @@ shaka.util.Timer = class {
|
|
|
99
104
|
this.ticker_.stop();
|
|
100
105
|
this.ticker_ = null;
|
|
101
106
|
}
|
|
107
|
+
if (goog.DEBUG) {
|
|
108
|
+
shaka.util.Timer.activeTimers.delete(this);
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
111
|
};
|
|
112
|
+
|
|
113
|
+
if (goog.DEBUG) {
|
|
114
|
+
/**
|
|
115
|
+
* Tracks all active timer instances, along with the stack trace that created
|
|
116
|
+
* that timer.
|
|
117
|
+
* @type {!Map.<!shaka.util.Timer, string>}
|
|
118
|
+
*/
|
|
119
|
+
shaka.util.Timer.activeTimers = new Map();
|
|
120
|
+
}
|
package/package.json
CHANGED
package/support.html
CHANGED
|
@@ -287,42 +287,6 @@ describe('DashParser Manifest', () => {
|
|
|
287
287
|
}));
|
|
288
288
|
});
|
|
289
289
|
|
|
290
|
-
it('rejects periods after one without duration', async () => {
|
|
291
|
-
const periodContents = [
|
|
292
|
-
' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
|
|
293
|
-
' <Representation bandwidth="100">',
|
|
294
|
-
' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
|
|
295
|
-
' <SegmentTimeline>',
|
|
296
|
-
' <S t="0" d="10" />',
|
|
297
|
-
' </SegmentTimeline>',
|
|
298
|
-
' </SegmentTemplate>',
|
|
299
|
-
' </Representation>',
|
|
300
|
-
' </AdaptationSet>',
|
|
301
|
-
].join('\n');
|
|
302
|
-
const template = [
|
|
303
|
-
'<MPD mediaPresentationDuration="PT75S">',
|
|
304
|
-
' <Period id="1">',
|
|
305
|
-
'%(periodContents)s',
|
|
306
|
-
' </Period>',
|
|
307
|
-
' <Period id="2">',
|
|
308
|
-
'%(periodContents)s',
|
|
309
|
-
' </Period>',
|
|
310
|
-
'</MPD>',
|
|
311
|
-
].join('\n');
|
|
312
|
-
const source = sprintf(template, {periodContents: periodContents});
|
|
313
|
-
|
|
314
|
-
fakeNetEngine.setResponseText('dummy://foo', source);
|
|
315
|
-
/** @type {shaka.extern.Manifest} */
|
|
316
|
-
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
317
|
-
const video = manifest.variants[0].video;
|
|
318
|
-
await video.createSegmentIndex();
|
|
319
|
-
|
|
320
|
-
// The first period has a segment from 0-10.
|
|
321
|
-
// With the second period skipping, we should fail to find a segment at 10.
|
|
322
|
-
expect(video.segmentIndex.find(0)).not.toBe(null);
|
|
323
|
-
expect(video.segmentIndex.find(10)).toBe(null);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
290
|
it('calculates Period times when missing', async () => {
|
|
327
291
|
const periodContents = [
|
|
328
292
|
' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
|