1. 1 : /**
  2. 2 : * @file picture-in-picture-toggle.js
  3. 3 : */
  4. 4 : import Button from '../button.js';
  5. 5 : import Component from '../component.js';
  6. 6 : import document from 'global/document';
  7. 7 : import window from 'global/window';
  8. 8 :
  9. 9 : /**
  10. 10 : * Toggle Picture-in-Picture mode
  11. 11 : *
  12. 12 : * @extends Button
  13. 13 : */
  14. 14 : class PictureInPictureToggle extends Button {
  15. 15 :
  16. 16 : /**
  17. 17 : * Creates an instance of this class.
  18. 18 : *
  19. 19 : * @param { import('./player').default } player
  20. 20 : * The `Player` that this class should be attached to.
  21. 21 : *
  22. 22 : * @param {Object} [options]
  23. 23 : * The key/value store of player options.
  24. 24 : *
  25. 25 : * @listens Player#enterpictureinpicture
  26. 26 : * @listens Player#leavepictureinpicture
  27. 27 : */
  28. 28 : constructor(player, options) {
  29. 29 : super(player, options);
  30. 30 : this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], (e) => this.handlePictureInPictureChange(e));
  31. 31 : this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], (e) => this.handlePictureInPictureEnabledChange(e));
  32. 32 :
  33. 33 : this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => {
  34. 34 : // This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
  35. 35 : const isSourceAudio = player.currentType().substring(0, 5) === 'audio';
  36. 36 :
  37. 37 : if (isSourceAudio || player.audioPosterMode() || player.audioOnlyMode()) {
  38. 38 : if (player.isInPictureInPicture()) {
  39. 39 : player.exitPictureInPicture();
  40. 40 : }
  41. 41 : this.hide();
  42. 42 : } else {
  43. 43 : this.show();
  44. 44 : }
  45. 45 :
  46. 46 : });
  47. 47 :
  48. 48 : // TODO: Deactivate button on player emptied event.
  49. 49 : this.disable();
  50. 50 : }
  51. 51 :
  52. 52 : /**
  53. 53 : * Builds the default DOM `className`.
  54. 54 : *
  55. 55 : * @return {string}
  56. 56 : * The DOM `className` for this object.
  57. 57 : */
  58. 58 : buildCSSClass() {
  59. 59 : return `vjs-picture-in-picture-control ${super.buildCSSClass()}`;
  60. 60 : }
  61. 61 :
  62. 62 : /**
  63. 63 : * Enables or disables button based on availability of a Picture-In-Picture mode.
  64. 64 : *
  65. 65 : * Enabled if
  66. 66 : * - `player.options().enableDocumentPictureInPicture` is true and
  67. 67 : * window.documentPictureInPicture is available; or
  68. 68 : * - `player.disablePictureInPicture()` is false and
  69. 69 : * element.requestPictureInPicture is available
  70. 70 : */
  71. 71 : handlePictureInPictureEnabledChange() {
  72. 72 : if (
  73. 73 : (document.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false) ||
  74. 74 : (this.player_.options_.enableDocumentPictureInPicture && 'documentPictureInPicture' in window)
  75. 75 : ) {
  76. 76 : this.enable();
  77. 77 : } else {
  78. 78 : this.disable();
  79. 79 : }
  80. 80 : }
  81. 81 :
  82. 82 : /**
  83. 83 : * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
  84. 84 : *
  85. 85 : * @param {Event} [event]
  86. 86 : * The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
  87. 87 : * called.
  88. 88 : *
  89. 89 : * @listens Player#enterpictureinpicture
  90. 90 : * @listens Player#leavepictureinpicture
  91. 91 : */
  92. 92 : handlePictureInPictureChange(event) {
  93. 93 : if (this.player_.isInPictureInPicture()) {
  94. 94 : this.controlText('Exit Picture-in-Picture');
  95. 95 : } else {
  96. 96 : this.controlText('Picture-in-Picture');
  97. 97 : }
  98. 98 : this.handlePictureInPictureEnabledChange();
  99. 99 : }
  100. 100 :
  101. 101 : /**
  102. 102 : * This gets called when an `PictureInPictureToggle` is "clicked". See
  103. 103 : * {@link ClickableComponent} for more detailed information on what a click can be.
  104. 104 : *
  105. 105 : * @param {Event} [event]
  106. 106 : * The `keydown`, `tap`, or `click` event that caused this function to be
  107. 107 : * called.
  108. 108 : *
  109. 109 : * @listens tap
  110. 110 : * @listens click
  111. 111 : */
  112. 112 : handleClick(event) {
  113. 113 : if (!this.player_.isInPictureInPicture()) {
  114. 114 : this.player_.requestPictureInPicture();
  115. 115 : } else {
  116. 116 : this.player_.exitPictureInPicture();
  117. 117 : }
  118. 118 : }
  119. 119 :
  120. 120 : }
  121. 121 :
  122. 122 : /**
  123. 123 : * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
  124. 124 : *
  125. 125 : * @type {string}
  126. 126 : * @protected
  127. 127 : */
  128. 128 : PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
  129. 129 :
  130. 130 : Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
  131. 131 : export default PictureInPictureToggle;