shaka-player 4.1.3 → 4.2.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 (68) hide show
  1. package/.github/workflows/selenium-lab-tests.yaml +158 -53
  2. package/CHANGELOG.md +39 -29
  3. package/demo/common/assets.js +25 -0
  4. package/demo/common/message_ids.js +2 -0
  5. package/demo/config.js +5 -1
  6. package/demo/index.html +1 -1
  7. package/demo/locales/en.json +2 -0
  8. package/demo/locales/source.json +8 -0
  9. package/dist/controls.css +1 -1
  10. package/dist/controls.css.map +1 -1
  11. package/dist/demo.compiled.debug.js +25 -23
  12. package/dist/demo.compiled.debug.map +3 -3
  13. package/dist/demo.compiled.js +25 -23
  14. package/dist/demo.compiled.map +3 -3
  15. package/dist/deps.js +3 -2
  16. package/dist/locales.js +32 -32
  17. package/dist/receiver.compiled.debug.js +24 -22
  18. package/dist/receiver.compiled.debug.map +3 -3
  19. package/dist/receiver.compiled.js +24 -22
  20. package/dist/receiver.compiled.map +3 -3
  21. package/dist/shaka-player.compiled.d.ts +57 -6
  22. package/dist/shaka-player.compiled.debug.d.ts +57 -6
  23. package/dist/shaka-player.compiled.debug.externs.js +62 -6
  24. package/dist/shaka-player.compiled.debug.js +305 -262
  25. package/dist/shaka-player.compiled.debug.map +4 -4
  26. package/dist/shaka-player.compiled.externs.js +62 -6
  27. package/dist/shaka-player.compiled.js +817 -801
  28. package/dist/shaka-player.compiled.map +4 -4
  29. package/dist/shaka-player.ui.d.ts +57 -6
  30. package/dist/shaka-player.ui.debug.d.ts +57 -6
  31. package/dist/shaka-player.ui.debug.externs.js +62 -6
  32. package/dist/shaka-player.ui.debug.js +308 -265
  33. package/dist/shaka-player.ui.debug.map +4 -4
  34. package/dist/shaka-player.ui.externs.js +62 -6
  35. package/dist/shaka-player.ui.js +942 -925
  36. package/dist/shaka-player.ui.map +4 -4
  37. package/docs/tutorials/fairplay.md +149 -0
  38. package/docs/tutorials/ui.md +6 -6
  39. package/externs/shaka/abr_manager.js +8 -0
  40. package/externs/shaka/manifest.js +43 -0
  41. package/externs/shaka/player.js +13 -2
  42. package/lib/abr/simple_abr_manager.js +72 -4
  43. package/lib/hls/hls_parser.js +138 -96
  44. package/lib/media/drm_engine.js +0 -2
  45. package/lib/media/segment_index.js +8 -0
  46. package/lib/media/segment_reference.js +7 -1
  47. package/lib/media/streaming_engine.js +38 -2
  48. package/lib/net/networking_engine.js +1 -0
  49. package/lib/player.js +20 -1
  50. package/lib/polyfill/patchedmediakeys_apple.js +729 -0
  51. package/lib/text/ui_text_displayer.js +8 -0
  52. package/lib/text/vtt_text_parser.js +61 -4
  53. package/lib/util/error.js +23 -11
  54. package/lib/util/fairplay_utils.js +100 -2
  55. package/lib/util/platform.js +12 -1
  56. package/lib/util/player_configuration.js +4 -10
  57. package/package.json +1 -1
  58. package/shaka-player.uncompiled.js +1 -0
  59. package/test/hls/hls_parser_unit.js +101 -34
  60. package/test/media/drm_engine_unit.js +29 -0
  61. package/test/media/streaming_engine_unit.js +79 -5
  62. package/test/player_src_equals_integration.js +2 -0
  63. package/test/player_unit.js +39 -1
  64. package/test/test/assets/small.mp4 +0 -0
  65. package/test/test/util/simple_fakes.js +4 -0
  66. package/test/test/util/streaming_engine_util.js +10 -2
  67. package/test/text/vtt_text_parser_unit.js +26 -0
  68. package/ui/less/ad_controls.less +4 -0
