shaka-player 4.3.6 → 4.3.8
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 +46 -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 +1 -1
- package/dist/locales.js +33 -33
- 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 +263 -256
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +9 -0
- package/dist/shaka-player.compiled.js +901 -895
- 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 +268 -262
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +9 -0
- package/dist/shaka-player.ui.js +926 -920
- 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 +29 -20
- package/lib/dash/dash_parser.js +18 -3
- package/lib/hls/hls_parser.js +66 -6
- 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 +48 -23
- package/lib/media/video_wrapper.js +6 -1
- package/lib/player.js +16 -5
- 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/stream_utils.js +1 -0
- package/package.json +9 -4
- package/test/cea/mp4_cea_parser_unit.js +45 -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 +144 -0
- package/test/media/media_source_engine_unit.js +30 -0
- package/test/media/region_observer_unit.js +2 -1
- package/test/media/streaming_engine_unit.js +26 -2
- package/test/test/assets/h265-cea-init.mp4 +0 -0
- package/test/test/assets/h265-cea-segment.mp4 +0 -0
- 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
|
@@ -590,7 +590,8 @@ describe('HlsParser live', () => {
|
|
|
590
590
|
// partialRef3
|
|
591
591
|
'#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
|
|
592
592
|
// preloadRef
|
|
593
|
-
'#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210
|
|
593
|
+
'#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210,',
|
|
594
|
+
'BYTERANGE-LENGTH=210\n',
|
|
594
595
|
].join('');
|
|
595
596
|
|
|
596
597
|
const partialRef = makeReference(
|
|
@@ -613,6 +614,7 @@ describe('HlsParser live', () => {
|
|
|
613
614
|
const preloadRef = makeReference(
|
|
614
615
|
'test:/partial.mp4', 6, 7.5, /* syncTime= */ null,
|
|
615
616
|
/* baseUri= */ '', /* startByte= */ 210, /* endByte= */ null);
|
|
617
|
+
preloadRef.markAsPreload();
|
|
616
618
|
|
|
617
619
|
// ref2 is not fully published yet, so it doesn't have a segment uri.
|
|
618
620
|
const ref2 = makeReference(
|
|
@@ -1150,6 +1150,74 @@ describe('HlsParser', () => {
|
|
|
1150
1150
|
await testHlsParser(master, media, manifest);
|
|
1151
1151
|
});
|
|
1152
1152
|
|
|
1153
|
+
it('adds subtitle role when characteristics are empty', async () => {
|
|
1154
|
+
const master = [
|
|
1155
|
+
'#EXTM3U\n',
|
|
1156
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
1157
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
|
1158
|
+
'video\n',
|
|
1159
|
+
|
|
1160
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
|
1161
|
+
'NAME="English",URI="audio"\n',
|
|
1162
|
+
|
|
1163
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
|
1164
|
+
'CHARACTERISTICS="public.accessibility.describes-video,',
|
|
1165
|
+
'public.accessibility.describes-music-and-sound",',
|
|
1166
|
+
'NAME="English (describes-video)",URI="audio2"\n',
|
|
1167
|
+
|
|
1168
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
|
1169
|
+
'NAME="English (subtitle)",DEFAULT=YES,AUTOSELECT=YES,',
|
|
1170
|
+
'URI="text"\n',
|
|
1171
|
+
|
|
1172
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
|
1173
|
+
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
|
1174
|
+
'CHARACTERISTICS="public.accessibility.describes-music-and-sound",',
|
|
1175
|
+
'URI="text2"\n',
|
|
1176
|
+
].join('');
|
|
1177
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
1178
|
+
manifest.anyTimeline();
|
|
1179
|
+
manifest.addPartialVariant((variant) => {
|
|
1180
|
+
variant.language = 'en';
|
|
1181
|
+
variant.addPartialStream(ContentType.VIDEO);
|
|
1182
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
1183
|
+
stream.language = 'en';
|
|
1184
|
+
stream.roles = [];
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
manifest.addPartialVariant((variant) => {
|
|
1188
|
+
variant.language = 'en';
|
|
1189
|
+
variant.addPartialStream(ContentType.VIDEO);
|
|
1190
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
1191
|
+
stream.language = 'en';
|
|
1192
|
+
stream.roles = [
|
|
1193
|
+
'public.accessibility.describes-video',
|
|
1194
|
+
'public.accessibility.describes-music-and-sound',
|
|
1195
|
+
];
|
|
1196
|
+
});
|
|
1197
|
+
});
|
|
1198
|
+
manifest.addPartialTextStream((stream) => {
|
|
1199
|
+
stream.language = 'en';
|
|
1200
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
1201
|
+
stream.roles = [
|
|
1202
|
+
'subtitle',
|
|
1203
|
+
];
|
|
1204
|
+
});
|
|
1205
|
+
manifest.addPartialTextStream((stream) => {
|
|
1206
|
+
stream.language = 'en';
|
|
1207
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
1208
|
+
stream.roles = [
|
|
1209
|
+
'public.accessibility.describes-music-and-sound',
|
|
1210
|
+
];
|
|
1211
|
+
});
|
|
1212
|
+
manifest.sequenceMode = true;
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
fakeNetEngine.setResponseText('test:/master', master);
|
|
1216
|
+
|
|
1217
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
1218
|
+
expect(actual).toEqual(manifest);
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1153
1221
|
it('parses characteristics from text tags', async () => {
|
|
1154
1222
|
const master = [
|
|
1155
1223
|
'#EXTM3U\n',
|
|
@@ -1898,6 +1966,82 @@ describe('HlsParser', () => {
|
|
|
1898
1966
|
}
|
|
1899
1967
|
});
|
|
1900
1968
|
|
|
1969
|
+
it('ignore segments with #EXTINF:0', async () => {
|
|
1970
|
+
const master = [
|
|
1971
|
+
'#EXTM3U\n',
|
|
1972
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
|
1973
|
+
'CHANNELS="2",URI="audio"\n',
|
|
1974
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
1975
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
|
1976
|
+
'video\n',
|
|
1977
|
+
].join('');
|
|
1978
|
+
|
|
1979
|
+
const video = [
|
|
1980
|
+
'#EXTM3U\n',
|
|
1981
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
1982
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
1983
|
+
'#EXTINF:5,\n',
|
|
1984
|
+
'main.mp4\n',
|
|
1985
|
+
'#EXTINF:0,\n',
|
|
1986
|
+
'main.mp4\n',
|
|
1987
|
+
'#EXTINF:5,\n',
|
|
1988
|
+
'main.mp4\n',
|
|
1989
|
+
].join('');
|
|
1990
|
+
|
|
1991
|
+
const audio = [
|
|
1992
|
+
'#EXTM3U\n',
|
|
1993
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
1994
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
1995
|
+
'#EXTINF:5,\n',
|
|
1996
|
+
'main.mp4\n',
|
|
1997
|
+
'#EXTINF:0,\n',
|
|
1998
|
+
'main.mp4\n',
|
|
1999
|
+
'#EXTINF:5,\n',
|
|
2000
|
+
'main.mp4\n',
|
|
2001
|
+
].join('');
|
|
2002
|
+
|
|
2003
|
+
fakeNetEngine
|
|
2004
|
+
.setResponseText('test:/master', master)
|
|
2005
|
+
.setResponseText('test:/audio', audio)
|
|
2006
|
+
.setResponseText('test:/video', video)
|
|
2007
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
2008
|
+
.setResponseValue('test:/main.mp4', segmentData);
|
|
2009
|
+
|
|
2010
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
2011
|
+
await loadAllStreamsFor(actual);
|
|
2012
|
+
|
|
2013
|
+
expect(actual.variants.length).toBe(1);
|
|
2014
|
+
|
|
2015
|
+
const variant = actual.variants[0];
|
|
2016
|
+
expect(variant.video).toBeTruthy();
|
|
2017
|
+
expect(variant.audio).toBeTruthy();
|
|
2018
|
+
|
|
2019
|
+
await variant.video.createSegmentIndex();
|
|
2020
|
+
goog.asserts.assert(variant.video.segmentIndex != null,
|
|
2021
|
+
'Null segmentIndex!');
|
|
2022
|
+
|
|
2023
|
+
const firstVideoReference = variant.video.segmentIndex.get(0);
|
|
2024
|
+
const secondVideoReference = variant.video.segmentIndex.get(1);
|
|
2025
|
+
const thirdVideoReference = variant.video.segmentIndex.get(2);
|
|
2026
|
+
|
|
2027
|
+
expect(firstVideoReference).not.toBe(null);
|
|
2028
|
+
expect(secondVideoReference).not.toBe(null);
|
|
2029
|
+
expect(thirdVideoReference).toBe(null);
|
|
2030
|
+
|
|
2031
|
+
|
|
2032
|
+
await variant.audio.createSegmentIndex();
|
|
2033
|
+
goog.asserts.assert(variant.audio.segmentIndex != null,
|
|
2034
|
+
'Null segmentIndex!');
|
|
2035
|
+
|
|
2036
|
+
const firstAudioReference = variant.audio.segmentIndex.get(0);
|
|
2037
|
+
const secondAudioReference = variant.audio.segmentIndex.get(1);
|
|
2038
|
+
const thirdAudioReference = variant.audio.segmentIndex.get(2);
|
|
2039
|
+
|
|
2040
|
+
expect(firstAudioReference).not.toBe(null);
|
|
2041
|
+
expect(secondAudioReference).not.toBe(null);
|
|
2042
|
+
expect(thirdAudioReference).toBe(null);
|
|
2043
|
+
});
|
|
2044
|
+
|
|
1901
2045
|
it('Disable audio does not create audio streams', async () => {
|
|
1902
2046
|
const master = [
|
|
1903
2047
|
'#EXTM3U\n',
|
|
@@ -171,9 +171,12 @@ describe('MediaSourceEngine', () => {
|
|
|
171
171
|
describe('constructor', () => {
|
|
172
172
|
const originalCreateObjectURL =
|
|
173
173
|
shaka.media.MediaSourceEngine.createObjectURL;
|
|
174
|
+
const originalRevokeObjectURL = window.URL.revokeObjectURL;
|
|
174
175
|
const originalMediaSource = window.MediaSource;
|
|
175
176
|
/** @type {jasmine.Spy} */
|
|
176
177
|
let createObjectURLSpy;
|
|
178
|
+
/** @type {jasmine.Spy} */
|
|
179
|
+
let revokeObjectURLSpy;
|
|
177
180
|
|
|
178
181
|
beforeEach(async () => {
|
|
179
182
|
// Mock out MediaSource so we can test the production version of
|
|
@@ -188,6 +191,9 @@ describe('MediaSourceEngine', () => {
|
|
|
188
191
|
shaka.media.MediaSourceEngine.createObjectURL =
|
|
189
192
|
Util.spyFunc(createObjectURLSpy);
|
|
190
193
|
|
|
194
|
+
revokeObjectURLSpy = jasmine.createSpy('revokeObjectURL');
|
|
195
|
+
window.URL.revokeObjectURL = Util.spyFunc(revokeObjectURLSpy);
|
|
196
|
+
|
|
191
197
|
const mediaSourceSpy = jasmine.createSpy('MediaSource');
|
|
192
198
|
// Because this is a fake constructor, it must be callable with "new".
|
|
193
199
|
// This will cause jasmine to invoke the callback with "new" as well, so
|
|
@@ -205,6 +211,7 @@ describe('MediaSourceEngine', () => {
|
|
|
205
211
|
afterAll(() => {
|
|
206
212
|
shaka.media.MediaSourceEngine.createObjectURL = originalCreateObjectURL;
|
|
207
213
|
window.MediaSource = originalMediaSource;
|
|
214
|
+
window.URL.revokeObjectURL = originalRevokeObjectURL;
|
|
208
215
|
});
|
|
209
216
|
|
|
210
217
|
it('creates a MediaSource object and sets video.src', () => {
|
|
@@ -216,6 +223,29 @@ describe('MediaSourceEngine', () => {
|
|
|
216
223
|
expect(createObjectURLSpy).toHaveBeenCalled();
|
|
217
224
|
expect(mockVideo.src).toBe('blob:foo');
|
|
218
225
|
});
|
|
226
|
+
|
|
227
|
+
it('revokes object URL after MediaSource opens', () => {
|
|
228
|
+
let onSourceOpenListener;
|
|
229
|
+
|
|
230
|
+
mockMediaSource.addEventListener.and.callFake((event, callback, _) => {
|
|
231
|
+
if (event == 'sourceopen') {
|
|
232
|
+
onSourceOpenListener = callback;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
mediaSourceEngine = new shaka.media.MediaSourceEngine(
|
|
237
|
+
video,
|
|
238
|
+
new shaka.test.FakeTextDisplayer());
|
|
239
|
+
|
|
240
|
+
expect(mockMediaSource.addEventListener).toHaveBeenCalledTimes(1);
|
|
241
|
+
expect(mockMediaSource.addEventListener.calls.mostRecent().args[0])
|
|
242
|
+
.toBe('sourceopen');
|
|
243
|
+
expect(typeof onSourceOpenListener).toBe(typeof Function);
|
|
244
|
+
|
|
245
|
+
onSourceOpenListener();
|
|
246
|
+
|
|
247
|
+
expect(revokeObjectURLSpy).toHaveBeenCalledWith('blob:foo');
|
|
248
|
+
});
|
|
219
249
|
});
|
|
220
250
|
|
|
221
251
|
describe('init', () => {
|
|
@@ -31,7 +31,8 @@ describe('RegionObserver', () => {
|
|
|
31
31
|
timeline = new shaka.media.RegionTimeline(
|
|
32
32
|
shaka.test.Util.spyFunc(onSeekRange));
|
|
33
33
|
|
|
34
|
-
observer = new shaka.media.RegionObserver(
|
|
34
|
+
observer = new shaka.media.RegionObserver(
|
|
35
|
+
timeline, /* startsPastZero= */ false);
|
|
35
36
|
observer.addEventListener('enter', (event) => {
|
|
36
37
|
shaka.test.Util.spyFunc(onEnterRegion)(event['region'], event['seeking']);
|
|
37
38
|
});
|
|
@@ -2913,10 +2913,14 @@ describe('StreamingEngine', () => {
|
|
|
2913
2913
|
expect(onManifestUpdate).toHaveBeenCalled();
|
|
2914
2914
|
});
|
|
2915
2915
|
|
|
2916
|
-
it('triggers metadata event', async () => {
|
|
2916
|
+
it('triggers both emsg event and metadata event for ID3', async () => {
|
|
2917
2917
|
setSegment0(emsgSegmentV0ID3);
|
|
2918
2918
|
videoStream.emsgSchemeIdUris = [id3SchemeUri];
|
|
2919
2919
|
|
|
2920
|
+
onEvent.and.callFake((emsgEvent) => {
|
|
2921
|
+
expect(emsgEvent.type).toBe('emsg');
|
|
2922
|
+
});
|
|
2923
|
+
|
|
2920
2924
|
// Here we go!
|
|
2921
2925
|
streamingEngine.switchVariant(variant);
|
|
2922
2926
|
streamingEngine.switchTextStream(textStream);
|
|
@@ -2924,10 +2928,30 @@ describe('StreamingEngine', () => {
|
|
|
2924
2928
|
playing = true;
|
|
2925
2929
|
await runTest();
|
|
2926
2930
|
|
|
2927
|
-
expect(onEvent).
|
|
2931
|
+
expect(onEvent).toHaveBeenCalled();
|
|
2928
2932
|
expect(onMetadata).toHaveBeenCalled();
|
|
2929
2933
|
});
|
|
2930
2934
|
|
|
2935
|
+
it('only triggers emsg event for ID3 if event canceled', async () => {
|
|
2936
|
+
setSegment0(emsgSegmentV0ID3);
|
|
2937
|
+
videoStream.emsgSchemeIdUris = [id3SchemeUri];
|
|
2938
|
+
|
|
2939
|
+
onEvent.and.callFake((emsgEvent) => {
|
|
2940
|
+
expect(emsgEvent.type).toBe('emsg');
|
|
2941
|
+
emsgEvent.preventDefault();
|
|
2942
|
+
});
|
|
2943
|
+
|
|
2944
|
+
// Here we go!
|
|
2945
|
+
streamingEngine.switchVariant(variant);
|
|
2946
|
+
streamingEngine.switchTextStream(textStream);
|
|
2947
|
+
await streamingEngine.start();
|
|
2948
|
+
playing = true;
|
|
2949
|
+
await runTest();
|
|
2950
|
+
|
|
2951
|
+
expect(onEvent).toHaveBeenCalled();
|
|
2952
|
+
expect(onMetadata).not.toHaveBeenCalled();
|
|
2953
|
+
});
|
|
2954
|
+
|
|
2931
2955
|
it('event start matches presentation time', async () => {
|
|
2932
2956
|
// This box has a non-zero event time, which doesn't matter.
|
|
2933
2957
|
setSegment0(emsgSegmentV1NonZeroStart);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -43,9 +43,10 @@ shaka.test.LayoutTests = class {
|
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
// Wait one extra
|
|
47
|
-
// updated. Without this, we saw some rare test flake in Firefox on
|
|
48
|
-
|
|
46
|
+
// Wait one extra second to make sure the font rendering on the page has
|
|
47
|
+
// been updated. Without this, we saw some rare test flake in Firefox on
|
|
48
|
+
// Mac.
|
|
49
|
+
await shaka.test.Util.delay(1);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
goog.provide('shaka.ui.ResolutionSelection');
|
|
9
9
|
|
|
10
10
|
goog.require('goog.asserts');
|
|
11
|
+
goog.require('shaka.Player');
|
|
11
12
|
goog.require('shaka.ui.Controls');
|
|
12
13
|
goog.require('shaka.ui.Enums');
|
|
13
14
|
goog.require('shaka.ui.Locales');
|
|
@@ -67,7 +68,12 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
|
|
|
67
68
|
/** @private */
|
|
68
69
|
updateResolutionSelection_() {
|
|
69
70
|
/** @type {!Array.<shaka.extern.Track>} */
|
|
70
|
-
let tracks =
|
|
71
|
+
let tracks = [];
|
|
72
|
+
// When played with src=, the variant tracks available from
|
|
73
|
+
// player.getVariantTracks() represent languages, not resolutions.
|
|
74
|
+
if (this.player.getLoadMode() != shaka.Player.LoadMode.SRC_EQUALS) {
|
|
75
|
+
tracks = this.player.getVariantTracks();
|
|
76
|
+
}
|
|
71
77
|
|
|
72
78
|
// If there is a selected variant track, then we filter out any tracks in
|
|
73
79
|
// a different language. Then we use those remaining tracks to display the
|