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.
Files changed (91) hide show
  1. package/README.md +5 -1
  2. package/dist/controls.css +3 -3
  3. package/dist/controls.css.map +1 -1
  4. package/dist/deps.js +11 -11
  5. package/dist/locales.js +38 -34
  6. package/dist/shaka-player.compiled.d.ts +68 -33
  7. package/dist/shaka-player.compiled.debug.d.ts +68 -33
  8. package/dist/shaka-player.compiled.debug.externs.js +584 -544
  9. package/dist/shaka-player.compiled.debug.js +715 -676
  10. package/dist/shaka-player.compiled.debug.map +4 -4
  11. package/dist/shaka-player.compiled.externs.js +584 -544
  12. package/dist/shaka-player.compiled.js +1394 -1371
  13. package/dist/shaka-player.compiled.map +4 -4
  14. package/dist/shaka-player.ui.d.ts +76 -34
  15. package/dist/shaka-player.ui.debug.d.ts +76 -34
  16. package/dist/shaka-player.ui.debug.externs.js +77 -26
  17. package/dist/shaka-player.ui.debug.js +774 -720
  18. package/dist/shaka-player.ui.debug.map +4 -4
  19. package/dist/shaka-player.ui.externs.js +77 -26
  20. package/dist/shaka-player.ui.js +1649 -1617
  21. package/dist/shaka-player.ui.map +4 -4
  22. package/externs/mediasession.js +10 -1
  23. package/externs/shaka/ads.js +79 -2
  24. package/externs/shaka/manifest.js +6 -1
  25. package/externs/shaka/manifest_parser.js +6 -2
  26. package/externs/shaka/net.js +23 -2
  27. package/externs/shaka/player.js +414 -103
  28. package/externs/shaka/text.js +2 -1
  29. package/lib/abr/simple_abr_manager.js +5 -0
  30. package/lib/ads/ad_manager.js +45 -1
  31. package/lib/ads/ad_utils.js +124 -1
  32. package/lib/ads/interstitial_ad.js +9 -2
  33. package/lib/ads/interstitial_ad_manager.js +216 -101
  34. package/lib/ads/media_tailor_ad.js +1 -39
  35. package/lib/ads/media_tailor_ad_manager.js +32 -16
  36. package/lib/cea/cea608_data_channel.js +40 -8
  37. package/lib/cea/cea608_memory.js +28 -0
  38. package/lib/dash/dash_parser.js +162 -25
  39. package/lib/dash/segment_base.js +3 -2
  40. package/lib/dash/segment_list.js +6 -3
  41. package/lib/dash/segment_template.js +104 -20
  42. package/lib/hls/hls_parser.js +319 -215
  43. package/lib/media/drm_engine.js +11 -3
  44. package/lib/media/media_source_engine.js +0 -8
  45. package/lib/media/preload_manager.js +16 -4
  46. package/lib/media/segment_reference.js +3 -0
  47. package/lib/media/segment_utils.js +8 -1
  48. package/lib/media/streaming_engine.js +10 -6
  49. package/lib/mss/mss_parser.js +14 -2
  50. package/lib/net/http_fetch_plugin.js +12 -5
  51. package/lib/net/http_xhr_plugin.js +7 -3
  52. package/lib/net/networking_engine.js +21 -1
  53. package/lib/offline/manifest_converter.js +1 -0
  54. package/lib/offline/storage.js +1 -0
  55. package/lib/player.js +336 -121
  56. package/lib/text/cue.js +373 -0
  57. package/lib/text/mp4_ttml_parser.js +51 -1
  58. package/lib/text/mp4_vtt_parser.js +2 -5
  59. package/lib/text/sbv_text_parser.js +2 -31
  60. package/lib/text/srt_text_parser.js +1 -1
  61. package/lib/text/text_engine.js +1 -1
  62. package/lib/text/text_utils.js +1 -0
  63. package/lib/text/ttml_text_parser.js +14 -4
  64. package/lib/text/ui_text_displayer.js +0 -4
  65. package/lib/text/vtt_text_parser.js +10 -415
  66. package/lib/util/abortable_operation.js +8 -0
  67. package/lib/util/cmcd_manager.js +38 -0
  68. package/lib/util/error.js +16 -0
  69. package/lib/util/fake_event.js +1 -0
  70. package/lib/util/manifest_parser_utils.js +12 -3
  71. package/lib/util/player_configuration.js +4 -2
  72. package/lib/util/stream_utils.js +57 -2
  73. package/lib/util/tXml.js +2 -1
  74. package/lib/util/text_parser.js +46 -0
  75. package/lib/util/ts_parser.js +32 -3
  76. package/package.json +1 -1
  77. package/shaka-player.uncompiled.js +1 -1
  78. package/ui/chapter_selection.js +179 -0
  79. package/ui/controls.js +151 -0
  80. package/ui/enums.js +1 -0
  81. package/ui/externs/ui.js +7 -1
  82. package/ui/less/containers.less +3 -4
  83. package/ui/less/overflow_menu.less +5 -0
  84. package/ui/less/tooltip.less +1 -1
  85. package/ui/locales/en.json +1 -0
  86. package/ui/locales/es.json +1 -0
  87. package/ui/locales/fr.json +1 -0
  88. package/ui/locales/source.json +4 -0
  89. package/ui/seek_bar.js +3 -2
  90. package/ui/ui.js +2 -0
  91. 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
+ ' >': ' &gt;',
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
- payload = payload.concat(this.parser_.parseMedia(data, time, uri));
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 = SbvTextParser.parseTime_(parser);
74
+ const start = parser.parseTime();
76
75
  const expect = parser.readRegex(/,/g);
77
- const end = SbvTextParser.parseTime_(parser);
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
 
@@ -67,7 +67,7 @@ shaka.text.SrtTextParser = class {
67
67
 
68
68
  const newData = BufferUtils.toUint8(StringUtils.toUTF8(vvtText));
69
69
 
70
- return this.parser_.parseMedia(newData, time, uri);
70
+ return this.parser_.parseMedia(newData, time, uri, /* images= */ []);
71
71
  }
72
72
 
73
73
  /**
@@ -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
  }
@@ -301,6 +301,7 @@ shaka.text.Utils = class {
301
301
  cue.region.width = vttCue.region.width;
302
302
  cue.region.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
303
303
  }
304
+ shaka.text.Cue.parseCuePayload(cue);
304
305
 
305
306
  return cue;
306
307
  }
@@ -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
- if (uri && backgroundImage && !backgroundImage.startsWith('#')) {
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;