shaka-player 4.2.5 → 4.3.1
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/build-and-test.yaml +7 -0
- package/.github/workflows/sync-labels.yaml +1 -1
- package/.github/workflows/update-issues.yaml +1 -1
- package/AUTHORS +4 -0
- package/CHANGELOG.md +114 -74
- package/CONTRIBUTORS +4 -0
- package/README.md +14 -0
- package/conditional/dummy_cast_proxy.js +120 -0
- package/demo/common/assets.js +58 -0
- package/demo/common/message_ids.js +22 -1
- package/demo/config.js +89 -5
- package/demo/demo.less +7 -1
- package/demo/index.html +8 -1
- package/demo/locales/en.json +21 -1
- package/demo/locales/source.json +82 -2
- package/demo/main.js +57 -1
- package/demo/search.js +2 -0
- package/demo/visualizer.js +337 -0
- package/demo/visualizer_button.js +103 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +107 -88
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +107 -88
- package/dist/demo.compiled.map +4 -4
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +21 -13
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +75 -77
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +75 -77
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +181 -27
- package/dist/shaka-player.compiled.debug.d.ts +181 -27
- package/dist/shaka-player.compiled.debug.externs.js +169 -31
- package/dist/shaka-player.compiled.debug.js +510 -450
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +169 -31
- package/dist/shaka-player.compiled.js +912 -874
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +181 -27
- package/dist/shaka-player.ui.debug.d.ts +181 -27
- package/dist/shaka-player.ui.debug.externs.js +169 -31
- package/dist/shaka-player.ui.debug.js +533 -471
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +169 -31
- package/dist/shaka-player.ui.js +1073 -1036
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/lcevc-architecture.png +0 -0
- package/docs/design/lcevc-demo.png +0 -0
- package/docs/design/lcevc-integration.md +72 -0
- package/docs/tutorials/fairplay.md +7 -0
- package/docs/tutorials/faq.md +1 -1
- package/docs/tutorials/ui.md +7 -7
- package/docs/tutorials/upgrade.md +6 -0
- package/externs/ima.js +13 -1
- package/externs/lcevc.js +81 -0
- package/externs/mux.js +1 -42
- package/externs/shaka/ads.js +11 -1
- package/externs/shaka/manifest_parser.js +5 -1
- package/externs/shaka/mp4_parser.js +3 -0
- package/externs/shaka/player.js +178 -15
- package/lib/abr/simple_abr_manager.js +7 -0
- package/lib/ads/ad_manager.js +0 -8
- package/lib/ads/client_side_ad.js +15 -0
- package/lib/ads/client_side_ad_manager.js +21 -8
- package/lib/ads/server_side_ad.js +15 -0
- package/lib/ads/server_side_ad_manager.js +3 -3
- package/lib/cast/cast_utils.js +2 -0
- package/lib/cea/dummy_cea_parser.js +28 -0
- package/lib/cea/i_cea_parser.js +14 -2
- package/lib/cea/mp4_cea_parser.js +108 -5
- package/lib/config/auto_show_text.js +33 -0
- package/lib/dash/dash_parser.js +1 -1
- package/lib/hls/hls_parser.js +808 -384
- package/lib/lcevc/lcevc_dil.js +152 -0
- package/lib/media/closed_caption_parser.js +9 -6
- package/lib/media/drm_engine.js +14 -6
- package/lib/media/media_source_capabilities.js +36 -0
- package/lib/media/media_source_engine.js +113 -28
- package/lib/media/presentation_timeline.js +26 -11
- package/lib/media/segment_reference.js +49 -1
- package/lib/media/streaming_engine.js +142 -12
- package/lib/media/transmuxer.js +78 -21
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +456 -77
- package/lib/polyfill/media_capabilities.js +9 -5
- package/lib/polyfill/patchedmediakeys_apple.js +63 -4
- package/lib/text/vtt_text_parser.js +35 -1
- package/lib/util/dom_utils.js +0 -1
- package/lib/util/error.js +17 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/id3_utils.js +300 -0
- package/lib/util/mime_utils.js +3 -6
- package/lib/util/mp4_box_parsers.js +39 -5
- package/lib/util/mp4_parser.js +35 -0
- package/lib/util/player_configuration.js +23 -1
- package/lib/util/stream_utils.js +5 -3
- package/lib/util/ts_parser.js +499 -0
- package/maintained-branches.md +2 -2
- package/package.json +7 -4
- package/roadmap.md +5 -3
- package/test/ads/ad_manager_unit.js +182 -29
- package/test/cast/cast_utils_unit.js +3 -1
- package/test/dash/dash_parser_content_protection_unit.js +1 -0
- package/test/dash/dash_parser_live_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +93 -1
- package/test/dash/dash_parser_segment_base_unit.js +1 -0
- package/test/dash/dash_parser_segment_list_unit.js +1 -0
- package/test/dash/dash_parser_segment_template_unit.js +1 -0
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_live_unit.js +253 -19
- package/test/hls/hls_parser_unit.js +631 -12
- package/test/media/closed_caption_parser_unit.js +2 -1
- package/test/media/drm_engine_integration.js +3 -1
- package/test/media/media_source_engine_integration.js +71 -6
- package/test/media/media_source_engine_unit.js +10 -5
- package/test/media/streaming_engine_integration.js +4 -1
- package/test/media/streaming_engine_unit.js +144 -1
- package/test/media/transmuxer_integration.js +21 -10
- package/test/player_integration.js +224 -82
- package/test/player_unit.js +5 -2
- package/test/polyfill/media_capabilities_unit.js +21 -23
- package/test/polyfill/patchedmediakeys_apple_unit.js +130 -0
- package/test/test/assets/id3-metadata.aac +0 -0
- package/test/test/assets/id3-metadata.ts +0 -0
- package/test/test/assets/thumbnails-sprites.vtt +10 -0
- package/test/test/assets/thumbnails.vtt +10 -0
- package/test/test/util/dash_parser_util.js +2 -0
- package/test/test/util/fake_ad.js +21 -0
- package/test/test/util/fake_media_source_engine.js +7 -0
- package/test/test/util/id3_generator.js +100 -0
- package/test/test/util/manifest_generator.js +3 -6
- package/test/test/util/simple_fakes.js +4 -0
- package/test/test/util/stream_generator.js +38 -0
- package/test/test/util/test_scheme.js +43 -14
- package/test/text/vtt_text_parser_unit.js +11 -0
- package/test/util/id3_utils_unit.js +116 -0
- package/test/util/ts_parser_unit.js +77 -0
- package/third_party/closure-uri/uri.js +1 -1
- package/ui/less/containers.less +7 -0
- package/ui/ui.js +43 -4
|
@@ -23,6 +23,8 @@ describe('HlsParser', () => {
|
|
|
23
23
|
let parser;
|
|
24
24
|
/** @type {!jasmine.Spy} */
|
|
25
25
|
let onEventSpy;
|
|
26
|
+
/** @type {!jasmine.Spy} */
|
|
27
|
+
let newDrmInfoSpy;
|
|
26
28
|
/** @type {shaka.extern.ManifestParser.PlayerInterface} */
|
|
27
29
|
let playerInterface;
|
|
28
30
|
/** @type {shaka.extern.ManifestConfiguration} */
|
|
@@ -87,6 +89,7 @@ describe('HlsParser', () => {
|
|
|
87
89
|
|
|
88
90
|
config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
89
91
|
onEventSpy = jasmine.createSpy('onEvent');
|
|
92
|
+
newDrmInfoSpy = jasmine.createSpy('newDrmInfo');
|
|
90
93
|
playerInterface = {
|
|
91
94
|
modifyManifestRequest: (request, manifestInfo) => {},
|
|
92
95
|
modifySegmentRequest: (request, segmentInfo) => {},
|
|
@@ -100,6 +103,7 @@ describe('HlsParser', () => {
|
|
|
100
103
|
isAutoLowLatencyMode: () => false,
|
|
101
104
|
enableLowLatencyMode: () => {},
|
|
102
105
|
updateDuration: () => {},
|
|
106
|
+
newDrmInfo: shaka.test.Util.spyFunc(newDrmInfoSpy),
|
|
103
107
|
};
|
|
104
108
|
|
|
105
109
|
parser = new shaka.hls.HlsParser();
|
|
@@ -110,17 +114,18 @@ describe('HlsParser', () => {
|
|
|
110
114
|
* @param {string} master
|
|
111
115
|
* @param {string} media
|
|
112
116
|
* @param {shaka.extern.Manifest} manifest
|
|
117
|
+
* @param {string=} media2
|
|
113
118
|
* @return {!Promise.<shaka.extern.Manifest>}
|
|
114
119
|
*/
|
|
115
|
-
async function testHlsParser(master, media, manifest) {
|
|
120
|
+
async function testHlsParser(master, media, manifest, media2) {
|
|
116
121
|
fakeNetEngine
|
|
117
122
|
.setResponseText('test:/master', master)
|
|
118
123
|
.setResponseText('test:/audio', media)
|
|
119
|
-
.setResponseText('test:/audio2', media)
|
|
124
|
+
.setResponseText('test:/audio2', media2 || media)
|
|
120
125
|
.setResponseText('test:/video', media)
|
|
121
|
-
.setResponseText('test:/video2', media)
|
|
126
|
+
.setResponseText('test:/video2', media2 || media)
|
|
122
127
|
.setResponseText('test:/text', media)
|
|
123
|
-
.setResponseText('test:/text2', media)
|
|
128
|
+
.setResponseText('test:/text2', media2 || media)
|
|
124
129
|
.setResponseText('test:/main.vtt', vttText)
|
|
125
130
|
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
126
131
|
.setResponseValue('test:/init2.mp4', initSegmentData)
|
|
@@ -130,10 +135,30 @@ describe('HlsParser', () => {
|
|
|
130
135
|
.setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
|
|
131
136
|
|
|
132
137
|
const actual = await parser.start('test:/master', playerInterface);
|
|
138
|
+
await loadAllStreamsFor(actual);
|
|
133
139
|
expect(actual).toEqual(manifest);
|
|
134
140
|
return actual;
|
|
135
141
|
}
|
|
136
142
|
|
|
143
|
+
/** @param {!shaka.extern.Manifest} manifest */
|
|
144
|
+
async function loadAllStreamsFor(manifest) {
|
|
145
|
+
const promises = [];
|
|
146
|
+
for (const variant of manifest.variants) {
|
|
147
|
+
for (const stream of [variant.video, variant.audio]) {
|
|
148
|
+
if (stream) {
|
|
149
|
+
promises.push(stream.createSegmentIndex());
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const text of manifest.textStreams) {
|
|
154
|
+
promises.push(text.createSegmentIndex());
|
|
155
|
+
}
|
|
156
|
+
for (const image of manifest.imageStreams) {
|
|
157
|
+
promises.push(image.createSegmentIndex());
|
|
158
|
+
}
|
|
159
|
+
await Promise.all(promises);
|
|
160
|
+
}
|
|
161
|
+
|
|
137
162
|
it('parses manifest attributes', async () => {
|
|
138
163
|
const master = [
|
|
139
164
|
'#EXTM3U\n',
|
|
@@ -206,6 +231,7 @@ describe('HlsParser', () => {
|
|
|
206
231
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
207
232
|
|
|
208
233
|
const actual = await parser.start('test:/master', playerInterface);
|
|
234
|
+
await loadAllStreamsFor(actual);
|
|
209
235
|
expect(actual).toEqual(manifest);
|
|
210
236
|
});
|
|
211
237
|
|
|
@@ -281,6 +307,7 @@ describe('HlsParser', () => {
|
|
|
281
307
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
282
308
|
|
|
283
309
|
const actual = await parser.start('test:/master', playerInterface);
|
|
310
|
+
await loadAllStreamsFor(actual);
|
|
284
311
|
expect(actual).toEqual(manifest);
|
|
285
312
|
});
|
|
286
313
|
|
|
@@ -322,6 +349,7 @@ describe('HlsParser', () => {
|
|
|
322
349
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
323
350
|
|
|
324
351
|
const actual = await parser.start('test:/master', playerInterface);
|
|
352
|
+
await loadAllStreamsFor(actual);
|
|
325
353
|
expect(actual).toEqual(manifest);
|
|
326
354
|
});
|
|
327
355
|
|
|
@@ -573,6 +601,33 @@ describe('HlsParser', () => {
|
|
|
573
601
|
await testHlsParser(master, media, manifest);
|
|
574
602
|
});
|
|
575
603
|
|
|
604
|
+
it('accepts mp4a.40.34 codec as audio/mpeg', async () => {
|
|
605
|
+
const master = [
|
|
606
|
+
'#EXTM3U\n',
|
|
607
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=63701,CODECS="mp4a.40.34"\n',
|
|
608
|
+
'audio\n',
|
|
609
|
+
].join('');
|
|
610
|
+
|
|
611
|
+
const media = [
|
|
612
|
+
'#EXTM3U\n',
|
|
613
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
614
|
+
'#EXTINF:5,\n',
|
|
615
|
+
'main.mp3',
|
|
616
|
+
].join('');
|
|
617
|
+
|
|
618
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
619
|
+
manifest.anyTimeline();
|
|
620
|
+
manifest.addPartialVariant((variant) => {
|
|
621
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
622
|
+
stream.mime('audio/mpeg', '');
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
manifest.sequenceMode = true;
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
await testHlsParser(master, media, manifest);
|
|
629
|
+
});
|
|
630
|
+
|
|
576
631
|
it('parses audio+video variant with closed captions', async () => {
|
|
577
632
|
const master = [
|
|
578
633
|
'#EXTM3U\n',
|
|
@@ -1035,6 +1090,77 @@ describe('HlsParser', () => {
|
|
|
1035
1090
|
await testHlsParser(master, media, manifest);
|
|
1036
1091
|
});
|
|
1037
1092
|
|
|
1093
|
+
// https://github.com/shaka-project/shaka-player/issues/4759
|
|
1094
|
+
it('makes roles available without loading tracks', async () => {
|
|
1095
|
+
const master = [
|
|
1096
|
+
'#EXTM3U\n',
|
|
1097
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
1098
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
|
1099
|
+
'video\n',
|
|
1100
|
+
|
|
1101
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
|
1102
|
+
'NAME="English",URI="audio"\n',
|
|
1103
|
+
|
|
1104
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
|
1105
|
+
'CHARACTERISTICS="public.accessibility.describes-video,',
|
|
1106
|
+
'public.accessibility.describes-music-and-sound",',
|
|
1107
|
+
'NAME="English (describes-video)",URI="audio2"\n',
|
|
1108
|
+
|
|
1109
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
|
1110
|
+
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
|
1111
|
+
'URI="text"\n',
|
|
1112
|
+
|
|
1113
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
|
1114
|
+
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
|
1115
|
+
'CHARACTERISTICS="public.accessibility.describes-spoken-dialog,',
|
|
1116
|
+
'public.accessibility.describes-music-and-sound",',
|
|
1117
|
+
'URI="text2"\n',
|
|
1118
|
+
].join('');
|
|
1119
|
+
|
|
1120
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
1121
|
+
manifest.anyTimeline();
|
|
1122
|
+
manifest.addPartialVariant((variant) => {
|
|
1123
|
+
variant.language = 'en';
|
|
1124
|
+
variant.addPartialStream(ContentType.VIDEO);
|
|
1125
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
1126
|
+
stream.language = 'en';
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1129
|
+
manifest.addPartialVariant((variant) => {
|
|
1130
|
+
variant.language = 'en';
|
|
1131
|
+
variant.addPartialStream(ContentType.VIDEO);
|
|
1132
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
1133
|
+
stream.language = 'en';
|
|
1134
|
+
stream.roles = [
|
|
1135
|
+
'public.accessibility.describes-video',
|
|
1136
|
+
'public.accessibility.describes-music-and-sound',
|
|
1137
|
+
];
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
manifest.addPartialTextStream((stream) => {
|
|
1141
|
+
stream.language = 'en';
|
|
1142
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
1143
|
+
});
|
|
1144
|
+
manifest.addPartialTextStream((stream) => {
|
|
1145
|
+
stream.language = 'en';
|
|
1146
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
1147
|
+
stream.roles = [
|
|
1148
|
+
'public.accessibility.describes-spoken-dialog',
|
|
1149
|
+
'public.accessibility.describes-music-and-sound',
|
|
1150
|
+
];
|
|
1151
|
+
});
|
|
1152
|
+
manifest.sequenceMode = true;
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
fakeNetEngine.setResponseText('test:/master', master);
|
|
1156
|
+
|
|
1157
|
+
// NOTE: Not using testHlsParser here because that unconditionally loads all
|
|
1158
|
+
// streams. We need to test the behavior specifically when streams are
|
|
1159
|
+
// _not_ loaded.
|
|
1160
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
1161
|
+
expect(actual).toEqual(manifest);
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1038
1164
|
it('gets mime type from header request', async () => {
|
|
1039
1165
|
const master = [
|
|
1040
1166
|
'#EXTM3U\n',
|
|
@@ -1130,6 +1256,7 @@ describe('HlsParser', () => {
|
|
|
1130
1256
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1131
1257
|
|
|
1132
1258
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1259
|
+
await loadAllStreamsFor(actual);
|
|
1133
1260
|
expect(actual).toEqual(manifest);
|
|
1134
1261
|
});
|
|
1135
1262
|
|
|
@@ -1201,6 +1328,7 @@ describe('HlsParser', () => {
|
|
|
1201
1328
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1202
1329
|
|
|
1203
1330
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1331
|
+
await loadAllStreamsFor(actual);
|
|
1204
1332
|
expect(actual).toEqual(manifest);
|
|
1205
1333
|
});
|
|
1206
1334
|
|
|
@@ -1264,6 +1392,7 @@ describe('HlsParser', () => {
|
|
|
1264
1392
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1265
1393
|
|
|
1266
1394
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1395
|
+
await loadAllStreamsFor(actual);
|
|
1267
1396
|
expect(actual).toEqual(manifest);
|
|
1268
1397
|
});
|
|
1269
1398
|
|
|
@@ -1325,6 +1454,7 @@ describe('HlsParser', () => {
|
|
|
1325
1454
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1326
1455
|
|
|
1327
1456
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1457
|
+
await loadAllStreamsFor(actual);
|
|
1328
1458
|
expect(actual).toEqual(manifest);
|
|
1329
1459
|
});
|
|
1330
1460
|
|
|
@@ -1388,6 +1518,7 @@ describe('HlsParser', () => {
|
|
|
1388
1518
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1389
1519
|
|
|
1390
1520
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1521
|
+
await loadAllStreamsFor(actual);
|
|
1391
1522
|
expect(actual).toEqual(manifest);
|
|
1392
1523
|
});
|
|
1393
1524
|
|
|
@@ -1443,6 +1574,7 @@ describe('HlsParser', () => {
|
|
|
1443
1574
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1444
1575
|
|
|
1445
1576
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1577
|
+
await loadAllStreamsFor(actual);
|
|
1446
1578
|
// Duration should be the minimum of the streams, but ignore the text
|
|
1447
1579
|
// stream.
|
|
1448
1580
|
const timeline = actual.presentationTimeline;
|
|
@@ -1525,6 +1657,7 @@ describe('HlsParser', () => {
|
|
|
1525
1657
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1526
1658
|
|
|
1527
1659
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1660
|
+
await loadAllStreamsFor(actual);
|
|
1528
1661
|
|
|
1529
1662
|
expect(actual.imageStreams.length).toBe(1);
|
|
1530
1663
|
expect(actual.textStreams.length).toBe(1);
|
|
@@ -1597,6 +1730,7 @@ describe('HlsParser', () => {
|
|
|
1597
1730
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1598
1731
|
|
|
1599
1732
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1733
|
+
await loadAllStreamsFor(actual);
|
|
1600
1734
|
|
|
1601
1735
|
expect(actual.variants.length).toBe(1);
|
|
1602
1736
|
|
|
@@ -1722,6 +1856,7 @@ describe('HlsParser', () => {
|
|
|
1722
1856
|
parser.configure(config);
|
|
1723
1857
|
|
|
1724
1858
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1859
|
+
await loadAllStreamsFor(actual);
|
|
1725
1860
|
const variant = actual.variants[0];
|
|
1726
1861
|
expect(variant.audio).toBe(null);
|
|
1727
1862
|
expect(variant.video).toBeTruthy();
|
|
@@ -1797,6 +1932,7 @@ describe('HlsParser', () => {
|
|
|
1797
1932
|
parser.configure(config);
|
|
1798
1933
|
|
|
1799
1934
|
const actual = await parser.start('test:/master', playerInterface);
|
|
1935
|
+
await loadAllStreamsFor(actual);
|
|
1800
1936
|
const variant = actual.variants[0];
|
|
1801
1937
|
expect(variant.audio).toBeTruthy();
|
|
1802
1938
|
expect(variant.video).toBe(null);
|
|
@@ -1872,6 +2008,7 @@ describe('HlsParser', () => {
|
|
|
1872
2008
|
parser.configure(config);
|
|
1873
2009
|
|
|
1874
2010
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2011
|
+
await loadAllStreamsFor(actual);
|
|
1875
2012
|
const stream = actual.textStreams[0];
|
|
1876
2013
|
expect(stream).toBeUndefined();
|
|
1877
2014
|
});
|
|
@@ -1946,6 +2083,7 @@ describe('HlsParser', () => {
|
|
|
1946
2083
|
parser.configure(config);
|
|
1947
2084
|
|
|
1948
2085
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2086
|
+
await loadAllStreamsFor(actual);
|
|
1949
2087
|
const stream = actual.imageStreams[0];
|
|
1950
2088
|
expect(stream).toBeUndefined();
|
|
1951
2089
|
});
|
|
@@ -1990,6 +2128,7 @@ describe('HlsParser', () => {
|
|
|
1990
2128
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
1991
2129
|
|
|
1992
2130
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2131
|
+
await loadAllStreamsFor(actual);
|
|
1993
2132
|
expect(actual).toEqual(manifest);
|
|
1994
2133
|
});
|
|
1995
2134
|
|
|
@@ -2041,6 +2180,7 @@ describe('HlsParser', () => {
|
|
|
2041
2180
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
2042
2181
|
|
|
2043
2182
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2183
|
+
await loadAllStreamsFor(actual);
|
|
2044
2184
|
expect(actual).toEqual(manifest);
|
|
2045
2185
|
});
|
|
2046
2186
|
|
|
@@ -2083,6 +2223,7 @@ describe('HlsParser', () => {
|
|
|
2083
2223
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
2084
2224
|
|
|
2085
2225
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2226
|
+
await loadAllStreamsFor(actual);
|
|
2086
2227
|
expect(actual).toEqual(manifest);
|
|
2087
2228
|
});
|
|
2088
2229
|
|
|
@@ -2149,6 +2290,7 @@ describe('HlsParser', () => {
|
|
|
2149
2290
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
2150
2291
|
|
|
2151
2292
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2293
|
+
await loadAllStreamsFor(actual);
|
|
2152
2294
|
expect(actual).toEqual(manifest);
|
|
2153
2295
|
}
|
|
2154
2296
|
|
|
@@ -2294,6 +2436,7 @@ describe('HlsParser', () => {
|
|
|
2294
2436
|
|
|
2295
2437
|
config.hls.ignoreTextStreamFailures = true;
|
|
2296
2438
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2439
|
+
await loadAllStreamsFor(actual);
|
|
2297
2440
|
expect(actual).toEqual(manifest);
|
|
2298
2441
|
});
|
|
2299
2442
|
|
|
@@ -2332,6 +2475,7 @@ describe('HlsParser', () => {
|
|
|
2332
2475
|
|
|
2333
2476
|
config.hls.ignoreImageStreamFailures = true;
|
|
2334
2477
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2478
|
+
await loadAllStreamsFor(actual);
|
|
2335
2479
|
expect(actual).toEqual(manifest);
|
|
2336
2480
|
});
|
|
2337
2481
|
|
|
@@ -2397,6 +2541,7 @@ describe('HlsParser', () => {
|
|
|
2397
2541
|
|
|
2398
2542
|
const actual =
|
|
2399
2543
|
await parser.start('test:/host/master.m3u8', playerInterface);
|
|
2544
|
+
await loadAllStreamsFor(actual);
|
|
2400
2545
|
const video = actual.variants[0].video;
|
|
2401
2546
|
const audio = actual.variants[0].audio;
|
|
2402
2547
|
|
|
@@ -2595,6 +2740,7 @@ describe('HlsParser', () => {
|
|
|
2595
2740
|
.setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
|
|
2596
2741
|
|
|
2597
2742
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2743
|
+
await loadAllStreamsFor(actual);
|
|
2598
2744
|
expect(actual).toEqual(manifest);
|
|
2599
2745
|
});
|
|
2600
2746
|
|
|
@@ -2639,7 +2785,8 @@ describe('HlsParser', () => {
|
|
|
2639
2785
|
shaka.util.Error.Severity.CRITICAL,
|
|
2640
2786
|
shaka.util.Error.Category.MANIFEST,
|
|
2641
2787
|
shaka.util.Error.Code.NO_WEB_CRYPTO_API));
|
|
2642
|
-
await
|
|
2788
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
2789
|
+
await expectAsync(loadAllStreamsFor(actual))
|
|
2643
2790
|
.toBeRejectedWith(expectedError);
|
|
2644
2791
|
} finally {
|
|
2645
2792
|
Object.defineProperty(window, 'crypto', {
|
|
@@ -2649,7 +2796,6 @@ describe('HlsParser', () => {
|
|
|
2649
2796
|
}
|
|
2650
2797
|
});
|
|
2651
2798
|
|
|
2652
|
-
|
|
2653
2799
|
it('constructs DrmInfo for Widevine', async () => {
|
|
2654
2800
|
const master = [
|
|
2655
2801
|
'#EXTM3U\n',
|
|
@@ -2693,6 +2839,7 @@ describe('HlsParser', () => {
|
|
|
2693
2839
|
});
|
|
2694
2840
|
|
|
2695
2841
|
await testHlsParser(master, media, manifest);
|
|
2842
|
+
expect(newDrmInfoSpy).toHaveBeenCalled();
|
|
2696
2843
|
});
|
|
2697
2844
|
|
|
2698
2845
|
it('constructs DrmInfo for PlayReady', async () => {
|
|
@@ -2733,6 +2880,7 @@ describe('HlsParser', () => {
|
|
|
2733
2880
|
});
|
|
2734
2881
|
|
|
2735
2882
|
await testHlsParser(master, media, manifest);
|
|
2883
|
+
expect(newDrmInfoSpy).toHaveBeenCalled();
|
|
2736
2884
|
});
|
|
2737
2885
|
|
|
2738
2886
|
it('constructs DrmInfo for FairPlay', async () => {
|
|
@@ -2770,6 +2918,229 @@ describe('HlsParser', () => {
|
|
|
2770
2918
|
});
|
|
2771
2919
|
|
|
2772
2920
|
await testHlsParser(master, media, manifest);
|
|
2921
|
+
expect(newDrmInfoSpy).toHaveBeenCalled();
|
|
2922
|
+
});
|
|
2923
|
+
|
|
2924
|
+
it('constructs DrmInfo for ClearKey with explicit KEYFORMAT', async () => {
|
|
2925
|
+
const master = [
|
|
2926
|
+
'#EXTM3U\n',
|
|
2927
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
2928
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
2929
|
+
'video\n',
|
|
2930
|
+
].join('');
|
|
2931
|
+
|
|
2932
|
+
const media = [
|
|
2933
|
+
'#EXTM3U\n',
|
|
2934
|
+
'#EXT-X-TARGETDURATION:6\n',
|
|
2935
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
2936
|
+
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
|
2937
|
+
'KEYFORMAT="identity",',
|
|
2938
|
+
'URI="key.bin",\n',
|
|
2939
|
+
'#EXT-X-MAP:URI="init.mp4"\n',
|
|
2940
|
+
'#EXTINF:5,\n',
|
|
2941
|
+
'main.mp4',
|
|
2942
|
+
].join('');
|
|
2943
|
+
|
|
2944
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
2945
|
+
manifest.anyTimeline();
|
|
2946
|
+
manifest.addPartialVariant((variant) => {
|
|
2947
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
2948
|
+
stream.encrypted = true;
|
|
2949
|
+
stream.addDrmInfo('org.w3.clearkey');
|
|
2950
|
+
});
|
|
2951
|
+
});
|
|
2952
|
+
manifest.sequenceMode = true;
|
|
2953
|
+
});
|
|
2954
|
+
|
|
2955
|
+
await testHlsParser(master, media, manifest);
|
|
2956
|
+
expect(newDrmInfoSpy).toHaveBeenCalled();
|
|
2957
|
+
});
|
|
2958
|
+
|
|
2959
|
+
it('constructs DrmInfo for ClearKey without explicit KEYFORMAT', async () => {
|
|
2960
|
+
const master = [
|
|
2961
|
+
'#EXTM3U\n',
|
|
2962
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
2963
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
2964
|
+
'video\n',
|
|
2965
|
+
].join('');
|
|
2966
|
+
|
|
2967
|
+
const media = [
|
|
2968
|
+
'#EXTM3U\n',
|
|
2969
|
+
'#EXT-X-TARGETDURATION:6\n',
|
|
2970
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
2971
|
+
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
|
2972
|
+
'URI="key.bin",\n',
|
|
2973
|
+
'#EXT-X-MAP:URI="init.mp4"\n',
|
|
2974
|
+
'#EXTINF:5,\n',
|
|
2975
|
+
'main.mp4',
|
|
2976
|
+
].join('');
|
|
2977
|
+
|
|
2978
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
2979
|
+
manifest.anyTimeline();
|
|
2980
|
+
manifest.addPartialVariant((variant) => {
|
|
2981
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
2982
|
+
stream.encrypted = true;
|
|
2983
|
+
stream.addDrmInfo('org.w3.clearkey');
|
|
2984
|
+
});
|
|
2985
|
+
});
|
|
2986
|
+
manifest.sequenceMode = true;
|
|
2987
|
+
});
|
|
2988
|
+
|
|
2989
|
+
await testHlsParser(master, media, manifest);
|
|
2990
|
+
expect(newDrmInfoSpy).toHaveBeenCalled();
|
|
2991
|
+
});
|
|
2992
|
+
|
|
2993
|
+
describe('constructs DrmInfo with EXT-X-SESSION-KEY', () => {
|
|
2994
|
+
it('for Widevine', async () => {
|
|
2995
|
+
const initDataBase64 =
|
|
2996
|
+
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';
|
|
2997
|
+
|
|
2998
|
+
const keyId = 'abc123';
|
|
2999
|
+
|
|
3000
|
+
const master = [
|
|
3001
|
+
'#EXTM3U\n',
|
|
3002
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
3003
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
3004
|
+
'video\n',
|
|
3005
|
+
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
|
3006
|
+
'KEYID=0X' + keyId + ',',
|
|
3007
|
+
'KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",',
|
|
3008
|
+
'URI="data:text/plain;base64,',
|
|
3009
|
+
initDataBase64, '",\n',
|
|
3010
|
+
].join('');
|
|
3011
|
+
|
|
3012
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3013
|
+
manifest.anyTimeline();
|
|
3014
|
+
manifest.addPartialVariant((variant) => {
|
|
3015
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3016
|
+
stream.addDrmInfo('com.widevine.alpha', (drmInfo) => {
|
|
3017
|
+
drmInfo.addCencInitData(initDataBase64);
|
|
3018
|
+
drmInfo.keyIds.add(keyId);
|
|
3019
|
+
});
|
|
3020
|
+
});
|
|
3021
|
+
});
|
|
3022
|
+
manifest.sequenceMode = true;
|
|
3023
|
+
});
|
|
3024
|
+
|
|
3025
|
+
fakeNetEngine.setResponseText('test:/master', master);
|
|
3026
|
+
|
|
3027
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
3028
|
+
expect(actual).toEqual(manifest);
|
|
3029
|
+
});
|
|
3030
|
+
|
|
3031
|
+
it('for PlayReady', async () => {
|
|
3032
|
+
const initDataBase64 =
|
|
3033
|
+
'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=';
|
|
3034
|
+
|
|
3035
|
+
const master = [
|
|
3036
|
+
'#EXTM3U\n',
|
|
3037
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
3038
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
3039
|
+
'video\n',
|
|
3040
|
+
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
|
3041
|
+
'KEYFORMAT="com.microsoft.playready",',
|
|
3042
|
+
'URI="data:text/plain;base64,UGxheXJlYWR5",\n',
|
|
3043
|
+
].join('');
|
|
3044
|
+
|
|
3045
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3046
|
+
manifest.anyTimeline();
|
|
3047
|
+
manifest.addPartialVariant((variant) => {
|
|
3048
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3049
|
+
stream.addDrmInfo('com.microsoft.playready', (drmInfo) => {
|
|
3050
|
+
drmInfo.addCencInitData(initDataBase64);
|
|
3051
|
+
});
|
|
3052
|
+
});
|
|
3053
|
+
});
|
|
3054
|
+
manifest.sequenceMode = true;
|
|
3055
|
+
});
|
|
3056
|
+
|
|
3057
|
+
fakeNetEngine.setResponseText('test:/master', master);
|
|
3058
|
+
|
|
3059
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
3060
|
+
expect(actual).toEqual(manifest);
|
|
3061
|
+
});
|
|
3062
|
+
|
|
3063
|
+
it('for FairPlay', async () => {
|
|
3064
|
+
const master = [
|
|
3065
|
+
'#EXTM3U\n',
|
|
3066
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
3067
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
3068
|
+
'video\n',
|
|
3069
|
+
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
|
3070
|
+
'KEYFORMAT="com.apple.streamingkeydelivery",',
|
|
3071
|
+
'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
|
|
3072
|
+
].join('');
|
|
3073
|
+
|
|
3074
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3075
|
+
manifest.anyTimeline();
|
|
3076
|
+
manifest.addPartialVariant((variant) => {
|
|
3077
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3078
|
+
stream.addDrmInfo('com.apple.fps', (drmInfo) => {
|
|
3079
|
+
drmInfo.addInitData('sinf', new Uint8Array(0));
|
|
3080
|
+
});
|
|
3081
|
+
});
|
|
3082
|
+
});
|
|
3083
|
+
manifest.sequenceMode = true;
|
|
3084
|
+
});
|
|
3085
|
+
|
|
3086
|
+
fakeNetEngine.setResponseText('test:/master', master);
|
|
3087
|
+
|
|
3088
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
3089
|
+
expect(actual).toEqual(manifest);
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
it('for ClearKey with explicit KEYFORMAT', async () => {
|
|
3093
|
+
const master = [
|
|
3094
|
+
'#EXTM3U\n',
|
|
3095
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
3096
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
3097
|
+
'video\n',
|
|
3098
|
+
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
|
3099
|
+
'KEYFORMAT="identity",',
|
|
3100
|
+
'URI="key.bin",\n',
|
|
3101
|
+
].join('');
|
|
3102
|
+
|
|
3103
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3104
|
+
manifest.anyTimeline();
|
|
3105
|
+
manifest.addPartialVariant((variant) => {
|
|
3106
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3107
|
+
stream.addDrmInfo('org.w3.clearkey');
|
|
3108
|
+
});
|
|
3109
|
+
});
|
|
3110
|
+
manifest.sequenceMode = true;
|
|
3111
|
+
});
|
|
3112
|
+
|
|
3113
|
+
fakeNetEngine.setResponseText('test:/master', master);
|
|
3114
|
+
|
|
3115
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
3116
|
+
expect(actual).toEqual(manifest);
|
|
3117
|
+
});
|
|
3118
|
+
|
|
3119
|
+
it('for ClearKey without explicit KEYFORMAT', async () => {
|
|
3120
|
+
const master = [
|
|
3121
|
+
'#EXTM3U\n',
|
|
3122
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
3123
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
3124
|
+
'video\n',
|
|
3125
|
+
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
|
3126
|
+
'URI="key.bin",\n',
|
|
3127
|
+
].join('');
|
|
3128
|
+
|
|
3129
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3130
|
+
manifest.anyTimeline();
|
|
3131
|
+
manifest.addPartialVariant((variant) => {
|
|
3132
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3133
|
+
stream.addDrmInfo('org.w3.clearkey');
|
|
3134
|
+
});
|
|
3135
|
+
});
|
|
3136
|
+
manifest.sequenceMode = true;
|
|
3137
|
+
});
|
|
3138
|
+
|
|
3139
|
+
fakeNetEngine.setResponseText('test:/master', master);
|
|
3140
|
+
|
|
3141
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
3142
|
+
expect(actual).toEqual(manifest);
|
|
3143
|
+
});
|
|
2773
3144
|
});
|
|
2774
3145
|
|
|
2775
3146
|
it('falls back to mp4 if HEAD request fails', async () => {
|
|
@@ -2818,8 +3189,9 @@ describe('HlsParser', () => {
|
|
|
2818
3189
|
* @param {string} master
|
|
2819
3190
|
* @param {string} media
|
|
2820
3191
|
* @param {!shaka.util.Error} error
|
|
3192
|
+
* @param {boolean=} onCreateSegmentIndex
|
|
2821
3193
|
*/
|
|
2822
|
-
async function verifyError(master, media, error) {
|
|
3194
|
+
async function verifyError(master, media, error, onCreateSegmentIndex) {
|
|
2823
3195
|
fakeNetEngine
|
|
2824
3196
|
.setResponseText('test:/master', master)
|
|
2825
3197
|
.setResponseText('test:/audio', media)
|
|
@@ -2830,8 +3202,14 @@ describe('HlsParser', () => {
|
|
|
2830
3202
|
.setResponseValue('data:text/plain;base64,AAECAwQFBgcICQoLDA0ODw==',
|
|
2831
3203
|
aes128Key);
|
|
2832
3204
|
|
|
2833
|
-
|
|
2834
|
-
|
|
3205
|
+
if (onCreateSegmentIndex) {
|
|
3206
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
3207
|
+
await expectAsync(loadAllStreamsFor(actual))
|
|
3208
|
+
.toBeRejectedWith(Util.jasmineError(error));
|
|
3209
|
+
} else {
|
|
3210
|
+
await expectAsync(parser.start('test:/master', playerInterface))
|
|
3211
|
+
.toBeRejectedWith(Util.jasmineError(error));
|
|
3212
|
+
}
|
|
2835
3213
|
}
|
|
2836
3214
|
|
|
2837
3215
|
it('if unable to guess codecs', async () => {
|
|
@@ -2968,7 +3346,7 @@ describe('HlsParser', () => {
|
|
|
2968
3346
|
shaka.util.Error.Category.MANIFEST,
|
|
2969
3347
|
Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
|
2970
3348
|
|
|
2971
|
-
await verifyError(master, media, error);
|
|
3349
|
+
await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
|
|
2972
3350
|
});
|
|
2973
3351
|
|
|
2974
3352
|
describe('if required tags are missing', () => {
|
|
@@ -2984,7 +3362,8 @@ describe('HlsParser', () => {
|
|
|
2984
3362
|
Code.HLS_REQUIRED_TAG_MISSING,
|
|
2985
3363
|
tagName);
|
|
2986
3364
|
|
|
2987
|
-
await verifyError(
|
|
3365
|
+
await verifyError(
|
|
3366
|
+
master, media, error, /* onCreateSegmentIndex= */ true);
|
|
2988
3367
|
}
|
|
2989
3368
|
|
|
2990
3369
|
it('EXTINF', async () => {
|
|
@@ -3271,6 +3650,67 @@ describe('HlsParser', () => {
|
|
|
3271
3650
|
await testHlsParser(master, media, manifest);
|
|
3272
3651
|
});
|
|
3273
3652
|
|
|
3653
|
+
// https://github.com/shaka-project/shaka-player/issues/4308
|
|
3654
|
+
it('handles unaligned streams', async () => {
|
|
3655
|
+
const master = [
|
|
3656
|
+
'#EXTM3U\n',
|
|
3657
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
3658
|
+
'RESOLUTION=1280x720,FRAME-RATE=30\n',
|
|
3659
|
+
'video\n',
|
|
3660
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=400,CODECS="avc1",',
|
|
3661
|
+
'RESOLUTION=1920x1080,FRAME-RATE=30\n',
|
|
3662
|
+
'video2\n',
|
|
3663
|
+
].join('');
|
|
3664
|
+
|
|
3665
|
+
const media = [
|
|
3666
|
+
'#EXTM3U\n',
|
|
3667
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
3668
|
+
'#EXTINF:5\n',
|
|
3669
|
+
'main.mp4\n',
|
|
3670
|
+
'#EXTINF:5\n',
|
|
3671
|
+
'main.mp4',
|
|
3672
|
+
].join('');
|
|
3673
|
+
|
|
3674
|
+
const media2 = [
|
|
3675
|
+
'#EXTM3U\n',
|
|
3676
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
3677
|
+
'#EXTINF:4\n',
|
|
3678
|
+
'main.mp4\n',
|
|
3679
|
+
'#EXTINF:5\n',
|
|
3680
|
+
'main.mp4\n',
|
|
3681
|
+
'#EXTINF:1\n',
|
|
3682
|
+
'main.mp4',
|
|
3683
|
+
].join('');
|
|
3684
|
+
|
|
3685
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3686
|
+
manifest.anyTimeline();
|
|
3687
|
+
manifest.addPartialVariant((variant) => {
|
|
3688
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3689
|
+
stream.size(1280, 720);
|
|
3690
|
+
});
|
|
3691
|
+
});
|
|
3692
|
+
manifest.addPartialVariant((variant) => {
|
|
3693
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3694
|
+
stream.size(1920, 1080);
|
|
3695
|
+
});
|
|
3696
|
+
});
|
|
3697
|
+
manifest.sequenceMode = true;
|
|
3698
|
+
});
|
|
3699
|
+
|
|
3700
|
+
const actual = await testHlsParser(master, media, manifest, media2);
|
|
3701
|
+
|
|
3702
|
+
const videoSegments =
|
|
3703
|
+
Array.from(actual.variants[0].video.segmentIndex || '');
|
|
3704
|
+
expect(videoSegments[0].endTime).toBe(5);
|
|
3705
|
+
expect(videoSegments[1].endTime).toBe(10);
|
|
3706
|
+
|
|
3707
|
+
const videoSegments2 =
|
|
3708
|
+
Array.from(actual.variants[1].video.segmentIndex || '');
|
|
3709
|
+
expect(videoSegments2[0].endTime).toBe(4);
|
|
3710
|
+
expect(videoSegments2[1].endTime).toBe(9);
|
|
3711
|
+
expect(videoSegments2[2].endTime).toBe(10);
|
|
3712
|
+
});
|
|
3713
|
+
|
|
3274
3714
|
// Issue #1875
|
|
3275
3715
|
it('ignores audio groups on audio-only content', async () => {
|
|
3276
3716
|
// NOTE: To reproduce the original issue accurately, the two audio playlist
|
|
@@ -3318,6 +3758,7 @@ describe('HlsParser', () => {
|
|
|
3318
3758
|
.setResponseValue('test:/main.mp4', segmentData);
|
|
3319
3759
|
|
|
3320
3760
|
const actual = await parser.start('test:/master', playerInterface);
|
|
3761
|
+
await loadAllStreamsFor(actual);
|
|
3321
3762
|
expect(actual.variants.length).toBe(1);
|
|
3322
3763
|
expect(actual).toEqual(manifest);
|
|
3323
3764
|
});
|
|
@@ -3350,6 +3791,7 @@ describe('HlsParser', () => {
|
|
|
3350
3791
|
|
|
3351
3792
|
const actual =
|
|
3352
3793
|
await parser.start('test:/host/master.m3u8', playerInterface);
|
|
3794
|
+
await loadAllStreamsFor(actual);
|
|
3353
3795
|
const video = actual.variants[0].video;
|
|
3354
3796
|
const audio = actual.variants[0].audio;
|
|
3355
3797
|
|
|
@@ -3397,6 +3839,7 @@ describe('HlsParser', () => {
|
|
|
3397
3839
|
|
|
3398
3840
|
const actual =
|
|
3399
3841
|
await parser.start('test:/host/master.m3u8', playerInterface);
|
|
3842
|
+
await loadAllStreamsFor(actual);
|
|
3400
3843
|
const video = actual.variants[0].video;
|
|
3401
3844
|
const audio = actual.variants[0].audio;
|
|
3402
3845
|
|
|
@@ -3444,6 +3887,7 @@ describe('HlsParser', () => {
|
|
|
3444
3887
|
|
|
3445
3888
|
const actual =
|
|
3446
3889
|
await parser.start('test:/host/master.m3u8', playerInterface);
|
|
3890
|
+
await loadAllStreamsFor(actual);
|
|
3447
3891
|
const video = actual.variants[0].video;
|
|
3448
3892
|
const audio = actual.variants[0].audio;
|
|
3449
3893
|
|
|
@@ -3658,7 +4102,9 @@ describe('HlsParser', () => {
|
|
|
3658
4102
|
});
|
|
3659
4103
|
});
|
|
3660
4104
|
|
|
3661
|
-
await testHlsParser(media, '', manifest);
|
|
4105
|
+
const actualManifest = await testHlsParser(media, '', manifest);
|
|
4106
|
+
|
|
4107
|
+
expect(actualManifest.presentationTimeline.getDuration()).toBe(5);
|
|
3662
4108
|
});
|
|
3663
4109
|
|
|
3664
4110
|
it('honors hls.mediaPlaylistFullMimeType', async () => {
|
|
@@ -3688,6 +4134,58 @@ describe('HlsParser', () => {
|
|
|
3688
4134
|
await testHlsParser(media, '', manifest);
|
|
3689
4135
|
});
|
|
3690
4136
|
|
|
4137
|
+
it('honors hls.mediaPlaylistFullMimeType but detects AAC', async () => {
|
|
4138
|
+
const media = [
|
|
4139
|
+
'#EXTM3U\n',
|
|
4140
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
4141
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
4142
|
+
'#EXTINF:5,\n',
|
|
4143
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
4144
|
+
'main.aac',
|
|
4145
|
+
].join('');
|
|
4146
|
+
|
|
4147
|
+
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
4148
|
+
parser.configure(config);
|
|
4149
|
+
|
|
4150
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
4151
|
+
manifest.sequenceMode = true;
|
|
4152
|
+
manifest.anyTimeline();
|
|
4153
|
+
manifest.addPartialVariant((variant) => {
|
|
4154
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
4155
|
+
stream.mime('audio/aac');
|
|
4156
|
+
});
|
|
4157
|
+
});
|
|
4158
|
+
});
|
|
4159
|
+
|
|
4160
|
+
await testHlsParser(media, '', manifest);
|
|
4161
|
+
});
|
|
4162
|
+
|
|
4163
|
+
it('honors hls.mediaPlaylistFullMimeType but detects MPEG', async () => {
|
|
4164
|
+
const media = [
|
|
4165
|
+
'#EXTM3U\n',
|
|
4166
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
4167
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
4168
|
+
'#EXTINF:5,\n',
|
|
4169
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
4170
|
+
'main.mp3',
|
|
4171
|
+
].join('');
|
|
4172
|
+
|
|
4173
|
+
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
4174
|
+
parser.configure(config);
|
|
4175
|
+
|
|
4176
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
4177
|
+
manifest.sequenceMode = true;
|
|
4178
|
+
manifest.anyTimeline();
|
|
4179
|
+
manifest.addPartialVariant((variant) => {
|
|
4180
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
4181
|
+
stream.mime('audio/mpeg');
|
|
4182
|
+
});
|
|
4183
|
+
});
|
|
4184
|
+
});
|
|
4185
|
+
|
|
4186
|
+
await testHlsParser(media, '', manifest);
|
|
4187
|
+
});
|
|
4188
|
+
|
|
3691
4189
|
it('syncs on sequence with ignoreManifestProgramDateTime', async () => {
|
|
3692
4190
|
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
3693
4191
|
config.hls.ignoreManifestProgramDateTime = true;
|
|
@@ -3775,4 +4273,125 @@ describe('HlsParser', () => {
|
|
|
3775
4273
|
'test:/audio4.mp4',
|
|
3776
4274
|
]);
|
|
3777
4275
|
});
|
|
4276
|
+
|
|
4277
|
+
it('lazy-loads TS content without filtering it out', async () => {
|
|
4278
|
+
const master = [
|
|
4279
|
+
'#EXTM3U\n',
|
|
4280
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
|
|
4281
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
4282
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
|
4283
|
+
'video\n',
|
|
4284
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
4285
|
+
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud1"\n',
|
|
4286
|
+
'video2\n',
|
|
4287
|
+
].join('');
|
|
4288
|
+
|
|
4289
|
+
const video = [
|
|
4290
|
+
'#EXTM3U\n',
|
|
4291
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
4292
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
4293
|
+
'#EXT-X-MEDIA-SEQUENCE:1\n',
|
|
4294
|
+
'#EXTINF:5,\n',
|
|
4295
|
+
'video1.ts\n',
|
|
4296
|
+
'#EXTINF:5,\n',
|
|
4297
|
+
'video2.ts\n',
|
|
4298
|
+
].join('');
|
|
4299
|
+
|
|
4300
|
+
const audio = [
|
|
4301
|
+
'#EXTM3U\n',
|
|
4302
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
4303
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
4304
|
+
'#EXT-X-MEDIA-SEQUENCE:3\n',
|
|
4305
|
+
'#EXTINF:5,\n',
|
|
4306
|
+
'audio1.ts\n',
|
|
4307
|
+
'#EXTINF:5,\n',
|
|
4308
|
+
'audio2.ts\n',
|
|
4309
|
+
].join('');
|
|
4310
|
+
|
|
4311
|
+
fakeNetEngine
|
|
4312
|
+
.setResponseText('test:/master', master)
|
|
4313
|
+
.setResponseText('test:/audio', audio)
|
|
4314
|
+
.setResponseText('test:/video', video)
|
|
4315
|
+
.setResponseText('test:/video2', video)
|
|
4316
|
+
.setResponseValue('test:/init.mp4', initSegmentData);
|
|
4317
|
+
|
|
4318
|
+
const actualManifest = await parser.start('test:/master', playerInterface);
|
|
4319
|
+
expect(actualManifest.variants.length).toBe(2);
|
|
4320
|
+
|
|
4321
|
+
const actualVideo0 = actualManifest.variants[0].video;
|
|
4322
|
+
const actualVideo1 = actualManifest.variants[1].video;
|
|
4323
|
+
|
|
4324
|
+
// Before loading, all MIME types agree, and are defaulted to video/mp4.
|
|
4325
|
+
expect(actualVideo0.mimeType).toBe('video/mp4');
|
|
4326
|
+
expect(actualVideo1.mimeType).toBe('video/mp4');
|
|
4327
|
+
|
|
4328
|
+
await actualVideo0.createSegmentIndex();
|
|
4329
|
+
|
|
4330
|
+
// After loading just ONE stream, all MIME types agree again, and have been
|
|
4331
|
+
// updated to reflect the TS content found inside the loaded playlist.
|
|
4332
|
+
// This is how we avoid having the unloaded tracks filtered out during
|
|
4333
|
+
// startup.
|
|
4334
|
+
expect(actualVideo0.mimeType).toBe('video/mp2t');
|
|
4335
|
+
expect(actualVideo1.mimeType).toBe('video/mp2t');
|
|
4336
|
+
});
|
|
4337
|
+
|
|
4338
|
+
it('lazy-loads AAC content without filtering it out', async () => {
|
|
4339
|
+
const master = [
|
|
4340
|
+
'#EXTM3U\n',
|
|
4341
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
|
|
4342
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="spa",URI="audio2"\n',
|
|
4343
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
4344
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
|
4345
|
+
'video\n',
|
|
4346
|
+
].join('');
|
|
4347
|
+
|
|
4348
|
+
const video = [
|
|
4349
|
+
'#EXTM3U\n',
|
|
4350
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
4351
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
4352
|
+
'#EXT-X-MEDIA-SEQUENCE:1\n',
|
|
4353
|
+
'#EXTINF:5,\n',
|
|
4354
|
+
'video1.ts\n',
|
|
4355
|
+
'#EXTINF:5,\n',
|
|
4356
|
+
'video2.ts\n',
|
|
4357
|
+
].join('');
|
|
4358
|
+
|
|
4359
|
+
const audio = [
|
|
4360
|
+
'#EXTM3U\n',
|
|
4361
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
4362
|
+
'#EXT-X-MEDIA-SEQUENCE:3\n',
|
|
4363
|
+
'#EXTINF:5,\n',
|
|
4364
|
+
'audio1.aac\n',
|
|
4365
|
+
'#EXTINF:5,\n',
|
|
4366
|
+
'audio2.aac\n',
|
|
4367
|
+
].join('');
|
|
4368
|
+
|
|
4369
|
+
fakeNetEngine
|
|
4370
|
+
.setResponseText('test:/master', master)
|
|
4371
|
+
.setResponseText('test:/audio', audio)
|
|
4372
|
+
.setResponseText('test:/audio2', audio)
|
|
4373
|
+
.setResponseText('test:/video', video)
|
|
4374
|
+
.setResponseValue('test:/init.mp4', initSegmentData);
|
|
4375
|
+
|
|
4376
|
+
const actualManifest = await parser.start('test:/master', playerInterface);
|
|
4377
|
+
expect(actualManifest.variants.length).toBe(2);
|
|
4378
|
+
|
|
4379
|
+
const actualAudio0 = actualManifest.variants[0].audio;
|
|
4380
|
+
const actualAudio1 = actualManifest.variants[1].audio;
|
|
4381
|
+
|
|
4382
|
+
// Before loading, all MIME types agree, and are defaulted to audio/mp4.
|
|
4383
|
+
expect(actualAudio0.mimeType).toBe('audio/mp4');
|
|
4384
|
+
expect(actualAudio1.mimeType).toBe('audio/mp4');
|
|
4385
|
+
|
|
4386
|
+
await actualAudio0.createSegmentIndex();
|
|
4387
|
+
|
|
4388
|
+
// After loading just ONE stream, all MIME types agree again, and have been
|
|
4389
|
+
// updated to reflect the AAC content found inside the loaded playlist.
|
|
4390
|
+
// This is how we avoid having the unloaded tracks filtered out during
|
|
4391
|
+
// startup.
|
|
4392
|
+
expect(actualAudio0.mimeType).toBe('audio/aac');
|
|
4393
|
+
expect(actualAudio0.codecs).toBe('');
|
|
4394
|
+
expect(actualAudio1.mimeType).toBe('audio/aac');
|
|
4395
|
+
expect(actualAudio1.codecs).toBe('');
|
|
4396
|
+
});
|
|
3778
4397
|
});
|