shaka-player 4.2.10 → 4.2.12
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/appspot.yaml +39 -0
- package/.github/workflows/demo-version-index.yaml +3 -3
- package/CHANGELOG.md +42 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +3 -0
- package/demo/custom.js +1 -1
- package/demo/demo.less +1 -0
- package/dist/demo.compiled.debug.js +2 -2
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +2 -2
- package/dist/demo.compiled.map +2 -2
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +2 -2
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.d.ts +30 -0
- package/dist/shaka-player.compiled.debug.d.ts +30 -0
- package/dist/shaka-player.compiled.debug.externs.js +9 -0
- package/dist/shaka-player.compiled.debug.js +250 -246
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +9 -0
- package/dist/shaka-player.compiled.js +789 -787
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +30 -0
- package/dist/shaka-player.ui.debug.d.ts +30 -0
- package/dist/shaka-player.ui.debug.externs.js +9 -0
- package/dist/shaka-player.ui.debug.js +290 -285
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +9 -0
- package/dist/shaka-player.ui.js +943 -941
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/drm-config.md +9 -6
- package/docs/tutorials/welcome.md +1 -1
- package/externs/ima.js +34 -0
- package/lib/abr/simple_abr_manager.js +1 -1
- package/lib/cea/mp4_cea_parser.js +30 -14
- package/lib/dash/dash_parser.js +18 -3
- package/lib/hls/hls_parser.js +19 -2
- package/lib/hls/manifest_text_parser.js +10 -1
- package/lib/media/drm_engine.js +23 -14
- package/lib/media/gap_jumping_controller.js +24 -2
- package/lib/media/media_source_engine.js +10 -6
- package/lib/media/playhead.js +11 -0
- package/lib/media/region_observer.js +19 -1
- package/lib/media/segment_reference.js +22 -0
- package/lib/media/streaming_engine.js +16 -7
- package/lib/media/video_wrapper.js +6 -1
- package/lib/player.js +13 -4
- package/lib/polyfill/abort_controller.js +7 -0
- package/lib/text/vtt_text_parser.js +2 -1
- package/lib/util/manifest_parser_utils.js +1 -1
- package/lib/util/mp4_box_parsers.js +8 -3
- package/lib/util/stream_utils.js +1 -0
- package/package.json +9 -4
- package/test/cea/mp4_cea_parser_unit.js +26 -3
- package/test/dash/dash_parser_manifest_unit.js +120 -0
- package/test/hls/hls_live_unit.js +3 -1
- package/test/hls/hls_parser_unit.js +150 -0
- package/test/media/media_source_engine_unit.js +31 -0
- package/test/media/region_observer_unit.js +2 -1
- package/test/test/assets/multiple-trun-init.mp4 +0 -0
- package/test/test/assets/multiple-trun-segment.mp4 +0 -0
- package/test/test/util/layout_tests.js +4 -3
- package/ui/resolution_selection.js +7 -1
|
@@ -640,10 +640,15 @@ shaka.media.StreamingEngine = class {
|
|
|
640
640
|
const mediaState = this.mediaStates_.get(type);
|
|
641
641
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
642
642
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
643
|
+
let segment = null;
|
|
644
|
+
if (mediaState.segmentIterator) {
|
|
645
|
+
segment = mediaState.segmentIterator.current();
|
|
646
|
+
}
|
|
647
|
+
// Only reset the iterator if we seek outside the current segment.
|
|
648
|
+
if (!segment || segment.startTime > presentationTime ||
|
|
649
|
+
segment.endTime < presentationTime) {
|
|
650
|
+
mediaState.segmentIterator = null;
|
|
651
|
+
}
|
|
647
652
|
|
|
648
653
|
if (!newTimeIsBuffered(type)) {
|
|
649
654
|
const bufferEnd =
|
|
@@ -1303,7 +1308,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1303
1308
|
const dataToAppend = remaining.subarray(0, offset);
|
|
1304
1309
|
remaining = remaining.subarray(offset);
|
|
1305
1310
|
await this.append_(
|
|
1306
|
-
mediaState, presentationTime, stream, reference, dataToAppend
|
|
1311
|
+
mediaState, presentationTime, stream, reference, dataToAppend,
|
|
1312
|
+
/* isChunkedData= */ true);
|
|
1307
1313
|
}
|
|
1308
1314
|
};
|
|
1309
1315
|
|
|
@@ -1691,10 +1697,12 @@ shaka.media.StreamingEngine = class {
|
|
|
1691
1697
|
* @param {shaka.extern.Stream} stream
|
|
1692
1698
|
* @param {!shaka.media.SegmentReference} reference
|
|
1693
1699
|
* @param {BufferSource} segment
|
|
1700
|
+
* @param {boolean=} isChunkedData
|
|
1694
1701
|
* @return {!Promise}
|
|
1695
1702
|
* @private
|
|
1696
1703
|
*/
|
|
1697
|
-
async append_(mediaState, presentationTime, stream, reference, segment
|
|
1704
|
+
async append_(mediaState, presentationTime, stream, reference, segment,
|
|
1705
|
+
isChunkedData = false) {
|
|
1698
1706
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
1699
1707
|
|
|
1700
1708
|
const hasClosedCaptions = stream.closedCaptions &&
|
|
@@ -1727,7 +1735,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1727
1735
|
reference,
|
|
1728
1736
|
hasClosedCaptions,
|
|
1729
1737
|
seeked,
|
|
1730
|
-
adaptation
|
|
1738
|
+
adaptation,
|
|
1739
|
+
isChunkedData);
|
|
1731
1740
|
this.destroyer_.ensureNotDestroyed();
|
|
1732
1741
|
shaka.log.v2(logPrefix, 'appended media segment');
|
|
1733
1742
|
}
|
|
@@ -27,15 +27,19 @@ shaka.media.VideoWrapper = class {
|
|
|
27
27
|
/**
|
|
28
28
|
* @param {!HTMLMediaElement} video
|
|
29
29
|
* @param {function()} onSeek Called when the video seeks.
|
|
30
|
+
* @param {function(number)} onStarted Called when the video has started.
|
|
30
31
|
* @param {number} startTime The time to start at.
|
|
31
32
|
*/
|
|
32
|
-
constructor(video, onSeek, startTime) {
|
|
33
|
+
constructor(video, onSeek, onStarted, startTime) {
|
|
33
34
|
/** @private {HTMLMediaElement} */
|
|
34
35
|
this.video_ = video;
|
|
35
36
|
|
|
36
37
|
/** @private {function()} */
|
|
37
38
|
this.onSeek_ = onSeek;
|
|
38
39
|
|
|
40
|
+
/** @private {function(number)} */
|
|
41
|
+
this.onStarted_ = onStarted;
|
|
42
|
+
|
|
39
43
|
/** @private {number} */
|
|
40
44
|
this.startTime_ = startTime;
|
|
41
45
|
|
|
@@ -165,6 +169,7 @@ shaka.media.VideoWrapper = class {
|
|
|
165
169
|
this.started_ = true;
|
|
166
170
|
|
|
167
171
|
this.eventManager_.listen(this.video_, 'seeking', () => this.onSeek_());
|
|
172
|
+
this.onStarted_(this.video_.currentTime);
|
|
168
173
|
}
|
|
169
174
|
};
|
|
170
175
|
|
package/lib/player.js
CHANGED
|
@@ -1996,7 +1996,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1996
1996
|
this.abrManager_.setMediaElement(mediaElement);
|
|
1997
1997
|
|
|
1998
1998
|
this.playhead_ = this.createPlayhead(has.startTime);
|
|
1999
|
-
this.playheadObservers_ =
|
|
1999
|
+
this.playheadObservers_ =
|
|
2000
|
+
this.createPlayheadObserversForMSE_(has.startTimeOfLoad);
|
|
2000
2001
|
|
|
2001
2002
|
// We need to start the buffer management code near the end because it will
|
|
2002
2003
|
// set the initial buffering state and that depends on other components
|
|
@@ -2761,17 +2762,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2761
2762
|
* Create the observers for MSE playback. These observers are responsible for
|
|
2762
2763
|
* notifying the app and player of specific events during MSE playback.
|
|
2763
2764
|
*
|
|
2765
|
+
* @param {number} startTimeOfLoad
|
|
2764
2766
|
* @return {!shaka.media.PlayheadObserverManager}
|
|
2765
2767
|
* @private
|
|
2766
2768
|
*/
|
|
2767
|
-
createPlayheadObserversForMSE_() {
|
|
2769
|
+
createPlayheadObserversForMSE_(startTimeOfLoad) {
|
|
2768
2770
|
goog.asserts.assert(this.manifest_, 'Must have manifest');
|
|
2769
2771
|
goog.asserts.assert(this.regionTimeline_, 'Must have region timeline');
|
|
2770
2772
|
goog.asserts.assert(this.video_, 'Must have video element');
|
|
2771
2773
|
|
|
2774
|
+
const startsPastZero = this.isLive() || startTimeOfLoad > 0;
|
|
2775
|
+
|
|
2772
2776
|
// Create the region observer. This will allow us to notify the app when we
|
|
2773
2777
|
// move in and out of timeline regions.
|
|
2774
|
-
const regionObserver = new shaka.media.RegionObserver(
|
|
2778
|
+
const regionObserver = new shaka.media.RegionObserver(
|
|
2779
|
+
this.regionTimeline_, startsPastZero);
|
|
2775
2780
|
|
|
2776
2781
|
regionObserver.addEventListener('enter', (event) => {
|
|
2777
2782
|
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
@@ -5686,6 +5691,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5686
5691
|
error.category != shaka.util.Error.Category.NETWORK) {
|
|
5687
5692
|
return false;
|
|
5688
5693
|
}
|
|
5694
|
+
if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE) {
|
|
5695
|
+
return false;
|
|
5696
|
+
}
|
|
5697
|
+
goog.asserts.assert(this.manifest_, 'Should have manifest!');
|
|
5689
5698
|
|
|
5690
5699
|
if (!navigator.onLine) {
|
|
5691
5700
|
// Don't restrict variants if we're completely offline, or else we end up
|
|
@@ -6626,7 +6635,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6626
6635
|
* @define {string} A version number taken from git at compile time.
|
|
6627
6636
|
* @export
|
|
6628
6637
|
*/
|
|
6629
|
-
shaka.Player.version = 'v4.2.
|
|
6638
|
+
shaka.Player.version = 'v4.2.12-uncompiled';
|
|
6630
6639
|
|
|
6631
6640
|
// Initialize the deprecation system using the version string we just set
|
|
6632
6641
|
// on the player.
|
|
@@ -785,7 +785,8 @@ shaka.text.VttTextParser = class {
|
|
|
785
785
|
} else if ((results = /^size:([\d.]+)%$/.exec(word))) {
|
|
786
786
|
cue.size = Number(results[1]);
|
|
787
787
|
} else if ((results =
|
|
788
|
-
|
|
788
|
+
// eslint-disable-next-line max-len
|
|
789
|
+
/^position:([\d.]+)%(?:,(line-left|line-right|middle|center|start|end))?$/
|
|
789
790
|
.exec(word))) {
|
|
790
791
|
cue.position = Number(results[1]);
|
|
791
792
|
if (results[2]) {
|
|
@@ -112,10 +112,11 @@ shaka.util.Mp4BoxParsers = class {
|
|
|
112
112
|
static parseTRUN(reader, version, flags) {
|
|
113
113
|
const sampleCount = reader.readUint32();
|
|
114
114
|
const sampleData = [];
|
|
115
|
+
let dataOffset = null;
|
|
115
116
|
|
|
116
|
-
//
|
|
117
|
+
// "data_offset"
|
|
117
118
|
if (flags & 0x000001) {
|
|
118
|
-
reader.
|
|
119
|
+
dataOffset = reader.readUint32();
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
// Skip "first_sample_flags" if present.
|
|
@@ -159,6 +160,7 @@ shaka.util.Mp4BoxParsers = class {
|
|
|
159
160
|
return {
|
|
160
161
|
sampleCount,
|
|
161
162
|
sampleData,
|
|
163
|
+
dataOffset,
|
|
162
164
|
};
|
|
163
165
|
}
|
|
164
166
|
|
|
@@ -252,13 +254,16 @@ shaka.util.ParsedTREXBox;
|
|
|
252
254
|
/**
|
|
253
255
|
* @typedef {{
|
|
254
256
|
* sampleCount: number,
|
|
255
|
-
* sampleData: !Array.<shaka.util.ParsedTRUNSample
|
|
257
|
+
* sampleData: !Array.<shaka.util.ParsedTRUNSample>,
|
|
258
|
+
* dataOffset: ?number
|
|
256
259
|
* }}
|
|
257
260
|
*
|
|
258
261
|
* @property {number} sampleCount
|
|
259
262
|
* As per the spec: the number of samples being added in this run;
|
|
260
263
|
* @property {!Array.<shaka.util.ParsedTRUNSample>} sampleData
|
|
261
264
|
* An array of size <sampleCount> containing data for each sample
|
|
265
|
+
* @property {?number} dataOffset
|
|
266
|
+
* If specified via flags, this indicate the offset of the sample in bytes.
|
|
262
267
|
*
|
|
263
268
|
* @exportDoc
|
|
264
269
|
*/
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -1066,6 +1066,7 @@ shaka.util.StreamUtils = class {
|
|
|
1066
1066
|
track.frameRate = video.frameRate || null;
|
|
1067
1067
|
track.pixelAspectRatio = video.pixelAspectRatio || null;
|
|
1068
1068
|
track.videoBandwidth = video.bandwidth || null;
|
|
1069
|
+
track.hdr = video.hdr || null;
|
|
1069
1070
|
}
|
|
1070
1071
|
|
|
1071
1072
|
if (audio) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shaka-player",
|
|
3
3
|
"description": "DASH/EME video player library",
|
|
4
|
-
"version": "4.2.
|
|
4
|
+
"version": "4.2.12",
|
|
5
5
|
"homepage": "https://github.com/shaka-project/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"awesomplete": "^1.1.5",
|
|
19
19
|
"babel-plugin-istanbul": "^6.1.1",
|
|
20
20
|
"cajon": "^0.4.4",
|
|
21
|
+
"clean-package": "^2.2.0",
|
|
21
22
|
"code-prettify": "^0.1.0",
|
|
22
23
|
"color-themes-for-google-code-prettify": "^2.0.4",
|
|
23
24
|
"core-js": "^3.21.1",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"karma-coverage": "^2.2.0",
|
|
42
43
|
"karma-jasmine": "^4.0.1",
|
|
43
44
|
"karma-jasmine-ajax": "^0.1.13",
|
|
44
|
-
"karma-local-wd-launcher": "^1.6.
|
|
45
|
+
"karma-local-wd-launcher": "^1.6.2",
|
|
45
46
|
"karma-opera-launcher": "^1.0.0",
|
|
46
47
|
"karma-sourcemap-loader": "^0.3.8",
|
|
47
48
|
"karma-spec-reporter": "^0.0.34",
|
|
@@ -91,12 +92,16 @@
|
|
|
91
92
|
},
|
|
92
93
|
"license": "Apache-2.0",
|
|
93
94
|
"scripts": {
|
|
95
|
+
"prepack": "clean-package",
|
|
96
|
+
"postpack": "clean-package restore",
|
|
94
97
|
"prepublishOnly": "python build/checkversion.py && python build/all.py --force"
|
|
95
98
|
},
|
|
96
99
|
"dependencies": {
|
|
97
100
|
"eme-encryption-scheme-polyfill": "^2.1.1"
|
|
98
101
|
},
|
|
99
|
-
"
|
|
100
|
-
"
|
|
102
|
+
"clean-package": {
|
|
103
|
+
"remove": [
|
|
104
|
+
"engines"
|
|
105
|
+
]
|
|
101
106
|
}
|
|
102
107
|
}
|
|
@@ -7,20 +7,33 @@
|
|
|
7
7
|
describe('Mp4CeaParser', () => {
|
|
8
8
|
const ceaInitSegmentUri = '/base/test/test/assets/cea-init.mp4';
|
|
9
9
|
const ceaSegmentUri = '/base/test/test/assets/cea-segment.mp4';
|
|
10
|
+
const multipleTrunInitSegmentUri =
|
|
11
|
+
'/base/test/test/assets/multiple-trun-init.mp4';
|
|
12
|
+
const multipleTrunSegmentUri =
|
|
13
|
+
'/base/test/test/assets/multiple-trun-segment.mp4';
|
|
10
14
|
const Util = shaka.test.Util;
|
|
11
15
|
|
|
12
16
|
/** @type {!ArrayBuffer} */
|
|
13
17
|
let ceaInitSegment;
|
|
14
18
|
/** @type {!ArrayBuffer} */
|
|
15
19
|
let ceaSegment;
|
|
20
|
+
/** @type {!ArrayBuffer} */
|
|
21
|
+
let multipleTrunInitSegment;
|
|
22
|
+
/** @type {!ArrayBuffer} */
|
|
23
|
+
let multipleTrunSegment;
|
|
16
24
|
|
|
17
25
|
beforeAll(async () => {
|
|
18
|
-
|
|
26
|
+
[
|
|
27
|
+
ceaInitSegment,
|
|
28
|
+
ceaSegment,
|
|
29
|
+
multipleTrunInitSegment,
|
|
30
|
+
multipleTrunSegment,
|
|
31
|
+
] = await Promise.all([
|
|
19
32
|
shaka.test.Util.fetch(ceaInitSegmentUri),
|
|
20
33
|
shaka.test.Util.fetch(ceaSegmentUri),
|
|
34
|
+
shaka.test.Util.fetch(multipleTrunInitSegmentUri),
|
|
35
|
+
shaka.test.Util.fetch(multipleTrunSegmentUri),
|
|
21
36
|
]);
|
|
22
|
-
ceaInitSegment = responses[0];
|
|
23
|
-
ceaSegment = responses[1];
|
|
24
37
|
});
|
|
25
38
|
|
|
26
39
|
/**
|
|
@@ -70,6 +83,16 @@ describe('Mp4CeaParser', () => {
|
|
|
70
83
|
.toEqual(expectedCea708Packet);
|
|
71
84
|
});
|
|
72
85
|
|
|
86
|
+
it('parses cea data from a segment with multiple trun boxes', () => {
|
|
87
|
+
const ceaParser = new shaka.cea.Mp4CeaParser();
|
|
88
|
+
|
|
89
|
+
ceaParser.init(multipleTrunInitSegment);
|
|
90
|
+
const ceaPackets = ceaParser.parse(multipleTrunSegment);
|
|
91
|
+
// The first trun box references samples with 48 CEA packets.
|
|
92
|
+
// The second trun box references samples with 132 more, for a total of 180.
|
|
93
|
+
expect(ceaPackets.length).toBe(180);
|
|
94
|
+
});
|
|
95
|
+
|
|
73
96
|
it('parses an invalid init segment', () => {
|
|
74
97
|
const cea708Parser = new shaka.cea.Mp4CeaParser();
|
|
75
98
|
|
|
@@ -1931,6 +1931,66 @@ describe('DashParser Manifest', () => {
|
|
|
1931
1931
|
expect(stream.hdr).toBe('PQ');
|
|
1932
1932
|
});
|
|
1933
1933
|
|
|
1934
|
+
it('supports SupplementalProperty MatrixCoefficients', async () => {
|
|
1935
|
+
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
1936
|
+
const scheme = cicpScheme('MatrixCoefficients');
|
|
1937
|
+
const manifestText = [
|
|
1938
|
+
'<MPD minBufferTime="PT75S">',
|
|
1939
|
+
' <Period id="1" duration="PT30S">',
|
|
1940
|
+
' <AdaptationSet id="2" mimeType="video/mp4">',
|
|
1941
|
+
` <SupplementalProperty schemeIdUri="${scheme}" value="9" />`,
|
|
1942
|
+
' <Representation codecs="hvc1.2.4.L153.B0">',
|
|
1943
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
1944
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1945
|
+
' </Representation>',
|
|
1946
|
+
' </AdaptationSet>',
|
|
1947
|
+
' <AdaptationSet id="3" mimeType="audio/mp4">',
|
|
1948
|
+
' <Representation id="audio-en">',
|
|
1949
|
+
' <BaseURL>a-en.mp4</BaseURL>',
|
|
1950
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1951
|
+
' </Representation>',
|
|
1952
|
+
' </AdaptationSet>',
|
|
1953
|
+
' </Period>',
|
|
1954
|
+
'</MPD>',
|
|
1955
|
+
].join('\n');
|
|
1956
|
+
|
|
1957
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
1958
|
+
|
|
1959
|
+
/** @type {shaka.extern.Manifest} */
|
|
1960
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
1961
|
+
expect(manifest.variants.length).toBe(1);
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
it('supports SupplementalProperty ColourPrimaries', async () => {
|
|
1965
|
+
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
1966
|
+
const scheme = cicpScheme('ColourPrimaries');
|
|
1967
|
+
const manifestText = [
|
|
1968
|
+
'<MPD minBufferTime="PT75S">',
|
|
1969
|
+
' <Period id="1" duration="PT30S">',
|
|
1970
|
+
' <AdaptationSet id="2" mimeType="video/mp4">',
|
|
1971
|
+
` <SupplementalProperty schemeIdUri="${scheme}" value="9" />`,
|
|
1972
|
+
' <Representation codecs="hvc1.2.4.L153.B0">',
|
|
1973
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
1974
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1975
|
+
' </Representation>',
|
|
1976
|
+
' </AdaptationSet>',
|
|
1977
|
+
' <AdaptationSet id="3" mimeType="audio/mp4">',
|
|
1978
|
+
' <Representation id="audio-en">',
|
|
1979
|
+
' <BaseURL>a-en.mp4</BaseURL>',
|
|
1980
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1981
|
+
' </Representation>',
|
|
1982
|
+
' </AdaptationSet>',
|
|
1983
|
+
' </Period>',
|
|
1984
|
+
'</MPD>',
|
|
1985
|
+
].join('\n');
|
|
1986
|
+
|
|
1987
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
1988
|
+
|
|
1989
|
+
/** @type {shaka.extern.Manifest} */
|
|
1990
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
1991
|
+
expect(manifest.variants.length).toBe(1);
|
|
1992
|
+
});
|
|
1993
|
+
|
|
1934
1994
|
it('supports HDR signaling via EssentialProperty', async () => {
|
|
1935
1995
|
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
1936
1996
|
const hdrScheme = cicpScheme('TransferCharacteristics');
|
|
@@ -1964,6 +2024,66 @@ describe('DashParser Manifest', () => {
|
|
|
1964
2024
|
expect(stream.hdr).toBe('HLG');
|
|
1965
2025
|
});
|
|
1966
2026
|
|
|
2027
|
+
it('supports EssentialProperty MatrixCoefficients', async () => {
|
|
2028
|
+
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
2029
|
+
const hdrScheme = cicpScheme('MatrixCoefficients');
|
|
2030
|
+
const manifestText = [
|
|
2031
|
+
'<MPD minBufferTime="PT75S">',
|
|
2032
|
+
' <Period id="1" duration="PT30S">',
|
|
2033
|
+
' <AdaptationSet id="2" mimeType="video/mp4">',
|
|
2034
|
+
` <EssentialProperty schemeIdUri="${hdrScheme}" value="9" />`,
|
|
2035
|
+
' <Representation codecs="hvc1.2.4.L153.B0">',
|
|
2036
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
2037
|
+
' <SegmentBase indexRange="100-200" />',
|
|
2038
|
+
' </Representation>',
|
|
2039
|
+
' </AdaptationSet>',
|
|
2040
|
+
' <AdaptationSet id="3" mimeType="audio/mp4">',
|
|
2041
|
+
' <Representation id="audio-en">',
|
|
2042
|
+
' <BaseURL>a-en.mp4</BaseURL>',
|
|
2043
|
+
' <SegmentBase indexRange="100-200" />',
|
|
2044
|
+
' </Representation>',
|
|
2045
|
+
' </AdaptationSet>',
|
|
2046
|
+
' </Period>',
|
|
2047
|
+
'</MPD>',
|
|
2048
|
+
].join('\n');
|
|
2049
|
+
|
|
2050
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
2051
|
+
|
|
2052
|
+
/** @type {shaka.extern.Manifest} */
|
|
2053
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
2054
|
+
expect(manifest.variants.length).toBe(1);
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
it('supports EssentialProperty ColourPrimaries', async () => {
|
|
2058
|
+
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
2059
|
+
const hdrScheme = cicpScheme('ColourPrimaries');
|
|
2060
|
+
const manifestText = [
|
|
2061
|
+
'<MPD minBufferTime="PT75S">',
|
|
2062
|
+
' <Period id="1" duration="PT30S">',
|
|
2063
|
+
' <AdaptationSet id="2" mimeType="video/mp4">',
|
|
2064
|
+
` <EssentialProperty schemeIdUri="${hdrScheme}" value="9" />`,
|
|
2065
|
+
' <Representation codecs="hvc1.2.4.L153.B0">',
|
|
2066
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
2067
|
+
' <SegmentBase indexRange="100-200" />',
|
|
2068
|
+
' </Representation>',
|
|
2069
|
+
' </AdaptationSet>',
|
|
2070
|
+
' <AdaptationSet id="3" mimeType="audio/mp4">',
|
|
2071
|
+
' <Representation id="audio-en">',
|
|
2072
|
+
' <BaseURL>a-en.mp4</BaseURL>',
|
|
2073
|
+
' <SegmentBase indexRange="100-200" />',
|
|
2074
|
+
' </Representation>',
|
|
2075
|
+
' </AdaptationSet>',
|
|
2076
|
+
' </Period>',
|
|
2077
|
+
'</MPD>',
|
|
2078
|
+
].join('\n');
|
|
2079
|
+
|
|
2080
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
2081
|
+
|
|
2082
|
+
/** @type {shaka.extern.Manifest} */
|
|
2083
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
2084
|
+
expect(manifest.variants.length).toBe(1);
|
|
2085
|
+
});
|
|
2086
|
+
|
|
1967
2087
|
it('supports SDR signalling via EssentialProperty', async () => {
|
|
1968
2088
|
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
1969
2089
|
const scheme = cicpScheme('TransferCharacteristics');
|
|
@@ -560,7 +560,8 @@ describe('HlsParser live', () => {
|
|
|
560
560
|
// partialRef3
|
|
561
561
|
'#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
|
|
562
562
|
// preloadRef
|
|
563
|
-
'#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210
|
|
563
|
+
'#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210,',
|
|
564
|
+
'BYTERANGE-LENGTH=210\n',
|
|
564
565
|
].join('');
|
|
565
566
|
|
|
566
567
|
const partialRef = makeReference(
|
|
@@ -583,6 +584,7 @@ describe('HlsParser live', () => {
|
|
|
583
584
|
const preloadRef = makeReference(
|
|
584
585
|
'test:/partial.mp4', 6, 7.5, /* syncTime= */ null,
|
|
585
586
|
/* baseUri= */ '', /* startByte= */ 210, /* endByte= */ null);
|
|
587
|
+
preloadRef.markAsPreload();
|
|
586
588
|
|
|
587
589
|
// ref2 is not fully published yet, so it doesn't have a segment uri.
|
|
588
590
|
const ref2 = makeReference(
|
|
@@ -983,6 +983,81 @@ describe('HlsParser', () => {
|
|
|
983
983
|
await testHlsParser(master, media, manifest);
|
|
984
984
|
});
|
|
985
985
|
|
|
986
|
+
it('adds subtitle role when characteristics are empty', async () => {
|
|
987
|
+
const master = [
|
|
988
|
+
'#EXTM3U\n',
|
|
989
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
990
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
|
991
|
+
'video\n',
|
|
992
|
+
|
|
993
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
|
994
|
+
'NAME="English",URI="audio"\n',
|
|
995
|
+
|
|
996
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
|
997
|
+
'CHARACTERISTICS="public.accessibility.describes-video,',
|
|
998
|
+
'public.accessibility.describes-music-and-sound",',
|
|
999
|
+
'NAME="English (describes-video)",URI="audio2"\n',
|
|
1000
|
+
|
|
1001
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
|
1002
|
+
'NAME="English (subtitle)",DEFAULT=YES,AUTOSELECT=YES,',
|
|
1003
|
+
'URI="text"\n',
|
|
1004
|
+
|
|
1005
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
|
1006
|
+
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
|
1007
|
+
'CHARACTERISTICS="public.accessibility.describes-music-and-sound",',
|
|
1008
|
+
'URI="text2"\n',
|
|
1009
|
+
].join('');
|
|
1010
|
+
|
|
1011
|
+
const media = [
|
|
1012
|
+
'#EXTM3U\n',
|
|
1013
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
1014
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
1015
|
+
'#EXTINF:5,\n',
|
|
1016
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
1017
|
+
'main.mp4',
|
|
1018
|
+
].join('');
|
|
1019
|
+
|
|
1020
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
1021
|
+
manifest.anyTimeline();
|
|
1022
|
+
manifest.addPartialVariant((variant) => {
|
|
1023
|
+
variant.language = 'en';
|
|
1024
|
+
variant.addPartialStream(ContentType.VIDEO);
|
|
1025
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
1026
|
+
stream.language = 'en';
|
|
1027
|
+
stream.roles = [];
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
manifest.addPartialVariant((variant) => {
|
|
1031
|
+
variant.language = 'en';
|
|
1032
|
+
variant.addPartialStream(ContentType.VIDEO);
|
|
1033
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
1034
|
+
stream.language = 'en';
|
|
1035
|
+
stream.roles = [
|
|
1036
|
+
'public.accessibility.describes-video',
|
|
1037
|
+
'public.accessibility.describes-music-and-sound',
|
|
1038
|
+
];
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
manifest.addPartialTextStream((stream) => {
|
|
1042
|
+
stream.language = 'en';
|
|
1043
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
1044
|
+
stream.roles = [
|
|
1045
|
+
'subtitle',
|
|
1046
|
+
];
|
|
1047
|
+
});
|
|
1048
|
+
manifest.addPartialTextStream((stream) => {
|
|
1049
|
+
stream.language = 'en';
|
|
1050
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
1051
|
+
stream.roles = [
|
|
1052
|
+
'public.accessibility.describes-music-and-sound',
|
|
1053
|
+
];
|
|
1054
|
+
});
|
|
1055
|
+
manifest.sequenceMode = true;
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
await testHlsParser(master, media, manifest);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
986
1061
|
it('parses characteristics from text tags', async () => {
|
|
987
1062
|
const master = [
|
|
988
1063
|
'#EXTM3U\n',
|
|
@@ -1652,6 +1727,81 @@ describe('HlsParser', () => {
|
|
|
1652
1727
|
}
|
|
1653
1728
|
});
|
|
1654
1729
|
|
|
1730
|
+
it('ignore segments with #EXTINF:0', async () => {
|
|
1731
|
+
const master = [
|
|
1732
|
+
'#EXTM3U\n',
|
|
1733
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
|
1734
|
+
'CHANNELS="2",URI="audio"\n',
|
|
1735
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
1736
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
|
1737
|
+
'video\n',
|
|
1738
|
+
].join('');
|
|
1739
|
+
|
|
1740
|
+
const video = [
|
|
1741
|
+
'#EXTM3U\n',
|
|
1742
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
1743
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
1744
|
+
'#EXTINF:5,\n',
|
|
1745
|
+
'main.mp4\n',
|
|
1746
|
+
'#EXTINF:0,\n',
|
|
1747
|
+
'main.mp4\n',
|
|
1748
|
+
'#EXTINF:5,\n',
|
|
1749
|
+
'main.mp4\n',
|
|
1750
|
+
].join('');
|
|
1751
|
+
|
|
1752
|
+
const audio = [
|
|
1753
|
+
'#EXTM3U\n',
|
|
1754
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
1755
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
1756
|
+
'#EXTINF:5,\n',
|
|
1757
|
+
'main.mp4\n',
|
|
1758
|
+
'#EXTINF:0,\n',
|
|
1759
|
+
'main.mp4\n',
|
|
1760
|
+
'#EXTINF:5,\n',
|
|
1761
|
+
'main.mp4\n',
|
|
1762
|
+
].join('');
|
|
1763
|
+
|
|
1764
|
+
fakeNetEngine
|
|
1765
|
+
.setResponseText('test:/master', master)
|
|
1766
|
+
.setResponseText('test:/audio', audio)
|
|
1767
|
+
.setResponseText('test:/video', video)
|
|
1768
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
1769
|
+
.setResponseValue('test:/main.mp4', segmentData);
|
|
1770
|
+
|
|
1771
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
1772
|
+
|
|
1773
|
+
expect(actual.variants.length).toBe(1);
|
|
1774
|
+
|
|
1775
|
+
const variant = actual.variants[0];
|
|
1776
|
+
expect(variant.video).toBeTruthy();
|
|
1777
|
+
expect(variant.audio).toBeTruthy();
|
|
1778
|
+
|
|
1779
|
+
await variant.video.createSegmentIndex();
|
|
1780
|
+
goog.asserts.assert(variant.video.segmentIndex != null,
|
|
1781
|
+
'Null segmentIndex!');
|
|
1782
|
+
|
|
1783
|
+
const firstVideoReference = variant.video.segmentIndex.get(0);
|
|
1784
|
+
const secondVideoReference = variant.video.segmentIndex.get(1);
|
|
1785
|
+
const thirdVideoReference = variant.video.segmentIndex.get(2);
|
|
1786
|
+
|
|
1787
|
+
expect(firstVideoReference).not.toBe(null);
|
|
1788
|
+
expect(secondVideoReference).not.toBe(null);
|
|
1789
|
+
expect(thirdVideoReference).toBe(null);
|
|
1790
|
+
|
|
1791
|
+
|
|
1792
|
+
await variant.audio.createSegmentIndex();
|
|
1793
|
+
goog.asserts.assert(variant.audio.segmentIndex != null,
|
|
1794
|
+
'Null segmentIndex!');
|
|
1795
|
+
|
|
1796
|
+
const firstAudioReference = variant.audio.segmentIndex.get(0);
|
|
1797
|
+
const secondAudioReference = variant.audio.segmentIndex.get(1);
|
|
1798
|
+
const thirdAudioReference = variant.audio.segmentIndex.get(2);
|
|
1799
|
+
|
|
1800
|
+
expect(firstAudioReference).not.toBe(null);
|
|
1801
|
+
expect(secondAudioReference).not.toBe(null);
|
|
1802
|
+
expect(thirdAudioReference).toBe(null);
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1655
1805
|
it('Disable audio does not create audio streams', async () => {
|
|
1656
1806
|
const master = [
|
|
1657
1807
|
'#EXTM3U\n',
|