1. 1 : /**
  2. 2 : * @file resize-manager.js
  3. 3 : */
  4. 4 : import window from 'global/window';
  5. 5 : import { debounce } from './utils/fn.js';
  6. 6 : import * as Events from './utils/events.js';
  7. 7 : import mergeOptions from './utils/merge-options.js';
  8. 8 : import Component from './component.js';
  9. 9 :
  10. 10 : /**
  11. 11 : * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
  12. 12 : *
  13. 13 : * It'll either create an iframe and use a debounced resize handler on it or use the new {@link https://wicg.github.io/ResizeObserver/|ResizeObserver}.
  14. 14 : *
  15. 15 : * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
  16. 16 : * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
  17. 17 : * @example <caption>How to disable the resize manager</caption>
  18. 18 : * const player = videojs('#vid', {
  19. 19 : * resizeManager: false
  20. 20 : * });
  21. 21 : *
  22. 22 : * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
  23. 23 : *
  24. 24 : * @extends Component
  25. 25 : */
  26. 26 : class ResizeManager extends Component {
  27. 27 :
  28. 28 : /**
  29. 29 : * Create the ResizeManager.
  30. 30 : *
  31. 31 : * @param {Object} player
  32. 32 : * The `Player` that this class should be attached to.
  33. 33 : *
  34. 34 : * @param {Object} [options]
  35. 35 : * The key/value store of ResizeManager options.
  36. 36 : *
  37. 37 : * @param {Object} [options.ResizeObserver]
  38. 38 : * A polyfill for ResizeObserver can be passed in here.
  39. 39 : * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
  40. 40 : */
  41. 41 : constructor(player, options) {
  42. 42 : let RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window.ResizeObserver;
  43. 43 :
  44. 44 : // if `null` was passed, we want to disable the ResizeObserver
  45. 45 : if (options.ResizeObserver === null) {
  46. 46 : RESIZE_OBSERVER_AVAILABLE = false;
  47. 47 : }
  48. 48 :
  49. 49 : // Only create an element when ResizeObserver isn't available
  50. 50 : const options_ = mergeOptions({
  51. 51 : createEl: !RESIZE_OBSERVER_AVAILABLE,
  52. 52 : reportTouchActivity: false
  53. 53 : }, options);
  54. 54 :
  55. 55 : super(player, options_);
  56. 56 :
  57. 57 : this.ResizeObserver = options.ResizeObserver || window.ResizeObserver;
  58. 58 : this.loadListener_ = null;
  59. 59 : this.resizeObserver_ = null;
  60. 60 : this.debouncedHandler_ = debounce(() => {
  61. 61 : this.resizeHandler();
  62. 62 : }, 100, false, this);
  63. 63 :
  64. 64 : if (RESIZE_OBSERVER_AVAILABLE) {
  65. 65 : this.resizeObserver_ = new this.ResizeObserver(this.debouncedHandler_);
  66. 66 : this.resizeObserver_.observe(player.el());
  67. 67 :
  68. 68 : } else {
  69. 69 : this.loadListener_ = () => {
  70. 70 : if (!this.el_ || !this.el_.contentWindow) {
  71. 71 : return;
  72. 72 : }
  73. 73 :
  74. 74 : const debouncedHandler_ = this.debouncedHandler_;
  75. 75 : let unloadListener_ = this.unloadListener_ = function() {
  76. 76 : Events.off(this, 'resize', debouncedHandler_);
  77. 77 : Events.off(this, 'unload', unloadListener_);
  78. 78 :
  79. 79 : unloadListener_ = null;
  80. 80 : };
  81. 81 :
  82. 82 : // safari and edge can unload the iframe before resizemanager dispose
  83. 83 : // we have to dispose of event handlers correctly before that happens
  84. 84 : Events.on(this.el_.contentWindow, 'unload', unloadListener_);
  85. 85 : Events.on(this.el_.contentWindow, 'resize', debouncedHandler_);
  86. 86 : };
  87. 87 :
  88. 88 : this.one('load', this.loadListener_);
  89. 89 : }
  90. 90 : }
  91. 91 :
  92. 92 : createEl() {
  93. 93 : return super.createEl('iframe', {
  94. 94 : className: 'vjs-resize-manager',
  95. 95 : tabIndex: -1
  96. 96 : }, {
  97. 97 : 'aria-hidden': 'true'
  98. 98 : });
  99. 99 : }
  100. 100 :
  101. 101 : /**
  102. 102 : * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
  103. 103 : *
  104. 104 : * @fires Player#playerresize
  105. 105 : */
  106. 106 : resizeHandler() {
  107. 107 : /**
  108. 108 : * Called when the player size has changed
  109. 109 : *
  110. 110 : * @event Player#playerresize
  111. 111 : * @type {EventTarget~Event}
  112. 112 : */
  113. 113 : // make sure player is still around to trigger
  114. 114 : // prevents this from causing an error after dispose
  115. 115 : if (!this.player_ || !this.player_.trigger) {
  116. 116 : return;
  117. 117 : }
  118. 118 :
  119. 119 : this.player_.trigger('playerresize');
  120. 120 : }
  121. 121 :
  122. 122 : dispose() {
  123. 123 : if (this.debouncedHandler_) {
  124. 124 : this.debouncedHandler_.cancel();
  125. 125 : }
  126. 126 :
  127. 127 : if (this.resizeObserver_) {
  128. 128 : if (this.player_.el()) {
  129. 129 : this.resizeObserver_.unobserve(this.player_.el());
  130. 130 : }
  131. 131 : this.resizeObserver_.disconnect();
  132. 132 : }
  133. 133 :
  134. 134 : if (this.loadListener_) {
  135. 135 : this.off('load', this.loadListener_);
  136. 136 : }
  137. 137 :
  138. 138 : if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
  139. 139 : this.unloadListener_.call(this.el_.contentWindow);
  140. 140 : }
  141. 141 :
  142. 142 : this.ResizeObserver = null;
  143. 143 : this.resizeObserver = null;
  144. 144 : this.debouncedHandler_ = null;
  145. 145 : this.loadListener_ = null;
  146. 146 : super.dispose();
  147. 147 : }
  148. 148 :
  149. 149 : }
  150. 150 :
  151. 151 : Component.registerComponent('ResizeManager', ResizeManager);
  152. 152 : export default ResizeManager;