shaka-player 2.5.1 → 2.5.5

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 (166) hide show
  1. package/.csslintrc +13 -0
  2. package/AUTHORS +3 -0
  3. package/CHANGELOG.md +156 -0
  4. package/CONTRIBUTORS +3 -0
  5. package/README.md +3 -0
  6. package/build/README.md +5 -5
  7. package/build/apps.py +3 -1
  8. package/build/build.py +5 -2
  9. package/build/check.py +17 -0
  10. package/build/compiler.py +49 -0
  11. package/build/conformance.textproto +1 -0
  12. package/build/docs.py +0 -6
  13. package/build/generateExterns.js +72 -20
  14. package/build/install-linux-prereqs.sh +4 -4
  15. package/build/shakaBuildHelpers.py +7 -8
  16. package/build/test.py +21 -9
  17. package/build/types/core +1 -0
  18. package/build/types/ui +6 -1
  19. package/demo/asset_card.js +129 -41
  20. package/demo/cast_receiver/receiver_app.css +27 -13
  21. package/demo/close_button.js +10 -15
  22. package/demo/common/asset.js +6 -6
  23. package/demo/common/assets.js +72 -14
  24. package/demo/config.js +28 -25
  25. package/demo/custom.js +20 -16
  26. package/demo/demo.less +107 -16
  27. package/demo/demo_uncompiled.js +33 -0
  28. package/demo/demo_utils.js +4 -3
  29. package/demo/front.js +12 -8
  30. package/demo/index.html +14 -17
  31. package/demo/input.js +30 -23
  32. package/demo/input_container.js +15 -11
  33. package/demo/load.js +5 -7
  34. package/demo/main.js +138 -49
  35. package/demo/search.js +40 -36
  36. package/demo/service_worker.js +1 -1
  37. package/demo/tooltip.js +16 -24
  38. package/dist/.csslintstamp +0 -0
  39. package/dist/controls.css +1 -1
  40. package/dist/controls.css.map +1 -1
  41. package/dist/demo.compiled.debug.js +165 -152
  42. package/dist/demo.compiled.debug.map +4 -4
  43. package/dist/demo.compiled.js +165 -152
  44. package/dist/demo.compiled.map +4 -4
  45. package/dist/demo.css +1 -1
  46. package/dist/demo.css.map +1 -1
  47. package/dist/deps.js +28 -11
  48. package/dist/receiver.compiled.debug.js +11 -11
  49. package/dist/receiver.compiled.debug.map +3 -3
  50. package/dist/receiver.compiled.js +11 -11
  51. package/dist/receiver.compiled.map +3 -3
  52. package/dist/shaka-player.compiled.debug.externs.js +232 -102
  53. package/dist/shaka-player.compiled.debug.js +189 -165
  54. package/dist/shaka-player.compiled.debug.map +4 -4
  55. package/dist/shaka-player.compiled.externs.js +232 -102
  56. package/dist/shaka-player.compiled.js +567 -553
  57. package/dist/shaka-player.compiled.map +4 -4
  58. package/dist/shaka-player.ui.debug.externs.js +338 -113
  59. package/dist/shaka-player.ui.debug.js +296 -276
  60. package/dist/shaka-player.ui.debug.map +4 -4
  61. package/dist/shaka-player.ui.externs.js +338 -113
  62. package/dist/shaka-player.ui.js +685 -671
  63. package/dist/shaka-player.ui.map +4 -4
  64. package/docs/tutorials/drm-config.md +1 -0
  65. package/docs/tutorials/fairplay.md +77 -0
  66. package/docs/tutorials/index.json +1 -0
  67. package/docs/tutorials/manifest-parser.md +1 -1
  68. package/docs/tutorials/ui-customization.md +5 -2
  69. package/docs/tutorials/ui.md +10 -4
  70. package/docs/tutorials/welcome.md +2 -2
  71. package/externs/mediasession.js +16 -1
  72. package/externs/shaka/net.js +14 -4
  73. package/externs/shaka/player.js +136 -90
  74. package/externs/tippy.js +30 -0
  75. package/externs/webkitmediakeys.js +4 -0
  76. package/karma.conf.js +30 -24
  77. package/lib/cast/cast_proxy.js +94 -23
  78. package/lib/cast/cast_receiver.js +8 -1
  79. package/lib/dash/content_protection.js +33 -1
  80. package/lib/dash/dash_parser.js +13 -5
  81. package/lib/hls/hls_parser.js +20 -4
  82. package/lib/media/drm_engine.js +29 -2
  83. package/lib/media/media_source_engine.js +9 -6
  84. package/lib/media/play_rate_controller.js +14 -3
  85. package/lib/media/playhead.js +8 -3
  86. package/lib/media/streaming_engine.js +68 -20
  87. package/lib/media/transmuxer.js +2 -2
  88. package/lib/media/video_wrapper.js +8 -3
  89. package/lib/net/data_uri_plugin.js +5 -2
  90. package/lib/net/http_fetch_plugin.js +3 -3
  91. package/lib/net/http_plugin_utils.js +2 -4
  92. package/lib/net/http_xhr_plugin.js +2 -2
  93. package/lib/net/networking_engine.js +1 -0
  94. package/lib/offline/offline_scheme.js +7 -2
  95. package/lib/player.js +18 -8
  96. package/lib/polyfill/input_event.js +7 -11
  97. package/lib/polyfill/mediasource.js +1 -1
  98. package/lib/polyfill/patchedmediakeys_apple.js +2 -72
  99. package/lib/text/cue.js +1 -1
  100. package/lib/text/ttml_text_parser.js +1 -1
  101. package/lib/util/config_utils.js +1 -0
  102. package/lib/util/error.js +45 -11
  103. package/lib/util/event_manager.js +211 -100
  104. package/lib/util/fairplay_utils.js +122 -0
  105. package/lib/util/i_releasable.js +1 -0
  106. package/lib/util/player_configuration.js +7 -0
  107. package/lib/util/pssh.js +41 -0
  108. package/lib/util/string_utils.js +42 -6
  109. package/lib/util/timer.js +1 -1
  110. package/package.json +33 -33
  111. package/roadmap.md +96 -0
  112. package/shaka-player.uncompiled.js +5 -1
  113. package/test/dash/dash_parser_content_protection_unit.js +35 -1
  114. package/test/dash/dash_parser_manifest_unit.js +60 -0
  115. package/test/hls/hls_live_unit.js +7 -1
  116. package/test/hls/hls_parser_unit.js +50 -0
  117. package/test/media/drm_engine_unit.js +10 -4
  118. package/test/media/media_source_engine_integration.js +12 -0
  119. package/test/media/media_source_engine_unit.js +3 -1
  120. package/test/media/playhead_unit.js +7 -5
  121. package/test/media/streaming_engine_unit.js +46 -1
  122. package/test/media/transmuxer_integration.js +13 -0
  123. package/test/net/data_uri_plugin_unit.js +13 -2
  124. package/test/net/http_plugin_unit.js +17 -12
  125. package/test/net/networking_engine_unit.js +17 -8
  126. package/test/offline/offline_scheme_unit.js +18 -8
  127. package/test/player_external.js +3 -2
  128. package/test/player_integration.js +94 -3
  129. package/test/player_src_equals_integration.js +2 -2
  130. package/test/player_unit.js +21 -8
  131. package/test/test/boot.js +20 -5
  132. package/test/test/externs/jasmine.js +48 -0
  133. package/test/test/util/expect_async_polyfill.js +145 -0
  134. package/test/test/util/fake_networking_engine.js +1 -0
  135. package/test/test/util/manifest_generator.js +2 -1
  136. package/test/test/util/test_scheme.js +34 -2
  137. package/test/test/util/ui_utils.js +41 -0
  138. package/test/test/util/util.js +16 -8
  139. package/test/ui/ui_unit.js +130 -4
  140. package/ui/audio_language_selection.js +17 -96
  141. package/ui/big_play_button.js +61 -0
  142. package/ui/controls.js +226 -335
  143. package/ui/controls.less +3 -5
  144. package/ui/element.js +24 -6
  145. package/ui/externs/ui.js +62 -9
  146. package/ui/fullscreen_button.js +2 -52
  147. package/ui/less/buttons.less +10 -16
  148. package/ui/less/containers.less +75 -20
  149. package/ui/less/general.less +18 -2
  150. package/ui/less/other_elements.less +2 -1
  151. package/ui/less/overflow_menu.less +11 -10
  152. package/ui/less/range_elements.less +12 -10
  153. package/ui/less/spinner.less +2 -0
  154. package/ui/overflow_menu.js +1 -1
  155. package/ui/pip_button.js +1 -1
  156. package/ui/{play_pause_button.js → play_button.js} +17 -47
  157. package/ui/presentation_time.js +17 -12
  158. package/ui/range_element.js +176 -0
  159. package/ui/resolution_selection.js +43 -106
  160. package/ui/seek_bar.js +204 -0
  161. package/ui/settings_menu.js +113 -0
  162. package/ui/small_play_button.js +73 -0
  163. package/ui/text_displayer.js +17 -14
  164. package/ui/text_selection.js +42 -107
  165. package/ui/ui.js +17 -40
  166. package/ui/volume_bar.js +40 -68