@@ -493,6 +493,14 @@ shaka.text.UITextDisplayer = class {
493
493
  style.backgroundRepeat = 'no-repeat';
494
494
  style.backgroundSize = 'contain';
495
495
  style.backgroundPosition = 'center';
496
+
497
+ // Quoting https://www.w3.org/TR/ttml-imsc1.2/:
498
+ // "The width and height (in pixels) of the image resource referenced by
499
+ // smpte:backgroundImage SHALL be equal to the width and height expressed
500
+ // by the tts:extent attribute of the region in which the div element is
501
+ // presented".
502
+ style.width = '100%';
503
+ style.height = '100%';
496
504
  } else {
497
505
  // If we have both text and nested cues, then style everything; otherwise
498
506
  // place the text in its own <span> so the background doesn't fill the
@@ -412,6 +412,7 @@ shaka.text.VttTextParser = class {
412
412
  VttTextParser.addDefaultTextColor_(styles);
413
413
  }
414
414
  payload = VttTextParser.replaceColorPayload_(payload);
415
+ payload = VttTextParser.replaceKaraokeStylePayload_(payload);
415
416
  const xmlPayload = '<span>' + payload + '</span>';
416
417
  const element = shaka.util.XmlUtils.parseXmlString(xmlPayload, 'span');
417
418
  if (element) {
@@ -437,6 +438,44 @@ shaka.text.VttTextParser = class {
437
438
  }
438
439
  }
439
440
 
441
+ /**
442
+ * Converts karaoke style tag to be valid for xml parsing
443
+ * For example,
444
+ * input: Text <00:00:00.450> time <00:00:01.450> 1
445
+ * output: Text <div time="00:00:00.450"> time
446
+ * <div time="00:00:01.450"> 1</div></div>
447
+ *
448
+ * @param {string} payload
449
+ * @return {string} processed payload
450
+ * @private
451
+ */
452
+ static replaceKaraokeStylePayload_(payload) {
453
+ const names = [];
454
+ let nameStart = -1;
455
+ for (let i = 0; i < payload.length; i++) {
456
+ if (payload[i] === '<') {
457
+ nameStart = i + 1;
458
+ } else if (payload[i] === '>') {
459
+ if (nameStart > 0) {
460
+ const name = payload.substr(nameStart, i - nameStart);
461
+ if (name.match(shaka.text.VttTextParser.timeFormat_)) {
462
+ names.push(name);
463
+ }
464
+ nameStart = -1;
465
+ }
466
+ }
467
+ }
468
+ let newPayload = payload;
469
+ for (const name of names) {
470
+ const replaceTag = '<' + name + '>';
471
+ const startTag = '<div time="' + name + '">';
472
+ const endTag = '</div>';
473
+ newPayload = newPayload.replace(replaceTag, startTag);
474
+ newPayload += endTag;
475
+ }
476
+ return newPayload;
477
+ }
478
+
440
479
  /**
441
480
  * Converts color end tag to be valid for xml parsing
442
481
  * For example,
@@ -560,6 +599,18 @@ shaka.text.VttTextParser = class {
560
599
  case 'u':
561
600
  nestedCue.textDecoration.push(underline);
562
601
  break;
602
+ case 'div': {
603
+ const time = /** @type {!Element} */(element).getAttribute('time');
604
+ if (!time) {
605
+ break;
606
+ }
607
+ const parser = new shaka.util.TextParser(time);
608
+ const cueTime = shaka.text.VttTextParser.parseTime_(parser);
609
+ if (cueTime) {
610
+ nestedCue.startTime = cueTime;
611
+ }
612
+ break;
613
+ }
563
614
  default:
