shaka-player 4.7.12 → 4.8.0

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 (120) hide show
  1. package/AUTHORS +1 -0
  2. package/CONTRIBUTORS +1 -0
  3. package/LICENSE +54 -0
  4. package/README.md +22 -1
  5. package/dist/controls.css +1 -1
  6. package/dist/controls.css.map +1 -1
  7. package/dist/deps.js +36 -25
  8. package/dist/locales.js +40 -30
  9. package/dist/shaka-player.compiled.d.ts +196 -46
  10. package/dist/shaka-player.compiled.debug.d.ts +196 -46
  11. package/dist/shaka-player.compiled.debug.externs.js +474 -337
  12. package/dist/shaka-player.compiled.debug.js +992 -871
  13. package/dist/shaka-player.compiled.debug.map +4 -4
  14. package/dist/shaka-player.compiled.externs.js +474 -337
  15. package/dist/shaka-player.compiled.js +1248 -1174
  16. package/dist/shaka-player.compiled.map +4 -4
  17. package/dist/shaka-player.ui.d.ts +259 -49
  18. package/dist/shaka-player.ui.debug.d.ts +259 -49
  19. package/dist/shaka-player.ui.debug.externs.js +591 -376
  20. package/dist/shaka-player.ui.debug.js +1208 -1003
  21. package/dist/shaka-player.ui.debug.map +4 -4
  22. package/dist/shaka-player.ui.externs.js +591 -376
  23. package/dist/shaka-player.ui.js +1533 -1408
  24. package/dist/shaka-player.ui.map +4 -4
  25. package/externs/device_sensor_event.js +18 -0
  26. package/externs/hisense.js +16 -0
  27. package/externs/playstation.js +31 -0
  28. package/externs/shaka/abr_manager.js +3 -1
  29. package/externs/shaka/ads.js +5 -0
  30. package/externs/shaka/codecs.js +13 -0
  31. package/externs/shaka/manifest.js +69 -7
  32. package/externs/shaka/net.js +13 -1
  33. package/externs/shaka/offline.js +7 -1
  34. package/externs/shaka/player.js +184 -13
  35. package/externs/shaka/text.js +19 -0
  36. package/externs/tizen.js +41 -0
  37. package/externs/webos.js +19 -0
  38. package/externs/xbox.js +43 -0
  39. package/lib/abr/simple_abr_manager.js +9 -1
  40. package/lib/ads/ad_manager.js +19 -2
  41. package/lib/ads/client_side_ad_manager.js +5 -3
  42. package/lib/ads/media_tailor_ad_manager.js +9 -0
  43. package/lib/cast/cast_receiver.js +0 -14
  44. package/lib/cast/cast_utils.js +1 -0
  45. package/lib/cea/mp4_cea_parser.js +6 -1
  46. package/lib/dash/content_protection.js +97 -53
  47. package/lib/dash/dash_parser.js +240 -179
  48. package/lib/dash/mpd_utils.js +72 -65
  49. package/lib/dash/segment_base.js +35 -22
  50. package/lib/dash/segment_list.js +39 -31
  51. package/lib/dash/segment_template.js +47 -23
  52. package/lib/hls/hls_parser.js +376 -165
  53. package/lib/media/buffering_observer.js +25 -1
  54. package/lib/media/drm_engine.js +91 -71
  55. package/lib/media/manifest_filterer.js +220 -0
  56. package/lib/media/media_source_engine.js +108 -29
  57. package/lib/media/playhead.js +3 -3
  58. package/lib/media/preload_manager.js +827 -0
  59. package/lib/media/presentation_timeline.js +11 -0
  60. package/lib/media/segment_index.js +60 -49
  61. package/lib/media/segment_prefetch.js +140 -43
  62. package/lib/media/segment_reference.js +31 -8
  63. package/lib/media/segment_utils.js +208 -25
  64. package/lib/media/streaming_engine.js +321 -77
  65. package/lib/mss/content_protection.js +31 -25
  66. package/lib/mss/mss_parser.js +78 -55
  67. package/lib/net/networking_engine.js +49 -4
  68. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  69. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  70. package/lib/offline/manifest_converter.js +10 -3
  71. package/lib/offline/storage.js +5 -3
  72. package/lib/player.js +896 -674
  73. package/lib/polyfill/media_capabilities.js +16 -1
  74. package/lib/text/simple_text_displayer.js +9 -0
  75. package/lib/text/stub_text_displayer.js +7 -0
  76. package/lib/text/text_engine.js +16 -3
  77. package/lib/text/ttml_text_parser.js +116 -114
  78. package/lib/text/ui_text_displayer.js +28 -11
  79. package/lib/text/vtt_text_parser.js +54 -115
  80. package/lib/transmuxer/opus.js +91 -0
  81. package/lib/transmuxer/ts_transmuxer.js +115 -0
  82. package/lib/util/cmcd_manager.js +152 -3
  83. package/lib/util/dom_utils.js +11 -0
  84. package/lib/util/error.js +5 -0
  85. package/lib/util/fake_event.js +2 -0
  86. package/lib/util/manifest_parser_utils.js +97 -3
  87. package/lib/util/mime_utils.js +10 -5
  88. package/lib/util/mp4_box_parsers.js +346 -17
  89. package/lib/util/mp4_generator.js +23 -0
  90. package/lib/util/periods.js +24 -37
  91. package/lib/util/platform.js +161 -9
  92. package/lib/util/player_configuration.js +61 -3
  93. package/lib/util/stats.js +16 -0
  94. package/lib/util/stream_utils.js +350 -277
  95. package/lib/util/string_utils.js +36 -0
  96. package/lib/util/tXml.js +814 -0
  97. package/lib/util/ts_parser.js +50 -0
  98. package/lib/util/xml_utils.js +10 -338
  99. package/package.json +2 -2
  100. package/shaka-player.uncompiled.js +8 -0
  101. package/ui/controls.js +187 -5
  102. package/ui/enums.js +2 -0
  103. package/ui/externs/ui.js +30 -3
  104. package/ui/gl_matrix/matrix_4x4.js +568 -0
  105. package/ui/gl_matrix/matrix_quaternion.js +45 -0
  106. package/ui/less/containers.less +11 -0
  107. package/ui/locales/en-GB.json +2 -0
  108. package/ui/locales/en.json +2 -0
  109. package/ui/locales/es-419.json +2 -0
  110. package/ui/locales/es.json +2 -0
  111. package/ui/locales/source.json +8 -0
  112. package/ui/recenter_vr.js +126 -0
  113. package/ui/resolution_selection.js +25 -5
  114. package/ui/statistics_button.js +12 -0
  115. package/ui/text_selection.js +1 -1
  116. package/ui/toggle_stereoscopic.js +127 -0
  117. package/ui/ui.js +74 -38
  118. package/ui/vr_manager.js +557 -0
  119. package/ui/vr_utils.js +232 -0
  120. package/ui/vr_webgl.js +697 -0
