shaka-player 3.1.5 → 3.2.3

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 (134) hide show
  1. package/CHANGELOG.md +129 -6
  2. package/README.md +4 -4
  3. package/build/types/polyfill +1 -0
  4. package/demo/common/asset.js +9 -7
  5. package/demo/common/assets.js +10 -1
  6. package/demo/common/message_ids.js +2 -1
  7. package/demo/config.js +5 -2
  8. package/demo/index.html +1 -1
  9. package/demo/locales/en.json +2 -1
  10. package/demo/locales/source.json +8 -4
  11. package/demo/main.js +2 -1
  12. package/dist/demo.compiled.debug.js +14 -14
  13. package/dist/demo.compiled.debug.map +2 -2
  14. package/dist/demo.compiled.js +14 -14
  15. package/dist/demo.compiled.map +2 -2
  16. package/dist/deps.js +21 -20
  17. package/dist/locales.js +30 -30
  18. package/dist/receiver.compiled.debug.js +8 -8
  19. package/dist/receiver.compiled.debug.map +2 -2
  20. package/dist/receiver.compiled.js +8 -8
  21. package/dist/receiver.compiled.map +2 -2
  22. package/dist/shaka-player.compiled.d.ts +63 -10
  23. package/dist/shaka-player.compiled.debug.d.ts +63 -10
  24. package/dist/shaka-player.compiled.debug.externs.js +786 -716
  25. package/dist/shaka-player.compiled.debug.js +563 -534
  26. package/dist/shaka-player.compiled.debug.map +4 -4
  27. package/dist/shaka-player.compiled.externs.js +786 -716
  28. package/dist/shaka-player.compiled.js +820 -809
  29. package/dist/shaka-player.compiled.map +4 -4
  30. package/dist/shaka-player.ui.d.ts +63 -10
  31. package/dist/shaka-player.ui.debug.d.ts +63 -10
  32. package/dist/shaka-player.ui.debug.externs.js +542 -472
  33. package/dist/shaka-player.ui.debug.js +594 -566
  34. package/dist/shaka-player.ui.debug.map +4 -4
  35. package/dist/shaka-player.ui.externs.js +542 -472
  36. package/dist/shaka-player.ui.js +938 -927
  37. package/dist/shaka-player.ui.map +4 -4
  38. package/docs/tutorials/ui.md +5 -5
  39. package/externs/aria.js +6 -0
  40. package/externs/shaka/ads.js +24 -1
  41. package/externs/shaka/manifest.js +6 -1
  42. package/externs/shaka/offline.js +5 -0
  43. package/externs/shaka/player.js +61 -9
  44. package/externs/shaka/text.js +10 -10
  45. package/externs/texttrack.js +3 -0
  46. package/lib/ads/ad_manager.js +16 -15
  47. package/lib/ads/client_side_ad_manager.js +6 -2
  48. package/lib/ads/server_side_ad_manager.js +19 -2
  49. package/lib/cast/cast_proxy.js +8 -0
  50. package/lib/cast/cast_utils.js +6 -1
  51. package/lib/cea/cea608_data_channel.js +56 -56
  52. package/lib/cea/cea608_memory.js +33 -33
  53. package/lib/cea/cea708_service.js +43 -43
  54. package/lib/cea/cea708_window.js +38 -38
  55. package/lib/cea/cea_decoder.js +13 -14
  56. package/lib/cea/cea_utils.js +23 -23
  57. package/lib/cea/dtvcc_packet_builder.js +7 -7
  58. package/lib/cea/i_caption_decoder.js +2 -3
  59. package/lib/cea/i_cea_parser.js +2 -2
  60. package/lib/cea/mp4_cea_parser.js +6 -6
  61. package/lib/dash/dash_parser.js +5 -0
  62. package/lib/hls/hls_parser.js +159 -12
  63. package/lib/media/closed_caption_parser.js +1 -2
  64. package/lib/media/drm_engine.js +162 -192
  65. package/lib/media/segment_index.js +4 -1
  66. package/lib/media/segment_reference.js +37 -2
  67. package/lib/media/streaming_engine.js +6 -6
  68. package/lib/net/networking_engine.js +1 -0
  69. package/lib/offline/download_manager.js +1 -2
  70. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  71. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  72. package/lib/offline/manifest_converter.js +3 -1
  73. package/lib/offline/storage.js +12 -14
  74. package/lib/player.js +426 -149
  75. package/lib/polyfill/aria.js +82 -0
  76. package/lib/routing/walker.js +2 -3
  77. package/lib/text/srt_text_parser.js +4 -4
  78. package/lib/text/text_engine.js +2 -2
  79. package/lib/text/vtt_text_parser.js +2 -2
  80. package/lib/text/web_vtt_generator.js +55 -12
  81. package/lib/util/error.js +6 -0
  82. package/lib/util/manifest_parser_utils.js +1 -0
  83. package/lib/util/mp4_box_parsers.js +20 -20
  84. package/lib/util/multi_map.js +8 -0
  85. package/lib/util/player_configuration.js +13 -1
  86. package/lib/util/stream_utils.js +168 -88
  87. package/lib/util/xml_utils.js +31 -12
  88. package/package.json +1 -1
  89. package/roadmap.md +24 -22
  90. package/shaka-player.uncompiled.js +1 -0
  91. package/test/cast/cast_utils_unit.js +1 -0
  92. package/test/cea/cea608_memory_unit.js +1 -1
  93. package/test/cea/cea708_service_unit.js +8 -8
  94. package/test/cea/cea708_window_unit.js +6 -6
  95. package/test/cea/cea_decoder_unit.js +1 -1
  96. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  97. package/test/dash/dash_parser_manifest_unit.js +43 -1
  98. package/test/hls/hls_parser_unit.js +250 -0
  99. package/test/media/drm_engine_integration.js +86 -12
  100. package/test/media/drm_engine_unit.js +310 -450
  101. package/test/offline/manifest_convert_unit.js +2 -0
  102. package/test/offline/storage_integration.js +9 -19
  103. package/test/player_integration.js +85 -0
  104. package/test/player_src_equals_integration.js +85 -0
  105. package/test/player_unit.js +25 -37
  106. package/test/test/assets/chapters.srt +11 -0
  107. package/test/test/assets/chapters.vtt +10 -0
  108. package/test/test/assets/chapters2.vtt +10 -0
  109. package/test/test/util/cea_utils.js +12 -12
  110. package/test/test/util/fake_ad_manager.js +7 -0
  111. package/test/test/util/manifest_generator.js +2 -0
  112. package/test/test/util/manifest_parser_util.js +3 -1
  113. package/test/text/web_vtt_generator_unit.js +81 -7
  114. package/test/util/stream_utils_unit.js +118 -10
  115. package/ui/airplay_button.js +1 -1
  116. package/ui/audio_language_selection.js +2 -5
  117. package/ui/cast_button.js +4 -6
  118. package/ui/constants.js +0 -3
  119. package/ui/fast_forward_button.js +2 -3
  120. package/ui/fullscreen_button.js +1 -3
  121. package/ui/language_utils.js +1 -1
  122. package/ui/loop_button.js +1 -3
  123. package/ui/mute_button.js +1 -3
  124. package/ui/overflow_menu.js +2 -3
  125. package/ui/pip_button.js +5 -7
  126. package/ui/playback_rate_selection.js +3 -6
  127. package/ui/presentation_time.js +1 -3
  128. package/ui/resolution_selection.js +4 -7
  129. package/ui/rewind_button.js +2 -3
  130. package/ui/seek_bar.js +2 -4
  131. package/ui/spacer.js +1 -1
  132. package/ui/text_selection.js +8 -11
  133. package/ui/ui_utils.js +1 -1
  134. package/ui/volume_bar.js +1 -3
