shaka-player 4.0.0 → 4.0.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.
Files changed (57) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/release-please.yaml +6 -11
  3. package/CHANGELOG.md +15 -0
  4. package/build/misspellings.txt +1 -0
  5. package/dist/controls.css +1 -1
  6. package/dist/locales.js +32 -32
  7. package/dist/shaka-player.compiled.d.ts +2 -1
  8. package/dist/shaka-player.compiled.debug.d.ts +2 -1
  9. package/dist/shaka-player.compiled.debug.externs.js +1 -0
  10. package/dist/shaka-player.compiled.debug.js +170 -165
  11. package/dist/shaka-player.compiled.debug.map +3 -3
  12. package/dist/shaka-player.compiled.externs.js +1 -0
  13. package/dist/shaka-player.compiled.js +783 -779
  14. package/dist/shaka-player.compiled.map +3 -3
  15. package/dist/shaka-player.ui.d.ts +2 -1
  16. package/dist/shaka-player.ui.debug.d.ts +2 -1
  17. package/dist/shaka-player.ui.debug.externs.js +1 -0
  18. package/dist/shaka-player.ui.debug.js +182 -177
  19. package/dist/shaka-player.ui.debug.map +3 -3
  20. package/dist/shaka-player.ui.externs.js +1 -0
  21. package/dist/shaka-player.ui.js +924 -920
  22. package/dist/shaka-player.ui.map +3 -3
  23. package/docs/tutorials/upgrade.md +4 -2
  24. package/externs/shaka/player.js +11 -0
  25. package/karma.conf.js +4 -0
  26. package/lib/dash/dash_parser.js +2 -2
  27. package/lib/hls/hls_parser.js +20 -11
  28. package/lib/media/drm_engine.js +16 -0
  29. package/lib/media/media_source_engine.js +91 -37
  30. package/lib/media/streaming_engine.js +1 -2
  31. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  32. package/lib/player.js +165 -107
  33. package/lib/text/vtt_text_parser.js +7 -7
  34. package/lib/util/config_utils.js +25 -0
  35. package/lib/util/error.js +7 -0
  36. package/lib/util/manifest_parser_utils.js +1 -1
  37. package/lib/util/mime_utils.js +48 -1
  38. package/lib/util/periods.js +5 -4
  39. package/lib/util/player_configuration.js +14 -14
  40. package/lib/util/stream_utils.js +4 -2
  41. package/package.json +4 -1
  42. package/test/hls/hls_live_unit.js +22 -0
  43. package/test/hls/manifest_text_parser_unit.js +19 -0
  44. package/test/offline/indexeddb_storage_unit.js +1 -1
  45. package/test/offline/storage_compatibility_unit.js +1 -1
  46. package/test/player_external.js +7 -1
  47. package/test/player_integration.js +6 -10
  48. package/test/player_src_equals_integration.js +81 -83
  49. package/test/player_unit.js +98 -1
  50. package/test/test/boot.js +9 -0
  51. package/test/test/externs/filters.js +4 -0
  52. package/test/test/util/simple_fakes.js +31 -2
  53. package/test/text/vtt_text_parser_unit.js +64 -5
  54. package/test/util/mime_utils_unit.js +54 -0
  55. package/test/util/periods_unit.js +79 -0
  56. package/ui/resolution_selection.js +1 -1
  57. package/docs/tutorials/gsoc-ideas-list.md +0 -3