@@ -446,7 +446,10 @@ describe('StreamingEngine', function() {
446
446
  config.bufferBehind = Infinity;
447
447
  }
448
448
 
449
- let playerInterface = {
449
+ goog.asserts.assert(
450
+ presentationTimeInSeconds != undefined,
451
+ 'All tests should have defined an initial presentation time by now!');
452
+ const playerInterface = {
450
453
  getPresentationTime: () => presentationTimeInSeconds,
451
454
  getBandwidthEstimate: Util.spyFunc(getBandwidthEstimate),
452
455
  mediaSourceEngine: mediaSourceEngine,
@@ -985,6 +988,27 @@ describe('StreamingEngine', function() {
985
988
  expect(timeline.setDuration).not.toHaveBeenCalled();
986
989
  });
987
990
 
991
+ // https://github.com/google/shaka-player/issues/1967
992
+ it('does not change duration when 0', () => {
993
+ setupVod();
994
+ mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
995
+ createStreamingEngine();
996
+
997
+ onStartupComplete.and.callFake(() => setupFakeGetTime(0));
998
+ onChooseStreams.and.callFake(defaultOnChooseStreams);
999
+
1000
+ // The duration can spuriously be set to 0, so we should ignore this and not
1001
+ // update the duration.
1002
+ mediaSourceEngine.getDuration.and.returnValue(0);
1003
+
1004
+ // Here we go!
1005
+ streamingEngine.start();
1006
+
1007
+ runTest();
1008
+ expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
1009
+ expect(timeline.setDuration).not.toHaveBeenCalled();
1010
+ });
1011
+
988
1012
  it('applies fudge factor for appendWindowStart', function() {
989
1013
  setupVod();
990
1014
  mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
@@ -1118,6 +1142,7 @@ describe('StreamingEngine', function() {
1118
1142
  });
1119
1143
 
1120
1144
  playing = false;
1145
+ presentationTimeInSeconds = 0;
1121
1146
  createStreamingEngine();
1122
1147
 
1123
1148
  onStartupComplete.and.callFake(setupFakeGetTime.bind(null, 0));
@@ -1932,6 +1957,26 @@ describe('StreamingEngine', function() {
1932
1957
  runTest();
1933
1958
  expect(onError).toHaveBeenCalled();
1934
1959
  });
1960
+
1961
+ it('from failed clear in switchVariant', () => {
1962
+ const expectedError = new shaka.util.Error(
1963
+ shaka.util.Error.Severity.CRITICAL,
1964
+ shaka.util.Error.Category.MEDIA,
1965
+ shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_FAILED);
1966
+ mediaSourceEngine.clear.and.returnValue(Promise.reject(expectedError));
1967
+
1968
+ onError.and.stub();
1969
+ onChooseStreams.and.callFake((period) => defaultOnChooseStreams(period));
1970
+ onStartupComplete.and.callFake(() => {
1971
+ streamingEngine.switchVariant(
1972
+ variant2, /* clear_buffer= */ true, /* safe_margin= */ 0);
1973
+ });
1974
+
1975
+ // Here we go!
1976
+ streamingEngine.start().catch(fail);
1977
+ runTest();
1978
+ expect(onError).toHaveBeenCalledWith(Util.jasmineError(expectedError));
1979
+ });
1935
1980
  });
1936
1981
 
1937
1982
  describe('handles network errors', function() {
@@ -59,6 +59,12 @@ describe('Transmuxer', function() {
59
59
  expect(isSupported(transportStreamVideoMimeType, ContentType.VIDEO))
60
60
  .toBe(true);
61
61
  });
62
+
63
+ // Issue #1991
64
+ it('handles upper-case MIME types', () => {
65
+ const mimeType = transportStreamVideoMimeType.replace('mp2t', 'MP2T');
66
+ expect(isSupported(mimeType, ContentType.VIDEO)).toBe(true);
67
+ });
62
68
  });
63
69
 
64
70
  describe('convertTsCodecs', function() {
@@ -86,6 +92,13 @@ describe('Transmuxer', function() {
86
92
  'video/mp2t; codecs="avc1.66.1"')).toEqual(
87
93
  'video/mp4; codecs="avc1.420001"');
88
94
  });
95
+
96
+ // Issue #1991
97
+ it('handles upper-case MIME types', () => {
98
+ expect(convertTsCodecs(
99
+ ContentType.VIDEO, 'video/MP2T; codecs="avc1.420001"'))
100
+ .toBe('video/mp4; codecs="avc1.420001"');
101
+ });
89
102
  });
90
103
 
91
104
  describe('transmuxing', function() {
@@ -65,10 +65,16 @@ describe('DataUriPlugin', function() {
65
65
  });
66
66
 
67
67
  function testSucceeds(uri, contentType, text, done) {
68
+ // An arbitrary request type.
69
+ const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
70
+ // A dummy progress callback.
71
+ const progressUpdated = (elapsedMs, bytes, bytesRemaining) => {};
72
+
68
73
  let request =
69
74
  shaka.net.NetworkingEngine.makeRequest([uri], retryParameters);
75
+
70
76
  // eslint-disable-next-line new-cap
71
- shaka.net.DataUriPlugin(uri, request).promise
77
+ shaka.net.DataUriPlugin(uri, request, requestType, progressUpdated).promise
72
78
  .then(function(response) {
73
79
  expect(response).toBeTruthy();
74
80
  expect(response.uri).toBe(uri);
@@ -82,10 +88,15 @@ describe('DataUriPlugin', function() {
82
88
  }
83
89
 
84
90
  function testFails(uri, done, code) {
91
+ // An arbitrary request type.
92
+ const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
93
+ // A dummy progress callback.
94
+ const progressUpdated = (elapsedMs, bytes, bytesRemaining) => {};
95
+
85
96
  let request =
86
97
  shaka.net.NetworkingEngine.makeRequest([uri], retryParameters);
87
98
  // eslint-disable-next-line new-cap
88
- shaka.net.DataUriPlugin(uri, request).promise
99
+ shaka.net.DataUriPlugin(uri, request, requestType, progressUpdated).promise
89
100
  .then(fail)
90
101
  .catch(function(error) { expect(error.code).toBe(code); })
91
102
  .then(function() {
@@ -25,6 +25,9 @@ function httpPluginTests(usingFetch) {
25
25
  // Neither plugin uses the request type, so this is arbitrary.
26
26
  const requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
27
27
 
28
+ // A dummy progress callback.
29
+ const progressUpdated = (elapsedMs, bytes, bytesRemaining) => {};
30
+
28
31
  /** @type {shaka.extern.RetryParameters} */
29
32
  let retryParameters;
30
33
 
@@ -169,9 +172,9 @@ function httpPluginTests(usingFetch) {
169
172
  request.method = 'POST';
170
173
  request.headers['BAZ'] = '123';
171
174
 
172
- plugin(request.uris[0], request, requestType).promise
173
- .then(function() {
174
- let actual = mostRecentRequest();
175
+ plugin(request.uris[0], request, requestType, progressUpdated).promise
176
+ .then(() => {
177
+ const actual = mostRecentRequest();
175
178
  expect(actual).toBeTruthy();
176
179
  expect(actual.url).toBe(request.uris[0]);
177
180
  expect(actual.method).toBe(request.method);
@@ -193,9 +196,9 @@ function httpPluginTests(usingFetch) {
193
196
  request.body = null;
194
197
  request.method = 'GET';
195
198
 
196
- plugin(request.uris[0], request, requestType).promise
197
- .then(function() {
198
- let actual = jasmine.Fetch.requests.mostRecent();
199
+ plugin(request.uris[0], request, requestType, progressUpdated).promise
200
+ .then(() => {
201
+ const actual = jasmine.Fetch.requests.mostRecent();
199
202
  expect(actual).toBeTruthy();
200
203
  expect(actual.body).toBeUndefined();
201
204
  })
@@ -267,7 +270,7 @@ function httpPluginTests(usingFetch) {
267
270
  it('detects cache headers', function(done) {
268
271
  let request = shaka.net.NetworkingEngine.makeRequest(
269
272
  ['https://foo.bar/cache'], retryParameters);
270
- plugin(request.uris[0], request, requestType).promise
273
+ plugin(request.uris[0], request, requestType, progressUpdated).promise
271
274
  .catch(fail)
272
275
  .then(function(response) {
273
276
  expect(response).toBeTruthy();
@@ -284,7 +287,8 @@ function httpPluginTests(usingFetch) {
284
287
  if (usingFetch) {
285
288
  let request = shaka.net.NetworkingEngine.makeRequest(
286
289
  ['https://foo.bar/timeout'], retryParameters);
287
- let operation = plugin(request.uris[0], request, requestType);
290
+ const operation = plugin(
291
+ request.uris[0], request, requestType, progressUpdated);
288
292
 
289
293
  /** @type {jasmine.Fetch.RequestStub} */
290
294
  let actual = jasmine.Fetch.requests.mostRecent();
@@ -326,7 +330,8 @@ function httpPluginTests(usingFetch) {
326
330
 
327
331
  let request = shaka.net.NetworkingEngine.makeRequest(
328
332
  ['https://foo.bar/'], retryParameters);
329
- operation = plugin(request.uris[0], request, requestType);
333
+ operation = plugin(
334
+ request.uris[0], request, requestType, progressUpdated);
330
335
  requestPromise = operation.promise;
331
336
  }
332
337
 
@@ -359,7 +364,7 @@ function httpPluginTests(usingFetch) {
359
364
  function testSucceeds(uri, done, overrideUri) {
360
365
  let request = shaka.net.NetworkingEngine.makeRequest(
361
366
  [uri], retryParameters);
362
- plugin(uri, request, requestType).promise
367
+ plugin(uri, request, requestType, progressUpdated).promise
363
368
  .catch(fail)
364
369
  .then(function(response) {
365
370
  expect(mostRecentRequest().url).toBe(uri);
@@ -385,7 +390,7 @@ function httpPluginTests(usingFetch) {
385
390
  function testFails(uri, done, severity, code, errorData) {
386
391
  let request = shaka.net.NetworkingEngine.makeRequest(
387
392
  [uri], retryParameters);
388
- plugin(uri, request, requestType).promise
393
+ plugin(uri, request, requestType, progressUpdated).promise
389
394
  .then(fail)
390
395
  .catch(function(error) {
391
396
  expect(error).toBeTruthy();
@@ -414,7 +419,7 @@ function httpPluginTests(usingFetch) {
414
419
  function testSucceedsWithEmptyLine(uri, done, overrideUri) {
415
420
  let request = shaka.net.NetworkingEngine.makeRequest(
416
421
  [uri], retryParameters);
417
- plugin(uri, request, requestType).promise
422
+ plugin(uri, request, requestType, progressUpdated).promise
418
423
  .catch(fail)
419
424
  .then(function(response) {
420
425
  expect(mostRecentRequest().url).toBe(uri);
@@ -124,7 +124,7 @@ describe('NetworkingEngine', /** @suppress {accessControls} */ function() {
124
124
  });
125
125
  });
126
126
 
127
- it('will fail overall', function(done) {
127
+ it('will fail overall', async () => {
128
128
  let request = createRequest('reject://foo', {
129
129
  maxAttempts: 3,
130
130
  baseDelay: 0,
@@ -132,13 +132,22 @@ describe('NetworkingEngine', /** @suppress {accessControls} */ function() {
132
132
  fuzzFactor: 0,
133
133
  timeout: 0,
134
134
  });
135
- networkingEngine.request(requestType, request).promise
136
- .then(fail)
137
- .catch(function(error) {
138
- // It is expected to fail with the most recent error.
139
- expect(error).toEqual(jasmine.any(shaka.util.Error));
140
- expect(rejectScheme.calls.count()).toBe(3);
141
- }).then(done);
135
+
136
+ // It is expected to fail with the most recent error, but at a CRITICAL
137
+ // severity, even though the original is at RECOVERABLE.
138
+
139
+ // Check our expectations.
140
+ expect(error.severity).toEqual(shaka.util.Error.Severity.RECOVERABLE);
141
+
142
+ // Modify the expected error. Note that |expected| here is a wrapper
143
+ // created by jasmine, and |expected.sample| is the Object containing the
144
+ // expected values.
145
+ const expected = Util.jasmineError(error);
146
+ expected.sample.severity = shaka.util.Error.Severity.CRITICAL;
147
+
148
+ await expectAsync(networkingEngine.request(requestType, request).promise)
149
+ .toBeRejectedWith(expected);
150
+ expect(rejectScheme.calls.count()).toBe(3);
142
151
  });
143
152
 
144
153
  describe('backoff', function() {
@@ -15,8 +15,14 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- describe('OfflineScheme', function() {
19
- beforeEach(checkAndRun(async function() {
18
+ describe('OfflineScheme', () => {
19
+ // An arbitrary request type.
20
+ const requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
21
+
22
+ // A dummy progress callback.
23
+ const progressUpdated = (elapsedMs, bytes, bytesRemaining) => {};
24
+
25
+ beforeEach(checkAndRun(async () => {
20
26
  // Make sure we start with a clean slate.
21
27
  await clearStorage();
22
28
  }));
@@ -35,8 +41,8 @@ describe('OfflineScheme', function() {
35
41
  'mechanism', 'cell', 1024);
36
42
 
37
43
  // eslint-disable-next-line new-cap
38
- let response = await shaka.offline.OfflineScheme(
39
- uri.toString(), request).promise;
44
+ const response = await shaka.offline.OfflineScheme(
45
+ uri.toString(), request, requestType, progressUpdated).promise;
40
46
 
41
47
  expect(response).toBeTruthy();
42
48
  expect(response.uri).toBe(uri.toString());
@@ -64,8 +70,8 @@ describe('OfflineScheme', function() {
64
70
  }
65
71
 
66
72
  // eslint-disable-next-line new-cap
67
- let response = await shaka.offline.OfflineScheme(
68
- uri.toString(), request).promise;
73
+ const response = await shaka.offline.OfflineScheme(
74
+ uri.toString(), request, requestType, progressUpdated).promise;
69
75
 
70
76
  expect(response).toBeTruthy();
71
77
  expect(response.data.byteLength).toBe(segment.data.byteLength);
@@ -95,7 +101,9 @@ describe('OfflineScheme', function() {
95
101
 
96
102
  try {
97
103
  // eslint-disable-next-line new-cap
98
- await shaka.offline.OfflineScheme(uri.toString(), request).promise;
104
+ const op = shaka.offline.OfflineScheme(
105
+ uri.toString(), request, requestType, progressUpdated);
106
+ await op.promise;
99
107
  fail();
100
108
  } catch (e) {
101
109
  expect(e.code).toBe(shaka.util.Error.Code.KEY_NOT_FOUND);
@@ -108,7 +116,9 @@ describe('OfflineScheme', function() {
108
116
 
109
117
  try {
110
118
  // eslint-disable-next-line new-cap
111
- await shaka.offline.OfflineScheme(uri, request).promise;
119
+ const op = shaka.offline.OfflineScheme(
120
+ uri, request, requestType, progressUpdated);
121
+ await op.promise;
112
122
  fail();
113
123
  } catch (e) {
114
124
  expect(e.code).toBe(shaka.util.Error.Code.MALFORMED_OFFLINE_URI);
@@ -89,7 +89,7 @@ describe('Player', () => {
89
89
 
90
90
  let wit = asset.focus ? fit : it;
91
91
  wit(testName, async () => {
92
- if (asset.drm.length &&
92
+ if (!asset.isClear() &&
93
93
  !asset.drm.some((keySystem) => support.drm[keySystem])) {
94
94
  pending('None of the required key systems are supported.');
95
95
  }
@@ -102,7 +102,8 @@ describe('Player', () => {
102
102
  if (asset.features.includes(Feature.MP4)) {
103
103
  mimeTypes.push('video/mp4');
104
104
  }
105
- if (!mimeTypes.some((type) => support.media[type])) {
105
+ if (mimeTypes.length &&
106
+ !mimeTypes.some((type) => support.media[type])) {
106
107
  pending('None of the required MIME types are supported.');
107
108
  }
108
109
  }
@@ -137,11 +137,11 @@ describe('Player', function() {
137
137
  // This should not be null initially.
138
138
  expect(textTrack.cues).not.toBe(null);
139
139
 
140
- player.setTextTrackVisibility(true);
140
+ await player.setTextTrackVisibility(true);
141
141
  // This should definitely not be null when visible.
142
142
  expect(textTrack.cues).not.toBe(null);
143
143
 
144
- player.setTextTrackVisibility(false);
144
+ await player.setTextTrackVisibility(false);
145
145
  // This should not transition to null when invisible.
146
146
  expect(textTrack.cues).not.toBe(null);
147
147
  }
@@ -281,7 +281,6 @@ describe('Player', function() {
281
281
  expect(textTracks[0].language).toEqual('en');
282
282
  });
283
283
 
284
-
285
284
  it('while changing languages with short Periods', async () => {
286
285
  // See: https://github.com/google/shaka-player/issues/797
287
286
  player.configure({preferredAudioLanguage: 'en'});
@@ -308,6 +307,23 @@ describe('Player', function() {
308
307
  expect(getActiveLanguage()).toBe('es');
309
308
  });
310
309
 
310
+ it('at higher playback rates', async () => {
311
+ await player.load('test:sintel_compiled');
312
+ video.play();
313
+ await waitUntilPlayheadReaches(eventManager, video, 1, 10);
314
+
315
+ // Enabling trick play should change our playback rate to the same rate.
316
+ player.trickPlay(2);
317
+ expect(video.playbackRate).toBe(2);
318
+
319
+ // Let playback continue playing for a bit longer.
320
+ await shaka.test.Util.delay(2);
321
+
322
+ // Cancelling trick play should return our playback rate to normal.
323
+ player.cancelTrickPlay();
324
+ expect(video.playbackRate).toBe(1);
325
+ });
326
+
311
327
  /**
312
328
  * Gets the language of the active Variant.
313
329
  * @return {string}
@@ -448,6 +464,81 @@ describe('Player', function() {
448
464
  }).catch(fail).then(done);
449
465
  });
450
466
  });
467
+
468
+ describe('tracks', () => {
469
+ // This is a regression test for b/138941217, in which tracks briefly
470
+ // vanished during the loading process. On Chromecast devices, where the
471
+ // timing is very different from on desktop, this could occur such that
472
+ // there were no tracks after load() is resolved.
473
+ // This is an integration test so that we can check the behavior of the
474
+ // Player against actual platform behavior on all supported platforms.
475
+ it('remain available at every stage of loading', async () => {
476
+ let tracksFound = false;
477
+
478
+ /**
479
+ * @param {string} when When the check takes place.
480
+ *
481
+ * Will fail the test if tracks disappear after they first become
482
+ * available.
483
+ */
484
+ const checkTracks = (when) => {
485
+ // If tracks have already been found, expect them to still be found.
486
+ const tracksNow = player.getVariantTracks().length != 0;
487
+ if (tracksFound) {
488
+ expect(tracksNow).toBe(true);
489
+ } else {
490
+ // If tracks are now found, they should not, at any point during
491
+ // the loading process, disappear again.
492
+ if (tracksNow) {
493
+ tracksFound = true;
494
+ }
495
+ }
496
+ shaka.log.debug(
497
+ 'checkTracks', when,
498
+ 'tracksFound=', tracksFound,
499
+ 'tracksNow=', tracksNow);
500
+ };
501
+
502
+ /** @param {Event} event */
503
+ const checkOnEvent = (event) => {
504
+ checkTracks(event.type + ' event');
505
+ };
506
+
507
+ // On each of these events, we will notice when tracks first appear, and
508
+ // verify that they never disappear at any point in the loading sequence.
509
+ eventManager.listen(video, 'canplay', checkOnEvent);
510
+ eventManager.listen(video, 'canplaythrough', checkOnEvent);
511
+ eventManager.listen(video, 'durationchange', checkOnEvent);
512
+ eventManager.listen(video, 'emptied', checkOnEvent);
513
+ eventManager.listen(video, 'loadeddata', checkOnEvent);
514
+ eventManager.listen(video, 'loadedmetadata', checkOnEvent);
515
+ eventManager.listen(video, 'loadstart', checkOnEvent);
516
+ eventManager.listen(video, 'pause', checkOnEvent);
517
+ eventManager.listen(video, 'play', checkOnEvent);
518
+ eventManager.listen(video, 'playing', checkOnEvent);
519
+ eventManager.listen(video, 'seeked', checkOnEvent);
520
+ eventManager.listen(video, 'seeking', checkOnEvent);
521
+ eventManager.listen(video, 'stalled', checkOnEvent);
522
+ eventManager.listen(video, 'waiting', checkOnEvent);
523
+ eventManager.listen(player, 'trackschanged', checkOnEvent);
524
+
525
+ const waiter = (new shaka.test.Waiter(eventManager)).timeoutAfter(10);
526
+ const canPlayThrough = waiter.waitForEvent(video, 'canplaythrough');
527
+
528
+ // Important: use a stream that starts somewhere other than zero, so that
529
+ // the video element's time is initially different from the start time of
530
+ // playback, and there is no content at time zero.
531
+ await player.load('test:sintel_start_at_3_compiled', 5);
532
+ shaka.log.debug('load resolved');
533
+
534
+ // When load is resolved(), tracks should definitely exist.
535
+ expect(tracksFound).toBe(true);
536
+
537
+ // Let the test keep running until we can play through. In the original
538
+ // bug, tracks would disappear _after_ load() on some platforms.
539
+ await canPlayThrough;
540
+ });
541
+ });
451
542
  });
452
543
 
453
544
  // TODO(vaage): Try to group the stat tests together.
@@ -278,10 +278,10 @@ describe('Player Src Equals', () => {
278
278
 
279
279
  expect(player.isTextTrackVisible()).toBe(false);
280
280
 
281
- player.setTextTrackVisibility(true);
281
+ await player.setTextTrackVisibility(true);
282
282
  expect(player.isTextTrackVisible()).toBe(true);
283
283
 
284
- player.setTextTrackVisibility(false);
284
+ await player.setTextTrackVisibility(false);
285
285
  expect(player.isTextTrackVisible()).toBe(false);
286
286
  });
287
287
 
@@ -308,30 +308,30 @@ describe('Player', function() {
308
308
 
309
309
  it('load text stream if caption is visible', async () => {
310
310
  await player.load(fakeManifestUri, 0, returnManifest(manifest));
311
- player.setTextTrackVisibility(true);
311
+ await player.setTextTrackVisibility(true);
312
312
  expect(streamingEngine.loadNewTextStream).toHaveBeenCalled();
313
313
  expect(streamingEngine.getBufferingText()).not.toBe(null);
314
314
  });
315
315
 
316
316
  it('does not load text stream if caption is invisible', async () => {
317
317
  await player.load(fakeManifestUri, 0, returnManifest(manifest));
318
- player.setTextTrackVisibility(false);
318
+ await player.setTextTrackVisibility(false);
319
319
  expect(streamingEngine.loadNewTextStream).not.toHaveBeenCalled();
320
320
  expect(streamingEngine.getBufferingText()).toBe(null);
321
321
  });
322
322
 
323
323
  it('loads text stream if alwaysStreamText is set', async () => {
324
- player.setTextTrackVisibility(false);
324
+ await player.setTextTrackVisibility(false);
325
325
  player.configure({streaming: {alwaysStreamText: true}});
326
326
 
327
327
  await player.load(fakeManifestUri, 0, returnManifest(manifest));
328
328
  expect(streamingEngine.getBufferingText()).not.toBe(null);
329
329
 
330
- player.setTextTrackVisibility(true);
330
+ await player.setTextTrackVisibility(true);
331
331
  expect(streamingEngine.loadNewTextStream).not.toHaveBeenCalled();
332
332
  expect(streamingEngine.unloadTextStream).not.toHaveBeenCalled();
333
333
 
334
- player.setTextTrackVisibility(false);
334
+ await player.setTextTrackVisibility(false);
335
335
  expect(streamingEngine.loadNewTextStream).not.toHaveBeenCalled();
336
336
  expect(streamingEngine.unloadTextStream).not.toHaveBeenCalled();
337
337
  });
@@ -1569,6 +1569,19 @@ describe('Player', function() {
1569
1569
  expect(getActiveTextTrack().language).toBe('en');
1570
1570
  });
1571
1571
 
1572
+ // https://github.com/google/shaka-player/issues/2010
1573
+ it('changing text lang changes active stream when not streaming', () => {
1574
+ streamingEngine.onCanSwitch();
1575
+ player.setTextTrackVisibility(false);
1576
+
1577
+ expect(getActiveTextTrack().language).not.toBe('en');
1578
+ expect(streamingEngine.switchTextStream).not.toHaveBeenCalled();
1579
+ player.selectTextLanguage('en');
1580
+
1581
+ expect(streamingEngine.switchTextStream).not.toHaveBeenCalled();
1582
+ expect(getActiveTextTrack().language).toBe('en');
1583
+ });
1584
+
1572
1585
  it('remembers the channel count when ABR is reenabled', () => {
1573
1586
  streamingEngine.onCanSwitch();
1574
1587
 
@@ -1701,7 +1714,7 @@ describe('Player', function() {
1701
1714
  expect(player.isTextTrackVisible()).toBe(true);
1702
1715
 
1703
1716
  // Turn text back off.
1704
- player.setTextTrackVisibility(false);
1717
+ await player.setTextTrackVisibility(false);
1705
1718
  expect(player.isTextTrackVisible()).toBe(false);
1706
1719
 
1707
1720
  // Change text languages after startup.
@@ -2817,8 +2830,8 @@ describe('Player', function() {
2817
2830
  });
2818
2831
 
2819
2832
  describe('setTextTrackVisibility', function() {
2820
- it('does not throw before load', function() {
2821
- player.setTextTrackVisibility(true);
2833
+ it('does not throw before load', async function() {
2834
+ await player.setTextTrackVisibility(true);
2822
2835
  });
2823
2836
  });
2824
2837
 
package/test/test/boot.js CHANGED
@@ -102,8 +102,10 @@ function getClientArg(name) {
102
102
  // the first time it needs to schedule something.
103
103
  Promise.resolve().then(function() {});
104
104
 
105
- // Set the default timeout to 120s for all asynchronous tests.
106
- jasmine.DEFAULT_TIMEOUT_INTERVAL = 120 * 1000;
105
+ const timeout = getClientArg('testTimeout');
106
+ if (timeout) {
107
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = Number(timeout);
108
+ }
107
109
 
108
110
  let logLevel = getClientArg('logLevel');
109
111
  if (logLevel) {
@@ -199,9 +201,12 @@ function getClientArg(name) {
199
201
  // Load required AMD modules, then proceed with tests.
200
202
  require(['promise-mock', 'sprintf-js', 'less'],
201
203
  (PromiseMock, sprintfJs, less) => {
202
- window.PromiseMock = PromiseMock;
203
- window.sprintf = sprintfJs.sprintf;
204
- window.less = less;
204
+ // These external interfaces are declared as "const" in the externs.
205
+ // Avoid "const"-ness complaints from the compiler by assigning these
206
+ // using bracket notation.
207
+ window['PromiseMock'] = PromiseMock;
208
+ window['sprintf'] = sprintfJs.sprintf;
209
+ window['less'] = less;
205
210
 
206
211
  // Patch a new convenience method into PromiseMock.
207
212
  // See https://github.com/taylorhakes/promise-mock/issues/7
@@ -231,4 +236,14 @@ function getClientArg(name) {
231
236
  originalSetTimeout(done, 100 /* ms */);
232
237
  });
233
238
  }
239
+
240
+ // Code in karma-jasmine's adapter will malform test failures when the
241
+ // expectation message contains a stack trace, losing the failure message and
242
+ // mixing up the stack trace of the failure. To avoid this, we modify
243
+ // shaka.util.Error not to create a stack trace. This trace is not available
244
+ // in production, and there is never any need for it in the tests.
245
+ // Shimming shaka.util.Error proved too complicated because of a combination
246
+ // of compiler restrictions and ES6 language features, so this is by far the
247
+ // simpler answer.
248
+ shaka.util.Error.createStack = false;
234
249
  })();
@@ -65,6 +65,13 @@ jasmine.Spec.prototype.getFullName = function() {};
65
65
  jasmine.Matchers = function() {};
66
66
 
67
67
 
68
+ /**
69
+ * @constructor
70
+ * @struct
71
+ */
72
+ jasmine.MatchersAsync = function() {};
73
+
74
+
68
75
  /**
69
76
  * @param {*} value
70
77
  * @return {!jasmine.Matchers}
@@ -72,6 +79,13 @@ jasmine.Matchers = function() {};
72
79
  var expect = function(value) {};
73
80
 
74
81
 
82
+ /**
83
+ * @param {!Promise} value
84
+ * @return {!jasmine.MatchersAsync}
85
+ */
86
+ var expectAsync = function(value) {};
87
+
88
+
75
89
  /** @param {string=} message */
76
90
  var pending = function(message) {};
77
91
 
@@ -173,6 +187,40 @@ jasmine.Matchers.prototype.toHaveBeenCalledOnceMore = function() {};
173
187
  */
174
188
  jasmine.Matchers.prototype.toHaveBeenCalledOnceMoreWith = function(args) {};
175
189
 
190
+ /**
191
+ * @param {string} message
192
+ * @return {!jasmine.Matchers}
193
+ */
194
+ jasmine.Matchers.prototype.withContext = function(message) {};
195
+
196
+
197
+ /** @type {!jasmine.MatchersAsync} */
198
+ jasmine.MatchersAsync.prototype.not;
199
+
200
+ /** @return {!Promise} */
201
+ jasmine.MatchersAsync.prototype.toBeRejected = function() {};
202
+
203
+ /**
204
+ * @param {*} expected
205
+ * @return {!Promise}
206
+ */
207
+ jasmine.MatchersAsync.prototype.toBeRejectedWith = function(expected) {};
208
+
209
+ /** @return {!Promise} */
210
+ jasmine.MatchersAsync.prototype.toBeResolved = function() {};
211
+
212
+ /**
213
+ * @param {*} expected
214
+ * @return {!Promise}
215
+ */
216
+ jasmine.MatchersAsync.prototype.toBeResolvedTo = function(expected) {};
217
+
218
+ /**
219
+ * @param {string} message
220
+ * @return {!jasmine.MatchersAsync}
221
+ */
222
+ jasmine.MatchersAsync.prototype.withContext = function(message) {};
223
+
176
224
 
177
225
  /**
178
226
  * @constructor