shaka-player 3.3.13 → 3.3.15

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 (42) hide show
  1. package/.github/workflows/build-and-test.yaml +13 -0
  2. package/AUTHORS +2 -0
  3. package/CHANGELOG.md +28 -0
  4. package/CONTRIBUTORS +2 -0
  5. package/dist/deps.js +1 -1
  6. package/dist/locales.js +31 -31
  7. package/dist/shaka-player.compiled.debug.js +118 -111
  8. package/dist/shaka-player.compiled.debug.map +3 -3
  9. package/dist/shaka-player.compiled.js +712 -710
  10. package/dist/shaka-player.compiled.map +3 -3
  11. package/dist/shaka-player.ui.debug.js +206 -199
  12. package/dist/shaka-player.ui.debug.map +3 -3
  13. package/dist/shaka-player.ui.js +821 -820
  14. package/dist/shaka-player.ui.map +3 -3
  15. package/docs/tutorials/welcome.md +2 -2
  16. package/externs/shaka/player.js +2 -1
  17. package/lib/ads/ad_manager.js +2 -2
  18. package/lib/cea/cea708_window.js +2 -2
  19. package/lib/dash/segment_template.js +1 -1
  20. package/lib/hls/hls_parser.js +8 -1
  21. package/lib/media/content_workarounds.js +12 -0
  22. package/lib/media/streaming_engine.js +20 -8
  23. package/lib/player.js +11 -2
  24. package/lib/polyfill/media_capabilities.js +59 -9
  25. package/lib/text/ttml_text_parser.js +32 -7
  26. package/lib/text/ui_text_displayer.js +22 -5
  27. package/lib/util/platform.js +21 -2
  28. package/lib/util/stream_utils.js +30 -10
  29. package/package.json +1 -1
  30. package/test/dash/dash_parser_manifest_unit.js +1 -0
  31. package/test/hls/hls_parser_unit.js +1 -0
  32. package/test/player_integration.js +10 -3
  33. package/test/polyfill/media_capabilities_unit.js +261 -0
  34. package/test/test/boot.js +29 -11
  35. package/test/test/util/test_scheme.js +2 -2
  36. package/test/text/mp4_ttml_parser_unit.js +4 -4
  37. package/test/text/ttml_text_parser_unit.js +119 -86
  38. package/test/text/ui_text_displayer_unit.js +76 -0
  39. package/test/ui/text_displayer_layout_unit.js +14 -0
  40. package/test/util/stream_utils_unit.js +18 -0
  41. package/test/util/xml_utils_unit.js +6 -1
  42. package/ui/controls.js +1 -1