@@ -263,13 +263,20 @@ shaka.util.StreamUtils = class {
263
263
  // |video.width| and |video.height| can be undefined, which breaks
264
264
  // the math, so make sure they are there first.
265
265
  if (video && video.width && video.height) {
266
- if (!inRange(video.width,
266
+ let videoWidth = video.width;
267
+ let videoHeight = video.height;
268
+ if (videoHeight > videoWidth) {
269
+ // Vertical video.
270
+ [videoWidth, videoHeight] = [videoHeight, videoWidth];
271
+ }
272
+
273
+ if (!inRange(videoWidth,
267
274
  restrictions.minWidth,
268
275
  Math.min(restrictions.maxWidth, maxHwRes.width))) {
269
276
  return false;
270
277
  }
271
278
 
272
- if (!inRange(video.height,
279
+ if (!inRange(videoHeight,
273
280
  restrictions.minHeight,
274
281
  Math.min(restrictions.maxHeight, maxHwRes.height))) {
275
282
  return false;
@@ -330,10 +337,12 @@ shaka.util.StreamUtils = class {
330
337
  *
331
338
  * @param {shaka.media.DrmEngine} drmEngine
332
339
  * @param {shaka.extern.Manifest} manifest
340
+ * @param {!Array<string>=} preferredKeySystems
333
341
  */
334
- static async filterManifest(drmEngine, manifest) {
342
+ static async filterManifest(drmEngine, manifest, preferredKeySystems = []) {
335
343
  await shaka.util.StreamUtils.filterManifestByMediaCapabilities(
336
- drmEngine, manifest, manifest.offlineSessionIds.length > 0);
344
+ drmEngine, manifest, manifest.offlineSessionIds.length > 0,
345
+ preferredKeySystems);
337
346
  shaka.util.StreamUtils.filterTextStreams_(manifest);
338
347
  await shaka.util.StreamUtils.filterImageStreams_(manifest);
339
348
  }
@@ -346,15 +355,16 @@ shaka.util.StreamUtils = class {
346
355
  * @param {shaka.media.DrmEngine} drmEngine
347
356
  * @param {shaka.extern.Manifest} manifest
348
357
  * @param {boolean} usePersistentLicenses
358
+ * @param {!Array<string>} preferredKeySystems
349
359
  */
350
360
  static async filterManifestByMediaCapabilities(
351
- drmEngine, manifest, usePersistentLicenses) {
361
+ drmEngine, manifest, usePersistentLicenses, preferredKeySystems) {
352
362
  goog.asserts.assert(navigator.mediaCapabilities,
353
363
  'MediaCapabilities should be valid.');
354
364
 
355
365
  await shaka.util.StreamUtils.getDecodingInfosForVariants(
356
366
  manifest.variants, usePersistentLicenses, /* srcEquals= */ false,
357
- /** preferredKeySystems= */ []);
367
+ preferredKeySystems);
358
368
 
359
369
  let keySystem = null;
360
370
  if (drmEngine) {
@@ -364,115 +374,122 @@ shaka.util.StreamUtils = class {
364
374
  }
365
375
  }
366
376
 
377
+ const StreamUtils = shaka.util.StreamUtils;
378
+
379
+ manifest.variants = manifest.variants.filter((variant) => {
380
+ const supported = StreamUtils.checkVariantSupported_(variant, keySystem);
381
+ // Filter out all unsupported variants.
382
+ if (!supported) {
383
+ shaka.log.debug('Dropping variant - not compatible with platform',
384
+ StreamUtils.getVariantSummaryString_(variant));
385
+ }
386
+ return supported;
387
+ });
388
+ }
389
+
390
+
391
+ /**
392
+ * @param {!shaka.extern.Variant} variant
393
+ * @param {?string} keySystem
394
+ * @return {boolean}
395
+ * @private
396
+ */
397
+ static checkVariantSupported_(variant, keySystem) {
367
398
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
368
399
  const Capabilities = shaka.media.Capabilities;
369
400
  const ManifestParserUtils = shaka.util.ManifestParserUtils;
370
401
  const MimeUtils = shaka.util.MimeUtils;
371
402
  const StreamUtils = shaka.util.StreamUtils;
372
- const isXboxOne = shaka.util.Platform.isXboxOne();
373
403
 
404
+ const isXboxOne = shaka.util.Platform.isXboxOne();
374
405
  const isFirefoxAndroid = shaka.util.Platform.isFirefox() &&
375
406
  shaka.util.Platform.isAndroid();
376
407
 
377
- manifest.variants = manifest.variants.filter((variant) => {
378
- // See: https://github.com/shaka-project/shaka-player/issues/3860
379
- const video = variant.video;
380
- const videoWidth = (video && video.width) || 0;
381
- const videoHeight = (video && video.height) || 0;
382
-
383
- // See: https://github.com/shaka-project/shaka-player/issues/3380
384
- // Note: it makes sense to drop early
385
- if (isXboxOne && video &&
386
- (videoWidth > 1920 || videoHeight > 1080) &&
387
- (video.codecs.includes('avc1.') || video.codecs.includes('avc3.'))
388
- ) {
389
- shaka.log.debug('Dropping variant - not compatible with platform',
390
- StreamUtils.getVariantSummaryString_(variant));
391
- return false;
392
- }
393
-
394
- if (video) {
395
- let videoCodecs = StreamUtils.getCorrectVideoCodecs_(video.codecs);
396
- // For multiplexed streams. Here we must check the audio of the
397
- // stream to see if it is compatible.
398
- if (video.codecs.includes(',')) {
399
- const allCodecs = video.codecs.split(',');
400
-
401
- videoCodecs = ManifestParserUtils.guessCodecs(
402
- ContentType.VIDEO, allCodecs);
403
- videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
404
- let audioCodecs = ManifestParserUtils.guessCodecs(
405
- ContentType.AUDIO, allCodecs);
406
- audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
408
+ // See: https://github.com/shaka-project/shaka-player/issues/3860
409
+ const video = variant.video;
410
+ const videoWidth = (video && video.width) || 0;
411
+ const videoHeight = (video && video.height) || 0;
407
412
 
408
- const audioFullType = MimeUtils.getFullOrConvertedType(
409
- video.mimeType, audioCodecs, ContentType.AUDIO);
413
+ // See: https://github.com/shaka-project/shaka-player/issues/3380
414
+ // Note: it makes sense to drop early
415
+ if (isXboxOne && video && (videoWidth > 1920 || videoHeight > 1080) &&
416
+ (video.codecs.includes('avc1.') || video.codecs.includes('avc3.'))) {
417
+ return false;
418
+ }
410
419
 
411
- if (!Capabilities.isTypeSupported(audioFullType)) {
412
- return false;
413
- }
420
+ if (video) {
421
+ let videoCodecs = StreamUtils.getCorrectVideoCodecs(video.codecs);
422
+ // For multiplexed streams. Here we must check the audio of the
423
+ // stream to see if it is compatible.
424
+ if (video.codecs.includes(',')) {
425
+ const allCodecs = video.codecs.split(',');
414
426
 
415
- // Update the codec string with the (possibly) converted codecs.
416
- videoCodecs = [videoCodecs, audioCodecs].join(',');
417
- }
427
+ videoCodecs = ManifestParserUtils.guessCodecs(
428
+ ContentType.VIDEO, allCodecs);
429
+ videoCodecs = StreamUtils.getCorrectVideoCodecs(videoCodecs);
430
+ let audioCodecs = ManifestParserUtils.guessCodecs(
431
+ ContentType.AUDIO, allCodecs);
432
+ audioCodecs = StreamUtils.getCorrectAudioCodecs(audioCodecs);
418
433
 
419
- const fullType = MimeUtils.getFullOrConvertedType(
420
- video.mimeType, videoCodecs, ContentType.VIDEO);
434
+ const audioFullType = MimeUtils.getFullOrConvertedType(
435
+ video.mimeType, audioCodecs, ContentType.AUDIO);
421
436
 
422
- if (!Capabilities.isTypeSupported(fullType)) {
437
+ if (!Capabilities.isTypeSupported(audioFullType)) {
423
438
  return false;
424
439
  }
425
440
 
426
441
  // Update the codec string with the (possibly) converted codecs.
427
- video.codecs = videoCodecs;
442
+ videoCodecs = [videoCodecs, audioCodecs].join(',');
428
443
  }
429
444
 
430
- const audio = variant.audio;
445
+ const fullType = MimeUtils.getFullOrConvertedType(
446
+ video.mimeType, videoCodecs, ContentType.VIDEO);
431
447
 
432
- // See: https://github.com/shaka-project/shaka-player/issues/6111
433
- // It seems that Firefox Android reports that it supports
434
- // Opus + Widevine, but it is not actually supported.
435
- // It makes sense to drop early.
436
- if (isFirefoxAndroid && audio && audio.encrypted &&
437
- audio.codecs.toLowerCase().includes('opus')) {
438
- shaka.log.debug('Dropping variant - not compatible with platform',
439
- StreamUtils.getVariantSummaryString_(variant));
448
+ if (!Capabilities.isTypeSupported(fullType)) {
440
449
  return false;
441
450
  }
442
451
 
443
- if (audio) {
444
- const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
445
- const fullType = MimeUtils.getFullOrConvertedType(
446
- audio.mimeType, codecs, ContentType.AUDIO);
452
+ // Update the codec string with the (possibly) converted codecs.
453
+ video.codecs = videoCodecs;
454
+ }
447
455
 
448
- if (!Capabilities.isTypeSupported(fullType)) {
449
- return false;
450
- }
456
+ const audio = variant.audio;
451
457
 
452
- // Update the codec string with the (possibly) converted codecs.
453
- audio.codecs = codecs;
458
+ // See: https://github.com/shaka-project/shaka-player/issues/6111
459
+ // It seems that Firefox Android reports that it supports
460
+ // Opus + Widevine, but it is not actually supported.
461
+ // It makes sense to drop early.
462
+ if (isFirefoxAndroid && audio && audio.encrypted &&
463
+ audio.codecs.toLowerCase().includes('opus')) {
464
+ return false;
465
+ }
466
+
467
+ if (audio) {
468
+ const codecs = StreamUtils.getCorrectAudioCodecs(audio.codecs);
469
+ const fullType = MimeUtils.getFullOrConvertedType(
470
+ audio.mimeType, codecs, ContentType.AUDIO);
471
+
472
+ if (!Capabilities.isTypeSupported(fullType)) {
473
+ return false;
454
474
  }
455
475
 
456
- const supported = variant.decodingInfos.some((decodingInfo) => {
457
- if (!decodingInfo.supported) {
458
- return false;
459
- }
460
- if (keySystem) {
461
- const keySystemAccess = decodingInfo.keySystemAccess;
462
- if (keySystemAccess) {
463
- if (keySystemAccess.keySystem != keySystem) {
464
- return false;
465
- }
476
+ // Update the codec string with the (possibly) converted codecs.
477
+ audio.codecs = codecs;
478
+ }
479
+
480
+ return variant.decodingInfos.some((decodingInfo) => {
481
+ if (!decodingInfo.supported) {
482
+ return false;
483
+ }
484
+ if (keySystem) {
485
+ const keySystemAccess = decodingInfo.keySystemAccess;
486
+ if (keySystemAccess) {
487
+ if (keySystemAccess.keySystem != keySystem) {
488
+ return false;
466
489
  }
467
490
  }
468
- return true;
469
- });
470
- // Filter out all unsupported variants.
471
- if (!supported) {
472
- shaka.log.debug('Dropping variant - not compatible with platform',
473
- StreamUtils.getVariantSummaryString_(variant));
474
491
  }
475
- return supported;
492
+ return true;
476
493
  });
477
494
  }
478
495
 
@@ -516,51 +533,69 @@ shaka.util.StreamUtils = class {
516
533
  * and assigns it to the given variant.
517
534
  * If that query has been done before, instead return a cached result.
518
535
  * @param {!shaka.extern.Variant} variant
519
- * @param {!MediaDecodingConfiguration} decodingConfig
536
+ * @param {!Array.<!MediaDecodingConfiguration>} decodingConfigs
520
537
  * @private
521
538
  */
522
- static async getDecodingInfosForVariant_(variant, decodingConfig) {
523
- const cacheKey =
524
- shaka.util.StreamUtils.alphabeticalKeyOrderStringify_(decodingConfig);
525
-
526
- const cache = shaka.util.StreamUtils.decodingConfigCache_;
527
- if (cache[cacheKey]) {
528
- shaka.log.v2('Using cached results of mediaCapabilities.decodingInfo',
529
- 'for key', cacheKey);
530
- variant.decodingInfos.push(cache[cacheKey]);
531
- } else {
532
- // Do a final pass-over of the decoding config: if a given stream has
533
- // multiple codecs, that suggests that it switches between those codecs
534
- // at points of the go-through.
535
- // mediaCapabilities by itself will report "not supported" when you
536
- // put in multiple different codecs, so each has to be checked
537
- // individually. So check each and take the worst result, to determine
538
- // overall variant compatibility.
539
- const results = await shaka.util.StreamUtils
540
- .checkEachDecodingConfigCombination_(decodingConfig);
541
- if (results && results.length) {
542
- /** @type {?MediaCapabilitiesDecodingInfo} */
543
- let overallResult = null;
544
- for (const result of results) {
545
- if (!overallResult) {
546
- overallResult = result;
547
- } else {
548
- overallResult.supported =
549
- overallResult.supported && result.supported;
550
- overallResult.powerEfficient =
551
- overallResult.powerEfficient && result.powerEfficient;
552
- overallResult.smooth = overallResult.smooth && result.smooth;
553
- if (!result.keySystemAccess) {
554
- overallResult.keySystemAccess = null;
555
- }
556
- }
557
- }
558
- if (overallResult) {
559
- cache[cacheKey] = overallResult;
560
- variant.decodingInfos.push(overallResult);
539
+ static async getDecodingInfosForVariant_(variant, decodingConfigs) {
540
+ /**
541
+ * @param {?MediaCapabilitiesDecodingInfo} a
542
+ * @param {!MediaCapabilitiesDecodingInfo} b
543
+ * @return {!MediaCapabilitiesDecodingInfo}
544
+ */
545
+ const merge = (a, b) => {
546
+ if (!a) {
547
+ return b;
548
+ } else {
549
+ const res = shaka.util.ObjectUtils.cloneObject(a);
550
+ res.supported = a.supported && b.supported;
551
+ res.powerEfficient = a.powerEfficient && b.powerEfficient;
552
+ res.smooth = a.smooth && b.smooth;
553
+ if (b.keySystemAccess && !res.keySystemAccess) {
554
+ res.keySystemAccess = b.keySystemAccess;
561
555
  }
556
+ return res;
557
+ }
558
+ };
559
+
560
+ const StreamUtils = shaka.util.StreamUtils;
561
+ /** @type {?MediaCapabilitiesDecodingInfo} */
562
+ let finalResult = null;
563
+ const promises = [];
564
+ for (const decodingConfig of decodingConfigs) {
565
+ const cacheKey =
566
+ StreamUtils.alphabeticalKeyOrderStringify_(decodingConfig);
567
+
568
+ const cache = StreamUtils.decodingConfigCache_;
569
+ if (cache[cacheKey]) {
570
+ shaka.log.v2('Using cached results of mediaCapabilities.decodingInfo',
571
+ 'for key', cacheKey);
572
+ finalResult = merge(finalResult, cache[cacheKey]);
573
+ } else {
574
+ // Do a final pass-over of the decoding config: if a given stream has
575
+ // multiple codecs, that suggests that it switches between those codecs
576
+ // at points of the go-through.
577
+ // mediaCapabilities by itself will report "not supported" when you
578
+ // put in multiple different codecs, so each has to be checked
579
+ // individually. So check each and take the worst result, to determine
580
+ // overall variant compatibility.
581
+ promises.push(StreamUtils
582
+ .checkEachDecodingConfigCombination_(decodingConfig).then((res) => {
583
+ /** @type {?MediaCapabilitiesDecodingInfo} */
584
+ let acc = null;
585
+ for (const result of (res || [])) {
586
+ acc = merge(acc, result);
587
+ }
588
+ if (acc) {
589
+ cache[cacheKey] = acc;
590
+ finalResult = merge(finalResult, acc);
591
+ }
592
+ }));
562
593
  }
563
594
  }
595
+ await Promise.all(promises);
596
+ if (finalResult) {
597
+ variant.decodingInfos.push(finalResult);
598
+ }
564
599
  }
565
600
 
566
601
  /**
@@ -634,10 +669,12 @@ shaka.util.StreamUtils = class {
634
669
  for (const preferredKeySystem of preferredKeySystems) {
635
670
  let keySystemSatisfied = false;
636
671
  for (const variant of variants) {
637
- /** @type {!Array.<!MediaDecodingConfiguration>} */
672
+ /** @type {!Array.<!Array.<!MediaDecodingConfiguration>>} */
638
673
  const decodingConfigs = shaka.util.StreamUtils.getDecodingConfigs_(
639
674
  variant, usePersistentLicenses, srcEquals)
640
- .filter((config) => {
675
+ .filter((configs) => {
676
+ // All configs in a batch will have the same keySystem.
677
+ const config = configs[0];
641
678
  const keySystem = config.keySystemConfiguration &&
642
679
  config.keySystemConfiguration.keySystem;
643
680
  return keySystem === preferredKeySystem;
@@ -646,10 +683,10 @@ shaka.util.StreamUtils = class {
646
683
  // The reason we are performing this await in a loop rather than
647
684
  // batching into a `promise.all` is performance related.
648
685
  // https://github.com/shaka-project/shaka-player/pull/4708#discussion_r1022581178
649
- for (const config of decodingConfigs) {
686
+ for (const configs of decodingConfigs) {
650
687
  // eslint-disable-next-line no-await-in-loop
651
688
  await shaka.util.StreamUtils.getDecodingInfosForVariant_(
652
- variant, config);
689
+ variant, configs);
653
690
  }
654
691
  if (variant.decodingInfos.length) {
655
692
  keySystemSatisfied = true;
@@ -662,10 +699,12 @@ shaka.util.StreamUtils = class {
662
699
  } // for (const preferredKeySystem of preferredKeySystems)
663
700
 
664
701
  for (const variant of variants) {
665
- /** @type {!Array.<!MediaDecodingConfiguration>} */
702
+ /** @type {!Array.<!Array.<!MediaDecodingConfiguration>>} */
666
703
  const decodingConfigs = shaka.util.StreamUtils.getDecodingConfigs_(
667
704
  variant, usePersistentLicenses, srcEquals)
668
- .filter((config) => {
705
+ .filter((configs) => {
706
+ // All configs in a batch will have the same keySystem.
707
+ const config = configs[0];
669
708
  const keySystem = config.keySystemConfiguration &&
670
709
  config.keySystemConfiguration.keySystem;
671
710
  // Avoid checking preferred systems twice.
@@ -675,22 +714,24 @@ shaka.util.StreamUtils = class {
675
714
  // The reason we are performing this await in a loop rather than
676
715
  // batching into a `promise.all` is performance related.
677
716
  // https://github.com/shaka-project/shaka-player/pull/4708#discussion_r1022581178
678
- for (const config of decodingConfigs) {
717
+ for (const configs of decodingConfigs) {
679
718
  // eslint-disable-next-line no-await-in-loop
680
719
  await shaka.util.StreamUtils.getDecodingInfosForVariant_(
681
- variant, config);
720
+ variant, configs);
682
721
  }
683
722
  }
684
723
  }
685
724
 
686
725
 
687
726
  /**
688
- * Generate a MediaDecodingConfiguration object to get the decodingInfo
689
- * results for each variant.
727
+ * Generate a batch of MediaDecodingConfiguration objects to get the
728
+ * decodingInfo results for each variant.
729
+ * Each batch shares the same DRM information, and represents the various
730
+ * fullMimeType combinations of the streams.
690
731
  * @param {!shaka.extern.Variant} variant
691
732
  * @param {boolean} usePersistentLicenses
692
733
  * @param {boolean} srcEquals
693
- * @return {!Array.<!MediaDecodingConfiguration>}
734
+ * @return {!Array.<!Array.<!MediaDecodingConfiguration>>}
694
735
  * @private
695
736
  */
696
737
  static getDecodingConfigs_(variant, usePersistentLicenses, srcEquals) {
@@ -702,87 +743,118 @@ shaka.util.StreamUtils = class {
702
743
  const MimeUtils = shaka.util.MimeUtils;
703
744
  const StreamUtils = shaka.util.StreamUtils;
704
745
 
705
- /** @type {!MediaDecodingConfiguration} */
706
- const mediaDecodingConfig = {
707
- type: srcEquals ? 'file' : 'media-source',
708
- };
709
-
746
+ const videoConfigs = [];
747
+ const audioConfigs = [];
710
748
  if (video) {
711
- let videoCodecs = video.codecs;
749
+ for (const fullMimeType of video.fullMimeTypes) {
750
+ let videoCodecs = MimeUtils.getCodecs(fullMimeType);
712
751
 
713
- // For multiplexed streams with audio+video codecs, the config should have
714
- // AudioConfiguration and VideoConfiguration.
715
- if (video.codecs.includes(',')) {
716
- const allCodecs = video.codecs.split(',');
752
+ // For multiplexed streams with audio+video codecs, the config should
753
+ // have AudioConfiguration and VideoConfiguration.
754
+ // We ignore the multiplexed audio when there is normal audio also.
755
+ if (videoCodecs.includes(',') && !audio) {
756
+ const allCodecs = videoCodecs.split(',');
717
757
 
718
- videoCodecs = ManifestParserUtils.guessCodecs(
719
- ContentType.VIDEO, allCodecs);
720
- videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
758
+ videoCodecs = ManifestParserUtils.guessCodecs(
759
+ ContentType.VIDEO, allCodecs);
721
760
 
722
- let audioCodecs = ManifestParserUtils.guessCodecs(
723
- ContentType.AUDIO, allCodecs);
724
- audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
761
+ let audioCodecs = ManifestParserUtils.guessCodecs(
762
+ ContentType.AUDIO, allCodecs);
763
+ audioCodecs = StreamUtils.getCorrectAudioCodecs(audioCodecs);
725
764
 
726
- const audioFullType = MimeUtils.getFullOrConvertedType(
727
- video.mimeType, audioCodecs, ContentType.AUDIO);
765
+ const audioFullType = MimeUtils.getFullOrConvertedType(
766
+ MimeUtils.getBasicType(fullMimeType), audioCodecs,
767
+ ContentType.AUDIO);
768
+
769
+ audioConfigs.push({
770
+ contentType: audioFullType,
771
+ channels: 2,
772
+ bitrate: variant.bandwidth || 1,
773
+ samplerate: 1,
774
+ spatialRendering: false,
775
+ });
776
+ }
728
777
 
729
- mediaDecodingConfig.audio = {
730
- contentType: audioFullType,
731
- channels: 2,
732
- bitrate: variant.bandwidth || 1,
733
- samplerate: 1,
734
- spatialRendering: false,
778
+ videoCodecs = StreamUtils.getCorrectVideoCodecs(videoCodecs);
779
+ const fullType = MimeUtils.getFullOrConvertedType(
780
+ MimeUtils.getBasicType(fullMimeType), videoCodecs,
781
+ ContentType.VIDEO);
782
+
783
+ // VideoConfiguration
784
+ const videoConfig = {
785
+ contentType: fullType,
786
+
787
+ // NOTE: Some decoders strictly check the width and height fields and
788
+ // won't decode smaller than 64x64. So if we don't have this info (as
789
+ // is the case in some of our simpler tests), assume a 64x64
790
+ // resolution to fill in this required field for MediaCapabilities.
791
+ //
792
+ // This became an issue specifically on Firefox on M1 Macs.
793
+ width: video.width || 64,
794
+ height: video.height || 64,
795
+
796
+ bitrate: video.bandwidth || variant.bandwidth || 1,
797
+ // framerate must be greater than 0, otherwise the config is invalid.
798
+ framerate: video.frameRate || 1,
735
799
  };
736
- }
737
-
738
- videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
739
- const fullType = MimeUtils.getFullOrConvertedType(
740
- video.mimeType, videoCodecs, ContentType.VIDEO);
741
-
742
- // VideoConfiguration
743
- mediaDecodingConfig.video = {
744
- contentType: fullType,
745
-
746
- // NOTE: Some decoders strictly check the width and height fields and
747
- // won't decode smaller than 64x64. So if we don't have this info (as
748
- // is the case in some of our simpler tests), assume a 64x64 resolution
749
- // to fill in this required field for MediaCapabilities.
750
- //
751
- // This became an issue specifically on Firefox on M1 Macs.
752
- width: video.width || 64,
753
- height: video.height || 64,
754
-
755
- bitrate: video.bandwidth || variant.bandwidth || 1,
756
- // framerate must be greater than 0, otherwise the config is invalid.
757
- framerate: video.frameRate || 1,
758
- };
759
- if (video.hdr) {
760
- switch (video.hdr) {
761
- case 'SDR':
762
- mediaDecodingConfig.video.transferFunction = 'srgb';
763
- break;
764
- case 'PQ':
765
- mediaDecodingConfig.video.transferFunction = 'pq';
766
- break;
767
- case 'HLG':
768
- mediaDecodingConfig.video.transferFunction = 'hlg';
769
- break;
800
+ if (video.hdr) {
801
+ switch (video.hdr) {
802
+ case 'SDR':
803
+ videoConfig.transferFunction = 'srgb';
804
+ break;
805
+ case 'PQ':
806
+ videoConfig.transferFunction = 'pq';
807
+ break;
808
+ case 'HLG':
809
+ videoConfig.transferFunction = 'hlg';
810
+ break;
811
+ }
770
812
  }
813
+ videoConfigs.push(videoConfig);
771
814
  }
772
815
  }
773
816
  if (audio) {
774
- const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
775
- const fullType = MimeUtils.getFullOrConvertedType(
776
- audio.mimeType, codecs, ContentType.AUDIO);
817
+ for (const fullMimeType of audio.fullMimeTypes) {
818
+ const codecs = StreamUtils.getCorrectAudioCodecs(MimeUtils.getCodecs(
819
+ fullMimeType));
820
+ const fullType = MimeUtils.getFullOrConvertedType(
821
+ MimeUtils.getBasicType(fullMimeType), codecs, ContentType.AUDIO);
822
+
823
+ // AudioConfiguration
824
+ audioConfigs.push({
825
+ contentType: fullType,
826
+ channels: audio.channelsCount || 2,
827
+ bitrate: audio.bandwidth || variant.bandwidth || 1,
828
+ samplerate: audio.audioSamplingRate || 1,
829
+ spatialRendering: audio.spatialAudio,
830
+ });
831
+ }
832
+ }
777
833
 
778
- // AudioConfiguration
779
- mediaDecodingConfig.audio = {
780
- contentType: fullType,
781
- channels: audio.channelsCount || 2,
782
- bitrate: audio.bandwidth || variant.bandwidth || 1,
783
- samplerate: audio.audioSamplingRate || 1,
784
- spatialRendering: audio.spatialAudio,
785
- };
834
+ // Generate each combination of video and audio config as a separate
835
+ // MediaDecodingConfiguration, inside the main "batch".
836
+ /** @type {!Array.<!MediaDecodingConfiguration>} */
837
+ const mediaDecodingConfigBatch = [];
838
+ if (videoConfigs.length == 0) {
839
+ videoConfigs.push(null);
840
+ }
841
+ if (audioConfigs.length == 0) {
842
+ audioConfigs.push(null);
843
+ }
844
+ for (const videoConfig of videoConfigs) {
845
+ for (const audioConfig of audioConfigs) {
846
+ /** @type {!MediaDecodingConfiguration} */
847
+ const mediaDecodingConfig = {
848
+ type: srcEquals ? 'file' : 'media-source',
849
+ };
850
+ if (videoConfig) {
851
+ mediaDecodingConfig.video = videoConfig;
852
+ }
853
+ if (audioConfig) {
854
+ mediaDecodingConfig.audio = audioConfig;
855
+ }
856
+ mediaDecodingConfigBatch.push(mediaDecodingConfig);
857
+ }
786
858
  }
787
859
 
788
860
  const videoDrmInfos = variant.video ? variant.video.drmInfos : [];
@@ -791,7 +863,7 @@ shaka.util.StreamUtils = class {
791
863
 
792
864
  // Return a list containing the mediaDecodingConfig for unencrypted variant.
793
865
  if (!allDrmInfos.length) {
794
- return [mediaDecodingConfig];
866
+ return [mediaDecodingConfigBatch];
795
867
  }
796
868
 
797
869
  // A list of MediaDecodingConfiguration objects created for the variant.
@@ -813,83 +885,87 @@ shaka.util.StreamUtils = class {
813
885
  usePersistentLicenses ? ['persistent-license'] : ['temporary'];
814
886
 
815
887
  for (const keySystem of drmInfoByKeySystems.keys()) {
816
- // Create a copy of the mediaDecodingConfig.
817
- const config = /** @type {!MediaDecodingConfiguration} */
818
- (Object.assign({}, mediaDecodingConfig));
819
-
820
- const drmInfos = drmInfoByKeySystems.get(keySystem);
821
-
822
- /** @type {!MediaCapabilitiesKeySystemConfiguration} */
823
- const keySystemConfig = {
824
- keySystem: keySystem,
825
- initDataType: 'cenc',
826
- persistentState: persistentState,
827
- distinctiveIdentifier: 'optional',
828
- sessionTypes: sessionTypes,
829
- };
830
-
831
- for (const info of drmInfos) {
832
- if (info.initData && info.initData.length) {
833
- const initDataTypes = new Set();
834
- for (const initData of info.initData) {
835
- initDataTypes.add(initData.initDataType);
836
- }
837
- if (initDataTypes.size > 1) {
838
- shaka.log.v2('DrmInfo contains more than one initDataType,',
839
- 'and we use the initDataType of the first initData.',
840
- info);
888
+ const modifiedMediaDecodingConfigBatch = [];
889
+ for (const base of mediaDecodingConfigBatch) {
890
+ // Create a copy of the mediaDecodingConfig.
891
+ const config = /** @type {!MediaDecodingConfiguration} */
892
+ (Object.assign({}, base));
893
+
894
+ const drmInfos = drmInfoByKeySystems.get(keySystem);
895
+
896
+ /** @type {!MediaCapabilitiesKeySystemConfiguration} */
897
+ const keySystemConfig = {
898
+ keySystem: keySystem,
899
+ initDataType: 'cenc',
900
+ persistentState: persistentState,
901
+ distinctiveIdentifier: 'optional',
902
+ sessionTypes: sessionTypes,
903
+ };
904
+
905
+ for (const info of drmInfos) {
906
+ if (info.initData && info.initData.length) {
907
+ const initDataTypes = new Set();
908
+ for (const initData of info.initData) {
909
+ initDataTypes.add(initData.initDataType);
910
+ }
911
+ if (initDataTypes.size > 1) {
912
+ shaka.log.v2('DrmInfo contains more than one initDataType,',
913
+ 'and we use the initDataType of the first initData.',
914
+ info);
915
+ }
916
+ keySystemConfig.initDataType = info.initData[0].initDataType;
841
917
  }
842
- keySystemConfig.initDataType = info.initData[0].initDataType;
843
- }
844
918
 
845
- if (info.distinctiveIdentifierRequired) {
846
- keySystemConfig.distinctiveIdentifier = 'required';
847
- }
848
- if (info.persistentStateRequired) {
849
- keySystemConfig.persistentState = 'required';
850
- }
851
- if (info.sessionType) {
852
- keySystemConfig.sessionTypes = [info.sessionType];
853
- }
919
+ if (info.distinctiveIdentifierRequired) {
920
+ keySystemConfig.distinctiveIdentifier = 'required';
921
+ }
922
+ if (info.persistentStateRequired) {
923
+ keySystemConfig.persistentState = 'required';
924
+ }
925
+ if (info.sessionType) {
926
+ keySystemConfig.sessionTypes = [info.sessionType];
927
+ }
854
928
 
855
- if (audio) {
856
- // See: https://github.com/shaka-project/shaka-player/issues/4659
857
- if (info.audioRobustness != '') {
858
- if (!keySystemConfig.audio) {
929
+ if (audio) {
930
+ // See: https://github.com/shaka-project/shaka-player/issues/4659
931
+ if (info.audioRobustness != '') {
932
+ if (!keySystemConfig.audio) {
933
+ // KeySystemTrackConfiguration
934
+ keySystemConfig.audio = {
935
+ robustness: info.audioRobustness,
936
+ };
937
+ } else {
938
+ keySystemConfig.audio.robustness =
939
+ keySystemConfig.audio.robustness || info.audioRobustness;
940
+ }
941
+ } else if (!keySystemConfig.audio) {
859
942
  // KeySystemTrackConfiguration
860
- keySystemConfig.audio = {
861
- robustness: info.audioRobustness,
862
- };
863
- } else {
864
- keySystemConfig.audio.robustness =
865
- keySystemConfig.audio.robustness || info.audioRobustness;
943
+ keySystemConfig.audio = {};
866
944
  }
867
- } else if (!keySystemConfig.audio) {
868
- // KeySystemTrackConfiguration
869
- keySystemConfig.audio = {};
870
945
  }
871
- }
872
946
 
873
- if (video) {
874
- // See: https://github.com/shaka-project/shaka-player/issues/4659
875
- if (info.videoRobustness != '') {
876
- if (!keySystemConfig.video) {
947
+ if (video) {
948
+ // See: https://github.com/shaka-project/shaka-player/issues/4659
949
+ if (info.videoRobustness != '') {
950
+ if (!keySystemConfig.video) {
951
+ // KeySystemTrackConfiguration
952
+ keySystemConfig.video = {
953
+ robustness: info.videoRobustness,
954
+ };
955
+ } else {
956
+ keySystemConfig.video.robustness =
957
+ keySystemConfig.video.robustness || info.videoRobustness;
958
+ }
959
+ } else if (!keySystemConfig.video) {
877
960
  // KeySystemTrackConfiguration
878
- keySystemConfig.video = {
879
- robustness: info.videoRobustness,
880
- };
881
- } else {
882
- keySystemConfig.video.robustness =
883
- keySystemConfig.video.robustness || info.videoRobustness;
961
+ keySystemConfig.video = {};
884
962
  }
885
- } else if (!keySystemConfig.video) {
886
- // KeySystemTrackConfiguration
887
- keySystemConfig.video = {};
888
963
  }
889
964
  }
965
+ config.keySystemConfiguration = keySystemConfig;
966
+ modifiedMediaDecodingConfigBatch.push(config);
890
967
  }
891
- config.keySystemConfiguration = keySystemConfig;
892
- configs.push(config);
968
+ configs.push(modifiedMediaDecodingConfigBatch);
893
969
  }
894
970
  return configs;
895
971
  }
@@ -900,9 +976,8 @@ shaka.util.StreamUtils = class {
900
976
  * for MediaSource.isTypeSupported.
901
977
  * @param {string} codecs
902
978
  * @return {string}
903
- * @private
904
979
  */
905
- static getCorrectAudioCodecs_(codecs) {
980
+ static getCorrectAudioCodecs(codecs) {
906
981
  // According to RFC 6381 section 3.3, 'fLaC' is actually the correct
907
982
  // codec string. We still need to map it to 'flac', as some browsers
908
983
  // currently don't support 'fLaC', while 'flac' is supported by most
@@ -926,9 +1001,8 @@ shaka.util.StreamUtils = class {
926
1001
  * for MediaSource.isTypeSupported.
927
1002
  * @param {string} codec
928
1003
  * @return {string}
929
- * @private
930
1004
  */
931
- static getCorrectVideoCodecs_(codec) {
1005
+ static getCorrectVideoCodecs(codec) {
932
1006
  if (codec.includes('avc1')) {
933
1007
  // Convert avc1 codec string from RFC-4281 to RFC-6381 for
934
1008
  // MediaSource.isTypeSupported
@@ -1390,8 +1464,6 @@ shaka.util.StreamUtils = class {
1390
1464
  * @return {shaka.extern.Track}
1391
1465
  */
1392
1466
  static html5TextTrackToTrack(textTrack) {
1393
- const CLOSED_CAPTION_MIMETYPE =
1394
- shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE;
1395
1467
  const StreamUtils = shaka.util.StreamUtils;
1396
1468
 
1397
1469
  /** @type {shaka.extern.Track} */
@@ -1400,7 +1472,8 @@ shaka.util.StreamUtils = class {
1400
1472
  track.type = 'text';
1401
1473
  track.originalTextId = textTrack.id;
1402
1474
  if (textTrack.kind == 'captions') {
1403
- track.mimeType = CLOSED_CAPTION_MIMETYPE;
1475
+ // See: https://github.com/shaka-project/shaka-player/issues/6233
1476
+ track.mimeType = 'unknown';
1404
1477
  }
1405
1478
  if (textTrack.kind == 'subtitles') {
1406
1479
  track.mimeType = 'text/vtt';