shaka-player 4.10.13 → 4.11.0
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/README.md +5 -1
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +11 -11
- package/dist/locales.js +38 -34
- package/dist/shaka-player.compiled.d.ts +68 -33
- package/dist/shaka-player.compiled.debug.d.ts +68 -33
- package/dist/shaka-player.compiled.debug.externs.js +584 -544
- package/dist/shaka-player.compiled.debug.js +715 -676
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +584 -544
- package/dist/shaka-player.compiled.js +1394 -1371
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +76 -34
- package/dist/shaka-player.ui.debug.d.ts +76 -34
- package/dist/shaka-player.ui.debug.externs.js +77 -26
- package/dist/shaka-player.ui.debug.js +774 -720
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +77 -26
- package/dist/shaka-player.ui.js +1649 -1617
- package/dist/shaka-player.ui.map +4 -4
- package/externs/mediasession.js +10 -1
- package/externs/shaka/ads.js +79 -2
- package/externs/shaka/manifest.js +6 -1
- package/externs/shaka/manifest_parser.js +6 -2
- package/externs/shaka/net.js +23 -2
- package/externs/shaka/player.js +414 -103
- package/externs/shaka/text.js +2 -1
- package/lib/abr/simple_abr_manager.js +5 -0
- package/lib/ads/ad_manager.js +45 -1
- package/lib/ads/ad_utils.js +124 -1
- package/lib/ads/interstitial_ad.js +9 -2
- package/lib/ads/interstitial_ad_manager.js +216 -101
- package/lib/ads/media_tailor_ad.js +1 -39
- package/lib/ads/media_tailor_ad_manager.js +32 -16
- package/lib/cea/cea608_data_channel.js +40 -8
- package/lib/cea/cea608_memory.js +28 -0
- package/lib/dash/dash_parser.js +162 -25
- package/lib/dash/segment_base.js +3 -2
- package/lib/dash/segment_list.js +6 -3
- package/lib/dash/segment_template.js +104 -20
- package/lib/hls/hls_parser.js +319 -215
- package/lib/media/drm_engine.js +11 -3
- package/lib/media/media_source_engine.js +0 -8
- package/lib/media/preload_manager.js +16 -4
- package/lib/media/segment_reference.js +3 -0
- package/lib/media/segment_utils.js +8 -1
- package/lib/media/streaming_engine.js +10 -6
- package/lib/mss/mss_parser.js +14 -2
- package/lib/net/http_fetch_plugin.js +12 -5
- package/lib/net/http_xhr_plugin.js +7 -3
- package/lib/net/networking_engine.js +21 -1
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +336 -121
- package/lib/text/cue.js +373 -0
- package/lib/text/mp4_ttml_parser.js +51 -1
- package/lib/text/mp4_vtt_parser.js +2 -5
- package/lib/text/sbv_text_parser.js +2 -31
- package/lib/text/srt_text_parser.js +1 -1
- package/lib/text/text_engine.js +1 -1
- package/lib/text/text_utils.js +1 -0
- package/lib/text/ttml_text_parser.js +14 -4
- package/lib/text/ui_text_displayer.js +0 -4
- package/lib/text/vtt_text_parser.js +10 -415
- package/lib/util/abortable_operation.js +8 -0
- package/lib/util/cmcd_manager.js +38 -0
- package/lib/util/error.js +16 -0
- package/lib/util/fake_event.js +1 -0
- package/lib/util/manifest_parser_utils.js +12 -3
- package/lib/util/player_configuration.js +4 -2
- package/lib/util/stream_utils.js +57 -2
- package/lib/util/tXml.js +2 -1
- package/lib/util/text_parser.js +46 -0
- package/lib/util/ts_parser.js +32 -3
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +1 -1
- package/ui/chapter_selection.js +179 -0
- package/ui/controls.js +151 -0
- package/ui/enums.js +1 -0
- package/ui/externs/ui.js +7 -1
- package/ui/less/containers.less +3 -4
- package/ui/less/overflow_menu.less +5 -0
- package/ui/less/tooltip.less +1 -1
- package/ui/locales/en.json +1 -0
- package/ui/locales/es.json +1 -0
- package/ui/locales/fr.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/seek_bar.js +3 -2
- package/ui/ui.js +2 -0
- package/lib/polyfill/abort_controller.js +0 -158
package/lib/text/cue.js
CHANGED
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
goog.provide('shaka.text.Cue');
|
|
8
8
|
|
|
9
|
+
goog.require('shaka.log');
|
|
9
10
|
goog.require('shaka.text.CueRegion');
|
|
10
11
|
goog.require('shaka.util.ArrayUtils');
|
|
12
|
+
goog.require('shaka.util.StringUtils');
|
|
13
|
+
goog.require('shaka.util.TextParser');
|
|
14
|
+
goog.require('shaka.util.TXml');
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
/**
|
|
@@ -401,6 +405,372 @@ shaka.text.Cue = class {
|
|
|
401
405
|
|
|
402
406
|
return true;
|
|
403
407
|
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Parses cue payload, searches for styling entities and, if needed,
|
|
411
|
+
* modifies original payload and creates nested cues to better represent
|
|
412
|
+
* styling found in payload. All changes are done in-place.
|
|
413
|
+
* @param {!shaka.text.Cue} cue
|
|
414
|
+
* @param {!Map<string, !shaka.text.Cue>=} styles
|
|
415
|
+
* @export
|
|
416
|
+
*/
|
|
417
|
+
static parseCuePayload(cue, styles = new Map()) {
|
|
418
|
+
const StringUtils = shaka.util.StringUtils;
|
|
419
|
+
const TXml = shaka.util.TXml;
|
|
420
|
+
let payload = cue.payload;
|
|
421
|
+
if (!payload.includes('<')) {
|
|
422
|
+
cue.payload = StringUtils.htmlUnescape(payload);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (styles.size === 0) {
|
|
426
|
+
shaka.text.Cue.addDefaultTextColor(styles);
|
|
427
|
+
}
|
|
428
|
+
payload = shaka.text.Cue.replaceKaraokeStylePayload_(payload);
|
|
429
|
+
payload = shaka.text.Cue.replaceVoiceStylePayload_(payload);
|
|
430
|
+
payload = shaka.text.Cue.escapeInvalidChevrons_(payload);
|
|
431
|
+
|
|
432
|
+
cue.payload = '';
|
|
433
|
+
|
|
434
|
+
const xmlPayload = '<span>' + payload + '</span>';
|
|
435
|
+
let element;
|
|
436
|
+
try {
|
|
437
|
+
element = TXml.parseXmlString(xmlPayload, 'span');
|
|
438
|
+
} catch (e) {
|
|
439
|
+
shaka.log.warning('cue parse fail: ', e);
|
|
440
|
+
}
|
|
441
|
+
if (element) {
|
|
442
|
+
const childNodes = element.children;
|
|
443
|
+
if (childNodes.length == 1) {
|
|
444
|
+
const childNode = childNodes[0];
|
|
445
|
+
if (!TXml.isNode(childNode)) {
|
|
446
|
+
cue.payload = StringUtils.htmlUnescape(payload);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
for (const childNode of childNodes) {
|
|
451
|
+
shaka.text.Cue.generateCueFromElement_(childNode, cue, styles);
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
shaka.log.warning('The cue\'s markup could not be parsed: ', payload);
|
|
455
|
+
cue.payload = StringUtils.htmlUnescape(payload);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Add default color
|
|
461
|
+
*
|
|
462
|
+
* @param {!Map<string, !shaka.text.Cue>} styles
|
|
463
|
+
*/
|
|
464
|
+
static addDefaultTextColor(styles) {
|
|
465
|
+
const textColor = shaka.text.Cue.defaultTextColor;
|
|
466
|
+
for (const [key, value] of Object.entries(textColor)) {
|
|
467
|
+
const cue = new shaka.text.Cue(0, 0, '');
|
|
468
|
+
cue.color = value;
|
|
469
|
+
styles.set('.' + key, cue);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const bgColor = shaka.text.Cue.defaultTextBackgroundColor;
|
|
473
|
+
for (const [key, value] of Object.entries(bgColor)) {
|
|
474
|
+
const cue = new shaka.text.Cue(0, 0, '');
|
|
475
|
+
cue.backgroundColor = value;
|
|
476
|
+
styles.set('.' + key, cue);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Converts karaoke style tag to be valid for xml parsing
|
|
482
|
+
* For example,
|
|
483
|
+
* input: Text <00:00:00.450> time <00:00:01.450> 1
|
|
484
|
+
* output: Text <div time="00:00:00.450"> time
|
|
485
|
+
* <div time="00:00:01.450"> 1</div></div>
|
|
486
|
+
*
|
|
487
|
+
* @param {string} payload
|
|
488
|
+
* @return {string} processed payload
|
|
489
|
+
* @private
|
|
490
|
+
*/
|
|
491
|
+
static replaceKaraokeStylePayload_(payload) {
|
|
492
|
+
const names = [];
|
|
493
|
+
let nameStart = -1;
|
|
494
|
+
for (let i = 0; i < payload.length; i++) {
|
|
495
|
+
if (payload[i] === '<') {
|
|
496
|
+
nameStart = i + 1;
|
|
497
|
+
} else if (payload[i] === '>') {
|
|
498
|
+
if (nameStart > 0) {
|
|
499
|
+
const name = payload.substr(nameStart, i - nameStart);
|
|
500
|
+
if (name.match(shaka.text.Cue.timeFormat_)) {
|
|
501
|
+
names.push(name);
|
|
502
|
+
}
|
|
503
|
+
nameStart = -1;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
let newPayload = payload;
|
|
508
|
+
for (const name of names) {
|
|
509
|
+
const replaceTag = '<' + name + '>';
|
|
510
|
+
const startTag = '<div time="' + name + '">';
|
|
511
|
+
const endTag = '</div>';
|
|
512
|
+
newPayload = newPayload.replace(replaceTag, startTag);
|
|
513
|
+
newPayload += endTag;
|
|
514
|
+
}
|
|
515
|
+
return newPayload;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Converts voice style tag to be valid for xml parsing
|
|
520
|
+
* For example,
|
|
521
|
+
* input: <v Shaka>Test
|
|
522
|
+
* output: <v.voice-Shaka>Test</v.voice-Shaka>
|
|
523
|
+
*
|
|
524
|
+
* @param {string} payload
|
|
525
|
+
* @return {string} processed payload
|
|
526
|
+
* @private
|
|
527
|
+
*/
|
|
528
|
+
static replaceVoiceStylePayload_(payload) {
|
|
529
|
+
const voiceTag = 'v';
|
|
530
|
+
const names = [];
|
|
531
|
+
let nameStart = -1;
|
|
532
|
+
let newPayload = '';
|
|
533
|
+
let hasVoiceEndTag = false;
|
|
534
|
+
for (let i = 0; i < payload.length; i++) {
|
|
535
|
+
// This condition is used to manage tags that have end tags.
|
|
536
|
+
if (payload[i] === '/') {
|
|
537
|
+
const end = payload.indexOf('>', i);
|
|
538
|
+
if (end === -1) {
|
|
539
|
+
return payload;
|
|
540
|
+
}
|
|
541
|
+
const tagEnd = payload.substring(i + 1, end);
|
|
542
|
+
if (!tagEnd || tagEnd != voiceTag) {
|
|
543
|
+
newPayload += payload[i];
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
hasVoiceEndTag = true;
|
|
547
|
+
let tagStart = null;
|
|
548
|
+
if (names.length) {
|
|
549
|
+
tagStart = names[names.length -1];
|
|
550
|
+
}
|
|
551
|
+
if (!tagStart) {
|
|
552
|
+
newPayload += payload[i];
|
|
553
|
+
} else if (tagStart === tagEnd) {
|
|
554
|
+
newPayload += '/' + tagEnd + '>';
|
|
555
|
+
i += tagEnd.length + 1;
|
|
556
|
+
} else {
|
|
557
|
+
if (!tagStart.startsWith(voiceTag)) {
|
|
558
|
+
newPayload += payload[i];
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
newPayload += '/' + tagStart + '>';
|
|
562
|
+
i += tagEnd.length + 1;
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
// Here we only want the tag name, not any other payload.
|
|
566
|
+
if (payload[i] === '<') {
|
|
567
|
+
nameStart = i + 1;
|
|
568
|
+
if (payload[nameStart] != voiceTag) {
|
|
569
|
+
nameStart = -1;
|
|
570
|
+
}
|
|
571
|
+
} else if (payload[i] === '>') {
|
|
572
|
+
if (nameStart > 0) {
|
|
573
|
+
names.push(payload.substr(nameStart, i - nameStart));
|
|
574
|
+
nameStart = -1;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
newPayload += payload[i];
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const name of names) {
|
|
581
|
+
const newName = name.replace(' ', '.voice-');
|
|
582
|
+
newPayload = newPayload.replace(`<${name}>`, `<${newName}>`);
|
|
583
|
+
newPayload = newPayload.replace(`</${name}>`, `</${newName}>`);
|
|
584
|
+
if (!hasVoiceEndTag) {
|
|
585
|
+
newPayload += `</${newName}>`;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return newPayload;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* This method converts invalid > chevrons to HTML entities.
|
|
593
|
+
* It also removes < chevrons as per spec.
|
|
594
|
+
*
|
|
595
|
+
* @param {!string} input
|
|
596
|
+
* @return {string}
|
|
597
|
+
* @private
|
|
598
|
+
*/
|
|
599
|
+
static escapeInvalidChevrons_(input) {
|
|
600
|
+
// Used to map HTML entities to characters.
|
|
601
|
+
const htmlEscapes = {
|
|
602
|
+
'< ': '',
|
|
603
|
+
' >': ' >',
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const reEscapedHtml = /(< +>|<\s|\s>)/g;
|
|
607
|
+
const reHasEscapedHtml = RegExp(reEscapedHtml.source);
|
|
608
|
+
// This check is an optimization, since replace always makes a copy
|
|
609
|
+
if (input && reHasEscapedHtml.test(input)) {
|
|
610
|
+
return input.replace(reEscapedHtml, (entity) => {
|
|
611
|
+
return htmlEscapes[entity] || '';
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
return input || '';
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* @param {!shaka.extern.xml.Node} element
|
|
619
|
+
* @param {!shaka.text.Cue} rootCue
|
|
620
|
+
* @param {!Map<string, !shaka.text.Cue>} styles
|
|
621
|
+
* @private
|
|
622
|
+
*/
|
|
623
|
+
static generateCueFromElement_(element, rootCue, styles) {
|
|
624
|
+
const TXml = shaka.util.TXml;
|
|
625
|
+
const nestedCue = rootCue.clone();
|
|
626
|
+
// We don't want propagate some properties.
|
|
627
|
+
nestedCue.nestedCues = [];
|
|
628
|
+
nestedCue.payload = '';
|
|
629
|
+
nestedCue.rubyTag = '';
|
|
630
|
+
// We don't want propagate some position settings
|
|
631
|
+
nestedCue.line = null;
|
|
632
|
+
nestedCue.region = new shaka.text.CueRegion();
|
|
633
|
+
nestedCue.position = null;
|
|
634
|
+
nestedCue.size = 0;
|
|
635
|
+
|
|
636
|
+
if (TXml.isNode(element)) {
|
|
637
|
+
const bold = shaka.text.Cue.fontWeight.BOLD;
|
|
638
|
+
const italic = shaka.text.Cue.fontStyle.ITALIC;
|
|
639
|
+
const underline = shaka.text.Cue.textDecoration.UNDERLINE;
|
|
640
|
+
const tags = element.tagName.split(/(?=[ .])+/g);
|
|
641
|
+
for (const tag of tags) {
|
|
642
|
+
let styleTag = tag;
|
|
643
|
+
// White blanks at start indicate that the style is a voice
|
|
644
|
+
if (styleTag.startsWith('.voice-')) {
|
|
645
|
+
const voice = styleTag.split('-').pop();
|
|
646
|
+
styleTag = `v[voice="${voice}"]`;
|
|
647
|
+
// The specification allows to have quotes and not, so we check to
|
|
648
|
+
// see which one is being used.
|
|
649
|
+
if (!styles.has(styleTag)) {
|
|
650
|
+
styleTag = `v[voice=${voice}]`;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (styles.has(styleTag)) {
|
|
654
|
+
shaka.text.Cue.mergeStyle_(nestedCue, styles.get(styleTag));
|
|
655
|
+
}
|
|
656
|
+
switch (tag) {
|
|
657
|
+
case 'br': {
|
|
658
|
+
const lineBreakCue = shaka.text.Cue.lineBreak(
|
|
659
|
+
nestedCue.startTime, nestedCue.endTime);
|
|
660
|
+
rootCue.nestedCues.push(lineBreakCue);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
case 'b':
|
|
664
|
+
nestedCue.fontWeight = bold;
|
|
665
|
+
break;
|
|
666
|
+
case 'i':
|
|
667
|
+
nestedCue.fontStyle = italic;
|
|
668
|
+
break;
|
|
669
|
+
case 'u':
|
|
670
|
+
nestedCue.textDecoration.push(underline);
|
|
671
|
+
break;
|
|
672
|
+
case 'font': {
|
|
673
|
+
const color = element.attributes['color'];
|
|
674
|
+
if (color) {
|
|
675
|
+
nestedCue.color = color;
|
|
676
|
+
}
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
case 'div': {
|
|
680
|
+
const time = element.attributes['time'];
|
|
681
|
+
if (!time) {
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
const cueTime = shaka.util.TextParser.parseTime(time);
|
|
685
|
+
if (cueTime) {
|
|
686
|
+
nestedCue.startTime = cueTime;
|
|
687
|
+
}
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
case 'ruby':
|
|
691
|
+
case 'rp':
|
|
692
|
+
case 'rt':
|
|
693
|
+
nestedCue.rubyTag = tag;
|
|
694
|
+
break;
|
|
695
|
+
default:
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const isTextNode = (item) => TXml.isText(item);
|
|
702
|
+
const childNodes = element.children;
|
|
703
|
+
if (isTextNode(element) ||
|
|
704
|
+
(childNodes.length == 1 && isTextNode(childNodes[0]))) {
|
|
705
|
+
// Trailing line breaks may lost when convert cue to HTML tag
|
|
706
|
+
// Need to insert line break cue to preserve line breaks
|
|
707
|
+
const textArr = TXml.getTextContents(element).split('\n');
|
|
708
|
+
let isFirst = true;
|
|
709
|
+
for (const text of textArr) {
|
|
710
|
+
if (!isFirst) {
|
|
711
|
+
const lineBreakCue = shaka.text.Cue.lineBreak(
|
|
712
|
+
nestedCue.startTime, nestedCue.endTime);
|
|
713
|
+
rootCue.nestedCues.push(lineBreakCue);
|
|
714
|
+
}
|
|
715
|
+
if (text.length > 0) {
|
|
716
|
+
const textCue = nestedCue.clone();
|
|
717
|
+
textCue.payload = shaka.util.StringUtils.htmlUnescape(text);
|
|
718
|
+
rootCue.nestedCues.push(textCue);
|
|
719
|
+
}
|
|
720
|
+
isFirst = false;
|
|
721
|
+
}
|
|
722
|
+
} else {
|
|
723
|
+
rootCue.nestedCues.push(nestedCue);
|
|
724
|
+
for (const childNode of childNodes) {
|
|
725
|
+
shaka.text.Cue.generateCueFromElement_(childNode, nestedCue, styles);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Merges values created in parseStyle_
|
|
732
|
+
* @param {!shaka.text.Cue} cue
|
|
733
|
+
* @param {shaka.text.Cue} refCue
|
|
734
|
+
* @private
|
|
735
|
+
*/
|
|
736
|
+
static mergeStyle_(cue, refCue) {
|
|
737
|
+
if (!refCue) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Overwrites if new value string length > 0
|
|
742
|
+
cue.backgroundColor = shaka.text.Cue.getOrDefault_(
|
|
743
|
+
refCue.backgroundColor, cue.backgroundColor);
|
|
744
|
+
cue.color = shaka.text.Cue.getOrDefault_(
|
|
745
|
+
refCue.color, cue.color);
|
|
746
|
+
cue.fontFamily = shaka.text.Cue.getOrDefault_(
|
|
747
|
+
refCue.fontFamily, cue.fontFamily);
|
|
748
|
+
cue.fontSize = shaka.text.Cue.getOrDefault_(
|
|
749
|
+
refCue.fontSize, cue.fontSize);
|
|
750
|
+
cue.textShadow = shaka.text.Cue.getOrDefault_(
|
|
751
|
+
refCue.textShadow, cue.textShadow);
|
|
752
|
+
|
|
753
|
+
// Overwrite with new values as unable to determine
|
|
754
|
+
// if new value is set or not
|
|
755
|
+
cue.fontWeight = refCue.fontWeight;
|
|
756
|
+
cue.fontStyle = refCue.fontStyle;
|
|
757
|
+
cue.opacity = refCue.opacity;
|
|
758
|
+
cue.rubyTag = refCue.rubyTag;
|
|
759
|
+
cue.textCombineUpright = refCue.textCombineUpright;
|
|
760
|
+
cue.wrapLine = refCue.wrapLine;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* @param {string} value
|
|
765
|
+
* @param {string} defaultValue
|
|
766
|
+
* @private
|
|
767
|
+
*/
|
|
768
|
+
static getOrDefault_(value, defaultValue) {
|
|
769
|
+
if (value && value.length > 0) {
|
|
770
|
+
return value;
|
|
771
|
+
}
|
|
772
|
+
return defaultValue;
|
|
773
|
+
}
|
|
404
774
|
};
|
|
405
775
|
|
|
406
776
|
|
|
@@ -553,3 +923,6 @@ shaka.text.Cue.textDecoration = {
|
|
|
553
923
|
'LINE_THROUGH': 'lineThrough',
|
|
554
924
|
'OVERLINE': 'overline',
|
|
555
925
|
};
|
|
926
|
+
|
|
927
|
+
/** @private */
|
|
928
|
+
shaka.text.Cue.timeFormat_ = /(?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{2,3})/g;
|
|
@@ -8,8 +8,10 @@ goog.provide('shaka.text.Mp4TtmlParser');
|
|
|
8
8
|
|
|
9
9
|
goog.require('shaka.text.TextEngine');
|
|
10
10
|
goog.require('shaka.text.TtmlTextParser');
|
|
11
|
+
goog.require('shaka.util.BufferUtils');
|
|
11
12
|
goog.require('shaka.util.Error');
|
|
12
13
|
goog.require('shaka.util.Mp4Parser');
|
|
14
|
+
goog.require('shaka.util.Uint8ArrayUtils');
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -81,12 +83,54 @@ shaka.text.Mp4TtmlParser = class {
|
|
|
81
83
|
let sawMDAT = false;
|
|
82
84
|
let payload = [];
|
|
83
85
|
|
|
86
|
+
/** @type {!Array.<number>} */
|
|
87
|
+
let subSizes = [];
|
|
88
|
+
|
|
84
89
|
const parser = new Mp4Parser()
|
|
90
|
+
.box('moof', Mp4Parser.children)
|
|
91
|
+
.box('traf', Mp4Parser.children)
|
|
92
|
+
.fullBox('subs', (box) => {
|
|
93
|
+
subSizes = [];
|
|
94
|
+
const reader = box.reader;
|
|
95
|
+
const entryCount = reader.readUint32();
|
|
96
|
+
for (let i = 0; i < entryCount; i++) {
|
|
97
|
+
reader.readUint32(); // sample_delta
|
|
98
|
+
const subsampleCount = reader.readUint16();
|
|
99
|
+
for (let j = 0; j < subsampleCount; j++) {
|
|
100
|
+
if (box.version == 1) {
|
|
101
|
+
subSizes.push(reader.readUint32());
|
|
102
|
+
} else {
|
|
103
|
+
subSizes.push(reader.readUint16());
|
|
104
|
+
}
|
|
105
|
+
reader.readUint8(); // priority
|
|
106
|
+
reader.readUint8(); // discardable
|
|
107
|
+
reader.readUint32(); // codec_specific_parameters
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
})
|
|
85
111
|
.box('mdat', Mp4Parser.allData((data) => {
|
|
86
112
|
sawMDAT = true;
|
|
87
113
|
// Join this to any previous payload, in case the mp4 has multiple
|
|
88
114
|
// mdats.
|
|
89
|
-
|
|
115
|
+
if (subSizes.length) {
|
|
116
|
+
const contentData =
|
|
117
|
+
shaka.util.BufferUtils.toUint8(data, 0, subSizes[0]);
|
|
118
|
+
const images = [];
|
|
119
|
+
let offset = subSizes[0];
|
|
120
|
+
for (let i = 1; i < subSizes.length; i++) {
|
|
121
|
+
const imageData =
|
|
122
|
+
shaka.util.BufferUtils.toUint8(data, offset, subSizes[i]);
|
|
123
|
+
const raw =
|
|
124
|
+
shaka.util.Uint8ArrayUtils.toStandardBase64(imageData);
|
|
125
|
+
images.push('data:image/png;base64,' + raw);
|
|
126
|
+
offset += subSizes[i];
|
|
127
|
+
}
|
|
128
|
+
payload = payload.concat(
|
|
129
|
+
this.parser_.parseMedia(contentData, time, uri, images));
|
|
130
|
+
} else {
|
|
131
|
+
payload = payload.concat(
|
|
132
|
+
this.parser_.parseMedia(data, time, uri, /* images= */ []));
|
|
133
|
+
}
|
|
90
134
|
}));
|
|
91
135
|
parser.parse(data, /* partialOkay= */ false);
|
|
92
136
|
|
|
@@ -107,9 +151,15 @@ shaka.text.TextEngine.registerParser(
|
|
|
107
151
|
shaka.text.TextEngine.registerParser(
|
|
108
152
|
'application/mp4; codecs="stpp.ttml"',
|
|
109
153
|
() => new shaka.text.Mp4TtmlParser());
|
|
154
|
+
shaka.text.TextEngine.registerParser(
|
|
155
|
+
'application/mp4; codecs="stpp.ttml.im1i"',
|
|
156
|
+
() => new shaka.text.Mp4TtmlParser());
|
|
110
157
|
shaka.text.TextEngine.registerParser(
|
|
111
158
|
'application/mp4; codecs="stpp.ttml.im1t"',
|
|
112
159
|
() => new shaka.text.Mp4TtmlParser());
|
|
160
|
+
shaka.text.TextEngine.registerParser(
|
|
161
|
+
'application/mp4; codecs="stpp.ttml.im2i"',
|
|
162
|
+
() => new shaka.text.Mp4TtmlParser());
|
|
113
163
|
shaka.text.TextEngine.registerParser(
|
|
114
164
|
'application/mp4; codecs="stpp.ttml.im2t"',
|
|
115
165
|
() => new shaka.text.Mp4TtmlParser());
|
|
@@ -308,11 +308,8 @@ shaka.text.Mp4VttParser = class {
|
|
|
308
308
|
* @private
|
|
309
309
|
*/
|
|
310
310
|
static assembleCue_(payload, id, settings, startTime, endTime) {
|
|
311
|
-
const cue = new shaka.text.Cue(startTime, endTime,
|
|
312
|
-
|
|
313
|
-
/** @type {!Map.<string, shaka.text.Cue>} */
|
|
314
|
-
const styles = new Map();
|
|
315
|
-
shaka.text.VttTextParser.parseCueStyles(payload, cue, styles);
|
|
311
|
+
const cue = new shaka.text.Cue(startTime, endTime, payload);
|
|
312
|
+
shaka.text.Cue.parseCuePayload(cue);
|
|
316
313
|
|
|
317
314
|
if (id) {
|
|
318
315
|
cue.id = id;
|
|
@@ -48,7 +48,6 @@ shaka.text.SbvTextParser = class {
|
|
|
48
48
|
* @export
|
|
49
49
|
*/
|
|
50
50
|
parseMedia(data, time) {
|
|
51
|
-
const SbvTextParser = shaka.text.SbvTextParser;
|
|
52
51
|
const StringUtils = shaka.util.StringUtils;
|
|
53
52
|
|
|
54
53
|
// Get the input as a string.
|
|
@@ -72,9 +71,9 @@ shaka.text.SbvTextParser = class {
|
|
|
72
71
|
const lines = block.split('\n');
|
|
73
72
|
// Parse the times.
|
|
74
73
|
const parser = new shaka.util.TextParser(lines[0]);
|
|
75
|
-
const start =
|
|
74
|
+
const start = parser.parseTime();
|
|
76
75
|
const expect = parser.readRegex(/,/g);
|
|
77
|
-
const end =
|
|
76
|
+
const end = parser.parseTime();
|
|
78
77
|
|
|
79
78
|
if (start == null || expect == null || end == null) {
|
|
80
79
|
throw new shaka.util.Error(
|
|
@@ -93,34 +92,6 @@ shaka.text.SbvTextParser = class {
|
|
|
93
92
|
|
|
94
93
|
return cues;
|
|
95
94
|
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Parses a SubViewer time from the given parser.
|
|
99
|
-
*
|
|
100
|
-
* @param {!shaka.util.TextParser} parser
|
|
101
|
-
* @return {?number}
|
|
102
|
-
* @private
|
|
103
|
-
*/
|
|
104
|
-
static parseTime_(parser) {
|
|
105
|
-
// 00:00.000 or 00:00:00.000 or 0:00:00.000 or
|
|
106
|
-
// 00:00.00 or 00:00:00.00 or 0:00:00.00
|
|
107
|
-
const regexExpresion = /(?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{2,3})/g;
|
|
108
|
-
const results = parser.readRegex(regexExpresion);
|
|
109
|
-
if (results == null) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
// This capture is optional, but will still be in the array as undefined,
|
|
113
|
-
// in which case it is 0.
|
|
114
|
-
const hours = Number(results[1]) || 0;
|
|
115
|
-
const minutes = Number(results[2]);
|
|
116
|
-
const seconds = Number(results[3]);
|
|
117
|
-
const milliseconds = Number(results[4]);
|
|
118
|
-
if (minutes > 59 || seconds > 59) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return (milliseconds / 1000) + seconds + (minutes * 60) + (hours * 3600);
|
|
123
|
-
}
|
|
124
95
|
};
|
|
125
96
|
|
|
126
97
|
|
package/lib/text/text_engine.js
CHANGED
|
@@ -209,7 +209,7 @@ shaka.text.TextEngine = class {
|
|
|
209
209
|
|
|
210
210
|
// Parse the buffer and add the new cues.
|
|
211
211
|
const allCues = this.parser_.parseMedia(
|
|
212
|
-
shaka.util.BufferUtils.toUint8(buffer), time, uri);
|
|
212
|
+
shaka.util.BufferUtils.toUint8(buffer), time, uri, /* images= */ []);
|
|
213
213
|
for (const cue of allCues) {
|
|
214
214
|
this.modifyCueCallback_(cue, uri || null, time);
|
|
215
215
|
}
|
package/lib/text/text_utils.js
CHANGED
|
@@ -51,7 +51,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
51
51
|
* @override
|
|
52
52
|
* @export
|
|
53
53
|
*/
|
|
54
|
-
parseMedia(data, time, uri) {
|
|
54
|
+
parseMedia(data, time, uri, images) {
|
|
55
55
|
const TtmlTextParser = shaka.text.TtmlTextParser;
|
|
56
56
|
const TXml = shaka.util.TXml;
|
|
57
57
|
const ttpNs = TtmlTextParser.parameterNs_;
|
|
@@ -148,7 +148,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
148
148
|
body, time, rateInfo, metadataElements, styles,
|
|
149
149
|
regionElements, cueRegions, collapseMultipleSpaces,
|
|
150
150
|
cellResolutionInfo, /* parentCueElement= */ null,
|
|
151
|
-
/* isContent= */ false, uri);
|
|
151
|
+
/* isContent= */ false, uri, images);
|
|
152
152
|
if (cue) {
|
|
153
153
|
// According to the TTML spec, backgrounds default to transparent.
|
|
154
154
|
// So default the background of the top-level element to transparent.
|
|
@@ -177,13 +177,14 @@ shaka.text.TtmlTextParser = class {
|
|
|
177
177
|
* @param {?shaka.extern.xml.Node} parentCueElement
|
|
178
178
|
* @param {boolean} isContent
|
|
179
179
|
* @param {?(string|undefined)} uri
|
|
180
|
+
* @param {!Array.<string>} images
|
|
180
181
|
* @return {shaka.text.Cue}
|
|
181
182
|
* @private
|
|
182
183
|
*/
|
|
183
184
|
static parseCue_(
|
|
184
185
|
cueNode, timeContext, rateInfo, metadataElements, styles, regionElements,
|
|
185
186
|
cueRegions, collapseMultipleSpaces, cellResolution, parentCueElement,
|
|
186
|
-
isContent, uri) {
|
|
187
|
+
isContent, uri, images) {
|
|
187
188
|
const TXml = shaka.util.TXml;
|
|
188
189
|
const StringUtils = shaka.util.StringUtils;
|
|
189
190
|
/** @type {shaka.extern.xml.Node} */
|
|
@@ -230,7 +231,15 @@ shaka.text.TtmlTextParser = class {
|
|
|
230
231
|
cueElement,
|
|
231
232
|
shaka.text.TtmlTextParser.smpteNsList_,
|
|
232
233
|
'backgroundImage');
|
|
233
|
-
|
|
234
|
+
const imsc1ImgUrnTester =
|
|
235
|
+
/^(urn:)(mpeg:[a-z0-9][a-z0-9-]{0,31}:)(subs:)([0-9]+)$/;
|
|
236
|
+
if (backgroundImage && imsc1ImgUrnTester.test(backgroundImage)) {
|
|
237
|
+
const index = parseInt(backgroundImage.split(':').pop(), 10) -1;
|
|
238
|
+
if (index >= images.length) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
imageUri = images[index];
|
|
242
|
+
} else if (uri && backgroundImage && !backgroundImage.startsWith('#')) {
|
|
234
243
|
const baseUri = new goog.Uri(uri);
|
|
235
244
|
const relativeUri = new goog.Uri(backgroundImage);
|
|
236
245
|
const newUri = baseUri.resolve(relativeUri).toString();
|
|
@@ -270,6 +279,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
270
279
|
cueElement,
|
|
271
280
|
isContent,
|
|
272
281
|
uri,
|
|
282
|
+
images,
|
|
273
283
|
);
|
|
274
284
|
|
|
275
285
|
// This node may or may not generate a nested cue.
|
|
@@ -710,10 +710,6 @@ shaka.text.UITextDisplayer = class {
|
|
|
710
710
|
// Only do this for the text itself.
|
|
711
711
|
elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
|
|
712
712
|
}
|
|
713
|
-
const fontFamily = inherit((c) => c.fontFamily);
|
|
714
|
-
if (fontFamily) {
|
|
715
|
-
elem.style.fontFamily = fontFamily;
|
|
716
|
-
}
|
|
717
713
|
}
|
|
718
714
|
if (text) {
|
|
719
715
|
elem.textContent = text;
|