shaka-player 2.5.1 → 2.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/.csslintrc +13 -0
  2. package/AUTHORS +3 -0
  3. package/CHANGELOG.md +156 -0
  4. package/CONTRIBUTORS +3 -0
  5. package/README.md +3 -0
  6. package/build/README.md +5 -5
  7. package/build/apps.py +3 -1
  8. package/build/build.py +5 -2
  9. package/build/check.py +17 -0
  10. package/build/compiler.py +49 -0
  11. package/build/conformance.textproto +1 -0
  12. package/build/docs.py +0 -6
  13. package/build/generateExterns.js +72 -20
  14. package/build/install-linux-prereqs.sh +4 -4
  15. package/build/shakaBuildHelpers.py +7 -8
  16. package/build/test.py +21 -9
  17. package/build/types/core +1 -0
  18. package/build/types/ui +6 -1
  19. package/demo/asset_card.js +129 -41
  20. package/demo/cast_receiver/receiver_app.css +27 -13
  21. package/demo/close_button.js +10 -15
  22. package/demo/common/asset.js +6 -6
  23. package/demo/common/assets.js +72 -14
  24. package/demo/config.js +28 -25
  25. package/demo/custom.js +20 -16
  26. package/demo/demo.less +107 -16
  27. package/demo/demo_uncompiled.js +33 -0
  28. package/demo/demo_utils.js +4 -3
  29. package/demo/front.js +12 -8
  30. package/demo/index.html +14 -17
  31. package/demo/input.js +30 -23
  32. package/demo/input_container.js +15 -11
  33. package/demo/load.js +5 -7
  34. package/demo/main.js +138 -49
  35. package/demo/search.js +40 -36
  36. package/demo/service_worker.js +1 -1
  37. package/demo/tooltip.js +16 -24
  38. package/dist/.csslintstamp +0 -0
  39. package/dist/controls.css +1 -1
  40. package/dist/controls.css.map +1 -1
  41. package/dist/demo.compiled.debug.js +165 -152
  42. package/dist/demo.compiled.debug.map +4 -4
  43. package/dist/demo.compiled.js +165 -152
  44. package/dist/demo.compiled.map +4 -4
  45. package/dist/demo.css +1 -1
  46. package/dist/demo.css.map +1 -1
  47. package/dist/deps.js +28 -11
  48. package/dist/receiver.compiled.debug.js +11 -11
  49. package/dist/receiver.compiled.debug.map +3 -3
  50. package/dist/receiver.compiled.js +11 -11
  51. package/dist/receiver.compiled.map +3 -3
  52. package/dist/shaka-player.compiled.debug.externs.js +232 -102
  53. package/dist/shaka-player.compiled.debug.js +189 -165
  54. package/dist/shaka-player.compiled.debug.map +4 -4
  55. package/dist/shaka-player.compiled.externs.js +232 -102
  56. package/dist/shaka-player.compiled.js +567 -553
  57. package/dist/shaka-player.compiled.map +4 -4
  58. package/dist/shaka-player.ui.debug.externs.js +338 -113
  59. package/dist/shaka-player.ui.debug.js +296 -276
  60. package/dist/shaka-player.ui.debug.map +4 -4
  61. package/dist/shaka-player.ui.externs.js +338 -113
  62. package/dist/shaka-player.ui.js +685 -671
  63. package/dist/shaka-player.ui.map +4 -4
  64. package/docs/tutorials/drm-config.md +1 -0
  65. package/docs/tutorials/fairplay.md +77 -0
  66. package/docs/tutorials/index.json +1 -0
  67. package/docs/tutorials/manifest-parser.md +1 -1
  68. package/docs/tutorials/ui-customization.md +5 -2
  69. package/docs/tutorials/ui.md +10 -4
  70. package/docs/tutorials/welcome.md +2 -2
  71. package/externs/mediasession.js +16 -1
  72. package/externs/shaka/net.js +14 -4
  73. package/externs/shaka/player.js +136 -90
  74. package/externs/tippy.js +30 -0
  75. package/externs/webkitmediakeys.js +4 -0
  76. package/karma.conf.js +30 -24
  77. package/lib/cast/cast_proxy.js +94 -23
  78. package/lib/cast/cast_receiver.js +8 -1
  79. package/lib/dash/content_protection.js +33 -1
  80. package/lib/dash/dash_parser.js +13 -5
  81. package/lib/hls/hls_parser.js +20 -4
  82. package/lib/media/drm_engine.js +29 -2
  83. package/lib/media/media_source_engine.js +9 -6
  84. package/lib/media/play_rate_controller.js +14 -3
  85. package/lib/media/playhead.js +8 -3
  86. package/lib/media/streaming_engine.js +68 -20
  87. package/lib/media/transmuxer.js +2 -2
  88. package/lib/media/video_wrapper.js +8 -3
  89. package/lib/net/data_uri_plugin.js +5 -2
  90. package/lib/net/http_fetch_plugin.js +3 -3
  91. package/lib/net/http_plugin_utils.js +2 -4
  92. package/lib/net/http_xhr_plugin.js +2 -2
  93. package/lib/net/networking_engine.js +1 -0
  94. package/lib/offline/offline_scheme.js +7 -2
  95. package/lib/player.js +18 -8
  96. package/lib/polyfill/input_event.js +7 -11
  97. package/lib/polyfill/mediasource.js +1 -1
  98. package/lib/polyfill/patchedmediakeys_apple.js +2 -72
  99. package/lib/text/cue.js +1 -1
  100. package/lib/text/ttml_text_parser.js +1 -1
  101. package/lib/util/config_utils.js +1 -0
  102. package/lib/util/error.js +45 -11
  103. package/lib/util/event_manager.js +211 -100
  104. package/lib/util/fairplay_utils.js +122 -0
  105. package/lib/util/i_releasable.js +1 -0
  106. package/lib/util/player_configuration.js +7 -0
  107. package/lib/util/pssh.js +41 -0
  108. package/lib/util/string_utils.js +42 -6
  109. package/lib/util/timer.js +1 -1
  110. package/package.json +33 -33
  111. package/roadmap.md +96 -0
  112. package/shaka-player.uncompiled.js +5 -1
  113. package/test/dash/dash_parser_content_protection_unit.js +35 -1
  114. package/test/dash/dash_parser_manifest_unit.js +60 -0
  115. package/test/hls/hls_live_unit.js +7 -1
  116. package/test/hls/hls_parser_unit.js +50 -0
  117. package/test/media/drm_engine_unit.js +10 -4
  118. package/test/media/media_source_engine_integration.js +12 -0
  119. package/test/media/media_source_engine_unit.js +3 -1
  120. package/test/media/playhead_unit.js +7 -5
  121. package/test/media/streaming_engine_unit.js +46 -1
  122. package/test/media/transmuxer_integration.js +13 -0
  123. package/test/net/data_uri_plugin_unit.js +13 -2
  124. package/test/net/http_plugin_unit.js +17 -12
  125. package/test/net/networking_engine_unit.js +17 -8
  126. package/test/offline/offline_scheme_unit.js +18 -8
  127. package/test/player_external.js +3 -2
  128. package/test/player_integration.js +94 -3
  129. package/test/player_src_equals_integration.js +2 -2
  130. package/test/player_unit.js +21 -8
  131. package/test/test/boot.js +20 -5
  132. package/test/test/externs/jasmine.js +48 -0
  133. package/test/test/util/expect_async_polyfill.js +145 -0
  134. package/test/test/util/fake_networking_engine.js +1 -0
  135. package/test/test/util/manifest_generator.js +2 -1
  136. package/test/test/util/test_scheme.js +34 -2
  137. package/test/test/util/ui_utils.js +41 -0
  138. package/test/test/util/util.js +16 -8
  139. package/test/ui/ui_unit.js +130 -4
  140. package/ui/audio_language_selection.js +17 -96
  141. package/ui/big_play_button.js +61 -0
  142. package/ui/controls.js +226 -335
  143. package/ui/controls.less +3 -5
  144. package/ui/element.js +24 -6
  145. package/ui/externs/ui.js +62 -9
  146. package/ui/fullscreen_button.js +2 -52
  147. package/ui/less/buttons.less +10 -16
  148. package/ui/less/containers.less +75 -20
  149. package/ui/less/general.less +18 -2
  150. package/ui/less/other_elements.less +2 -1
  151. package/ui/less/overflow_menu.less +11 -10
  152. package/ui/less/range_elements.less +12 -10
  153. package/ui/less/spinner.less +2 -0
  154. package/ui/overflow_menu.js +1 -1
  155. package/ui/pip_button.js +1 -1
  156. package/ui/{play_pause_button.js → play_button.js} +17 -47
  157. package/ui/presentation_time.js +17 -12
  158. package/ui/range_element.js +176 -0
  159. package/ui/resolution_selection.js +43 -106
  160. package/ui/seek_bar.js +204 -0
  161. package/ui/settings_menu.js +113 -0
  162. package/ui/small_play_button.js +73 -0
  163. package/ui/text_displayer.js +17 -14
  164. package/ui/text_selection.js +42 -107
  165. package/ui/ui.js +17 -40
  166. package/ui/volume_bar.js +40 -68
