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.
Files changed (66) hide show
  1. package/.github/workflows/appspot.yaml +39 -0
  2. package/.github/workflows/demo-version-index.yaml +3 -3
  3. package/CHANGELOG.md +46 -0
  4. package/CONTRIBUTORS +1 -0
  5. package/README.md +3 -0
  6. package/demo/custom.js +1 -1
  7. package/demo/demo.less +1 -0
  8. package/dist/demo.compiled.debug.js +2 -2
  9. package/dist/demo.compiled.debug.map +2 -2
  10. package/dist/demo.compiled.js +2 -2
  11. package/dist/demo.compiled.map +2 -2
  12. package/dist/demo.css +1 -1
  13. package/dist/demo.css.map +1 -1
  14. package/dist/deps.js +1 -1
  15. package/dist/locales.js +33 -33
  16. package/dist/shaka-player.compiled.d.ts +30 -0
  17. package/dist/shaka-player.compiled.debug.d.ts +30 -0
  18. package/dist/shaka-player.compiled.debug.externs.js +9 -0
  19. package/dist/shaka-player.compiled.debug.js +263 -256
  20. package/dist/shaka-player.compiled.debug.map +3 -3
  21. package/dist/shaka-player.compiled.externs.js +9 -0
  22. package/dist/shaka-player.compiled.js +901 -895
  23. package/dist/shaka-player.compiled.map +4 -4
  24. package/dist/shaka-player.ui.d.ts +30 -0
  25. package/dist/shaka-player.ui.debug.d.ts +30 -0
  26. package/dist/shaka-player.ui.debug.externs.js +9 -0
  27. package/dist/shaka-player.ui.debug.js +268 -262
  28. package/dist/shaka-player.ui.debug.map +3 -3
  29. package/dist/shaka-player.ui.externs.js +9 -0
  30. package/dist/shaka-player.ui.js +926 -920
  31. package/dist/shaka-player.ui.map +4 -4
  32. package/docs/tutorials/drm-config.md +9 -6
  33. package/docs/tutorials/welcome.md +1 -1
  34. package/externs/ima.js +34 -0
  35. package/lib/abr/simple_abr_manager.js +1 -1
  36. package/lib/cea/mp4_cea_parser.js +29 -20
  37. package/lib/dash/dash_parser.js +18 -3
  38. package/lib/hls/hls_parser.js +66 -6
  39. package/lib/hls/manifest_text_parser.js +10 -1
  40. package/lib/media/drm_engine.js +23 -14
  41. package/lib/media/gap_jumping_controller.js +24 -2
  42. package/lib/media/media_source_engine.js +10 -6
  43. package/lib/media/playhead.js +11 -0
  44. package/lib/media/region_observer.js +19 -1
  45. package/lib/media/segment_reference.js +22 -0
  46. package/lib/media/streaming_engine.js +48 -23
  47. package/lib/media/video_wrapper.js +6 -1
  48. package/lib/player.js +16 -5
  49. package/lib/polyfill/abort_controller.js +7 -0
  50. package/lib/text/vtt_text_parser.js +2 -1
  51. package/lib/util/manifest_parser_utils.js +1 -1
  52. package/lib/util/stream_utils.js +1 -0
  53. package/package.json +9 -4
  54. package/test/cea/mp4_cea_parser_unit.js +45 -3
  55. package/test/dash/dash_parser_manifest_unit.js +120 -0
  56. package/test/hls/hls_live_unit.js +3 -1
  57. package/test/hls/hls_parser_unit.js +144 -0
  58. package/test/media/media_source_engine_unit.js +30 -0
  59. package/test/media/region_observer_unit.js +2 -1
  60. package/test/media/streaming_engine_unit.js +26 -2
  61. package/test/test/assets/h265-cea-init.mp4 +0 -0
  62. package/test/test/assets/h265-cea-segment.mp4 +0 -0
  63. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  64. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  65. package/test/test/util/layout_tests.js +4 -3
  66. 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\n',
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(timeline);
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).not.toHaveBeenCalled();
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);
@@ -43,9 +43,10 @@ shaka.test.LayoutTests = class {
43
43
  });
44
44
  });
45
45
 
46
- // Wait one extra tick to make sure the font rendering on the page has been
47
- // updated. Without this, we saw some rare test flake in Firefox on Mac.
48
- await shaka.test.Util.shortDelay();
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 = this.player.getVariantTracks();
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