@@ -19,7 +19,6 @@ goog.require('shaka.util.FakeEvent');
19
19
  goog.require('shaka.util.IDestroyable');
20
20
  goog.require('shaka.util.Iterables');
21
21
  goog.require('shaka.util.Lazy');
22
- goog.require('shaka.util.ManifestParserUtils');
23
22
  goog.require('shaka.util.MapUtils');
24
23
  goog.require('shaka.util.MimeUtils');
25
24
  goog.require('shaka.util.Platform');
@@ -213,10 +212,9 @@ shaka.media.DrmEngine = class {
213
212
  * @param {boolean} usePersistentLicenses
214
213
  * Whether or not persistent licenses should be requested and stored for
215
214
  * |manifest|.
216
- * @param {boolean=} useMediaCapabilities
217
215
  * @return {!Promise}
218
216
  */
219
- initForStorage(variants, usePersistentLicenses, useMediaCapabilities) {
217
+ initForStorage(variants, usePersistentLicenses) {
220
218
  this.initializedForStorage_ = true;
221
219
  // There are two cases for this call:
222
220
  // 1. We are about to store a manifest - in that case, there are no offline
@@ -230,7 +228,7 @@ shaka.media.DrmEngine = class {
230
228
  // persistent licenses.
231
229
  this.usePersistentLicenses_ = usePersistentLicenses;
232
230
 
233
- return this.init_(variants, !!useMediaCapabilities);
231
+ return this.init_(variants);
234
232
  }
235
233
 
236
234
  /**
@@ -239,14 +237,13 @@ shaka.media.DrmEngine = class {
239
237
  * @param {!Array.<shaka.extern.Variant>} variants
240
238
  * The variants that we want to support playing.
241
239
  * @param {!Array.<string>} offlineSessionIds
242
- * @param {boolean=} useMediaCapabilities
243
240
  * @return {!Promise}
244
241
  */
245
- initForPlayback(variants, offlineSessionIds, useMediaCapabilities) {
242
+ initForPlayback(variants, offlineSessionIds) {
246
243
  this.offlineSessionIds_ = offlineSessionIds;
247
244
  this.usePersistentLicenses_ = offlineSessionIds.length > 0;
248
245
 
249
- return this.init_(variants, !!useMediaCapabilities);
246
+ return this.init_(variants);
250
247
  }
251
248
 
252
249
  /**
@@ -285,14 +282,14 @@ shaka.media.DrmEngine = class {
285
282
  audioRobustness: '', // Not required by queryMediaKeys_
286
283
  videoRobustness: '', // Same
287
284
  serverCertificate: serverCertificate,
285
+ serverCertificateUri: '',
288
286
  initData: null,
289
287
  keyIds: null,
290
288
  }];
291
289
 
292
290
  configsByKeySystem.set(keySystem, config);
293
291
  return this.queryMediaKeys_(configsByKeySystem,
294
- /* variants= */ [],
295
- /* useMediaCapabilities= */ false);
292
+ /* variants= */ []);
296
293
  }
297
294
 
298
295
  /**
@@ -303,11 +300,10 @@ shaka.media.DrmEngine = class {
303
300
  * @param {!Array.<shaka.extern.Variant>} variants
304
301
  * The variants that we expect to operate with during the drm engine's
305
302
  * lifespan of the drm engine.
306
- * @param {boolean} useMediaCapabilities
307
303
  * @return {!Promise} Resolved if/when a key system has been chosen.
308
304
  * @private
309
305
  */
310
- async init_(variants, useMediaCapabilities) {
306
+ async init_(variants) {
311
307
  goog.asserts.assert(this.config_,
312
308
  'DrmEngine configure() must be called before init()!');
313
309
 
@@ -365,13 +361,8 @@ shaka.media.DrmEngine = class {
365
361
 
366
362
  // We should get the decodingInfo results for the variants after we filling
367
363
  // in the drm infos, and before queryMediaKeys_().
368
- if (useMediaCapabilities) {
369
- await shaka.util.StreamUtils.getDecodingInfosForVariants(variants,
370
- this.usePersistentLicenses_, this.srcEquals_);
371
- } else {
372
- configsByKeySystem =
373
- this.prepareMediaKeyConfigsForVariants_(variants);
374
- }
364
+ await shaka.util.StreamUtils.getDecodingInfosForVariants(variants,
365
+ this.usePersistentLicenses_, this.srcEquals_);
375
366
 
376
367
  const hasDrmInfo = hadDrmInfo || Object.keys(this.config_.servers).length;
377
368
  // An unencrypted content is initialized.
@@ -380,8 +371,7 @@ shaka.media.DrmEngine = class {
380
371
  return Promise.resolve();
381
372
  }
382
373
 
383
- const p = this.queryMediaKeys_(configsByKeySystem, variants,
384
- useMediaCapabilities);
374
+ const p = this.queryMediaKeys_(configsByKeySystem, variants);
385
375
 
386
376
  // TODO(vaage): Look into the assertion below. If we do not have any drm
387
377
  // info, we create drm info so that content can play if it has drm info
@@ -463,26 +453,63 @@ shaka.media.DrmEngine = class {
463
453
  goog.asserts.assert(this.initialized_,
464
454
  'Must call init() before setServerCertificate');
465
455
 
466
- if (this.mediaKeys_ &&
467
- this.currentDrmInfo_ &&
468
- this.currentDrmInfo_.serverCertificate &&
469
- this.currentDrmInfo_.serverCertificate.length) {
456
+ if (!this.mediaKeys_ || !this.currentDrmInfo_) {
457
+ return;
458
+ }
459
+
460
+ if (this.currentDrmInfo_.serverCertificateUri &&
461
+ (!this.currentDrmInfo_.serverCertificate ||
462
+ !this.currentDrmInfo_.serverCertificate.length)) {
463
+ const request = shaka.net.NetworkingEngine.makeRequest(
464
+ [this.currentDrmInfo_.serverCertificateUri],
465
+ this.config_.retryParameters);
466
+
470
467
  try {
471
- const supported = await this.mediaKeys_.setServerCertificate(
472
- this.currentDrmInfo_.serverCertificate);
473
- if (!supported) {
474
- shaka.log.warning('Server certificates are not supported by the ' +
475
- 'key system. The server certificate has been ' +
476
- 'ignored.');
477
- }
478
- } catch (exception) {
468
+ const operation = this.playerInterface_.netEngine.request(
469
+ shaka.net.NetworkingEngine.RequestType.SERVER_CERTIFICATE,
470
+ request);
471
+ const response = await operation.promise;
472
+
473
+ this.currentDrmInfo_.serverCertificate =
474
+ shaka.util.BufferUtils.toUint8(response.data);
475
+ } catch (error) {
476
+ // Request failed!
477
+ goog.asserts.assert(error instanceof shaka.util.Error,
478
+ 'Wrong NetworkingEngine error type!');
479
+
479
480
  throw new shaka.util.Error(
480
481
  shaka.util.Error.Severity.CRITICAL,
481
482
  shaka.util.Error.Category.DRM,
482
- shaka.util.Error.Code.INVALID_SERVER_CERTIFICATE,
483
- exception.message);
483
+ shaka.util.Error.Code.SERVER_CERTIFICATE_REQUEST_FAILED,
484
+ error);
485
+ }
486
+
487
+ if (this.destroyer_.destroyed()) {
488
+ return;
484
489
  }
485
490
  }
491
+
492
+ if (!this.currentDrmInfo_.serverCertificate ||
493
+ !this.currentDrmInfo_.serverCertificate.length) {
494
+ return;
495
+ }
496
+
497
+ try {
498
+ const supported = await this.mediaKeys_.setServerCertificate(
499
+ this.currentDrmInfo_.serverCertificate);
500
+
501
+ if (!supported) {
502
+ shaka.log.warning('Server certificates are not supported by the ' +
503
+ 'key system. The server certificate has been ' +
504
+ 'ignored.');
505
+ }
506
+ } catch (exception) {
507
+ throw new shaka.util.Error(
508
+ shaka.util.Error.Severity.CRITICAL,
509
+ shaka.util.Error.Category.DRM,
510
+ shaka.util.Error.Code.INVALID_SERVER_CERTIFICATE,
511
+ exception.message);
512
+ }
486
513
  }
487
514
 
488
515
  /**
@@ -578,7 +605,8 @@ shaka.media.DrmEngine = class {
578
605
  }
579
606
  }
580
607
 
581
- this.createTemporarySession_(initDataType, initData);
608
+ this.createSession(initDataType, initData,
609
+ this.currentDrmInfo_.sessionType);
582
610
  }
583
611
 
584
612
  /** @return {boolean} */
@@ -604,6 +632,17 @@ shaka.media.DrmEngine = class {
604
632
  return false;
605
633
  }
606
634
 
635
+ /**
636
+ * @param {?string} keySystem
637
+ * @return {boolean} */
638
+ static isFairPlayKeySystem(keySystem) {
639
+ if (keySystem) {
640
+ return !!keySystem.match(/^com\.apple\.fps/);
641
+ }
642
+
643
+ return false;
644
+ }
645
+
607
646
  /**
608
647
  * Check if DrmEngine (as initialized) will likely be able to support the
609
648
  * given content type.
@@ -707,149 +746,15 @@ shaka.media.DrmEngine = class {
707
746
  }
708
747
 
709
748
  /**
710
- * @param {!Array.<shaka.extern.Variant>} variants
711
- * @see https://bit.ly/EmeConfig for MediaKeySystemConfiguration spec
712
- * @return {!Map.<string, MediaKeySystemConfiguration>}
713
- * @private
749
+ * Returns the current media key sessions.
750
+ *
751
+ * @return {!Array.<MediaKeySession>}
714
752
  */
715
- prepareMediaKeyConfigsForVariants_(variants) {
716
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
717
-
718
- // Get all the drm info so that we can avoid using nested loops when we just
719
- // need the drm info.
720
- const allDrmInfo = new Set();
721
- for (const variant of variants) {
722
- const drmInfos = this.getVariantDrmInfos_(variant);
723
- for (const info of drmInfos) {
724
- allDrmInfo.add(info);
725
- }
726
- }
727
-
728
- // Make sure all the drm infos are valid and filled in correctly.
729
- for (const info of allDrmInfo) {
730
- shaka.media.DrmEngine.fillInDrmInfoDefaults_(
731
- info,
732
- shaka.util.MapUtils.asMap(this.config_.servers),
733
- shaka.util.MapUtils.asMap(this.config_.advanced || {}));
734
- }
735
-
736
- const persistentState =
737
- this.usePersistentLicenses_ ? 'required' : 'optional';
738
- const sessionTypes =
739
- this.usePersistentLicenses_ ? ['persistent-license'] : ['temporary'];
740
-
741
- const configs = new Map();
742
-
743
- // Create a config entry for each key system.
744
- for (const info of allDrmInfo) {
745
- const config = {
746
- initDataTypes: ['cenc'],
747
- audioCapabilities: [],
748
- videoCapabilities: [],
749
- distinctiveIdentifier: 'optional',
750
- persistentState: persistentState,
751
- sessionTypes: sessionTypes,
752
- label: info.keySystem, // Tracked by us, ignored by EME.
753
- };
754
- // TODO: refactor, don't stick drmInfos onto MediaKeySystemConfiguration
755
- config['drmInfos'] = []; // Non-standard attribute, ignored by EME.
756
-
757
- // Multiple calls to |set| will still respect the insertion order of the
758
- // first call to |set| for a given key.
759
- configs.set(info.keySystem, config);
760
- }
761
-
762
- // Connect each key system with each stream using it.
763
- for (const variant of variants) {
764
- /** @type {?shaka.extern.Stream} */
765
- const audio = variant.audio;
766
- /** @type {?shaka.extern.Stream} */
767
- const video = variant.video;
768
-
769
- // Add the last bit of information to each config;
770
- for (const stream of [audio, video]) {
771
- if (!stream) {
772
- continue;
773
- }
774
-
775
- const mimeType = shaka.media.DrmEngine.computeMimeType_(stream);
776
- let fallbackMimeType = null;
777
-
778
- if (stream.codecs.toLowerCase() == 'ac-3' &&
779
- shaka.util.Platform.isTizen()) {
780
- // Some Tizen devices seem to misreport AC-3 support, but correctly
781
- // report EC-3 support. So query EC-3 as a fallback for AC-3.
782
- // See https://github.com/google/shaka-player/issues/2989 for details.
783
- fallbackMimeType = shaka.media.DrmEngine.computeMimeType_(stream,
784
- 'ec-3');
785
- }
786
-
787
- for (const info of stream.drmInfos) {
788
- const config = configs.get(info.keySystem);
789
- goog.asserts.assert(
790
- config,
791
- 'Any missing configs should have be filled in before.');
792
-
793
- // TODO: refactor, don't stick drmInfos onto
794
- // MediaKeySystemConfiguration
795
- config['drmInfos'].push(info);
796
-
797
- if (info.initData && info.initData.length) {
798
- config.initDataTypes = [
799
- ...new Set(
800
- info.initData.map((initData) => initData.initDataType)),
801
- ];
802
- }
803
-
804
- if (info.distinctiveIdentifierRequired) {
805
- config.distinctiveIdentifier = 'required';
806
- }
807
-
808
- if (info.persistentStateRequired) {
809
- config.persistentState = 'required';
810
- }
811
-
812
- if (info.sessionType) {
813
- config.sessionTypes = [info.sessionType];
814
- }
815
-
816
- const robustness = (stream.type == ContentType.AUDIO) ?
817
- info.audioRobustness : info.videoRobustness;
818
-
819
- /** @type {MediaKeySystemMediaCapability} */
820
- const capability = {
821
- robustness: robustness || '',
822
- contentType: mimeType,
823
- };
824
-
825
- if (stream.type == ContentType.AUDIO) {
826
- config.audioCapabilities.push(capability);
827
- } else {
828
- config.videoCapabilities.push(capability);
829
- }
830
-
831
- // This is how we work around some misbehaving platforms by adding
832
- // synthetic capability records using a fallback MIME type.
833
- if (fallbackMimeType) {
834
- /** @type {MediaKeySystemMediaCapability} */
835
- const fallbackCapability = {
836
- robustness: robustness || '',
837
- contentType: fallbackMimeType,
838
- };
839
-
840
- if (stream.type == ContentType.AUDIO) {
841
- config.audioCapabilities.push(fallbackCapability);
842
- } else {
843
- config.videoCapabilities.push(fallbackCapability);
844
- }
845
- }
846
- } // for (const info of stream.drmInfos)
847
- } // for (const stream of [audio, video])
848
- } // for (const variant of variants)
849
-
850
- return configs;
753
+ getMediaKeySessions() {
754
+ return Array.from(this.activeSessions_.keys());
851
755
  }
852
756
 
757
+
853
758
  /**
854
759
  * @param {shaka.extern.Stream} stream
855
760
  * @param {string=} codecOverride
@@ -872,14 +777,13 @@ shaka.media.DrmEngine = class {
872
777
  * A dictionary of configs, indexed by key system, with an iteration order
873
778
  * (insertion order) that reflects the preference for the application.
874
779
  * @param {!Array.<shaka.extern.Variant>} variants
875
- * @param {boolean} useMediaCapabilities
876
780
  * @return {!Promise} Resolved if/when a key system has been chosen.
877
781
  * @private
878
782
  */
879
- async queryMediaKeys_(configsByKeySystem, variants, useMediaCapabilities) {
783
+ async queryMediaKeys_(configsByKeySystem, variants) {
880
784
  const drmInfosByKeySystem = new Map();
881
785
 
882
- const mediaKeySystemAccess = useMediaCapabilities ?
786
+ const mediaKeySystemAccess = variants.length ?
883
787
  this.getKeySystemAccessFromVariants_(variants, drmInfosByKeySystem) :
884
788
  await this.getKeySystemAccessByConfigs_(configsByKeySystem);
885
789
 
@@ -918,7 +822,7 @@ shaka.media.DrmEngine = class {
918
822
  goog.asserts.assert(this.supportedTypes_.size,
919
823
  'We should get at least one supported MIME type');
920
824
 
921
- if (useMediaCapabilities) {
825
+ if (variants.length) {
922
826
  this.currentDrmInfo_ = this.createDrmInfoByInfos_(
923
827
  mediaKeySystemAccess.keySystem,
924
828
  drmInfosByKeySystem.get(mediaKeySystemAccess.keySystem));
@@ -990,6 +894,22 @@ shaka.media.DrmEngine = class {
990
894
  shaka.util.Error.Category.DRM,
991
895
  shaka.util.Error.Code.NO_RECOGNIZED_KEY_SYSTEMS);
992
896
  }
897
+
898
+ // If we have configured preferredKeySystems, choose a preferred keySystem
899
+ // if available.
900
+ for (const preferredKeySystem of this.config_.preferredKeySystems) {
901
+ for (const variant of variants) {
902
+ const decodingInfo = variant.decodingInfos.find((decodingInfo) => {
903
+ return decodingInfo.supported &&
904
+ decodingInfo.keySystemAccess != null &&
905
+ decodingInfo.keySystemAccess.keySystem == preferredKeySystem;
906
+ });
907
+ if (decodingInfo) {
908
+ return decodingInfo.keySystemAccess;
909
+ }
910
+ }
911
+ }
912
+
993
913
  // Try key systems with configured license servers first. We only have to
994
914
  // try key systems without configured license servers for diagnostic
995
915
  // reasons, so that we can differentiate between "none of these key
@@ -1045,6 +965,24 @@ shaka.media.DrmEngine = class {
1045
965
  }
1046
966
  }
1047
967
 
968
+ // If we have configured preferredKeySystems, choose the preferred one if
969
+ // available.
970
+ for (const keySystem of this.config_.preferredKeySystems) {
971
+ if (configsByKeySystem.has(keySystem)) {
972
+ const config = configsByKeySystem.get(keySystem);
973
+ try {
974
+ mediaKeySystemAccess = // eslint-disable-next-line no-await-in-loop
975
+ await navigator.requestMediaKeySystemAccess(keySystem, [config]);
976
+ return mediaKeySystemAccess;
977
+ } catch (error) {
978
+ // Suppress errors.
979
+ shaka.log.v2(
980
+ 'Requesting', keySystem, 'failed with config', config, error);
981
+ }
982
+ this.destroyer_.ensureNotDestroyed();
983
+ }
984
+ }
985
+
1048
986
  // Try key systems with configured license servers first. We only have to
1049
987
  // try key systems without configured license servers for diagnostic
1050
988
  // reasons, so that we can differentiate between "none of these key
@@ -1066,14 +1004,13 @@ shaka.media.DrmEngine = class {
1066
1004
 
1067
1005
  try {
1068
1006
  mediaKeySystemAccess = // eslint-disable-next-line no-await-in-loop
1069
- await navigator.requestMediaKeySystemAccess(
1070
- keySystem, [config]);
1007
+ await navigator.requestMediaKeySystemAccess(keySystem, [config]);
1071
1008
  return mediaKeySystemAccess;
1072
1009
  } catch (error) {
1010
+ // Suppress errors.
1073
1011
  shaka.log.v2(
1074
- 'Requesting', keySystem, 'failed with config',
1075
- config, error);
1076
- } // Suppress errors.
1012
+ 'Requesting', keySystem, 'failed with config', config, error);
1013
+ }
1077
1014
  this.destroyer_.ensureNotDestroyed();
1078
1015
  }
1079
1016
  }
@@ -1130,6 +1067,7 @@ shaka.media.DrmEngine = class {
1130
1067
  audioRobustness: '',
1131
1068
  videoRobustness: '',
1132
1069
  serverCertificate: null,
1070
+ serverCertificateUri: '',
1133
1071
  sessionType: '',
1134
1072
  initData: initDatas,
1135
1073
  keyIds: new Set(keyIds),
@@ -1214,16 +1152,14 @@ shaka.media.DrmEngine = class {
1214
1152
  /**
1215
1153
  * @param {string} initDataType
1216
1154
  * @param {!Uint8Array} initData
1217
- * @private
1155
+ * @param {string} sessionType
1218
1156
  */
1219
- createTemporarySession_(initDataType, initData) {
1157
+ createSession(initDataType, initData, sessionType) {
1220
1158
  goog.asserts.assert(this.mediaKeys_,
1221
1159
  'mediaKeys_ should be valid when creating temporary session.');
1222
1160
 
1223
1161
  let session;
1224
1162
 
1225
- const sessionType = this.currentDrmInfo_.sessionType;
1226
-
1227
1163
  try {
1228
1164
  shaka.log.info('Creating new', sessionType, 'session');
1229
1165
 
@@ -1912,6 +1848,8 @@ shaka.media.DrmEngine = class {
1912
1848
  videoRobustness: drm1.videoRobustness || drm2.videoRobustness,
1913
1849
  audioRobustness: drm1.audioRobustness || drm2.audioRobustness,
1914
1850
  serverCertificate: drm1.serverCertificate || drm2.serverCertificate,
1851
+ serverCertificateUri: drm1.serverCertificateUri ||
1852
+ drm2.serverCertificateUri,
1915
1853
  initData,
1916
1854
  keyIds,
1917
1855
  };
@@ -1986,6 +1924,7 @@ shaka.media.DrmEngine = class {
1986
1924
  audioRobustness: '',
1987
1925
  videoRobustness: '',
1988
1926
  serverCertificate: null,
1927
+ serverCertificateUri: '',
1989
1928
  initData: [],
1990
1929
  keyIds: new Set(),
1991
1930
  });
@@ -2016,6 +1955,9 @@ shaka.media.DrmEngine = class {
2016
1955
  /** @type {!Array.<string>} */
2017
1956
  const licenseServers = [];
2018
1957
 
1958
+ /** @type {!Array.<string>} */
1959
+ const serverCertificateUris = [];
1960
+
2019
1961
  /** @type {!Array.<!Uint8Array>} */
2020
1962
  const serverCerts = [];
2021
1963
 
@@ -2025,8 +1967,9 @@ shaka.media.DrmEngine = class {
2025
1967
  /** @type {!Set.<string>} */
2026
1968
  const keyIds = new Set();
2027
1969
 
2028
- shaka.media.DrmEngine.processDrmInfos_(drmInfos, licenseServers,
2029
- serverCerts, initDatas, keyIds);
1970
+ shaka.media.DrmEngine.processDrmInfos_(
1971
+ drmInfos, licenseServers, serverCerts,
1972
+ serverCertificateUris, initDatas, keyIds);
2030
1973
 
2031
1974
  if (serverCerts.length > 1) {
2032
1975
  shaka.log.warning('Multiple unique server certificates found! ' +
@@ -2038,6 +1981,11 @@ shaka.media.DrmEngine = class {
2038
1981
  'Only the first will be used.');
2039
1982
  }
2040
1983
 
1984
+ if (serverCertificateUris.length > 1) {
1985
+ shaka.log.warning('Multiple unique server certificate URIs found! ' +
1986
+ 'Only the first will be used.');
1987
+ }
1988
+
2041
1989
  const defaultSessionType =
2042
1990
  this.usePersistentLicenses_ ? 'persistent-license' : 'temporary';
2043
1991
 
@@ -2051,6 +1999,7 @@ shaka.media.DrmEngine = class {
2051
1999
  audioRobustness: drmInfos[0].audioRobustness || '',
2052
2000
  videoRobustness: drmInfos[0].videoRobustness || '',
2053
2001
  serverCertificate: serverCerts[0],
2002
+ serverCertificateUri: serverCertificateUris[0],
2054
2003
  initData: initDatas,
2055
2004
  keyIds,
2056
2005
  };
@@ -2082,6 +2031,9 @@ shaka.media.DrmEngine = class {
2082
2031
  /** @type {!Array.<string>} */
2083
2032
  const licenseServers = [];
2084
2033
 
2034
+ /** @type {!Array.<string>} */
2035
+ const serverCertificateUris = [];
2036
+
2085
2037
  /** @type {!Array.<!Uint8Array>} */
2086
2038
  const serverCerts = [];
2087
2039
 
@@ -2093,13 +2045,19 @@ shaka.media.DrmEngine = class {
2093
2045
 
2094
2046
  // TODO: refactor, don't stick drmInfos onto MediaKeySystemConfiguration
2095
2047
  shaka.media.DrmEngine.processDrmInfos_(
2096
- config['drmInfos'], licenseServers, serverCerts, initDatas, keyIds);
2048
+ config['drmInfos'], licenseServers, serverCerts,
2049
+ serverCertificateUris, initDatas, keyIds);
2097
2050
 
2098
2051
  if (serverCerts.length > 1) {
2099
2052
  shaka.log.warning('Multiple unique server certificates found! ' +
2100
2053
  'Only the first will be used.');
2101
2054
  }
2102
2055
 
2056
+ if (serverCertificateUris.length > 1) {
2057
+ shaka.log.warning('Multiple unique server certificate URIs found! ' +
2058
+ 'Only the first will be used.');
2059
+ }
2060
+
2103
2061
  if (licenseServers.length > 1) {
2104
2062
  shaka.log.warning('Multiple unique license server URIs found! ' +
2105
2063
  'Only the first will be used.');
@@ -2121,6 +2079,7 @@ shaka.media.DrmEngine = class {
2121
2079
  audioRobustness: audioRobustness || '',
2122
2080
  videoRobustness: videoRobustness || '',
2123
2081
  serverCertificate: serverCerts[0],
2082
+ serverCertificateUri: serverCertificateUris[0],
2124
2083
  initData: initDatas,
2125
2084
  keyIds,
2126
2085
  };
@@ -2133,12 +2092,14 @@ shaka.media.DrmEngine = class {
2133
2092
  * @param {!Array.<shaka.extern.DrmInfo>} drmInfos
2134
2093
  * @param {!Array.<string>} licenseServers
2135
2094
  * @param {!Array.<!Uint8Array>} serverCerts
2095
+ * @param {!Array.<string>} serverCertificateUris
2136
2096
  * @param {!Array.<!shaka.extern.InitDataOverride>} initDatas
2137
2097
  * @param {!Set.<string>} keyIds
2138
2098
  * @private
2139
2099
  */
2140
2100
  static processDrmInfos_(
2141
- drmInfos, licenseServers, serverCerts, initDatas, keyIds) {
2101
+ drmInfos, licenseServers, serverCerts,
2102
+ serverCertificateUris, initDatas, keyIds) {
2142
2103
  /** @type {function(shaka.extern.InitDataOverride,
2143
2104
  * shaka.extern.InitDataOverride):boolean} */
2144
2105
  const initDataOverrideEqual = (a, b) => {
@@ -2157,6 +2118,11 @@ shaka.media.DrmEngine = class {
2157
2118
  licenseServers.push(drmInfo.licenseServerUri);
2158
2119
  }
2159
2120
 
2121
+ // Build an array of unique license servers.
2122
+ if (!serverCertificateUris.includes(drmInfo.serverCertificateUri)) {
2123
+ serverCertificateUris.push(drmInfo.serverCertificateUri);
2124
+ }
2125
+
2160
2126
  // Build an array of unique server certs.
2161
2127
  if (drmInfo.serverCertificate) {
2162
2128
  const found = serverCerts.some(
@@ -2269,6 +2235,10 @@ shaka.media.DrmEngine = class {
2269
2235
  if (advancedConfig.sessionType) {
2270
2236
  drmInfo.sessionType = advancedConfig.sessionType;
2271
2237
  }
2238
+
2239
+ if (!drmInfo.serverCertificateUri) {
2240
+ drmInfo.serverCertificateUri = advancedConfig.serverCertificateUri;
2241
+ }
2272
2242
  }
2273
2243
 
2274
2244
  // Chromecast has a variant of PlayReady that uses a different key
@@ -345,7 +345,10 @@ shaka.media.SegmentIndex = class {
345
345
  lastReference.initSegmentReference,
346
346
  lastReference.timestampOffset,
347
347
  lastReference.appendWindowStart,
348
- lastReference.appendWindowEnd);
348
+ lastReference.appendWindowEnd,
349
+ lastReference.partialReferences,
350
+ lastReference.tilesLayout,
351
+ lastReference.tileDuration);
349
352
  }
350
353
 
351
354
 
@@ -135,12 +135,20 @@ shaka.media.SegmentReference = class {
135
135
  * presentation. Any content from after this time will be removed by
136
136
  * MediaSource.
137
137
  * @param {!Array.<!shaka.media.SegmentReference>=} partialReferences
138
- A list of SegmentReferences for the partial segments.
138
+ * A list of SegmentReferences for the partial segments.
139
+ * @param {?string=} tilesLayout
140
+ * The value is a grid-item-dimension consisting of two positive decimal
141
+ * integers in the format: column-x-row ('4x3'). It describes the
142
+ * arrangement of Images in a Grid. The minimum valid LAYOUT is '1x1'.
143
+ * @param {?number=} tileDuration
144
+ * The explicit duration of an individual tile within the tiles grid.
145
+ * If not provided, the duration should be automatically calculated based on
146
+ * the duration of the reference.
139
147
  */
140
148
  constructor(
141
149
  startTime, endTime, uris, startByte, endByte, initSegmentReference,
142
150
  timestampOffset, appendWindowStart, appendWindowEnd,
143
- partialReferences = []) {
151
+ partialReferences = [], tilesLayout = '', tileDuration = null) {
144
152
  // A preload hinted Partial Segment has the same startTime and endTime.
145
153
  goog.asserts.assert(startTime <= endTime,
146
154
  'startTime must be less than or equal to endTime');
@@ -184,6 +192,12 @@ shaka.media.SegmentReference = class {
184
192
 
185
193
  /** @type {!Array.<!shaka.media.SegmentReference>} */
186
194
  this.partialReferences = partialReferences;
195
+
196
+ /** @type {?string} */
197
+ this.tilesLayout = tilesLayout;
198
+
199
+ /** @type {?number} */
200
+ this.tileDuration = tileDuration;
187
201
  }
188
202
 
189
203
  /**
@@ -258,6 +272,27 @@ shaka.media.SegmentReference = class {
258
272
  hasPartialSegments() {
259
273
  return this.partialReferences.length > 0;
260
274
  }
275
+
276
+ /**
277
+ * Returns the segment's tiles layout. Only defined in image segments.
278
+ *
279
+ * @return {?string}
280
+ * @export
281
+ */
282
+ getTilesLayout() {
283
+ return this.tilesLayout;
284
+ }
285
+
286
+ /**
287
+ * Returns the segment's explicit tile duration.
288
+ * Only defined in image segments.
289
+ *
290
+ * @return {?number}
291
+ * @export
292
+ */
293
+ getTileDuration() {
294
+ return this.tileDuration;
295
+ }
261
296
  };
262
297
 
263
298