@@ -69,14 +69,28 @@ shaka.cast.CastProxy = function(video, player, receiverAppId) {
69
69
  /** @private {shaka.util.EventManager} */
70
70
  this.eventManager_ = null;
71
71
 
72
- /** @private {shaka.cast.CastSender} */
73
- this.sender_ = new shaka.cast.CastSender(
74
- receiverAppId,
75
- this.onCastStatusChanged_.bind(this),
76
- this.onFirstCastStateUpdate_.bind(this),
77
- this.onRemoteEvent_.bind(this),
78
- this.onResumeLocal_.bind(this),
79
- this.getInitState_.bind(this));
72
+ /** @private {string} */
73
+ this.receiverAppId_ = receiverAppId;
74
+
75
+ if (this.receiverAppId_) {
76
+ // TODO: This is temporary fix to get casting working.
77
+ // It masks the fact that we're at the moment unable to
78
+ // change the receiver id sucessfully.
79
+ // It relies on the assumption that changing one actual
80
+ // id to another is a very edgy use case (usually apps have
81
+ // only one receiver app).
82
+ // It unblocks casting in the world where UI can be configured,
83
+ // but it's hacky and must be changed to a permanent solution
84
+ // allowing to change Receiver App ID for reals.
85
+ /** @private {shaka.cast.CastSender} */
86
+ this.sender_ = new shaka.cast.CastSender(
87
+ receiverAppId,
88
+ () => this.onCastStatusChanged_(),
89
+ () => this.onFirstCastStateUpdate_(),
90
+ (targetName, event) => this.onRemoteEvent_(targetName, event),
91
+ () => this.onResumeLocal_(),
92
+ () => this.getInitState_());
93
+ }
80
94
 
81
95
  this.init_();
82
96
  };
