1. 1 : /**
  2. 2 : * @file progress-control.js
  3. 3 : */
  4. 4 : import Component from '../../component.js';
  5. 5 : import * as Dom from '../../utils/dom.js';
  6. 6 : import {clamp} from '../../utils/num.js';
  7. 7 : import {bind_, throttle, UPDATE_REFRESH_INTERVAL} from '../../utils/fn.js';
  8. 8 : import {silencePromise} from '../../utils/promise';
  9. 9 :
  10. 10 : import './seek-bar.js';
  11. 11 :
  12. 12 : /**
  13. 13 : * The Progress Control component contains the seek bar, load progress,
  14. 14 : * and play progress.
  15. 15 : *
  16. 16 : * @extends Component
  17. 17 : */
  18. 18 : class ProgressControl extends Component {
  19. 19 :
  20. 20 : /**
  21. 21 : * Creates an instance of this class.
  22. 22 : *
  23. 23 : * @param { import('../../player').default } player
  24. 24 : * The `Player` that this class should be attached to.
  25. 25 : *
  26. 26 : * @param {Object} [options]
  27. 27 : * The key/value store of player options.
  28. 28 : */
  29. 29 : constructor(player, options) {
  30. 30 : super(player, options);
  31. 31 : this.handleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
  32. 32 : this.throttledHandleMouseSeek = throttle(bind_(this, this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
  33. 33 : this.handleMouseUpHandler_ = (e) => this.handleMouseUp(e);
  34. 34 : this.handleMouseDownHandler_ = (e) => this.handleMouseDown(e);
  35. 35 :
  36. 36 : this.enable();
  37. 37 : }
  38. 38 :
  39. 39 : /**
  40. 40 : * Create the `Component`'s DOM element
  41. 41 : *
  42. 42 : * @return {Element}
  43. 43 : * The element that was created.
  44. 44 : */
  45. 45 : createEl() {
  46. 46 : return super.createEl('div', {
  47. 47 : className: 'vjs-progress-control vjs-control'
  48. 48 : });
  49. 49 : }
  50. 50 :
  51. 51 : /**
  52. 52 : * When the mouse moves over the `ProgressControl`, the pointer position
  53. 53 : * gets passed down to the `MouseTimeDisplay` component.
  54. 54 : *
  55. 55 : * @param {Event} event
  56. 56 : * The `mousemove` event that caused this function to run.
  57. 57 : *
  58. 58 : * @listen mousemove
  59. 59 : */
  60. 60 : handleMouseMove(event) {
  61. 61 : const seekBar = this.getChild('seekBar');
  62. 62 :
  63. 63 : if (!seekBar) {
  64. 64 : return;
  65. 65 : }
  66. 66 :
  67. 67 : const playProgressBar = seekBar.getChild('playProgressBar');
  68. 68 : const mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
  69. 69 :
  70. 70 : if (!playProgressBar && !mouseTimeDisplay) {
  71. 71 : return;
  72. 72 : }
  73. 73 :
  74. 74 : const seekBarEl = seekBar.el();
  75. 75 : const seekBarRect = Dom.findPosition(seekBarEl);
  76. 76 : let seekBarPoint = Dom.getPointerPosition(seekBarEl, event).x;
  77. 77 :
  78. 78 : // The default skin has a gap on either side of the `SeekBar`. This means
  79. 79 : // that it's possible to trigger this behavior outside the boundaries of
  80. 80 : // the `SeekBar`. This ensures we stay within it at all times.
  81. 81 : seekBarPoint = clamp(seekBarPoint, 0, 1);
  82. 82 :
  83. 83 : if (mouseTimeDisplay) {
  84. 84 : mouseTimeDisplay.update(seekBarRect, seekBarPoint);
  85. 85 : }
  86. 86 :
  87. 87 : if (playProgressBar) {
  88. 88 : playProgressBar.update(seekBarRect, seekBar.getProgress());
  89. 89 : }
  90. 90 :
  91. 91 : }
  92. 92 :
  93. 93 : /**
  94. 94 : * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
  95. 95 : *
  96. 96 : * @method ProgressControl#throttledHandleMouseSeek
  97. 97 : * @param {Event} event
  98. 98 : * The `mousemove` event that caused this function to run.
  99. 99 : *
  100. 100 : * @listen mousemove
  101. 101 : * @listen touchmove
  102. 102 : */
  103. 103 :
  104. 104 : /**
  105. 105 : * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
  106. 106 : *
  107. 107 : * @param {Event} event
  108. 108 : * `mousedown` or `touchstart` event that triggered this function
  109. 109 : *
  110. 110 : * @listens mousemove
  111. 111 : * @listens touchmove
  112. 112 : */
  113. 113 : handleMouseSeek(event) {
  114. 114 : const seekBar = this.getChild('seekBar');
  115. 115 :
  116. 116 : if (seekBar) {
  117. 117 : seekBar.handleMouseMove(event);
  118. 118 : }
  119. 119 : }
  120. 120 :
  121. 121 : /**
  122. 122 : * Are controls are currently enabled for this progress control.
  123. 123 : *
  124. 124 : * @return {boolean}
  125. 125 : * true if controls are enabled, false otherwise
  126. 126 : */
  127. 127 : enabled() {
  128. 128 : return this.enabled_;
  129. 129 : }
  130. 130 :
  131. 131 : /**
  132. 132 : * Disable all controls on the progress control and its children
  133. 133 : */
  134. 134 : disable() {
  135. 135 : this.children().forEach((child) => child.disable && child.disable());
  136. 136 :
  137. 137 : if (!this.enabled()) {
  138. 138 : return;
  139. 139 : }
  140. 140 :
  141. 141 : this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
  142. 142 : this.off(this.el_, 'mousemove', this.handleMouseMove);
  143. 143 :
  144. 144 : this.removeListenersAddedOnMousedownAndTouchstart();
  145. 145 :
  146. 146 : this.addClass('disabled');
  147. 147 :
  148. 148 : this.enabled_ = false;
  149. 149 :
  150. 150 : // Restore normal playback state if controls are disabled while scrubbing
  151. 151 : if (this.player_.scrubbing()) {
  152. 152 : const seekBar = this.getChild('seekBar');
  153. 153 :
  154. 154 : this.player_.scrubbing(false);
  155. 155 :
  156. 156 : if (seekBar.videoWasPlaying) {
  157. 157 : silencePromise(this.player_.play());
  158. 158 : }
  159. 159 : }
  160. 160 : }
  161. 161 :
  162. 162 : /**
  163. 163 : * Enable all controls on the progress control and its children
  164. 164 : */
  165. 165 : enable() {
  166. 166 : this.children().forEach((child) => child.enable && child.enable());
  167. 167 :
  168. 168 : if (this.enabled()) {
  169. 169 : return;
  170. 170 : }
  171. 171 :
  172. 172 : this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
  173. 173 : this.on(this.el_, 'mousemove', this.handleMouseMove);
  174. 174 : this.removeClass('disabled');
  175. 175 :
  176. 176 : this.enabled_ = true;
  177. 177 : }
  178. 178 :
  179. 179 : /**
  180. 180 : * Cleanup listeners after the user finishes interacting with the progress controls
  181. 181 : */
  182. 182 : removeListenersAddedOnMousedownAndTouchstart() {
  183. 183 : const doc = this.el_.ownerDocument;
  184. 184 :
  185. 185 : this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
  186. 186 : this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
  187. 187 : this.off(doc, 'mouseup', this.handleMouseUpHandler_);
  188. 188 : this.off(doc, 'touchend', this.handleMouseUpHandler_);
  189. 189 : }
  190. 190 :
  191. 191 : /**
  192. 192 : * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
  193. 193 : *
  194. 194 : * @param {Event} event
  195. 195 : * `mousedown` or `touchstart` event that triggered this function
  196. 196 : *
  197. 197 : * @listens mousedown
  198. 198 : * @listens touchstart
  199. 199 : */
  200. 200 : handleMouseDown(event) {
  201. 201 : const doc = this.el_.ownerDocument;
  202. 202 : const seekBar = this.getChild('seekBar');
  203. 203 :
  204. 204 : if (seekBar) {
  205. 205 : seekBar.handleMouseDown(event);
  206. 206 : }
  207. 207 :
  208. 208 : this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
  209. 209 : this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
  210. 210 : this.on(doc, 'mouseup', this.handleMouseUpHandler_);
  211. 211 : this.on(doc, 'touchend', this.handleMouseUpHandler_);
  212. 212 : }
  213. 213 :
  214. 214 : /**
  215. 215 : * Handle `mouseup` or `touchend` events on the `ProgressControl`.
  216. 216 : *
  217. 217 : * @param {Event} event
  218. 218 : * `mouseup` or `touchend` event that triggered this function.
  219. 219 : *
  220. 220 : * @listens touchend
  221. 221 : * @listens mouseup
  222. 222 : */
  223. 223 : handleMouseUp(event) {
  224. 224 : const seekBar = this.getChild('seekBar');
  225. 225 :
  226. 226 : if (seekBar) {
  227. 227 : seekBar.handleMouseUp(event);
  228. 228 : }
  229. 229 :
  230. 230 : this.removeListenersAddedOnMousedownAndTouchstart();
  231. 231 : }
  232. 232 : }
  233. 233 :
  234. 234 : /**
  235. 235 : * Default options for `ProgressControl`
  236. 236 : *
  237. 237 : * @type {Object}
  238. 238 : * @private
  239. 239 : */
  240. 240 : ProgressControl.prototype.options_ = {
  241. 241 : children: [
  242. 242 : 'seekBar'
  243. 243 : ]
  244. 244 : };
  245. 245 :
  246. 246 : Component.registerComponent('ProgressControl', ProgressControl);
  247. 247 : export default ProgressControl;