@@ -879,7 +879,7 @@ describe('Player', () => {
879
879
  expect(getBufferedBehind()).toBe(20); // Buffered to start still.
880
880
  video.currentTime = 50;
881
881
  await waitUntilBuffered(30);
882
- expect(getBufferedBehind()).toBeLessThan(30);
882
+ expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
883
883
 
884
884
  player.configure('streaming.bufferBehind', 10);
885
885
  // We only evict content when we append a segment, so increase the
@@ -906,7 +906,7 @@ describe('Player', () => {
906
906
  }
907
907
 
908
908
  async function waitUntilBuffered(amount) {
909
- for (let i = 0; i < 25; i++) {
909
+ for (let i = 0; i < 50; i++) {
910
910
  // We buffer from an internal segment, so this shouldn't take long to
911
911
  // buffer.
912
912
  await Util.delay(0.1); // eslint-disable-line no-await-in-loop
@@ -914,7 +914,14 @@ describe('Player', () => {
914
914
  return;
915
915
  }
916
916
  }
917
- throw new Error('Timeout waiting to buffer');
917
+
918
+ const ranges =
919
+ shaka.media.TimeRangesUtils.getBufferedInfo(video.buffered);
920
+ const currentTime = video.currentTime;
921
+ const target = currentTime + amount;
922
+
923
+ throw new Error('Timeout waiting to buffer! ' +
924
+ JSON.stringify({ranges, currentTime, target}));
918
925
  }
919
926
  }); // describe('buffering')
920
927
 
@@ -0,0 +1,261 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ describe('MediaCapabilities', () => {
8
+ const Util = shaka.test.Util;
9
+ const originalCast = window['cast'];
10
+ const originalVendor = navigator.vendor;
11
+ const originalUserAgent = navigator.userAgent;
12
+ const originalRequestMediaKeySystemAccess =
13
+ navigator.requestMediaKeySystemAccess;
14
+ const originalMediaCapabilities = navigator.mediaCapabilities;
15
+
16
+ /** @type {MediaDecodingConfiguration} */
17
+ let mockDecodingConfig;
18
+ /** @type {!jasmine.Spy} */
19
+ let mockCanDisplayType;
20
+
21
+ beforeAll(() => {
22
+ Object.defineProperty(window['navigator'],
23
+ 'userAgent', {
24
+ value: 'unknown', configurable: true,
25
+ writable: true,
26
+ });
27
+ Object.defineProperty(window['navigator'],
28
+ 'vendor', {
29
+ value: 'unknown', configurable: true,
30
+ writable: true,
31
+ });
32
+ Object.defineProperty(window['navigator'],
33
+ 'requestMediaKeySystemAccess', {
34
+ value: 'unknown', configurable: true,
35
+ writable: true,
36
+ });
37
+ Object.defineProperty(window['navigator'],
38
+ 'mediaCapabilities', {
39
+ value: undefined, configurable: true,
40
+ writable: true,
41
+ });
42
+ });
43
+
44
+ beforeEach(() => {
45
+ mockDecodingConfig = {
46
+ audio: {
47
+ bitrate: 100891,
48
+ channels: 2,
49
+ contentType: 'audio/mp4; codecs="mp4a.40.2"',
50
+ samplerate: 48000,
51
+ spatialRendering: false,
52
+ },
53
+ keySystemConfiguration: {
54
+ audio: {robustness: 'SW_SECURE_CRYPTO'},
55
+ distinctiveIdentifier: 'optional',
56
+ initDataType: 'cenc',
57
+ keySystem: 'com.widevine.alpha',
58
+ persistentState: 'optional',
59
+ sessionTypes: ['temporary'],
60
+ video: {robustness: 'SW_SECURE_CRYPTO'},
61
+ },
62
+ type: 'media-source',
63
+ video: {
64
+ bitrate: 349265,
65
+ contentType: 'video/mp4; codecs="avc1.4D4015"',
66
+ framerate: 23.976023976023978,
67
+ height: 288,
68
+ width: 512,
69
+ },
70
+ };
71
+ shaka.polyfill.MediaCapabilities.memoizedMediaKeySystemAccessRequests_ = {};
72
+
73
+ mockCanDisplayType = jasmine.createSpy('canDisplayType');
74
+ mockCanDisplayType.and.returnValue(false);
75
+ });
76
+
77
+ afterEach(() => {
78
+ window['cast'] = originalCast;
79
+ });
80
+
81
+ afterAll(() => {
82
+ window['cast'] = originalCast;
83
+ Object.defineProperty(window['navigator'],
84
+ 'userAgent', {value: originalUserAgent});
85
+ Object.defineProperty(window['navigator'],
86
+ 'vendor', {value: originalVendor});
87
+ Object.defineProperty(window['navigator'],
88
+ 'requestMediaKeySystemAccess',
89
+ {value: originalRequestMediaKeySystemAccess});
90
+ Object.defineProperty(window['navigator'],
91
+ 'mediaCapabilities', {value: originalMediaCapabilities});
92
+ });
93
+
94
+ describe('install', () => {
95
+ it('should define decoding info method', () => {
96
+ shaka.polyfill.MediaCapabilities.install();
97
+
98
+ expect(navigator.mediaCapabilities.decodingInfo).toBeDefined();
99
+ });
100
+ });
101
+
102
+ describe('decodingInfo', () => {
103
+ it('should check codec support when MediaDecodingConfiguration.type ' +
104
+ 'is "media-source"', () => {
105
+ const isTypeSupportedSpy =
106
+ spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
107
+ shaka.polyfill.MediaCapabilities.install();
108
+ navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
109
+
110
+ expect(isTypeSupportedSpy).toHaveBeenCalledTimes(2);
111
+ expect(isTypeSupportedSpy).toHaveBeenCalledWith(
112
+ mockDecodingConfig.video.contentType,
113
+ );
114
+ expect(isTypeSupportedSpy).toHaveBeenCalledWith(
115
+ mockDecodingConfig.audio.contentType,
116
+ );
117
+ });
118
+
119
+ it('should check codec support when MediaDecodingConfiguration.type ' +
120
+ 'is "file"', () => {
121
+ const supportsMediaTypeSpy =
122
+ spyOn(shaka['util']['Platform'],
123
+ 'supportsMediaType').and.returnValue(true);
124
+ mockDecodingConfig.type = 'file';
125
+ shaka.polyfill.MediaCapabilities.install();
126
+ navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
127
+
128
+ expect(supportsMediaTypeSpy).toHaveBeenCalledTimes(2);
129
+ expect(supportsMediaTypeSpy).toHaveBeenCalledWith(
130
+ mockDecodingConfig.video.contentType,
131
+ );
132
+ expect(supportsMediaTypeSpy).toHaveBeenCalledWith(
133
+ mockDecodingConfig.audio.contentType,
134
+ );
135
+ });
136
+
137
+ it('should check MediaKeySystem when keySystemConfiguration is present',
138
+ async () => {
139
+ const mockResult = {mockKeySystemAccess: 'mockKeySystemAccess'};
140
+ spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
141
+ const requestKeySystemAccessSpy =
142
+ spyOn(window['navigator'],
143
+ 'requestMediaKeySystemAccess').and.returnValue(mockResult);
144
+
145
+ shaka.polyfill.MediaCapabilities.install();
146
+ const result = await navigator.mediaCapabilities
147
+ .decodingInfo(mockDecodingConfig);
148
+
149
+ expect(requestKeySystemAccessSpy).toHaveBeenCalledWith(
150
+ 'com.widevine.alpha',
151
+ [{
152
+ audioCapabilities: [
153
+ {
154
+ robustness: 'SW_SECURE_CRYPTO',
155
+ contentType: 'audio/mp4; codecs="mp4a.40.2"',
156
+ },
157
+ ],
158
+ distinctiveIdentifier: 'optional',
159
+ initDataTypes: ['cenc'],
160
+ persistentState: 'optional',
161
+ sessionTypes: ['temporary'],
162
+ videoCapabilities: [{
163
+ robustness: 'SW_SECURE_CRYPTO',
164
+ contentType: 'video/mp4; codecs="avc1.4D4015"',
165
+ }],
166
+ }],
167
+ );
168
+ expect(result.keySystemAccess).toEqual(mockResult);
169
+ });
170
+
171
+ it('throws when the cast namespace is not available', async () => {
172
+ // Temporarily remove window.cast to trigger error. It's restored after
173
+ // every test.
174
+ delete window['cast'];
175
+
176
+ const isChromecastSpy =
177
+ spyOn(shaka['util']['Platform'],
178
+ 'isChromecast').and.returnValue(true);
179
+ const expected = Util.jasmineError(new shaka.util.Error(
180
+ shaka.util.Error.Severity.CRITICAL,
181
+ shaka.util.Error.Category.CAST,
182
+ shaka.util.Error.Code.CAST_API_UNAVAILABLE));
183
+ const isTypeSupportedSpy =
184
+ spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
185
+
186
+ shaka.polyfill.MediaCapabilities.install();
187
+ await expectAsync(
188
+ navigator.mediaCapabilities.decodingInfo(mockDecodingConfig))
189
+ .toBeRejectedWith(expected);
190
+
191
+ expect(isTypeSupportedSpy).not.toHaveBeenCalled();
192
+ // 1 (during install()) + 1 (for video config check).
193
+ expect(isChromecastSpy).toHaveBeenCalledTimes(2);
194
+ });
195
+
196
+ it('falls back to isTypeSupported() when canDisplayType() missing',
197
+ async () => {
198
+ // We only set the cast namespace, but not the canDisplayType() API.
199
+ window['cast'] = {};
200
+ const isChromecastSpy =
201
+ spyOn(shaka['util']['Platform'],
202
+ 'isChromecast').and.returnValue(true);
203
+ const isTypeSupportedSpy =
204
+ spyOn(window['MediaSource'], 'isTypeSupported')
205
+ .and.returnValue(true);
206
+
207
+ shaka.polyfill.MediaCapabilities.install();
208
+ await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
209
+
210
+ expect(mockCanDisplayType).not.toHaveBeenCalled();
211
+ // 1 (during install()) + 1 (for video config check).
212
+ expect(isChromecastSpy).toHaveBeenCalledTimes(2);
213
+ // 1 (fallback in canCastDisplayType()) +
214
+ // 1 (mockDecodingConfig.audio).
215
+ expect(isTypeSupportedSpy).toHaveBeenCalledTimes(2);
216
+ });
217
+
218
+ it('should use cast.__platform__.canDisplayType for "supported" field ' +
219
+ 'when platform is Cast', async () => {
220
+ // We're using quotes to access window.cast because the compiler
221
+ // knows about lots of Cast-specific APIs we aren't mocking. We
222
+ // don't need this mock strictly type-checked.
223
+ window['cast'] = {
224
+ __platform__: {canDisplayType: mockCanDisplayType},
225
+ };
226
+ const isChromecastSpy =
227
+ spyOn(shaka['util']['Platform'],
228
+ 'isChromecast').and.returnValue(true);
229
+ const isTypeSupportedSpy =
230
+ spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
231
+
232
+ // Tests an HDR stream's extended MIME type is correctly provided.
233
+ mockDecodingConfig.video.transferFunction = 'pq';
234
+ mockDecodingConfig.video.contentType =
235
+ 'video/mp4; codecs="hev1.2.4.L153.B0"';
236
+ // Round to a whole number since we can't rely on number => string
237
+ // conversion precision on all devices.
238
+ mockDecodingConfig.video.framerate = 24;
239
+ mockCanDisplayType.and.callFake((type) => {
240
+ expect(type).toBe(
241
+ 'video/mp4; ' +
242
+ 'codecs="hev1.2.4.L153.B0"; ' +
243
+ 'width=512; ' +
244
+ 'height=288; ' +
245
+ 'framerate=24; ' +
246
+ 'eotf=smpte2084');
247
+ return true;
248
+ });
249
+
250
+ shaka.polyfill.MediaCapabilities.install();
251
+ await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
252
+
253
+ // 1 (during install()) + 1 (for video config check).
254
+ expect(isChromecastSpy).toHaveBeenCalledTimes(2);
255
+ // 1 (mockDecodingConfig.audio).
256
+ expect(isTypeSupportedSpy).toHaveBeenCalledTimes(1);
257
+ // Called once in canCastDisplayType.
258
+ expect(mockCanDisplayType).toHaveBeenCalledTimes(1);
259
+ });
260
+ });
261
+ });
package/test/test/boot.js CHANGED
@@ -66,22 +66,40 @@ function failTestsOnNamespacedElementOrAttributeNames() {
66
66
  }
67
67
 
68
68
  /**
69
- * Listen for unhandled Promise rejections (which may occur after a test) and
70
- * convert them into test failures.
69
+ * Fail the current test on a given error, constructed with a given header and
70
+ * the full stack trace of the error.
71
+ *
72
+ * @param {string} messageHeader
73
+ * @param {!Error} error
71
74
  */
72
- function failTestsOnUnhandledRejections() {
75
+ function failOnError(messageHeader, error) {
76
+ let message = `${messageHeader}: ${error}`;
77
+ // Shaka errors have the stack trace in their toString() already, so don't
78
+ // add it again. For native errors, we need to see where it came from.
79
+ if (error && error.stack && !(error instanceof shaka.util.Error)) {
80
+ message += '\n' + error.stack;
81
+ }
82
+ fail(message);
83
+ }
84
+
85
+ /**
86
+ * Listen for unhandled errors and Promise rejections (which may occur after a
87
+ * test) and convert them into test failures.
88
+ */
89
+ function failTestsOnUnhandledErrors() {
73
90
  // https://developer.mozilla.org/en-US/docs/Web/Events/unhandledrejection
74
91
  window.addEventListener('unhandledrejection', (event) => {
75
92
  /** @type {?} */
76
93
  const error = event.reason;
77
- let message = 'Unhandled rejection in Promise: ' + error;
94
+ failOnError('Unhandled rejection in Promise', error);
95
+ });
78
96
 
79
- // Shaka errors have the stack trace in their toString() already, so don't
80
- // add it again. For native errors, we need to see where it came from.
81
- if (error && error.stack && !(error instanceof shaka.util.Error)) {
82
- message += '\n' + error.stack;
83
- }
84
- fail(message);
97
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event
98
+ // https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
99
+ window.addEventListener('error', (event) => {
100
+ /** @type {?} */
101
+ const error = event['error'];
102
+ failOnError('Unhandled error', error);
85
103
  });
86
104
  }
87
105
 
@@ -387,7 +405,7 @@ function configureJasmineEnvironment() {
387
405
  function setupTestEnvironment() {
388
406
  failTestsOnFailedAssertions();
389
407
  failTestsOnNamespacedElementOrAttributeNames();
390
- failTestsOnUnhandledRejections();
408
+ failTestsOnUnhandledErrors();
391
409
  disableScrollbars();
392
410
  workAroundLegacyEdgePromiseIssues();
393
411
 
@@ -402,7 +402,7 @@ const sintelAudioSegment = {
402
402
  mdhdOffset: 0x1b6,
403
403
  segmentUri: '/base/test/test/assets/sintel-audio-segment.mp4',
404
404
  tfdtOffset: 0x3c,
405
- segmentDuration: 10.005,
405
+ segmentDuration: 10,
406
406
  mimeType: 'audio/mp4',
407
407
  codecs: 'mp4a.40.2',
408
408
  };
@@ -428,7 +428,7 @@ const sintelEncryptedAudio = {
428
428
  mdhdOffset: 0x1b6,
429
429
  segmentUri: '/base/test/test/assets/encrypted-sintel-audio-segment.mp4',
430
430
  tfdtOffset: 0x3c,
431
- segmentDuration: 10.005,
431
+ segmentDuration: 10,
432
432
  mimeType: 'audio/mp4',
433
433
  codecs: 'mp4a.40.2',
434
434
  initData:
@@ -41,7 +41,7 @@ describe('Mp4TtmlParser', () => {
41
41
  it('handles media segments with multiple mdats', () => {
42
42
  const parser = new shaka.text.Mp4TtmlParser();
43
43
  parser.parseInit(ttmlInitSegment);
44
- const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
44
+ const time = {periodStart: 0, segmentStart: 60, segmentEnd: 0};
45
45
  const ret = parser.parseMedia(ttmlSegmentMultipleMDAT, time);
46
46
  // Bodies.
47
47
  expect(ret.length).toBe(2);
@@ -54,8 +54,8 @@ describe('Mp4TtmlParser', () => {
54
54
  });
55
55
 
56
56
  it('accounts for offset', () => {
57
- const time1 = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
58
- const time2 = {periodStart: 7, segmentStart: 0, segmentEnd: 0};
57
+ const time1 = {periodStart: 0, segmentStart: 0, segmentEnd: 70};
58
+ const time2 = {periodStart: 7, segmentStart: 0, segmentEnd: 70};
59
59
 
60
60
  const parser = new shaka.text.Mp4TtmlParser();
61
61
  parser.parseInit(ttmlInitSegment);
@@ -159,7 +159,7 @@ describe('Mp4TtmlParser', () => {
159
159
  ];
160
160
  const parser = new shaka.text.Mp4TtmlParser();
161
161
  parser.parseInit(ttmlInitSegment);
162
- const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
162
+ const time = {periodStart: 0, segmentStart: 0, segmentEnd: 60};
163
163
  const result = parser.parseMedia(ttmlSegment, time);
164
164
  shaka.test.TtmlUtils.verifyHelper(
165
165
  cues, result, {startTime: 23, endTime: 53.5});