@@ -191,6 +205,13 @@ shaka.cast.CastProxy.prototype.receiverName = function() {
191
205
  * @export
192
206
  */
193
207
  shaka.cast.CastProxy.prototype.cast = function() {
208
+ if (!this.sender_) {
209
+ throw new shaka.util.Error(
210
+ shaka.util.Error.Severity.RECOVERABLE,
211
+ shaka.util.Error.Category.CAST,
212
+ shaka.util.Error.Code.CAST_RECEIVER_APP_ID_MISSING);
213
+ }
214
+
194
215
  let initState = this.getInitState_();
195
216
 
196
217
  // TODO: transfer manually-selected tracks?
@@ -215,7 +236,9 @@ shaka.cast.CastProxy.prototype.cast = function() {
215
236
  * @export
216
237
  */
217
238
  shaka.cast.CastProxy.prototype.setAppData = function(appData) {
218
- this.sender_.setAppData(appData);
239
+ if (this.sender_) {
240
+ this.sender_.setAppData(appData);
241
+ }
219
242
  };
220
243
 
221
244
 
@@ -224,7 +247,44 @@ shaka.cast.CastProxy.prototype.setAppData = function(appData) {
224
247
  * @export
225
248
  */
226
249
  shaka.cast.CastProxy.prototype.suggestDisconnect = function() {
227
- this.sender_.showDisconnectDialog();
250
+ if (this.sender_) {
251
+ this.sender_.showDisconnectDialog();
252
+ }
253
+ };
254
+
255
+
256
+ /**
257
+ * @param {string} newAppId
258
+ * @export
259
+ */
260
+ shaka.cast.CastProxy.prototype.changeReceiverId = async function(newAppId) {
261
+ if (newAppId == this.receiverAppId_) {
262
+ // Nothing to change
263
+ return;
264
+ }
265
+
266
+ this.receiverAppId_ = newAppId;
267
+
268
+ // TODO: This code doesn't work correctly at the moment. Changing
269
+ // one working receiver id to another needs to be figured out.
270
+ // Destroy the old sender
271
+ if (this.sender_) {
272
+ this.sender_.forceDisconnect();
273
+ await this.sender_.destroy();
274
+ this.sender_ = null;
275
+ }
276
+
277
+
278
+ // Create the new one
279
+ this.sender_ = new shaka.cast.CastSender(
280
+ newAppId,
281
+ () => this.onCastStatusChanged_(),
282
+ () => this.onFirstCastStateUpdate_(),
283
+ (targetName, event) => this.onRemoteEvent_(targetName, event),
284
+ () => this.onResumeLocal_(),
285
+ () => this.getInitState_());
286
+
287
+ this.sender_.init();
228
288
  };
229
289
 
230
290
 
@@ -233,7 +293,9 @@ shaka.cast.CastProxy.prototype.suggestDisconnect = function() {
233
293
  * @export
234
294
  */
235
295
  shaka.cast.CastProxy.prototype.forceDisconnect = function() {
236
- this.sender_.forceDisconnect();
296
+ if (this.sender_) {
297
+ this.sender_.forceDisconnect();
298
+ }
237
299
  };
238
300
 
239
301
 
@@ -242,7 +304,9 @@ shaka.cast.CastProxy.prototype.forceDisconnect = function() {
242
304
  * @private
243
305
  */
244
306
  shaka.cast.CastProxy.prototype.init_ = function() {
245
- this.sender_.init();
307
+ if (this.sender_) {
308
+ this.sender_.init();
309
+ }
246
310
 
247
311
  this.eventManager_ = new shaka.util.EventManager();
248
312
 
@@ -360,6 +424,9 @@ shaka.cast.CastProxy.prototype.onFirstCastStateUpdate_ = function() {
360
424
  * @private
361
425
  */
362
426
  shaka.cast.CastProxy.prototype.onResumeLocal_ = function() {
427
+ goog.asserts.assert(this.sender_,
428
+ 'Cast sender should not be null!');
429
+
363
430
  // Transfer back the player state.
364
431
  shaka.cast.CastUtils.PlayerInitState.forEach(function(pair) {
365
432
  let getter = pair[0];
@@ -447,7 +514,8 @@ shaka.cast.CastProxy.prototype.videoProxyGet_ = function(name) {
447
514
 
448
515
  // If we are casting, but the first update has not come in yet, use local
449
516
  // values, but not local methods.
450
- if (this.sender_.isCasting() && !this.sender_.hasRemoteProperties()) {
517
+ if (this.sender_ && this.sender_.isCasting() &&
518
+ !this.sender_.hasRemoteProperties()) {
451
519
  let value = this.localVideo_[name];
452
520
  if (typeof value != 'function') {
453
521
  return value;
@@ -455,7 +523,7 @@ shaka.cast.CastProxy.prototype.videoProxyGet_ = function(name) {
455
523
  }
456
524
 
457
525
  // Use local values and methods if we are not casting.
458
- if (!this.sender_.isCasting()) {
526
+ if (!this.sender_ || !this.sender_.isCasting()) {
459
527
  let value = this.localVideo_[name];
460
528
  if (typeof value == 'function') {
461
529
  value = value.bind(this.localVideo_);
@@ -473,7 +541,7 @@ shaka.cast.CastProxy.prototype.videoProxyGet_ = function(name) {
473
541
  * @private
474
542
  */
475
543
  shaka.cast.CastProxy.prototype.videoProxySet_ = function(name, value) {
476
- if (!this.sender_.isCasting()) {
544
+ if (!this.sender_ || !this.sender_.isCasting()) {
477
545
  this.localVideo_[name] = value;
478
546
  return;
479
547
  }
@@ -487,7 +555,7 @@ shaka.cast.CastProxy.prototype.videoProxySet_ = function(name, value) {
487
555
  * @private
488
556
  */
489
557
  shaka.cast.CastProxy.prototype.videoProxyLocalEvent_ = function(event) {
490
- if (this.sender_.isCasting()) {
558
+ if (this.sender_ && this.sender_.isCasting()) {
491
559
  // Ignore any unexpected local events while casting. Events can still be
492
560
  // fired by the local video and Player when we unload() after the Cast
493
561
  // connection is complete.
@@ -523,20 +591,22 @@ shaka.cast.CastProxy.prototype.playerProxyGet_ = function(name) {
523
591
  if (name == 'getSharedConfiguration') {
524
592
  shaka.log.warning(
525
593
  'Can\'t share configuration across a network. Returning copy.');
526
- return this.sender_.get('player', 'getConfiguration');
594
+ return this.sender_ ?
595
+ this.sender_.get('player', 'getConfiguration') :
596
+ this.localPlayer_.getConfiguration();
527
597
  }
528
598
 
529
599
  if (name == 'getNetworkingEngine') {
530
600
  // Always returns a local instance, in case you need to make a request.
531
601
  // Issues a warning, in case you think you are making a remote request
532
602
  // or affecting remote filters.
533
- if (this.sender_.isCasting()) {
603
+ if (this.sender_ && this.sender_.isCasting()) {
534
604
  shaka.log.warning('NOTE: getNetworkingEngine() is always local!');
535
605
  }
536
606
  return this.localPlayer_.getNetworkingEngine.bind(this.localPlayer_);
537
607
  }
538
608
 
539
- if (this.sender_.isCasting()) {
609
+ if (this.sender_ && this.sender_.isCasting()) {
540
610
  // These methods are unavailable or otherwise stubbed during casting.
541
611
  if (name == 'getManifest' || name == 'drmInfo') {
542
612
  return function() {
@@ -564,7 +634,8 @@ shaka.cast.CastProxy.prototype.playerProxyGet_ = function(name) {
564
634
 
565
635
  // If we are casting, but the first update has not come in yet, use local
566
636
  // getters, but not local methods.
567
- if (this.sender_.isCasting() && !this.sender_.hasRemoteProperties()) {
637
+ if (this.sender_ && this.sender_.isCasting() &&
638
+ !this.sender_.hasRemoteProperties()) {
568
639
  if (shaka.cast.CastUtils.PlayerGetterMethods[name]) {
569
640
  let value = /** @type {Object} */(this.localPlayer_)[name];
570
641
  goog.asserts.assert(typeof value == 'function', 'only methods on Player');
@@ -573,7 +644,7 @@ shaka.cast.CastProxy.prototype.playerProxyGet_ = function(name) {
573
644
  }
574
645
 
575
646
  // Use local getters and methods if we are not casting.
576
- if (!this.sender_.isCasting()) {
647
+ if (!this.sender_ || !this.sender_.isCasting()) {
577
648
  let value = /** @type {Object} */(this.localPlayer_)[name];
578
649
  goog.asserts.assert(typeof value == 'function', 'only methods on Player');
579
650
  return value.bind(this.localPlayer_);
@@ -588,7 +659,7 @@ shaka.cast.CastProxy.prototype.playerProxyGet_ = function(name) {
588
659
  * @private
589
660
  */
590
661
  shaka.cast.CastProxy.prototype.playerProxyLocalEvent_ = function(event) {
591
- if (this.sender_.isCasting()) {
662
+ if (this.sender_ && this.sender_.isCasting()) {
592
663
  // Ignore any unexpected local events while casting.
593
664
  return;
594
665
  }
@@ -603,7 +674,7 @@ shaka.cast.CastProxy.prototype.playerProxyLocalEvent_ = function(event) {
603
674
  * @private
604
675
  */
605
676
  shaka.cast.CastProxy.prototype.onRemoteEvent_ = function(targetName, event) {
606
- goog.asserts.assert(this.sender_.isCasting(),
677
+ goog.asserts.assert(this.sender_ && this.sender_.isCasting(),
607
678
  'Should only receive remote events while casting');
608
679
  if (!this.sender_.isCasting()) {
609
680
  // Ignore any unexpected remote events.
@@ -39,7 +39,10 @@ goog.require('shaka.util.Timer');
39
39
  * local Player instance.
40
40
  * @param {!shaka.Player} player A local Player instance.
41
41
  * @param {function(Object)=} appDataCallback A callback to handle
42
- * application-specific data passed from the sender.
42
+ * application-specific data passed from the sender. This can come either
43
+ * from a Shaka-based sender through CastProxy.setAppData, or from a
44
+ * sender using the customData field of the LOAD message of the standard
45
+ * Cast message namespace. It can also be null if no such data is sent.
43
46
  * @param {function(string):string=} contentIdCallback A callback to
44
47
  * retrieve manifest URI from the provided content id.
45
48
  * @implements {shaka.util.IDestroyable}
@@ -685,6 +688,10 @@ shaka.cast.CastReceiver.prototype.onGenericMessage_ = function(event) {
685
688
  let currentTime = message['currentTime'];
686
689
  let assetUri = this.contentIdCallback_(contentId);
687
690
  let autoplay = message['autoplay'] || true;
691
+ let customData = mediaInfo['customData'];
692
+
693
+ this.appDataCallback_(customData);
694
+
688
695
  if (autoplay) {
689
696
  this.video_.autoplay = true;
690
697
  }
@@ -21,6 +21,7 @@ goog.require('goog.asserts');
21
21
  goog.require('shaka.log');
22
22
  goog.require('shaka.util.Error');
23
23
  goog.require('shaka.util.ManifestParserUtils');
24
+ goog.require('shaka.util.Pssh');
24
25
  goog.require('shaka.util.Uint8ArrayUtils');
25
26
  goog.require('shaka.util.XmlUtils');
26
27
 
@@ -442,6 +443,36 @@ shaka.dash.ContentProtection.getPlayReadyLicenseUrl = function(element) {
442
443
  };
443
444
 
444
445
 
446
+ /**
447
+ * Gets a PlayReady initData from a content protection element
448
+ * containing a PlayReady Pro Object
449
+ *
450
+ * @param {shaka.dash.ContentProtection.Element} element
451
+ * @return {?Array.<shaka.extern.InitDataOverride>}
452
+ * @private
453
+ */
454
+ shaka.dash.ContentProtection.getInitDataFromPro_ = function(element) {
455
+ const proNode = shaka.util.XmlUtils.findChildNS(
456
+ element.node, 'urn:microsoft:playready', 'pro');
457
+ if (!proNode) {
458
+ return null;
459
+ }
460
+ const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
461
+ const data = Uint8ArrayUtils.fromBase64(proNode.textContent);
462
+ const systemId = new Uint8Array([
463
+ 0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
464
+ 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
465
+ ]);
466
+ const pssh = shaka.util.Pssh.createPssh(data, systemId);
467
+ return [
468
+ {
469
+ initData: pssh,
470
+ initDataType: 'cenc',
471
+ keyId: element.keyId,
472
+ },
473
+ ];
474
+ };
475
+
445
476
  /**
446
477
  * Creates DrmInfo objects from the given element.
447
478
  *
@@ -468,7 +499,8 @@ shaka.dash.ContentProtection.convertElements_ = function(
468
499
  !element.init || element.init.length,
469
500
  'Init data must be null or non-empty.');
470
501
 
471
- const initData = element.init || defaultInit;
502
+ const proInitData = ContentProtection.getInitDataFromPro_(element);
503
+ const initData = element.init || defaultInit || proInitData;
472
504
  const info = ManifestParserUtils.createDrmInfo(keySystem, initData);
473
505
  const licenseParser = licenseUrlParsers.get(keySystem);
474
506
  if (licenseParser) {
@@ -509,7 +509,7 @@ shaka.dash.DashParser.prototype.processManifest_ =
509
509
  manifestBaseUris, uris);
510
510
 
511
511
  let ignoreMinBufferTime = this.config_.dash.ignoreMinBufferTime;
512
- let minBufferTime;
512
+ let minBufferTime = 0;
513
513
  if (!ignoreMinBufferTime) {
514
514
  minBufferTime =
515
515
  XmlUtils.parseAttr(mpd, 'minBufferTime', XmlUtils.parseDuration);
@@ -1016,7 +1016,7 @@ shaka.dash.DashParser.prototype.parseAdaptationSet_ = function(context, elem) {
1016
1016
  case 'subtitle':
1017
1017
  kind = value;
1018
1018
  break;
1019
- }
1019
+ }
1020
1020
  }
1021
1021
  }
1022
1022
 
@@ -1038,12 +1038,12 @@ shaka.dash.DashParser.prototype.parseAdaptationSet_ = function(context, elem) {
1038
1038
  let closedCaptions = new Map();
1039
1039
  for (const prop of accessibilities) {
1040
1040
  let schemeId = prop.getAttribute('schemeIdUri');
1041
+ let value = prop.getAttribute('value');
1041
1042
  if (schemeId == 'urn:scte:dash:cc:cea-608:2015' ||
1042
1043
  schemeId == 'urn:scte:dash:cc:cea-708:2015') {
1043
1044
  let channelId = 1;
1044
- let closedCaptionsValue = prop.getAttribute('value');
1045
- if (closedCaptionsValue != null) {
1046
- closedCaptionsValue.split(';').forEach((captionStr) => {
1045
+ if (value != null) {
1046
+ value.split(';').forEach((captionStr) => {
1047
1047
  let channel;
1048
1048
  let language;
1049
1049
  // Some closed caption descriptions have channel number and language,
@@ -1075,6 +1075,14 @@ shaka.dash.DashParser.prototype.parseAdaptationSet_ = function(context, elem) {
1075
1075
  // 'CC1' as channel id and 'und' as language info.
1076
1076
  closedCaptions.set('CC1', 'und');
1077
1077
  }
1078
+ } else if (schemeId == 'urn:mpeg:dash:role:2011') {
1079
+ // See DASH IOP 3.9.2 Table 4.
1080
+ if (value != null) {
1081
+ roleValues.push(value);
1082
+ if (value == 'captions') {
1083
+ kind = ManifestParserUtils.TextStreamKind.CLOSED_CAPTION;
1084
+ }
1085
+ }
1078
1086
  }
1079
1087
  }
1080
1088
 
@@ -539,8 +539,15 @@ shaka.hls.HlsParser.prototype.createPeriod_ = async function(playlist) {
539
539
  return type == 'SUBTITLES';
540
540
  }.bind(this));
541
541
 
542
- let textStreamPromises = textStreamTags.map(function(tag) {
543
- return this.createTextStream_(tag, playlist);
542
+ let textStreamPromises = textStreamTags.map(async function(tag) {
543
+ try {
544
+ return await this.createTextStream_(tag, playlist);
545
+ } catch (e) {
546
+ if (this.config_.hls.ignoreTextStreamFailures) {
547
+ return null;
548
+ }
549
+ throw e;
550
+ }
544
551
  }.bind(this));
545
552
 
546
553
  const closedCaptionsTags = mediaTags.filter((tag) => {
@@ -567,7 +574,7 @@ shaka.hls.HlsParser.prototype.createPeriod_ = async function(playlist) {
567
574
  return {
568
575
  startTime: 0,
569
576
  variants: variants,
570
- textStreams: textStreams,
577
+ textStreams: textStreams.filter((t) => t != null),
571
578
  };
572
579
  };
573
580
 
@@ -660,7 +667,9 @@ shaka.hls.HlsParser.prototype.createVariantsForTag_ =
660
667
  // attached to the stream.
661
668
  const textStreamInfo =
662
669
  this.mediaTagsToStreamInfosMap_.get(textTags[0].id);
663
- textStreamInfo.stream.codecs = textCodecs;
670
+ if (textStreamInfo) {
671
+ textStreamInfo.stream.codecs = textCodecs;
672
+ }
664
673
  }
665
674
  }
666
675
 
@@ -1672,6 +1681,13 @@ shaka.hls.HlsParser.prototype.getStartTime_ = async function(
1672
1681
  return this.getStartTimeFromTextSegment_(mimeType, codecs, response.data);
1673
1682
  }
1674
1683
 
1684
+ if (mimeType == 'video/webm') {
1685
+ shaka.log.warning(
1686
+ 'Hls+WebM combination is not supported at the moment. Skipping.');
1687
+ return null;
1688
+ }
1689
+
1690
+
1675
1691
  // TODO(vaage): Add support for additional formats.
1676
1692
  //
1677
1693
  // Formats:
@@ -22,6 +22,7 @@ goog.require('shaka.log');
22
22
  goog.require('shaka.net.NetworkingEngine');
23
23
  goog.require('shaka.util.Error');
24
24
  goog.require('shaka.util.EventManager');
25
+ goog.require('shaka.util.FairPlayUtils');
25
26
  goog.require('shaka.util.FakeEvent');
26
27
  goog.require('shaka.util.IDestroyable');
27
28
  goog.require('shaka.util.Iterables');
@@ -1115,6 +1116,29 @@ shaka.media.DrmEngine.prototype.createTemporarySession_ =
1115
1116
  };
1116
1117
  this.activeSessions_.set(session, metadata);
1117
1118
 
1119
+ try {
1120
+ if (this.config_.initDataTransform) {
1121
+ initData = this.config_.initDataTransform(initData);
1122
+ } else if (this.keySystem().startsWith('com.apple.fps')) {
1123
+ const cert = this.currentDrmInfo_.serverCertificate;
1124
+ const contentId =
1125
+ shaka.util.FairPlayUtils.defaultGetContentId(initData);
1126
+ initData = shaka.util.FairPlayUtils.initDataTransform(
1127
+ initData, contentId, cert);
1128
+ }
1129
+ } catch (error) {
1130
+ let shakaError = error;
1131
+ if (!(error instanceof shaka.util.Error)) {
1132
+ shakaError = new shaka.util.Error(
1133
+ shaka.util.Error.Severity.CRITICAL,
1134
+ shaka.util.Error.Category.DRM,
1135
+ shaka.util.Error.Code.INIT_DATA_TRANSFORM_ERROR,
1136
+ error);
1137
+ }
1138
+ this.onError_(shakaError);
1139
+ return;
1140
+ }
1141
+
1118
1142
  session.generateRequest(initDataType, initData.buffer).catch((error) => {
1119
1143
  if (this.isDestroying_) { return; }
1120
1144
 
@@ -1186,13 +1210,15 @@ shaka.media.DrmEngine.prototype.sendLicenseRequest_ = function(event) {
1186
1210
  request.body = event.message;
1187
1211
  request.method = 'POST';
1188
1212
  request.licenseRequestType = event.messageType;
1213
+ request.sessionId = session.sessionId;
1189
1214
  // NOTE: allowCrossSiteCredentials can be set in a request filter.
1190
1215
 
1191
1216
  if (this.currentDrmInfo_.keySystem == 'com.microsoft.playready' ||
1192
1217
  this.currentDrmInfo_.keySystem == 'com.chromecast.playready') {
1193
1218
  this.unpackPlayReadyRequest_(request);
1194
1219
  }
1195
- if (this.currentDrmInfo_.keySystem.startsWith('com.apple.fps')) {
1220
+ if (this.currentDrmInfo_.keySystem.startsWith('com.apple.fps') &&
1221
+ this.config_.fairPlayTransform) {
1196
1222
  this.formatFairPlayRequest_(request);
1197
1223
  }
1198
1224
 
@@ -1200,7 +1226,8 @@ shaka.media.DrmEngine.prototype.sendLicenseRequest_ = function(event) {
1200
1226
  .then(function(response) {
1201
1227
  if (this.isDestroying_) { return Promise.reject(); }
1202
1228
 
1203
- if (this.currentDrmInfo_.keySystem.startsWith('com.apple.fps')) {
1229
+ if (this.currentDrmInfo_.keySystem.startsWith('com.apple.fps') &&
1230
+ this.config_.fairPlayTransform) {
1204
1231
  this.parseFairPlayResponse_(response);
1205
1232
  }
1206
1233
 
@@ -241,7 +241,6 @@ shaka.media.MediaSourceEngine.prototype.destroy = function() {
241
241
 
242
242
  // The rest will be rejected silently if possible.
243
243
  for (let i = 1; i < q.length; ++i) {
244
- q[i].p.catch(Functional.noop);
245
244
  q[i].p.reject();
246
245
  }
247
246
  }
@@ -705,9 +704,13 @@ shaka.media.MediaSourceEngine.prototype.setStreamProperties = function(
705
704
  */
706
705
  shaka.media.MediaSourceEngine.prototype.endOfStream = function(reason) {
707
706
  return this.enqueueBlockingOperation_(function() {
708
- // Chrome won't let us pass undefined, but it will let us omit the
709
- // argument. Firefox does not have this problem.
710
- // TODO: File a bug about this.
707
+ // If endOfStream() has already been called on the media source,
708
+ // don't call it again.
709
+ if (this.ended()) {
710
+ return;
711
+ }
712
+ // Tizen and IE11 won't let us pass undefined, but it will let us omit the
713
+ // argument.
711
714
  if (reason) {
712
715
  this.mediaSource_.endOfStream(reason);
713
716
  } else {
@@ -1016,7 +1019,7 @@ shaka.media.MediaSourceEngine.prototype.enqueueBlockingOperation_ =
1016
1019
  }
1017
1020
 
1018
1021
  return ret;
1019
- }.bind(this), function() {
1022
+ }.bind(this), function(error) {
1020
1023
  // One of the waiters failed, which means we've been destroyed.
1021
1024
  goog.asserts.assert(this.destroyed_, 'Should be destroyed by now');
1022
1025
  // We haven't popped from the queue. Canceled waiters have been removed by
@@ -1036,7 +1039,7 @@ shaka.media.MediaSourceEngine.prototype.enqueueBlockingOperation_ =
1036
1039
  }
1037
1040
  }
1038
1041
  }
1039
- return Promise.reject();
1042
+ throw error;
1040
1043
  }.bind(this));
1041
1044
  };
1042
1045
 
@@ -113,11 +113,22 @@ shaka.media.PlayRateController = class {
113
113
  const rate = this.calculateCurrentRate_();
114
114
 
115
115
  if (rate >= 0) {
116
- this.applyRate_(rate);
117
- return;
116
+ try {
117
+ this.applyRate_(rate);
118
+ return;
119
+ } catch (e) {
120
+ // Fall through to the next clause.
121
+ //
122
+ // Fast forward is accomplished through setting video.playbackRate.
123
+ // If the play rate value is not supported by the browser (too big),
124
+ // the browsers will throw.
125
+ // Use this as a cue to fall back to fast forward through repeated
126
+ // seeking, which is what we do for rewind as well.
127
+ }
118
128
  }
119
129
 
120
- // When moving backwards, set the playback rate to 0 so that we can manually
130
+ // When moving backwards or forwards in large steps,
131
+ // set the playback rate to 0 so that we can manually
121
132
  // seek backwards with out fighting the playhead.
122
133
  this.timer_.tickEvery(this.pollRate_);
123
134
  this.applyRate_(0);
@@ -86,9 +86,14 @@ shaka.media.SrcEqualsPlayhead = class {
86
86
  // We listen for the loaded-metadata-event so that we know when we can
87
87
  // interact with |currentTime|.
88
88
  const onLoaded = () => {
89
- this.started_ = true;
90
-
91
- if (this.startTime_ != null) {
89
+ if (this.startTime_ == null) {
90
+ this.started_ = true;
91
+ } else {
92
+ // Startup is complete only when the video element acknowledges the
93
+ // seek.
94
+ this.eventManager_.listenOnce(this.mediaElement_, 'seeking', () => {
95
+ this.started_ = true;
96
+ });
92
97
  this.mediaElement_.currentTime = this.startTime_;
93
98
  }
94
99
  };