@@ -573,8 +573,8 @@ shaka.util.PeriodCombiner = class {
573
573
  // TODO(#1528): Consider changing this when we support codec switching.
574
574
  const hasCodec = outputStreams.some((s) => {
575
575
  return s.mimeType == stream.mimeType &&
576
- shaka.util.MimeUtils.getCodecBase(s.codecs) ==
577
- shaka.util.MimeUtils.getCodecBase(stream.codecs);
576
+ shaka.util.MimeUtils.getNormalizedCodec(s.codecs) ==
577
+ shaka.util.MimeUtils.getNormalizedCodec(stream.codecs);
578
578
  });
579
579
  if (!hasCodec) {
580
580
  continue;
@@ -1050,10 +1050,11 @@ shaka.util.PeriodCombiner = class {
1050
1050
  * @private
1051
1051
  */
1052
1052
  static areAVStreamsCompatible_(outputStream, candidate) {
1053
- const getCodecBase = (codecs) => shaka.util.MimeUtils.getCodecBase(codecs);
1053
+ const getCodec = (codecs) =>
1054
+ shaka.util.MimeUtils.getNormalizedCodec(codecs);
1054
1055
  // Check MIME type and codecs, which should always be the same.
1055
1056
  if (candidate.mimeType != outputStream.mimeType ||
1056
- getCodecBase(candidate.codecs) != getCodecBase(outputStream.codecs)) {
1057
+ getCodec(candidate.codecs) != getCodec(outputStream.codecs)) {
1057
1058
  return false;
1058
1059
  }
1059
1060
 
@@ -16,14 +16,6 @@ goog.require('shaka.util.ManifestParserUtils');
16
16
  goog.require('shaka.util.Platform');
17
17
 
18
18
 
19
- // TODO(vaage): Many times in our configs, we need to create an empty
20
- // implementation of a method, but to avoid closure from removing unused
21
- // parameters (and breaking our merge config code) we need to use each
22
- // parameter. Is there a better solution to this problem than what we are
23
- // doing now?
24
- //
25
- // NOTE: Chrome App Content Security Policy prohibits usage of new Function()
26
-
27
19
  /**
28
20
  * @final
29
21
  * @export
@@ -74,6 +66,11 @@ shaka.util.PlayerConfiguration = class {
74
66
  clearKeys: {}, // key is arbitrary key system ID, value must be string
75
67
  advanced: {}, // key is arbitrary key system ID, value is a record type
76
68
  delayLicenseRequestUntilPlayed: false,
69
+ initDataTransform: (initData, initDataType, drmInfo) => {
70
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
71
+ [initData, initDataType, drmInfo],
72
+ initData);
73
+ },
77
74
  logLicenseExchange: false,
78
75
  updateExpirationTime: 1,
79
76
  preferredKeySystems: [],
@@ -113,11 +110,10 @@ shaka.util.PlayerConfiguration = class {
113
110
  'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb':
114
111
  'com.adobe.primetime',
115
112
  },
116
- // Need some operation in the callback or else closure may remove calls
117
- // to the function as it would be a no-op. The operation can't just be
118
- // a log message, because those are stripped in the compiled build.
119
113
  manifestPreprocessor: (element) => {
120
- return element;
114
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
115
+ [element],
116
+ element);
121
117
  },
122
118
  },
123
119
  hls: {
@@ -138,7 +134,9 @@ shaka.util.PlayerConfiguration = class {
138
134
  // log message, because those are stripped in the compiled build.
139
135
  failureCallback: (error) => {
140
136
  shaka.log.error('Unhandled streaming error', error);
141
- return [error];
137
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
138
+ [error],
139
+ undefined);
142
140
  },
143
141
  // When low latency streaming is enabled, rebufferingGoal will default to
144
142
  // 0.01 if not specified.
@@ -214,7 +212,9 @@ shaka.util.PlayerConfiguration = class {
214
212
  // to the function as it would be a no-op. The operation can't just be a
215
213
  // log message, because those are stripped in the compiled build.
216
214
  progressCallback: (content, progress) => {
217
- return [content, progress];
215
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
216
+ [content, progress],
217
+ undefined);
218
218
  },
219
219
 
220
220
  // By default we use persistent licenses as forces errors to surface if
@@ -283,12 +283,14 @@ shaka.util.StreamUtils = class {
283
283
  // both be considered the same codec: avc1.42c01e, avc1.4d401f
284
284
  let baseVideoCodec = '';
285
285
  if (variant.video) {
286
- baseVideoCodec = shaka.util.MimeUtils.getCodecBase(variant.video.codecs);
286
+ baseVideoCodec =
287
+ shaka.util.MimeUtils.getNormalizedCodec(variant.video.codecs);
287
288
  }
288
289
 
289
290
  let baseAudioCodec = '';
290
291
  if (variant.audio) {
291
- baseAudioCodec = shaka.util.MimeUtils.getCodecBase(variant.audio.codecs);
292
+ baseAudioCodec =
293
+ shaka.util.MimeUtils.getNormalizedCodec(variant.audio.codecs);
292
294
  }
293
295
 
294
296
  return baseVideoCodec + '-' + baseAudioCodec;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.0.0",
4
+ "version": "4.0.1",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -97,5 +97,8 @@
97
97
  },
98
98
  "dependencies": {
99
99
  "eme-encryption-scheme-polyfill": "^2.0.3"
100
+ },
101
+ "engines": {
102
+ "node": ">=14"
100
103
  }
101
104
  }
@@ -603,6 +603,28 @@ describe('HlsParser live', () => {
603
603
  ManifestParser.verifySegmentIndex(video, [ref, ref2]);
604
604
  });
605
605
 
606
+ // Test for https://github.com/shaka-project/shaka-player/issues/4185
607
+ it('does not fail on preload hints with LL mode off', async () => {
608
+ // LL mode must be off for this test!
609
+ playerInterface.isLowLatencyMode = () => false;
610
+
611
+ const mediaWithPartialSegments = [
612
+ '#EXTM3U\n',
613
+ '#EXT-X-TARGETDURATION:5\n',
614
+ '#EXTINF:4,\n',
615
+ 'main.mp4\n',
616
+ '#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
617
+ '#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n',
618
+ ].join('');
619
+
620
+ fakeNetEngine
621
+ .setResponseText('test:/master', master)
622
+ .setResponseText('test:/video', mediaWithPartialSegments);
623
+
624
+ // If this throws, the test fails. Otherwise, it passes.
625
+ await parser.start('test:/master', playerInterface);
626
+ });
627
+
606
628
  describe('update', () => {
607
629
  it('adds new segments when they appear', async () => {
608
630
  const ref1 = makeReference(
@@ -238,6 +238,25 @@ describe('ManifestTextParser', () => {
238
238
  '#EXTM3U\n' +
239
239
  '#EXT-X-MEDIA:CODECS="avc1.64002a,mp4a.40.2",AUDIO="a1,a2"',
240
240
 
241
+ // manifest URI:
242
+ 'https://test/manifest.m3u8');
243
+
244
+ verifyPlaylist(
245
+ {
246
+ type: shaka.hls.PlaylistType.MASTER,
247
+ tags: [
248
+ new shaka.hls.Tag(/* id= */ 3, 'EXT-X-MEDIA',
249
+ [
250
+ new shaka.hls.Attribute('CODECS',
251
+ 'av01.0.08M.08,mp4a.40.2'),
252
+ ]),
253
+ ],
254
+ },
255
+
256
+ // playlist text:
257
+ '#EXTM3U\n' +
258
+ '#EXT-X-MEDIA:CODECS="av01.0.08M.08,mp4a.40.2"',
259
+
241
260
  // manifest URI:
242
261
  'https://test/manifest.m3u8');