564
615
  break;
565
616
  }
@@ -776,10 +827,7 @@ shaka.text.VttTextParser = class {
776
827
  * @private
777
828
  */
778
829
  static parseTime_(parser) {
779
- // 00:00.000 or 00:00:00.000 or 0:00:00.000 or
780
- // 00:00.00 or 00:00:00.00 or 0:00:00.00
781
- const regexExpresion = /(?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{2,3})/g;
782
- const results = parser.readRegex(regexExpresion);
830
+ const results = parser.readRegex(shaka.text.VttTextParser.timeFormat_);
783
831
  if (results == null) {
784
832
  return null;
785
833
  }
@@ -810,6 +858,15 @@ shaka.text.VttTextParser.MPEG_TIMESCALE_ = 90000;
810
858
  */
811
859
  shaka.text.VttTextParser.TS_ROLLOVER_ = 0x200000000;
812
860
 
861
+ /**
862
+ * @const
863
+ * @private {!RegExp}
864
+ * @example 00:00.000 or 00:00:00.000 or 0:00:00.000 or
865
+ * 00:00.00 or 00:00:00.00 or 0:00:00.00
866
+ */
867
+ shaka.text.VttTextParser.timeFormat_ =
868
+ /(?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{2,3})/g;
869
+
813
870
  shaka.text.TextEngine.registerParser(
814
871
  'text/vtt', () => new shaka.text.VttTextParser());
815
872
 
package/lib/util/error.js CHANGED
@@ -656,16 +656,8 @@ shaka.util.Error.Code = {
656
656
  */
657
657
  'CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM': 4033,
658
658
 
659
- /**
660
- * We do not support AES-128 encryption with HLS yet.
661
- */
662
- 'HLS_AES_128_ENCRYPTION_NOT_SUPPORTED': 4034,
663
-
664
- /**
665
- * An internal error code that should never be seen by applications, thrown
666
- * to force the HLS parser to skip an unsupported stream.
667
- */
668
- 'HLS_INTERNAL_SKIP_STREAM': 4035,
659
+ // RETIRED: 'HLS_AES_128_ENCRYPTION_NOT_SUPPORTED': 4034,
660
+ // RETIRED: 'HLS_INTERNAL_SKIP_STREAM': 4035,
669
661
 
670
662
  /** The Manifest contained no Variants. */
671
663
  'NO_VARIANTS': 4036,
@@ -693,7 +685,27 @@ shaka.util.Error.Code = {
693
685
  */
694
686
  'HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED': 4040,
695
687
 
696
- // RETIRED: 'HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED': 4041,
688
+ /**
689
+ * We do not support playing encrypted content (different than mp2t) with MSE
690
+ * and legacy Apple MediaKeys API.
691
+ */
692
+ 'HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED': 4041,
693
+
694
+ /**
695
+ * Web Crypto API is not available (to decrypt AES-128 streams). Web Crypto
696
+ * only exists in secure origins like https.
697
+ */
698
+ 'NO_WEB_CRYPTO_API': 4042,
699
+
700
+ /**
701
+ * AES-128 iv length should be 16 bytes.
702
+ */
703
+ 'HLS_AES_128_INVALID_IV_LENGTH': 4043,
704
+
705
+ /**
706
+ * AES-128 encryption key length should be 16 bytes.
707
+ */
708
+ 'HLS_AES_128_INVALID_KEY_LENGTH': 4044,
697
709
 
698
710
 
699
711
  // RETIRED: 'INCONSISTENT_BUFFER_STATE': 5000,
@@ -133,13 +133,83 @@ shaka.util.FairPlayUtils = class {
133
133
  }
134
134
 
135
135
  /**
136
- * SPC FairPlay request.
136
+ * Verimatrix initDataTransform configuration.
137
+ *
138
+ * @param {!Uint8Array} initData
139
+ * @param {string} initDataType
140
+ * @param {?shaka.extern.DrmInfo} drmInfo
141
+ * @export
142
+ */
143
+ static verimatrixInitDataTransform(initData, initDataType, drmInfo) {
144
+ if (initDataType !== 'skd') {
145
+ return initData;
146
+ }
147
+ const StringUtils = shaka.util.StringUtils;
148
+ const FairPlayUtils = shaka.util.FairPlayUtils;
149
+ const cert = drmInfo.serverCertificate;
150
+ const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
151
+ const contentId = initDataAsString.split('skd://').pop();
152
+ return FairPlayUtils.initDataTransform(initData, contentId, cert);
153
+ }
154
+
155
+ /**
156
+ * EZDRM initDataTransform configuration.
157
+ *
158
+ * @param {!Uint8Array} initData
159
+ * @param {string} initDataType
160
+ * @param {?shaka.extern.DrmInfo} drmInfo
161
+ * @export
162
+ */
163
+ static ezdrmInitDataTransform(initData, initDataType, drmInfo) {
164
+ if (initDataType !== 'skd') {
165
+ return initData;
166
+ }
167
+ const StringUtils = shaka.util.StringUtils;
168
+ const FairPlayUtils = shaka.util.FairPlayUtils;
169
+ const cert = drmInfo.serverCertificate;
170
+ const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
171
+ const contentId = initDataAsString.split(';').pop();
172
+ return FairPlayUtils.initDataTransform(initData, contentId, cert);
173
+ }
174
+
175
+ /**
176
+ * Conax initDataTransform configuration.
177
+ *
178
+ * @param {!Uint8Array} initData
179
+ * @param {string} initDataType
180
+ * @param {?shaka.extern.DrmInfo} drmInfo
181
+ * @export
182
+ */
183
+ static conaxInitDataTransform(initData, initDataType, drmInfo) {
184
+ if (initDataType !== 'skd') {
185
+ return initData;
186
+ }
187
+ const StringUtils = shaka.util.StringUtils;
188
+ const FairPlayUtils = shaka.util.FairPlayUtils;
189
+ const cert = drmInfo.serverCertificate;
190
+ const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
191
+ const skdValue = initDataAsString.split('skd://').pop().split('?').shift();
192
+ const stringToArray = (string) => {
193
+ // 2 bytes for each char
194
+ const buffer = new ArrayBuffer(string.length * 2);
195
+ const array = new Uint16Array(buffer);
196
+ for (let i = 0, strLen = string.length; i < strLen; i++) {
197
+ array[i] = string.charCodeAt(i);
198
+ }
199
+ return array;
200
+ };
201
+ const contentId = stringToArray(window.atob(skdValue));
202
+ return FairPlayUtils.initDataTransform(initData, contentId, cert);
203
+ }
204
+
205
+ /**
206
+ * Verimatrix FairPlay request.
137
207
  *
138
208
  * @param {shaka.net.NetworkingEngine.RequestType} type
139
209
  * @param {shaka.extern.Request} request
140
210
  * @export
141
211
  */
142
- static spcFairPlayRequest(type, request) {
212
+ static verimatrixFairPlayRequest(type, request) {
143
213
  if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
144
214
  return;
145
215
  }
@@ -150,6 +220,34 @@ shaka.util.FairPlayUtils = class {
150
220
  request.body = shaka.util.StringUtils.toUTF8('spc=' + base64Payload);
151
221
  }
152
222
 
223
+ /**
224
+ * EZDRM FairPlay request.
225
+ *
226
+ * @param {shaka.net.NetworkingEngine.RequestType} type
227
+ * @param {shaka.extern.Request} request
228
+ * @export
229
+ */
230
+ static ezdrmFairPlayRequest(type, request) {
231
+ if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
232
+ return;
233
+ }
234
+ request.headers['Content-Type'] = 'application/octet-stream';
235
+ }
236
+
237
+ /**
238
+ * Conax FairPlay request.
239
+ *
240
+ * @param {shaka.net.NetworkingEngine.RequestType} type
241
+ * @param {shaka.extern.Request} request
242
+ * @export
243
+ */
244
+ static conaxFairPlayRequest(type, request) {
245
+ if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
246
+ return;
247
+ }
248
+ request.headers['Content-Type'] = 'application/octet-stream';
249
+ }
250
+
153
251
  /**
154
252
  * Common FairPlay response transform for some DRMs providers.
155
253
  *
@@ -199,7 +199,8 @@ shaka.util.Platform = class {
199
199
  return !!navigator.vendor && navigator.vendor.includes('Apple') &&
200
200
  !shaka.util.Platform.isTizen() &&
201
201
  !shaka.util.Platform.isEOS() &&
202
- !shaka.util.Platform.isPS4();
202
+ !shaka.util.Platform.isPS4() &&
203
+ !shaka.util.Platform.isAmazonFireTV();
203
204
  }
204
205
 
205
206
  /**
@@ -222,6 +223,16 @@ shaka.util.Platform = class {
222
223
  return shaka.util.Platform.userAgentContains_('PlayStation 4');
223
224
  }
224
225
 
226
+ /**
227
+ * Check if the current platform is Amazon Fire TV.
228
+ * https://developer.amazon.com/docs/fire-tv/identify-amazon-fire-tv-devices.html
229
+ *
230
+ * @return {boolean}
231
+ */
232
+ static isAmazonFireTV() {
233
+ return shaka.util.Platform.userAgentContains_('AFT');
234
+ }
235
+
225
236
  /**
226
237
  * Returns a major version number for Safari, or Safari-based iOS browsers.
227
238
  *
@@ -147,7 +147,7 @@ shaka.util.PlayerConfiguration = class {
147
147
  ignoreTextStreamFailures: false,
148
148
  alwaysStreamText: false,
149
149
  startAtSegmentBoundary: false,
150
- gapDetectionThreshold: 0.1,
150
+ gapDetectionThreshold: 0.5,
151
151
  durationBackoff: 1,
152
152
  forceTransmuxTS: false,
153
153
  // Offset by 5 seconds since Chromecast takes a few seconds to start
@@ -173,15 +173,6 @@ shaka.util.PlayerConfiguration = class {
173
173
  maxDisabledTime: 30,
174
174
  };
175
175
 
176
- // Some browsers will stop earlier than others before a gap (e.g., Edge
177
- // stops 0.5 seconds before a gap). So for some browsers we need to use a
178
- // larger threshold. See: https://bit.ly/2K5xmJO
179
- if (shaka.util.Platform.isLegacyEdge() ||
180
- shaka.util.Platform.isTizen() ||
181
- shaka.util.Platform.isChromecast()) {
182
- streaming.gapDetectionThreshold = 0.5;
183
- }
184
-
185
176
  // WebOS, Tizen, and Chromecast have long hardware pipelines that respond
186
177
  // slowly to seeking. Therefore we should not seek when we detect a stall
187
178
  // on one of these platforms. Instead, default stallSkip to 0 to force the
@@ -253,6 +244,8 @@ shaka.util.PlayerConfiguration = class {
253
244
  fastHalfLife: 2,
254
245
  slowHalfLife: 5,
255
246
  },
247
+ restrictToElementSize: false,
248
+ ignoreDevicePixelRatio: false,
256
249
  };
257
250
 
258
251
  const cmcd = {
@@ -321,6 +314,7 @@ shaka.util.PlayerConfiguration = class {
321
314
  */
322
315
  static mergeConfigObjects(destination, updates, template) {
323
316
  const overrides = {
317
+ '.drm.keySystemsMapping': '',
324
318
  '.drm.servers': '',
325
319
  '.drm.clearKeys': '',
326
320
  '.drm.advanced': {
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.1.3",
4
+ "version": "4.2.1",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -38,6 +38,7 @@ goog.require('shaka.polyfill.Fullscreen');
38
38
  goog.require('shaka.polyfill.MediaSource');
39
39
  goog.require('shaka.polyfill.MediaCapabilities');
40
40
  goog.require('shaka.polyfill.Orientation');
41
+ goog.require('shaka.polyfill.PatchedMediaKeysApple');
41
42
  goog.require('shaka.polyfill.PatchedMediaKeysNop');
42
43
  goog.require('shaka.polyfill.PatchedMediaKeysWebkit');
43
44
  goog.require('shaka.polyfill.PiPWebkit');
@@ -33,6 +33,8 @@ describe('HlsParser', () => {
33
33
  let segmentData;
34
34
  /** @type {!Uint8Array} */
35
35
  let selfInitializingSegmentData;
36
+ /** @type {!Uint8Array} */
37
+ let aes128Key;
36
38
 
37
39
  afterEach(() => {
38
40
  shaka.log.alwaysWarn = originalAlwaysWarn;
@@ -75,6 +77,11 @@ describe('HlsParser', () => {
75
77
  selfInitializingSegmentData =
76
78
  shaka.util.Uint8ArrayUtils.concat(initSegmentData, segmentData);
77
79
 
80
+ aes128Key = new Uint8Array([
81
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
82
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
83
+ ]);
84
+
78
85
  fakeNetEngine = new shaka.test.FakeNetworkingEngine();
79
86
 
80
87
  config = shaka.util.PlayerConfiguration.createDefault().manifest;
@@ -2432,7 +2439,7 @@ describe('HlsParser', () => {
2432
2439
  expect(initSegments[1].getUris()[0]).toBe('test:/init2.mp4');
2433
2440
  });
2434
2441
 
2435
- it('drops variants encrypted with AES-128', async () => {
2442
+ it('parses variants encrypted with AES-128', async () => {
2436
2443
  const master = [
2437
2444
  '#EXTM3U\n',
2438
2445
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
@@ -2441,10 +2448,15 @@ describe('HlsParser', () => {
2441
2448
  '#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
2442
2449
  'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
2443
2450
  'video2\n',
2451
+ '#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
2452
+ 'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud3"\n',
2453
+ 'video3\n',
2444
2454
  '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
2445
2455
  'URI="audio"\n',
2446
2456
  '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
2447
2457
  'URI="audio2"\n',
2458
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud3",LANGUAGE="de",',
2459
+ 'URI="audio3"\n',
2448
2460
  ].join('');
2449
2461
 
2450
2462
  const media = [
@@ -2456,17 +2468,26 @@ describe('HlsParser', () => {
2456
2468
  'main.mp4',
2457
2469
  ].join('');
2458
2470
 
2459
- const mediaWithAesEncryption = [
2471
+ const mediaWithMp4AesEncryption = [
2460
2472
  '#EXTM3U\n',
2461
2473
  '#EXT-X-PLAYLIST-TYPE:VOD\n',
2462
2474
  '#EXT-X-KEY:METHOD=AES-128,',
2463
- 'URI="800k.key\n',
2475
+ 'URI="800k.key"\n',
2464
2476
  '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2465
2477
  '#EXTINF:5,\n',
2466
2478
  '#EXT-X-BYTERANGE:121090@616\n',
2467
2479
  'main.mp4',
2468
2480
  ].join('');
2469
2481
 
2482
+ const mediaWithTSAesEncryption = [
2483
+ '#EXTM3U\n',
2484
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2485
+ '#EXT-X-KEY:METHOD=AES-128,',
2486
+ 'URI="800k.key"\n',
2487
+ '#EXTINF:5,\n',
2488
+ 'main.ts',
2489
+ ].join('');
2490
+
2470
2491
  const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2471
2492
  manifest.anyTimeline();
2472
2493
  manifest.addPartialVariant((variant) => {
@@ -2478,6 +2499,24 @@ describe('HlsParser', () => {
2478
2499
  stream.language = 'en';
2479
2500
  });
2480
2501
  });
2502
+ manifest.addPartialVariant((variant) => {
2503
+ variant.bandwidth = 300;
2504
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2505
+ stream.size(960, 540);
2506
+ });
2507
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
2508
+ stream.language = 'fr';
2509
+ });
2510
+ });
2511
+ manifest.addPartialVariant((variant) => {
2512
+ variant.bandwidth = 300;
2513
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2514
+ stream.size(960, 540);
2515
+ });
2516
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
2517
+ stream.language = 'de';
2518
+ });
2519
+ });
2481
2520
  manifest.sequenceMode = true;
2482
2521
  });
2483
2522
 
@@ -2485,19 +2524,73 @@ describe('HlsParser', () => {
2485
2524
  .setResponseText('test:/master', master)
2486
2525
  .setResponseText('test:/audio', media)
2487
2526
  .setResponseText('test:/audio2', media)
2527
+ .setResponseText('test:/audio3', media)
2488
2528
  .setResponseText('test:/video', media)
2489
- .setResponseText('test:/video2', mediaWithAesEncryption)
2529
+ .setResponseText('test:/video2', mediaWithMp4AesEncryption)
2530
+ .setResponseText('test:/video3', mediaWithTSAesEncryption)
2490
2531
  .setResponseText('test:/main.vtt', vttText)
2491
2532
  .setResponseValue('test:/init.mp4', initSegmentData)
2492
2533
  .setResponseValue('test:/main.mp4', segmentData)
2493
2534
  .setResponseValue('test:/main.test', segmentData)
2535
+ .setResponseValue('test:/800k.key', aes128Key)
2494
2536
  .setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
2495
2537
 
2496
2538
  const actual = await parser.start('test:/master', playerInterface);
2497
2539
  expect(actual).toEqual(manifest);
2498
- return actual;
2499
2540
  });
2500
2541
 
2542
+ it('fails on AES-128 if WebCrypto APIs are not available', async () => {
2543
+ const master = [
2544
+ '#EXTM3U\n',
2545
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
2546
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
2547
+ 'video\n',
2548
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
2549
+ 'URI="audio"\n',
2550
+ ].join('');
2551
+
2552
+ const mediaWithMp4AesEncryption = [
2553
+ '#EXTM3U\n',
2554
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2555
+ '#EXT-X-KEY:METHOD=AES-128,',
2556
+ 'URI="800k.key"\n',
2557
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2558
+ '#EXTINF:5,\n',
2559
+ '#EXT-X-BYTERANGE:121090@616\n',
2560
+ 'main.mp4',
2561
+ ].join('');
2562
+
2563
+ fakeNetEngine
2564
+ .setResponseText('test:/master', master)
2565
+ .setResponseText('test:/audio', mediaWithMp4AesEncryption)
2566
+ .setResponseText('test:/video', mediaWithMp4AesEncryption)
2567
+ .setResponseValue('test:/init.mp4', initSegmentData)
2568
+ .setResponseValue('test:/main.mp4', segmentData)
2569
+ .setResponseValue('test:/800k.key', aes128Key);
2570
+
2571
+ let originalWebCrypto = null;
2572
+ try {
2573
+ originalWebCrypto = window.crypto;
2574
+ Object.defineProperty(window, 'crypto', {
2575
+ configurable: true,
2576
+ value: null,
2577
+ });
2578
+
2579
+ const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
2580
+ shaka.util.Error.Severity.CRITICAL,
2581
+ shaka.util.Error.Category.MANIFEST,
2582
+ shaka.util.Error.Code.NO_WEB_CRYPTO_API));
2583
+ await expectAsync(parser.start('test:/master', playerInterface))
2584
+ .toBeRejectedWith(expectedError);
2585
+ } finally {
2586
+ Object.defineProperty(window, 'crypto', {
2587
+ configurable: true,
2588
+ value: originalWebCrypto,
2589
+ });
2590
+ }
2591
+ });
2592
+
2593
+
2501
2594
  it('constructs DrmInfo for Widevine', async () => {
2502
2595
  const master = [
2503
2596
  '#EXTM3U\n',
@@ -2674,7 +2767,9 @@ describe('HlsParser', () => {
2674
2767
  .setResponseText('test:/video', media)
2675
2768
  .setResponseValue('test:/main.exe', segmentData)
2676
2769
  .setResponseValue('test:/init.mp4', initSegmentData)
2677
- .setResponseValue('test:/main.mp4', segmentData);
2770
+ .setResponseValue('test:/main.mp4', segmentData)
2771
+ .setResponseValue('data:text/plain;base64,AAECAwQFBgcICQoLDA0ODw==',
2772
+ aes128Key);
2678
2773
 
2679
2774
  await expectAsync(parser.start('test:/master', playerInterface))
2680
2775
  .toBeRejectedWith(Util.jasmineError(error));
@@ -2708,34 +2803,6 @@ describe('HlsParser', () => {
2708
2803
  await verifyError(master, media, error);
2709
2804
  });
2710
2805
 
2711
- it('if all variants are encrypted with AES-128', async () => {
2712
- const master = [
2713
- '#EXTM3U\n',
2714
- '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2715
- 'RESOLUTION=960x540,FRAME-RATE=60\n',
2716
- 'video\n',
2717
- ].join('');
2718
-
2719
- const media = [
2720
- '#EXTM3U\n',
2721
- '#EXT-X-TARGETDURATION:6\n',
2722
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
2723
- '#EXT-X-KEY:METHOD=AES-128,',
2724
- 'URI="data:text/plain;base64\n',
2725
- '#EXT-X-MAP:URI="init.mp4"\n',
2726
- '#EXTINF:5,\n',
2727
- '#EXT-X-BYTERANGE:121090@616\n',
2728
- 'main.mp4',
2729
- ].join('');
2730
-
2731
- const error = new shaka.util.Error(
2732
- shaka.util.Error.Severity.CRITICAL,
2733
- shaka.util.Error.Category.MANIFEST,
2734
- Code.HLS_AES_128_ENCRYPTION_NOT_SUPPORTED);
2735
-
2736
- await verifyError(master, media, error);
2737
- });
2738
-
2739
2806
  describe('if required attributes are missing', () => {
2740
2807
  /**
2741
2808
  * @param {string} master
@@ -677,6 +677,35 @@ describe('DrmEngine', () => {
677
677
  .toBeRejectedWith(expected);
678
678
  });
679
679
 
680
+ it('uses key system IDs from keySystemsMapping config', async () => {
681
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
682
+ manifest.addVariant(0, (variant) => {
683
+ variant.addVideo(1, (stream) => {
684
+ stream.encrypted = true;
685
+ stream.addDrmInfo('drm.abc');
686
+ });
687
+ });
688
+ });
689
+
690
+ setDecodingInfoSpy([]);
691
+
692
+ config.keySystemsMapping['drm.abc'] = 'drm.def';
693
+ drmEngine.configure(config);
694
+
695
+ const variants = manifest.variants;
696
+ await expectAsync(
697
+ drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
698
+ .toBeRejected();
699
+
700
+ expect(drmEngine.initialized()).toBe(false);
701
+ expect(decodingInfoSpy).toHaveBeenCalledTimes(1);
702
+ expect(decodingInfoSpy).toHaveBeenCalledWith(containing({
703
+ keySystemConfiguration: containing({
704
+ keySystem: 'drm.def',
705
+ }),
706
+ }));
707
+ });
708
+
680
709
  it('maps TS MIME types through the transmuxer', async () => {
681
710
  const originalIsSupported = shaka.media.Transmuxer.isSupported;
682
711