shaka-player 4.7.13 → 4.8.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/AUTHORS +1 -0
- package/CONTRIBUTORS +1 -0
- package/LICENSE +54 -0
- package/README.md +22 -1
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +34 -23
- package/dist/locales.js +44 -34
- package/dist/shaka-player.compiled.d.ts +194 -44
- package/dist/shaka-player.compiled.debug.d.ts +194 -44
- package/dist/shaka-player.compiled.debug.externs.js +474 -337
- package/dist/shaka-player.compiled.debug.js +949 -832
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +474 -337
- package/dist/shaka-player.compiled.js +1237 -1171
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +257 -47
- package/dist/shaka-player.ui.debug.d.ts +257 -47
- package/dist/shaka-player.ui.debug.externs.js +591 -376
- package/dist/shaka-player.ui.debug.js +1165 -965
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +591 -376
- package/dist/shaka-player.ui.js +1521 -1404
- package/dist/shaka-player.ui.map +4 -4
- package/externs/device_sensor_event.js +18 -0
- package/externs/hisense.js +16 -0
- package/externs/playstation.js +31 -0
- package/externs/shaka/abr_manager.js +3 -1
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/codecs.js +13 -0
- package/externs/shaka/manifest.js +58 -6
- package/externs/shaka/net.js +13 -1
- package/externs/shaka/player.js +184 -13
- package/externs/shaka/text.js +19 -0
- package/externs/tizen.js +41 -0
- package/externs/webos.js +19 -0
- package/externs/xbox.js +43 -0
- package/lib/abr/simple_abr_manager.js +9 -1
- package/lib/ads/ad_manager.js +19 -2
- package/lib/ads/client_side_ad_manager.js +5 -3
- package/lib/ads/media_tailor_ad_manager.js +9 -0
- package/lib/cast/cast_receiver.js +0 -14
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/mp4_cea_parser.js +6 -1
- package/lib/dash/content_protection.js +97 -53
- package/lib/dash/dash_parser.js +230 -176
- package/lib/dash/mpd_utils.js +72 -65
- package/lib/dash/segment_base.js +27 -21
- package/lib/dash/segment_list.js +18 -16
- package/lib/dash/segment_template.js +26 -21
- package/lib/hls/hls_parser.js +358 -164
- package/lib/media/buffering_observer.js +25 -1
- package/lib/media/drm_engine.js +91 -71
- package/lib/media/manifest_filterer.js +220 -0
- package/lib/media/media_source_engine.js +34 -5
- package/lib/media/preload_manager.js +827 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/segment_index.js +40 -30
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +31 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +319 -63
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +74 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/storage.js +3 -3
- package/lib/player.js +882 -673
- package/lib/polyfill/media_capabilities.js +16 -1
- package/lib/text/simple_text_displayer.js +9 -0
- package/lib/text/stub_text_displayer.js +7 -0
- package/lib/text/text_engine.js +16 -3
- package/lib/text/ttml_text_parser.js +116 -114
- package/lib/text/ui_text_displayer.js +28 -11
- package/lib/text/vtt_text_parser.js +54 -115
- package/lib/transmuxer/opus.js +91 -0
- package/lib/transmuxer/ts_transmuxer.js +115 -0
- package/lib/util/cmcd_manager.js +152 -3
- package/lib/util/dom_utils.js +11 -0
- package/lib/util/error.js +5 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/manifest_parser_utils.js +97 -3
- package/lib/util/mp4_box_parsers.js +346 -17
- package/lib/util/mp4_generator.js +23 -0
- package/lib/util/periods.js +19 -0
- package/lib/util/platform.js +161 -9
- package/lib/util/player_configuration.js +61 -3
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +16 -6
- package/lib/util/string_utils.js +36 -0
- package/lib/util/tXml.js +814 -0
- package/lib/util/ts_parser.js +50 -0
- package/lib/util/xml_utils.js +10 -338
- package/package.json +2 -2
- package/shaka-player.uncompiled.js +8 -0
- package/ui/controls.js +187 -5
- package/ui/enums.js +2 -0
- package/ui/externs/ui.js +30 -3
- package/ui/gl_matrix/matrix_4x4.js +568 -0
- package/ui/gl_matrix/matrix_quaternion.js +45 -0
- package/ui/less/containers.less +11 -0
- package/ui/locales/en-GB.json +2 -0
- package/ui/locales/en.json +2 -0
- package/ui/locales/es-419.json +2 -0
- package/ui/locales/es.json +2 -0
- package/ui/locales/source.json +8 -0
- package/ui/recenter_vr.js +126 -0
- package/ui/resolution_selection.js +25 -5
- package/ui/statistics_button.js +12 -0
- package/ui/text_selection.js +1 -1
- package/ui/toggle_stereoscopic.js +127 -0
- package/ui/ui.js +74 -38
- package/ui/vr_manager.js +557 -0
- package/ui/vr_utils.js +232 -0
- package/ui/vr_webgl.js +697 -0
|
@@ -15,7 +15,7 @@ goog.require('shaka.text.TextEngine');
|
|
|
15
15
|
goog.require('shaka.util.Error');
|
|
16
16
|
goog.require('shaka.util.StringUtils');
|
|
17
17
|
goog.require('shaka.util.TextParser');
|
|
18
|
-
goog.require('shaka.util.
|
|
18
|
+
goog.require('shaka.util.TXml');
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -458,26 +458,34 @@ shaka.text.VttTextParser = class {
|
|
|
458
458
|
*/
|
|
459
459
|
static parseCueStyles(payload, rootCue, styles) {
|
|
460
460
|
const VttTextParser = shaka.text.VttTextParser;
|
|
461
|
+
const StringUtils = shaka.util.StringUtils;
|
|
462
|
+
const TXml = shaka.util.TXml;
|
|
461
463
|
// Optimization for unstyled payloads.
|
|
462
464
|
if (!payload.includes('<')) {
|
|
463
|
-
rootCue.payload =
|
|
465
|
+
rootCue.payload = StringUtils.htmlUnescape(payload);
|
|
464
466
|
return;
|
|
465
467
|
}
|
|
466
468
|
if (styles.size === 0) {
|
|
467
469
|
VttTextParser.addDefaultTextColor_(styles);
|
|
468
470
|
}
|
|
469
|
-
|
|
471
|
+
|
|
470
472
|
payload = VttTextParser.replaceKaraokeStylePayload_(payload);
|
|
471
473
|
payload = VttTextParser.replaceVoiceStylePayload_(payload);
|
|
474
|
+
payload = VttTextParser.escapeInvalidChevrons_(payload);
|
|
472
475
|
const xmlPayload = '<span>' + payload + '</span>';
|
|
473
|
-
|
|
476
|
+
let element;
|
|
477
|
+
try {
|
|
478
|
+
element = TXml.parseXmlString(xmlPayload, 'span');
|
|
479
|
+
} catch (e) {
|
|
480
|
+
shaka.log.warning('cue parse fail: ', e);
|
|
481
|
+
}
|
|
482
|
+
|
|
474
483
|
if (element) {
|
|
475
|
-
const childNodes = element.
|
|
484
|
+
const childNodes = element.children;
|
|
476
485
|
if (childNodes.length == 1) {
|
|
477
486
|
const childNode = childNodes[0];
|
|
478
|
-
if (childNode
|
|
479
|
-
|
|
480
|
-
rootCue.payload = VttTextParser.htmlUnescape_(payload);
|
|
487
|
+
if (!TXml.isNode(childNode)) {
|
|
488
|
+
rootCue.payload = StringUtils.htmlUnescape(payload);
|
|
481
489
|
return;
|
|
482
490
|
}
|
|
483
491
|
}
|
|
@@ -486,8 +494,34 @@ shaka.text.VttTextParser = class {
|
|
|
486
494
|
}
|
|
487
495
|
} else {
|
|
488
496
|
shaka.log.warning('The cue\'s markup could not be parsed: ', payload);
|
|
489
|
-
rootCue.payload =
|
|
497
|
+
rootCue.payload = StringUtils.htmlUnescape(payload);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* This method converts invalid > chevrons to HTML entities.
|
|
503
|
+
* It also removes < chevrons as per spec.
|
|
504
|
+
*
|
|
505
|
+
* @param {!string} input
|
|
506
|
+
* @return {string}
|
|
507
|
+
* @private
|
|
508
|
+
*/
|
|
509
|
+
static escapeInvalidChevrons_(input) {
|
|
510
|
+
// Used to map HTML entities to characters.
|
|
511
|
+
const htmlEscapes = {
|
|
512
|
+
'< ': '',
|
|
513
|
+
' >': ' >',
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const reEscapedHtml = /(< +>|<\s|\s>)/g;
|
|
517
|
+
const reHasEscapedHtml = RegExp(reEscapedHtml.source);
|
|
518
|
+
// This check is an optimization, since replace always makes a copy
|
|
519
|
+
if (input && reHasEscapedHtml.test(input)) {
|
|
520
|
+
return input.replace(reEscapedHtml, (entity) => {
|
|
521
|
+
return htmlEscapes[entity] || '';
|
|
522
|
+
});
|
|
490
523
|
}
|
|
524
|
+
return input || '';
|
|
491
525
|
}
|
|
492
526
|
|
|
493
527
|
/**
|
|
@@ -601,66 +635,6 @@ shaka.text.VttTextParser = class {
|
|
|
601
635
|
return newPayload;
|
|
602
636
|
}
|
|
603
637
|
|
|
604
|
-
/**
|
|
605
|
-
* Converts color end tag to be valid for xml parsing
|
|
606
|
-
* For example,
|
|
607
|
-
* input: <c.yellow.bg_blue>Yellow text on blue bg</c>
|
|
608
|
-
* output: <c.yellow.bg_blue>Yellow text on blue bg</c.yellow.bg_blue>
|
|
609
|
-
*
|
|
610
|
-
* Returns original payload if invalid tag is found.
|
|
611
|
-
* Invalid tag example: <c.yellow><b>Example</c></b>
|
|
612
|
-
*
|
|
613
|
-
* @param {string} payload
|
|
614
|
-
* @return {string} processed payload
|
|
615
|
-
* @private
|
|
616
|
-
*/
|
|
617
|
-
static replaceColorPayload_(payload) {
|
|
618
|
-
const names = [];
|
|
619
|
-
let nameStart = -1;
|
|
620
|
-
let newPayload = '';
|
|
621
|
-
for (let i = 0; i < payload.length; i++) {
|
|
622
|
-
if (payload[i] === '/' && i > 0 && payload[i - 1] === '<') {
|
|
623
|
-
const end = payload.indexOf('>', i);
|
|
624
|
-
if (end <= i) {
|
|
625
|
-
return payload;
|
|
626
|
-
}
|
|
627
|
-
const tagEnd = payload.substring(i + 1, end);
|
|
628
|
-
if (!tagEnd || tagEnd !== 'c') {
|
|
629
|
-
newPayload += payload[i];
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
|
-
const tagStart = names.pop();
|
|
633
|
-
if (!tagStart) {
|
|
634
|
-
newPayload += payload[i];
|
|
635
|
-
} else if (tagStart === tagEnd) {
|
|
636
|
-
newPayload += '/' + tagEnd + '>';
|
|
637
|
-
i += tagEnd.length + 1;
|
|
638
|
-
} else {
|
|
639
|
-
if (!tagStart.startsWith('c.')) {
|
|
640
|
-
newPayload += payload[i];
|
|
641
|
-
continue;
|
|
642
|
-
}
|
|
643
|
-
i += tagEnd.length + 1;
|
|
644
|
-
newPayload += '/' + tagStart + '>';
|
|
645
|
-
}
|
|
646
|
-
} else {
|
|
647
|
-
if (payload[i] === '<') {
|
|
648
|
-
nameStart = i + 1;
|
|
649
|
-
if (payload[nameStart] != 'c') {
|
|
650
|
-
nameStart = -1;
|
|
651
|
-
}
|
|
652
|
-
} else if (payload[i] === '>') {
|
|
653
|
-
if (nameStart > 0) {
|
|
654
|
-
names.push(payload.substr(nameStart, i - nameStart));
|
|
655
|
-
nameStart = -1;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
newPayload += payload[i];
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
return newPayload;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
638
|
/**
|
|
665
639
|
* @param {string} value
|
|
666
640
|
* @param {string} defaultValue
|
|
@@ -708,13 +682,14 @@ shaka.text.VttTextParser = class {
|
|
|
708
682
|
}
|
|
709
683
|
|
|
710
684
|
/**
|
|
711
|
-
* @param {!Node} element
|
|
685
|
+
* @param {!shaka.extern.xml.Node} element
|
|
712
686
|
* @param {!shaka.text.Cue} rootCue
|
|
713
687
|
* @param {!Map.<string, shaka.text.Cue>} styles
|
|
714
688
|
* @private
|
|
715
689
|
*/
|
|
716
690
|
static generateCueFromElement_(element, rootCue, styles) {
|
|
717
691
|
const VttTextParser = shaka.text.VttTextParser;
|
|
692
|
+
const TXml = shaka.util.TXml;
|
|
718
693
|
const nestedCue = rootCue.clone();
|
|
719
694
|
// We don't want propagate some properties.
|
|
720
695
|
nestedCue.nestedCues = [];
|
|
@@ -725,11 +700,12 @@ shaka.text.VttTextParser = class {
|
|
|
725
700
|
nestedCue.region = new shaka.text.CueRegion();
|
|
726
701
|
nestedCue.position = null;
|
|
727
702
|
nestedCue.size = 0;
|
|
728
|
-
|
|
703
|
+
|
|
704
|
+
if (shaka.util.TXml.isNode(element)) {
|
|
729
705
|
const bold = shaka.text.Cue.fontWeight.BOLD;
|
|
730
706
|
const italic = shaka.text.Cue.fontStyle.ITALIC;
|
|
731
707
|
const underline = shaka.text.Cue.textDecoration.UNDERLINE;
|
|
732
|
-
const tags = element.
|
|
708
|
+
const tags = element.tagName.split(/(?=[ .])+/g);
|
|
733
709
|
for (const tag of tags) {
|
|
734
710
|
let styleTag = tag;
|
|
735
711
|
// White blanks at start indicate that the style is a voice
|
|
@@ -762,15 +738,14 @@ shaka.text.VttTextParser = class {
|
|
|
762
738
|
nestedCue.textDecoration.push(underline);
|
|
763
739
|
break;
|
|
764
740
|
case 'font': {
|
|
765
|
-
const color =
|
|
766
|
-
/** @type {!Element} */(element).getAttribute('color');
|
|
741
|
+
const color = element.attributes['color'];
|
|
767
742
|
if (color) {
|
|
768
743
|
nestedCue.color = color;
|
|
769
744
|
}
|
|
770
745
|
break;
|
|
771
746
|
}
|
|
772
747
|
case 'div': {
|
|
773
|
-
const time =
|
|
748
|
+
const time = element.attributes['time'];
|
|
774
749
|
if (!time) {
|
|
775
750
|
break;
|
|
776
751
|
}
|
|
@@ -792,13 +767,13 @@ shaka.text.VttTextParser = class {
|
|
|
792
767
|
}
|
|
793
768
|
}
|
|
794
769
|
|
|
795
|
-
const isTextNode = (item) => shaka.util.
|
|
796
|
-
const childNodes = element.
|
|
770
|
+
const isTextNode = (item) => shaka.util.TXml.isText(item);
|
|
771
|
+
const childNodes = element.children;
|
|
797
772
|
if (isTextNode(element) ||
|
|
798
773
|
(childNodes.length == 1 && isTextNode(childNodes[0]))) {
|
|
799
774
|
// Trailing line breaks may lost when convert cue to HTML tag
|
|
800
775
|
// Need to insert line break cue to preserve line breaks
|
|
801
|
-
const textArr = element.
|
|
776
|
+
const textArr = TXml.getTextContents(element).split('\n');
|
|
802
777
|
let isFirst = true;
|
|
803
778
|
for (const text of textArr) {
|
|
804
779
|
if (!isFirst) {
|
|
@@ -808,7 +783,7 @@ shaka.text.VttTextParser = class {
|
|
|
808
783
|
}
|
|
809
784
|
if (text.length > 0) {
|
|
810
785
|
const textCue = nestedCue.clone();
|
|
811
|
-
textCue.payload =
|
|
786
|
+
textCue.payload = shaka.util.StringUtils.htmlUnescape(text);
|
|
812
787
|
rootCue.nestedCues.push(textCue);
|
|
813
788
|
}
|
|
814
789
|
isFirst = false;
|
|
@@ -1024,42 +999,6 @@ shaka.text.VttTextParser = class {
|
|
|
1024
999
|
|
|
1025
1000
|
return (milliseconds / 1000) + seconds + (minutes * 60) + (hours * 3600);
|
|
1026
1001
|
}
|
|
1027
|
-
|
|
1028
|
-
/**
|
|
1029
|
-
* This method converts the HTML entities &, <, >, ", ',
|
|
1030
|
-
* , ‎ and ‏ in string to their corresponding characters.
|
|
1031
|
-
*
|
|
1032
|
-
* @param {!string} input
|
|
1033
|
-
* @return {string}
|
|
1034
|
-
* @private
|
|
1035
|
-
*/
|
|
1036
|
-
static htmlUnescape_(input) {
|
|
1037
|
-
// Used to map HTML entities to characters.
|
|
1038
|
-
const htmlUnescapes = {
|
|
1039
|
-
'&': '&',
|
|
1040
|
-
'<': '<',
|
|
1041
|
-
'>': '>',
|
|
1042
|
-
'"': '"',
|
|
1043
|
-
''': '\'',
|
|
1044
|
-
' ': '\u{a0}',
|
|
1045
|
-
'‎': '\u{200e}',
|
|
1046
|
-
'‏': '\u{200f}',
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
// Used to match HTML entities and HTML characters.
|
|
1050
|
-
const reEscapedHtml = /&(?:amp|lt|gt|quot|#(0+)?39|nbsp|lrm|rlm);/g;
|
|
1051
|
-
const reHasEscapedHtml = RegExp(reEscapedHtml.source);
|
|
1052
|
-
// This check is an optimization, since replace always makes a copy
|
|
1053
|
-
if (input && reHasEscapedHtml.test(input)) {
|
|
1054
|
-
return input.replace(reEscapedHtml, (entity) => {
|
|
1055
|
-
// The only thing that might not match the dictionary above is the
|
|
1056
|
-
// single quote, which can be matched by many strings in the regex, but
|
|
1057
|
-
// only has a single entry in the dictionary.
|
|
1058
|
-
return htmlUnescapes[entity] || '\'';
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
return input || '';
|
|
1062
|
-
}
|
|
1063
1002
|
};
|
|
1064
1003
|
|
|
1065
1004
|
/**
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.provide('shaka.transmuxer.Opus');
|
|
8
|
+
|
|
9
|
+
goog.requireType('shaka.util.TsParser');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Opus utils
|
|
14
|
+
*/
|
|
15
|
+
shaka.transmuxer.Opus = class {
|
|
16
|
+
/**
|
|
17
|
+
* @param {!shaka.util.TsParser.OpusMetadata} metadata
|
|
18
|
+
* @return {!Uint8Array}
|
|
19
|
+
*/
|
|
20
|
+
static getAudioConfig(metadata) {
|
|
21
|
+
let mapping = [];
|
|
22
|
+
switch (metadata.channelConfigCode) {
|
|
23
|
+
case 0x01:
|
|
24
|
+
case 0x02:
|
|
25
|
+
mapping = [0x0];
|
|
26
|
+
break;
|
|
27
|
+
case 0x00: // dualmono
|
|
28
|
+
mapping = [0xFF, 1, 1, 0, 1];
|
|
29
|
+
break;
|
|
30
|
+
case 0x80: // dualmono
|
|
31
|
+
mapping = [0xFF, 2, 0, 0, 1];
|
|
32
|
+
break;
|
|
33
|
+
case 0x03:
|
|
34
|
+
mapping = [0x01, 2, 1, 0, 2, 1];
|
|
35
|
+
break;
|
|
36
|
+
case 0x04:
|
|
37
|
+
mapping = [0x01, 2, 2, 0, 1, 2, 3];
|
|
38
|
+
break;
|
|
39
|
+
case 0x05:
|
|
40
|
+
mapping = [0x01, 3, 2, 0, 4, 1, 2, 3];
|
|
41
|
+
break;
|
|
42
|
+
case 0x06:
|
|
43
|
+
mapping = [0x01, 4, 2, 0, 4, 1, 2, 3, 5];
|
|
44
|
+
break;
|
|
45
|
+
case 0x07:
|
|
46
|
+
mapping = [0x01, 4, 2, 0, 4, 1, 2, 3, 5, 6];
|
|
47
|
+
break;
|
|
48
|
+
case 0x08:
|
|
49
|
+
mapping = [0x01, 5, 3, 0, 6, 1, 2, 3, 4, 5, 7];
|
|
50
|
+
break;
|
|
51
|
+
case 0x82:
|
|
52
|
+
mapping = [0x01, 1, 2, 0, 1];
|
|
53
|
+
break;
|
|
54
|
+
case 0x83:
|
|
55
|
+
mapping = [0x01, 1, 3, 0, 1, 2];
|
|
56
|
+
break;
|
|
57
|
+
case 0x84:
|
|
58
|
+
mapping = [0x01, 1, 4, 0, 1, 2, 3];
|
|
59
|
+
break;
|
|
60
|
+
case 0x85:
|
|
61
|
+
mapping = [0x01, 1, 5, 0, 1, 2, 3, 4];
|
|
62
|
+
break;
|
|
63
|
+
case 0x86:
|
|
64
|
+
mapping = [0x01, 1, 6, 0, 1, 2, 3, 4, 5];
|
|
65
|
+
break;
|
|
66
|
+
case 0x87:
|
|
67
|
+
mapping = [0x01, 1, 7, 0, 1, 2, 3, 4, 5, 6];
|
|
68
|
+
break;
|
|
69
|
+
case 0x88:
|
|
70
|
+
mapping = [0x01, 1, 8, 0, 1, 2, 3, 4, 5, 6, 7];
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return new Uint8Array([
|
|
75
|
+
0x00, // Version (1)
|
|
76
|
+
metadata.channelCount, // OutputChannelCount: 2
|
|
77
|
+
0x00, 0x00, // PreSkip: 2
|
|
78
|
+
(metadata.sampleRate >>> 24) & 0xFF, // Audio sample rate: 4
|
|
79
|
+
(metadata.sampleRate >>> 17) & 0xFF,
|
|
80
|
+
(metadata.sampleRate >>> 8) & 0xFF,
|
|
81
|
+
(metadata.sampleRate >>> 0) & 0xFF,
|
|
82
|
+
0x00, 0x00, // Global Gain : 2
|
|
83
|
+
...mapping,
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @const {number}
|
|
90
|
+
*/
|
|
91
|
+
shaka.transmuxer.Opus.OPUS_AUDIO_SAMPLE_PER_FRAME = 960;
|
|
@@ -14,6 +14,7 @@ goog.require('shaka.transmuxer.Ec3');
|
|
|
14
14
|
goog.require('shaka.transmuxer.H264');
|
|
15
15
|
goog.require('shaka.transmuxer.H265');
|
|
16
16
|
goog.require('shaka.transmuxer.MpegAudio');
|
|
17
|
+
goog.require('shaka.transmuxer.Opus');
|
|
17
18
|
goog.require('shaka.transmuxer.TransmuxerEngine');
|
|
18
19
|
goog.require('shaka.util.BufferUtils');
|
|
19
20
|
goog.require('shaka.util.Error');
|
|
@@ -274,6 +275,10 @@ shaka.transmuxer.TsTransmuxer = class {
|
|
|
274
275
|
streamInfo =
|
|
275
276
|
this.getMp3StreamInfo_(tsParser, stream, duration);
|
|
276
277
|
break;
|
|
278
|
+
case 'opus':
|
|
279
|
+
streamInfo =
|
|
280
|
+
this.getOpusStreamInfo_(tsParser, stream, duration);
|
|
281
|
+
break;
|
|
277
282
|
}
|
|
278
283
|
if (streamInfo) {
|
|
279
284
|
streamInfos.push(streamInfo);
|
|
@@ -734,6 +739,115 @@ shaka.transmuxer.TsTransmuxer = class {
|
|
|
734
739
|
}
|
|
735
740
|
|
|
736
741
|
|
|
742
|
+
/**
|
|
743
|
+
* @param {shaka.util.TsParser} tsParser
|
|
744
|
+
* @param {shaka.extern.Stream} stream
|
|
745
|
+
* @param {number} duration
|
|
746
|
+
* @return {shaka.util.Mp4Generator.StreamInfo}
|
|
747
|
+
* @private
|
|
748
|
+
*/
|
|
749
|
+
getOpusStreamInfo_(tsParser, stream, duration) {
|
|
750
|
+
const Opus = shaka.transmuxer.Opus;
|
|
751
|
+
const timescale = shaka.util.TsParser.Timescale;
|
|
752
|
+
|
|
753
|
+
/** @type {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
|
|
754
|
+
const samples = [];
|
|
755
|
+
|
|
756
|
+
let firstPts = null;
|
|
757
|
+
|
|
758
|
+
/** @type {?shaka.util.TsParser.OpusMetadata} */
|
|
759
|
+
const opusMetadata = tsParser.getOpusMetadata();
|
|
760
|
+
|
|
761
|
+
if (!opusMetadata) {
|
|
762
|
+
throw new shaka.util.Error(
|
|
763
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
764
|
+
shaka.util.Error.Category.MEDIA,
|
|
765
|
+
shaka.util.Error.Code.TRANSMUXING_FAILED);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** @type {!Uint8Array} */
|
|
769
|
+
const audioConfig = Opus.getAudioConfig(opusMetadata);
|
|
770
|
+
|
|
771
|
+
/** @type {number} */
|
|
772
|
+
const sampleRate = opusMetadata.sampleRate;
|
|
773
|
+
|
|
774
|
+
for (const audioData of tsParser.getAudioData()) {
|
|
775
|
+
const data = audioData.data;
|
|
776
|
+
if (firstPts == null && audioData.pts !== null) {
|
|
777
|
+
firstPts = audioData.pts;
|
|
778
|
+
}
|
|
779
|
+
let offset = 0;
|
|
780
|
+
while (offset < data.length) {
|
|
781
|
+
const opusPendingTrimStart = (data[offset + 1] & 0x10) !== 0;
|
|
782
|
+
const trimEnd = (data[offset + 1] & 0x08) !== 0;
|
|
783
|
+
let index = offset + 2;
|
|
784
|
+
let size = 0;
|
|
785
|
+
|
|
786
|
+
while (data[index] === 0xFF) {
|
|
787
|
+
size += 255;
|
|
788
|
+
index += 1;
|
|
789
|
+
}
|
|
790
|
+
size += data[index];
|
|
791
|
+
index += 1;
|
|
792
|
+
index += opusPendingTrimStart ? 2 : 0;
|
|
793
|
+
index += trimEnd ? 2 : 0;
|
|
794
|
+
|
|
795
|
+
const sample = data.slice(index, index + size);
|
|
796
|
+
|
|
797
|
+
samples.push({
|
|
798
|
+
data: sample,
|
|
799
|
+
size: sample.byteLength,
|
|
800
|
+
duration: Opus.OPUS_AUDIO_SAMPLE_PER_FRAME,
|
|
801
|
+
cts: 0,
|
|
802
|
+
flags: {
|
|
803
|
+
isLeading: 0,
|
|
804
|
+
isDependedOn: 0,
|
|
805
|
+
hasRedundancy: 0,
|
|
806
|
+
degradPrio: 0,
|
|
807
|
+
dependsOn: 2,
|
|
808
|
+
isNonSync: 0,
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
offset = index + size;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (audioConfig.byteLength == 0 || firstPts == null) {
|
|
816
|
+
throw new shaka.util.Error(
|
|
817
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
818
|
+
shaka.util.Error.Category.MEDIA,
|
|
819
|
+
shaka.util.Error.Code.TRANSMUXING_FAILED);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
stream.audioSamplingRate = opusMetadata.sampleRate;
|
|
823
|
+
stream.channelsCount = opusMetadata.channelCount;
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
/** @type {number} */
|
|
827
|
+
const baseMediaDecodeTime = firstPts / timescale * sampleRate;
|
|
828
|
+
|
|
829
|
+
return {
|
|
830
|
+
id: stream.id,
|
|
831
|
+
type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
|
|
832
|
+
codecs: 'opus',
|
|
833
|
+
encrypted: stream.encrypted && stream.drmInfos.length > 0,
|
|
834
|
+
timescale: sampleRate,
|
|
835
|
+
duration: duration,
|
|
836
|
+
videoNalus: [],
|
|
837
|
+
audioConfig: audioConfig,
|
|
838
|
+
videoConfig: new Uint8Array([]),
|
|
839
|
+
hSpacing: 0,
|
|
840
|
+
vSpacing: 0,
|
|
841
|
+
data: {
|
|
842
|
+
sequenceNumber: this.frameIndex_,
|
|
843
|
+
baseMediaDecodeTime: baseMediaDecodeTime,
|
|
844
|
+
samples: samples,
|
|
845
|
+
},
|
|
846
|
+
stream: stream,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
|
|
737
851
|
/**
|
|
738
852
|
* @param {shaka.util.TsParser} tsParser
|
|
739
853
|
* @param {shaka.extern.Stream} stream
|
|
@@ -933,6 +1047,7 @@ shaka.transmuxer.TsTransmuxer.SUPPORTED_AUDIO_CODECS_ = [
|
|
|
933
1047
|
'ac-3',
|
|
934
1048
|
'ec-3',
|
|
935
1049
|
'mp3',
|
|
1050
|
+
'opus',
|
|
936
1051
|
];
|
|
937
1052
|
|
|
938
1053
|
/**
|
package/lib/util/cmcd_manager.js
CHANGED
|
@@ -9,6 +9,9 @@ goog.provide('shaka.util.CmcdManager');
|
|
|
9
9
|
goog.require('goog.Uri');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
11
|
goog.require('shaka.net.NetworkingEngine');
|
|
12
|
+
goog.require('shaka.util.ArrayUtils');
|
|
13
|
+
|
|
14
|
+
goog.requireType('shaka.media.SegmentReference');
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* @summary
|
|
@@ -186,11 +189,49 @@ shaka.util.CmcdManager = class {
|
|
|
186
189
|
if (stream) {
|
|
187
190
|
if (isMedia) {
|
|
188
191
|
data.bl = this.getBufferLength_(stream.type);
|
|
192
|
+
if (data.ot !== ObjectType.TIMED_TEXT) {
|
|
193
|
+
const remainingBufferLength =
|
|
194
|
+
this.getRemainingBufferLength_(stream.type);
|
|
195
|
+
const playbackRate = this.playerInterface_.getPlaybackRate();
|
|
196
|
+
if (playbackRate) {
|
|
197
|
+
data.dl = remainingBufferLength / Math.abs(playbackRate);
|
|
198
|
+
} else {
|
|
199
|
+
data.dl = remainingBufferLength;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
189
202
|
}
|
|
190
203
|
|
|
191
204
|
if (stream.bandwidth) {
|
|
192
205
|
data.br = stream.bandwidth / 1000;
|
|
193
206
|
}
|
|
207
|
+
|
|
208
|
+
if (stream.segmentIndex && segment) {
|
|
209
|
+
const iterator = stream.segmentIndex.getIteratorForTime(
|
|
210
|
+
segment.endTime, /* allowNonIndepedent= */ true);
|
|
211
|
+
if (iterator) {
|
|
212
|
+
const nextSegment = iterator.next().value;
|
|
213
|
+
if (nextSegment && nextSegment != segment) {
|
|
214
|
+
if (!shaka.util.ArrayUtils.equal(
|
|
215
|
+
segment.getUris(), nextSegment.getUris())) {
|
|
216
|
+
data.nor = this.urlToRelativePath_(
|
|
217
|
+
nextSegment.getUris()[0], request.uris[0]);
|
|
218
|
+
}
|
|
219
|
+
if ((nextSegment.startByte || nextSegment.endByte) &&
|
|
220
|
+
(segment.startByte != nextSegment.startByte ||
|
|
221
|
+
segment.endByte != nextSegment.endByte)) {
|
|
222
|
+
let range = nextSegment.startByte + '-';
|
|
223
|
+
if (nextSegment.endByte) {
|
|
224
|
+
range += nextSegment.endByte;
|
|
225
|
+
}
|
|
226
|
+
data.nrr = range;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const rtp = this.calculateRtp_(stream, segment);
|
|
231
|
+
if (!isNaN(rtp)) {
|
|
232
|
+
data.rtp = rtp;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
194
235
|
}
|
|
195
236
|
|
|
196
237
|
if (isMedia && data.ot !== ObjectType.TIMED_TEXT) {
|
|
@@ -328,17 +369,17 @@ shaka.util.CmcdManager = class {
|
|
|
328
369
|
data.su = this.buffering_;
|
|
329
370
|
}
|
|
330
371
|
|
|
331
|
-
|
|
372
|
+
const output = this.filterKeys_(data);
|
|
332
373
|
|
|
333
374
|
if (useHeaders) {
|
|
334
|
-
const headers = shaka.util.CmcdManager.toHeaders(
|
|
375
|
+
const headers = shaka.util.CmcdManager.toHeaders(output);
|
|
335
376
|
if (!Object.keys(headers).length) {
|
|
336
377
|
return;
|
|
337
378
|
}
|
|
338
379
|
|
|
339
380
|
Object.assign(request.headers, headers);
|
|
340
381
|
} else {
|
|
341
|
-
const query = shaka.util.CmcdManager.toQuery(
|
|
382
|
+
const query = shaka.util.CmcdManager.toQuery(output);
|
|
342
383
|
if (!query) {
|
|
343
384
|
return;
|
|
344
385
|
}
|
|
@@ -349,6 +390,29 @@ shaka.util.CmcdManager = class {
|
|
|
349
390
|
}
|
|
350
391
|
}
|
|
351
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Filter the CMCD data object to include only the keys specified in the
|
|
395
|
+
* configuration.
|
|
396
|
+
*
|
|
397
|
+
* @param {CmcdData} data
|
|
398
|
+
* @return {CmcdData}
|
|
399
|
+
* @private
|
|
400
|
+
*/
|
|
401
|
+
filterKeys_(data) {
|
|
402
|
+
const includeKeys = this.config_.includeKeys;
|
|
403
|
+
|
|
404
|
+
if (!includeKeys.length) {
|
|
405
|
+
return data;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return Object.keys(data).reduce((acc, key) => {
|
|
409
|
+
if (includeKeys.includes(key)) {
|
|
410
|
+
acc[key] = data[key];
|
|
411
|
+
}
|
|
412
|
+
return acc;
|
|
413
|
+
}, {});
|
|
414
|
+
}
|
|
415
|
+
|
|
352
416
|
/**
|
|
353
417
|
* The CMCD object type.
|
|
354
418
|
*
|
|
@@ -451,6 +515,91 @@ shaka.util.CmcdManager = class {
|
|
|
451
515
|
return (range.end - start) * 1000;
|
|
452
516
|
}
|
|
453
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Get the remaining buffer length for a media type in milliseconds
|
|
520
|
+
*
|
|
521
|
+
* @param {string} type
|
|
522
|
+
* @return {number}
|
|
523
|
+
* @private
|
|
524
|
+
*/
|
|
525
|
+
getRemainingBufferLength_(type) {
|
|
526
|
+
const ranges = this.playerInterface_.getBufferedInfo()[type];
|
|
527
|
+
|
|
528
|
+
if (!ranges.length) {
|
|
529
|
+
return 0;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const start = this.playerInterface_.getCurrentTime();
|
|
533
|
+
const range = ranges.find((r) => r.start <= start && r.end >= start);
|
|
534
|
+
|
|
535
|
+
if (!range) {
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return (range.end - start) * 1000;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Constructs a relative path from a URL
|
|
544
|
+
*
|
|
545
|
+
* @param {string} url
|
|
546
|
+
* @param {string} base
|
|
547
|
+
* @return {string}
|
|
548
|
+
* @private
|
|
549
|
+
*/
|
|
550
|
+
urlToRelativePath_(url, base) {
|
|
551
|
+
const to = new URL(url);
|
|
552
|
+
const from = new URL(base);
|
|
553
|
+
|
|
554
|
+
if (to.origin !== from.origin) {
|
|
555
|
+
return url;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const toPath = to.pathname.split('/').slice(1);
|
|
559
|
+
const fromPath = from.pathname.split('/').slice(1, -1);
|
|
560
|
+
|
|
561
|
+
// remove common parents
|
|
562
|
+
while (toPath[0] === fromPath[0]) {
|
|
563
|
+
toPath.shift();
|
|
564
|
+
fromPath.shift();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// add back paths
|
|
568
|
+
while (fromPath.length) {
|
|
569
|
+
fromPath.shift();
|
|
570
|
+
toPath.unshift('..');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return toPath.join('/');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Calculate requested maximun throughput
|
|
578
|
+
*
|
|
579
|
+
* @param {shaka.extern.Stream} stream
|
|
580
|
+
* @param {shaka.media.SegmentReference} segment
|
|
581
|
+
* @return {number}
|
|
582
|
+
* @private
|
|
583
|
+
*/
|
|
584
|
+
calculateRtp_(stream, segment) {
|
|
585
|
+
const playbackRate = this.playerInterface_.getPlaybackRate() || 1;
|
|
586
|
+
const currentBufferLevel =
|
|
587
|
+
this.getRemainingBufferLength_(stream.type) || 500;
|
|
588
|
+
const bandwidth = stream.bandwidth;
|
|
589
|
+
if (!bandwidth) {
|
|
590
|
+
return NaN;
|
|
591
|
+
}
|
|
592
|
+
const segmentDuration = segment.endTime - segment.startTime;
|
|
593
|
+
// Calculate file size in kilobits
|
|
594
|
+
const segmentSize = bandwidth * segmentDuration / 1000;
|
|
595
|
+
// Calculate time available to load file in seconds
|
|
596
|
+
const timeToLoad = (currentBufferLevel / playbackRate) / 1000;
|
|
597
|
+
// Calculate the exact bandwidth required
|
|
598
|
+
const minBandwidth = segmentSize / timeToLoad;
|
|
599
|
+
// Include a safety buffer
|
|
600
|
+
return minBandwidth * this.config_.rtpSafetyFactor;
|
|
601
|
+
}
|
|
602
|
+
|
|
454
603
|
/**
|
|
455
604
|
* Get the stream format
|
|
456
605
|
*
|