243
262
  });
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- filterDescribe('IndexeddbStorageCell', () => window.indexedDB, () => {
7
+ filterDescribe('IndexeddbStorageCell', offlineSupported, () => {
8
8
  const IndexedDBUtils = shaka.test.IndexedDBUtils;
9
9
  const OfflineUtils = shaka.test.OfflineUtils;
10
10
  const Util = shaka.test.Util;
@@ -91,7 +91,7 @@ const compatibilityTestsMetadata = [
91
91
  },
92
92
  ];
93
93
 
94
- filterDescribe('Storage Compatibility', () => window.indexedDB, () => {
94
+ filterDescribe('Storage Compatibility', offlineSupported, () => {
95
95
  for (const metadata of compatibilityTestsMetadata) {
96
96
  describe(metadata.name, () => {
97
97
  makeTests(metadata);
@@ -114,7 +114,13 @@ describe('Player', () => {
114
114
  const networkingEngine = player.getNetworkingEngine();
115
115
  asset.applyFilters(networkingEngine);
116
116
 
117
- await player.load(asset.manifestUri);
117
+ // Rather than awaiting the load() method, catch any load() errors and
118
+ // wait on the 'canplay' event. This has the advantage that we will
119
+ // get better logging of the media state on a timeout, since that
120
+ // capabilitiy is built into the waiter for media element events.
121
+ player.load(asset.manifestUri).catch(fail);
122
+ await waiter.timeoutAfter(60).waitForEvent(video, 'canplay');
123
+
118
124
  if (asset.features) {
119
125
  const isLive = asset.features.includes(Feature.LIVE);
120
126
  expect(player.isLive()).toBe(isLive);
@@ -1069,16 +1069,16 @@ describe('Player', () => {
1069
1069
  });
1070
1070
  }); // describe('unloading')
1071
1071
 
1072
- describe('chapters', () => {
1073
- it('add external chapters in vtt format', async () => {
1072
+ describe('addChaptersTrack', () => {
1073
+ it('adds external chapters in vtt format', async () => {
1074
1074
  await player.load('test:sintel_no_text_compiled');
1075
1075
  const locationUri = new goog.Uri(location.href);
1076
1076
  const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
1077
1077
  const absoluteUri1 = locationUri.resolve(partialUri1);
1078
1078
  await player.addChaptersTrack(absoluteUri1.toString(), 'en');
1079
1079
 
1080
- await shaka.test.Util.delay(1.5);
1081
-
1080
+ // Data should be available as soon as addChaptersTrack resolves.
1081
+ // See https://github.com/shaka-project/shaka-player/issues/4186
1082
1082
  const chapters = player.getChapters('en');
1083
1083
  expect(chapters.length).toBe(3);
1084
1084
  const chapter1 = chapters[0];
@@ -1098,8 +1098,6 @@ describe('Player', () => {
1098
1098
  const absoluteUri2 = locationUri.resolve(partialUri2);
1099
1099
  await player.addChaptersTrack(absoluteUri2.toString(), 'en');
1100
1100
 
1101
- await shaka.test.Util.delay(1.5);
1102
-
1103
1101
  const chaptersUpdated = player.getChapters('en');
1104
1102
  expect(chaptersUpdated.length).toBe(6);
1105
1103
  const chapterUpdated1 = chaptersUpdated[0];
@@ -1128,15 +1126,13 @@ describe('Player', () => {
1128
1126
  expect(chapterUpdated6.endTime).toBe(61.349);
1129
1127
  });
1130
1128
 
1131
- it('add external chapters in srt format', async () => {
1129
+ it('adds external chapters in srt format', async () => {
1132
1130
  await player.load('test:sintel_no_text_compiled');
1133
1131
  const locationUri = new goog.Uri(location.href);
1134
1132
  const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
1135
1133
  const absoluteUri = locationUri.resolve(partialUri);
1136
1134
  await player.addChaptersTrack(absoluteUri.toString(), 'es');
1137
1135
 
1138
- await shaka.test.Util.delay(1.5);
1139
-
1140
1136
  const chapters = player.getChapters('es');
1141
1137
  expect(chapters.length).toBe(3);
1142
1138
  const chapter1 = chapters[0];
@@ -1152,5 +1148,5 @@ describe('Player', () => {
1152
1148
  expect(chapter3.startTime).toBe(30);
1153
1149
  expect(chapter3.endTime).toBe(61.349);
1154
1150
  });
1155
- }); // describe('chapters')
1151
+ }); // describe('addChaptersTrack')
1156
1152
  });
@@ -319,90 +319,88 @@ describe('Player Src Equals', () => {
319
319
  expect(newTrack).toBeTruthy();
320
320
  });
321
321
 
322
- it('add external chapters in vtt format', async () => {
323
- await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
324
-
325
- const locationUri = new goog.Uri(location.href);
326
- const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
327
- const absoluteUri1 = locationUri.resolve(partialUri1);
328
- await player.addChaptersTrack(absoluteUri1.toString(), 'en');
329
-
330
- await shaka.test.Util.delay(1.5);
331
-
332
- const chapters = player.getChapters('en');
333
- expect(chapters.length).toBe(3);
334
- const chapter1 = chapters[0];
335
- expect(chapter1.title).toBe('Chapter 1');
336
- expect(chapter1.startTime).toBe(0);
337
- expect(chapter1.endTime).toBe(5);
338
- const chapter2 = chapters[1];
339
- expect(chapter2.title).toBe('Chapter 2');
340
- expect(chapter2.startTime).toBe(5);
341
- expect(chapter2.endTime).toBe(10);
342
- const chapter3 = chapters[2];
343
- expect(chapter3.title).toBe('Chapter 3');
344
- expect(chapter3.startTime).toBe(10);
345
- expect(chapter3.endTime).toBe(20);
346
-
347
- const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
348
- const absoluteUri2 = locationUri.resolve(partialUri2);
349
- await player.addChaptersTrack(absoluteUri2.toString(), 'en');
350
-
351
- await shaka.test.Util.delay(1.5);
352
-
353
- const chaptersUpdated = player.getChapters('en');
354
- expect(chaptersUpdated.length).toBe(6);
355
- const chapterUpdated1 = chaptersUpdated[0];
356
- expect(chapterUpdated1.title).toBe('Chapter 1');
357
- expect(chapterUpdated1.startTime).toBe(0);
358
- expect(chapterUpdated1.endTime).toBe(5);
359
- const chapterUpdated2 = chaptersUpdated[1];
360
- expect(chapterUpdated2.title).toBe('Chapter 2');
361
- expect(chapterUpdated2.startTime).toBe(5);
362
- expect(chapterUpdated2.endTime).toBe(10);
363
- const chapterUpdated3 = chaptersUpdated[2];
364
- expect(chapterUpdated3.title).toBe('Chapter 3');
365
- expect(chapterUpdated3.startTime).toBe(10);
366
- expect(chapterUpdated3.endTime).toBe(20);
367
- const chapterUpdated4 = chaptersUpdated[3];
368
- expect(chapterUpdated4.title).toBe('Chapter 4');
369
- expect(chapterUpdated4.startTime).toBe(20);
370
- expect(chapterUpdated4.endTime).toBe(30);
371
- const chapterUpdated5 = chaptersUpdated[4];
372
- expect(chapterUpdated5.title).toBe('Chapter 5');
373
- expect(chapterUpdated5.startTime).toBe(30);
374
- expect(chapterUpdated5.endTime).toBe(40);
375
- const chapterUpdated6 = chaptersUpdated[5];
376
- expect(chapterUpdated6.title).toBe('Chapter 6');
377
- expect(chapterUpdated6.startTime).toBe(40);
378
- expect(chapterUpdated6.endTime).toBe(61.349);
379
- });
380
-
381
- it('add external chapters in srt format', async () => {
382
- await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
383
-
384
- const locationUri = new goog.Uri(location.href);
385
- const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
386
- const absoluteUri = locationUri.resolve(partialUri);
387
- await player.addChaptersTrack(absoluteUri.toString(), 'es');
388
-
389
- await shaka.test.Util.delay(1.5);
322
+ describe('addChaptersTrack', () => {
323
+ it('adds external chapters in vtt format', async () => {
324
+ await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
325
+
326
+ const locationUri = new goog.Uri(location.href);
327
+ const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
328
+ const absoluteUri1 = locationUri.resolve(partialUri1);
329
+ await player.addChaptersTrack(absoluteUri1.toString(), 'en');
330
+
331
+ // Data should be available as soon as addChaptersTrack resolves.
332
+ // See https://github.com/shaka-project/shaka-player/issues/4186
333
+ const chapters = player.getChapters('en');
334
+ expect(chapters.length).toBe(3);
335
+ const chapter1 = chapters[0];
336
+ expect(chapter1.title).toBe('Chapter 1');
337
+ expect(chapter1.startTime).toBe(0);
338
+ expect(chapter1.endTime).toBe(5);
339
+ const chapter2 = chapters[1];
340
+ expect(chapter2.title).toBe('Chapter 2');
341
+ expect(chapter2.startTime).toBe(5);
342
+ expect(chapter2.endTime).toBe(10);
343
+ const chapter3 = chapters[2];
344
+ expect(chapter3.title).toBe('Chapter 3');
345
+ expect(chapter3.startTime).toBe(10);
346
+ expect(chapter3.endTime).toBe(20);
347
+
348
+ const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
349
+ const absoluteUri2 = locationUri.resolve(partialUri2);
350
+ await player.addChaptersTrack(absoluteUri2.toString(), 'en');
351
+
352
+ const chaptersUpdated = player.getChapters('en');
353
+ expect(chaptersUpdated.length).toBe(6);
354
+ const chapterUpdated1 = chaptersUpdated[0];
355
+ expect(chapterUpdated1.title).toBe('Chapter 1');
356
+ expect(chapterUpdated1.startTime).toBe(0);
357
+ expect(chapterUpdated1.endTime).toBe(5);
358
+ const chapterUpdated2 = chaptersUpdated[1];
359
+ expect(chapterUpdated2.title).toBe('Chapter 2');
360
+ expect(chapterUpdated2.startTime).toBe(5);
361
+ expect(chapterUpdated2.endTime).toBe(10);
362
+ const chapterUpdated3 = chaptersUpdated[2];
363
+ expect(chapterUpdated3.title).toBe('Chapter 3');
364
+ expect(chapterUpdated3.startTime).toBe(10);
365
+ expect(chapterUpdated3.endTime).toBe(20);
366
+ const chapterUpdated4 = chaptersUpdated[3];
367
+ expect(chapterUpdated4.title).toBe('Chapter 4');
368
+ expect(chapterUpdated4.startTime).toBe(20);
369
+ expect(chapterUpdated4.endTime).toBe(30);
370
+ const chapterUpdated5 = chaptersUpdated[4];
371
+ expect(chapterUpdated5.title).toBe('Chapter 5');
372
+ expect(chapterUpdated5.startTime).toBe(30);
373
+ expect(chapterUpdated5.endTime).toBe(40);
374
+ const chapterUpdated6 = chaptersUpdated[5];
375
+ expect(chapterUpdated6.title).toBe('Chapter 6');
376
+ expect(chapterUpdated6.startTime).toBe(40);
377
+ expect(chapterUpdated6.endTime).toBe(61.349);
378
+ });
390
379
 
391
- const chapters = player.getChapters('es');
392
- expect(chapters.length).toBe(3);
393
- const chapter1 = chapters[0];
394
- expect(chapter1.title).toBe('Chapter 1');
395
- expect(chapter1.startTime).toBe(0);
396
- expect(chapter1.endTime).toBe(5);
397
- const chapter2 = chapters[1];
398
- expect(chapter2.title).toBe('Chapter 2');
399
- expect(chapter2.startTime).toBe(5);
400
- expect(chapter2.endTime).toBe(30);
401
- const chapter3 = chapters[2];
402
- expect(chapter3.title).toBe('Chapter 3');
403
- expect(chapter3.startTime).toBe(30);
404
- expect(chapter3.endTime).toBe(61.349);
405
- });
380
+ it('add external chapters in srt format', async () => {
381
+ await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
382
+
383
+ const locationUri = new goog.Uri(location.href);
384
+ const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
385
+ const absoluteUri = locationUri.resolve(partialUri);
386
+ await player.addChaptersTrack(absoluteUri.toString(), 'es');
387
+
388
+ const chapters = player.getChapters('es');
389
+ expect(chapters.length).toBe(3);
390
+ const chapter1 = chapters[0];
391
+ expect(chapter1.title).toBe('Chapter 1');
392
+ expect(chapter1.startTime).toBe(0);
393
+ expect(chapter1.endTime).toBe(5);
394
+ const chapter2 = chapters[1];
395
+ expect(chapter2.title).toBe('Chapter 2');
396
+ expect(chapter2.startTime).toBe(5);
397
+ expect(chapter2.endTime).toBe(30);
398
+ const chapter3 = chapters[2];
399
+ expect(chapter3.title).toBe('Chapter 3');
400
+ expect(chapter3.startTime).toBe(30);
401
+ expect(chapter3.endTime).toBe(61.349);
402
+ });
403
+ }); // describe('addChaptersTrack')
406
404
 
407
405
  // Since we are not in-charge of streaming, calling |retryStreaming| should
408
406
  // have no effect.
@@ -124,7 +124,13 @@ describe('Player', () => {
124
124
 
125
125
  player.createDrmEngine = () => drmEngine;
126
126
  player.createNetworkingEngine = () => networkingEngine;
127
- player.createPlayhead = () => playhead;
127
+ player.createPlayhead = (startTime) => {
128
+ const callableSetStartTime =
129
+ shaka.test.Util.spyFunc(playhead.setStartTime);
130
+ callableSetStartTime(startTime);
131
+ playhead.setStartTime.calls.reset();
132
+ return playhead;
133
+ };
128
134
  player.createMediaSourceEngine = () => mediaSourceEngine;
129
135
  player.createStreamingEngine = () => streamingEngine;
130
136
  }
@@ -156,6 +162,7 @@ describe('Player', () => {
156
162
  window.MediaSource.isTypeSupported = originalIsTypeSupported;
157
163
  shaka.media.ManifestParser.unregisterParserByMime(fakeMimeType);
158
164
  navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
165
+ onError.calls.reset();
159
166
  }
160
167
  });
161
168
 
@@ -2658,6 +2665,51 @@ describe('Player', () => {
2658
2665
  expect(activeVariant.id).toBe(1);
2659
2666
  });
2660
2667
 
2668
+ // Regression test for https://github.com/shaka-project/shaka-player/issues/4190
2669
+ it('throws only one RESTRICTIONS_CANNOT_BE_MET error', async () => {
2670
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2671
+ manifest.addVariant(0, (variant) => {
2672
+ variant.addVideo(1, (stream) => {
2673
+ stream.keyIds = new Set(['abc']);
2674
+ });
2675
+ });
2676
+ });
2677
+
2678
+ // Check that error is RESTRICTIONS_CANNOT_BE_MET
2679
+ onError.and.callFake((e) => {
2680
+ const error = e.detail;
2681
+ shaka.test.Util.expectToEqualError(
2682
+ error,
2683
+ new shaka.util.Error(
2684
+ shaka.util.Error.Severity.CRITICAL,
2685
+ shaka.util.Error.Category.MANIFEST,
2686
+ shaka.util.Error.Code.RESTRICTIONS_CANNOT_BE_MET, {
2687
+ hasAppRestrictions: false,
2688
+ missingKeys: ['abc'],
2689
+ restrictedKeyStatuses: [],
2690
+ }));
2691
+ });
2692
+
2693
+ await player.load(fakeManifestUri, 0, fakeMimeType);
2694
+ const activeVariant = getActiveVariantTrack();
2695
+ expect(activeVariant.id).toBe(0);
2696
+
2697
+ abrManager.chooseIndex = 0;
2698
+ abrManager.chooseVariant.calls.reset();
2699
+
2700
+ onKeyStatus({'abc': 'output-restricted'});
2701
+
2702
+ // Ensure that RESTRICTIONS_CANNOT_BE_MET is thrown once
2703
+ expect(onError).toHaveBeenCalledTimes(1);
2704
+
2705
+ // It does not call chooseVariant
2706
+ expect(abrManager.chooseVariant).not.toHaveBeenCalled();
2707
+
2708
+ // The first variant is disallowed.
2709
+ expect(manifest.variants[0].id).toBe(0);
2710
+ expect(manifest.variants[0].allowedByKeySystem).toBe(false);
2711
+ });
2712
+
2661
2713
  it('doesn\'t switch if the active stream isn\'t restricted', async () => {
2662
2714
  manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2663
2715
  manifest.addVariant(0, (variant) => {
@@ -3664,6 +3716,51 @@ describe('Player', () => {
3664
3716
  });
3665
3717
  });
3666
3718
 
3719
+ describe('config streaming.startAtSegmentBoundary', () => {
3720
+ // Test for https://github.com/shaka-project/shaka-player/issues/4188
3721
+ // In that issue, using streaming.startAtSegmentBoundary in v4.0.0 caused
3722
+ // an exception due to the use of a removed method.
3723
+ it('adjusts start time', async () => {
3724
+ player.configure('streaming.startAtSegmentBoundary', true);
3725
+
3726
+ // In order to adjust the start time to a segment boundary, we need
3727
+ // segment descriptions in the segment index. So create a non-default
3728
+ // fake manifest.
3729
+ const timeline = new shaka.media.PresentationTimeline(300, 0);
3730
+ timeline.setStatic(true);
3731
+ // This duration is used by useSegmentTemplate below to decide how many
3732
+ // references to generate.
3733
+ timeline.setDuration(300);
3734
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3735
+ manifest.presentationTimeline = timeline;
3736
+ manifest.addVariant(0, (variant) => {
3737
+ variant.addVideo(1, (stream) => {
3738
+ stream.useSegmentTemplate(
3739
+ '$Number$.mp4', /* segmentDuration= */ 10);
3740
+ });
3741
+ });
3742
+ });
3743
+
3744
+ await player.load(fakeManifestUri, 12, fakeMimeType);
3745
+
3746
+ expect(playhead.setStartTime).toHaveBeenCalledWith(10);
3747
+ });
3748
+
3749
+ it('does not fail with no segments', async () => {
3750
+ player.configure('streaming.startAtSegmentBoundary', true);
3751
+
3752
+ // Without useSegmentTemplate in the fake manifest, the call to
3753
+ // getIteratorForTime() in Player produces a null iterator. Using the
3754
+ // default fake manifest should cover that case. But just to make sure,
3755
+ // verify that we have no segments before calling load().
3756
+ const variant = manifest.variants[0];
3757
+ expect(variant.video.segmentIndex.getIteratorForTime(0)).toBeNull();
3758
+ expect(variant.audio.segmentIndex.getIteratorForTime(0)).toBeNull();
3759
+
3760
+ await player.load(fakeManifestUri, 0, fakeMimeType);
3761
+ });
3762
+ });
3763
+
3667
3764
  /**
3668
3765
  * Gets the currently active variant track.
3669
3766
  * @return {shaka.extern.Track}
package/test/test/boot.js CHANGED
@@ -242,6 +242,15 @@ window.xfilterDescribe = (describeName, cond, describeBody) => {
242
242
  window['describe'] = oldDescribe;
243
243
  };
244
244
 
245
+ /**
246
+ * A very short alias for filtering offline integration tests.
247
+ *
248
+ * @return {boolean}
249
+ */
250
+ window.offlineSupported = () => {
251
+ return shaka.offline.StorageMuxer.support();
252
+ };
253
+
245
254
  /**
246
255
  * Load node modules used in testing and install them into window.
247
256
  * @return {!Promise}
@@ -38,3 +38,7 @@ var filterDescribe = function(name, cond, callback) {};
38
38
  * @param {function()} callback
39
39
  */
40
40
  var xfilterDescribe = function(name, cond, callback) {};
41
+
42
+
43
+ /** @return {boolean} */
44
+ var offlineSupported = function() {};
@@ -152,9 +152,27 @@ shaka.test.FakeVideo = class {
152
152
  constructor(currentTime) {
153
153
  /** @const {!Object.<string, !Function>} */
154
154
  this.on = {}; // event listeners
155
+
155
156
  /** @type {!Array.<!TextTrack>} */
156
157
  this.textTracks = [];
157
158
 
159
+ // In a real video element, textTracks is an event target.
160
+ // Since Player listens to events on textTracks, we need to fake that
161
+ // interface.
162
+ this.textTracksEventTarget = new shaka.util.FakeEventTarget();
163
+ this.textTracks.addEventListener =
164
+ // eslint-disable-next-line no-restricted-syntax
165
+ this.textTracksEventTarget.addEventListener.bind(
166
+ this.textTracksEventTarget);
167
+ this.textTracks.removeEventListener =
168
+ // eslint-disable-next-line no-restricted-syntax
169
+ this.textTracksEventTarget.removeEventListener.bind(
170
+ this.textTracksEventTarget);
171
+ this.textTracks.dispatchEvent =
172
+ // eslint-disable-next-line no-restricted-syntax
173
+ this.textTracksEventTarget.dispatchEvent.bind(
174
+ this.textTracksEventTarget);
175
+
158
176
  this.currentTime = currentTime || 0;
159
177
  this.readyState = 0;
160
178
  this.playbackRate = 1;
@@ -173,6 +191,10 @@ shaka.test.FakeVideo = class {
173
191
  jasmine.createSpy('addTextTrack').and.callFake((kind, id) => {
174
192
  const track = new shaka.test.FakeTextTrack();
175
193
  this.textTracks.push(track);
194
+
195
+ const trackEvent = new shaka.util.FakeEvent('addtrack', {track});
196
+ this.textTracksEventTarget.dispatchEvent(trackEvent);
197
+
176
198
  return track;
177
199
  });
178
200
 
@@ -308,11 +330,18 @@ shaka.test.FakePlayhead = class {
308
330
  /** @type {!jasmine.Spy} */
309
331
  this.setRebufferingGoal = jasmine.createSpy('setRebufferingGoal');
310
332
 
333
+ /** @private {number} */
334
+ this.startTime_ = 0;
335
+
311
336
  /** @type {!jasmine.Spy} */
312
- this.setStartTime = jasmine.createSpy('setStartTime');
337
+ this.setStartTime = jasmine.createSpy('setStartTime')
338
+ .and.callFake((value) => {
339
+ this.startTime_ = value;
340
+ });
313
341
 
314
342
  /** @type {!jasmine.Spy} */
315
- this.getTime = jasmine.createSpy('getTime').and.returnValue(0);
343
+ this.getTime = jasmine.createSpy('getTime')
344
+ .and.callFake(() => this.startTime_);
316
345
 
317
346
  /** @type {!jasmine.Spy} */
318
347
  this.setBuffering = jasmine.createSpy('setBuffering');