1. 1 : /**
  2. 2 : * @file slider.js
  3. 3 : */
  4. 4 : import Component from '../component.js';
  5. 5 : import * as Dom from '../utils/dom.js';
  6. 6 : import {assign} from '../utils/obj';
  7. 7 :
  8. 8 : /**
  9. 9 : * The base functionality for a slider. Can be vertical or horizontal.
  10. 10 : * For instance the volume bar or the seek bar on a video is a slider.
  11. 11 : *
  12. 12 : * @extends Component
  13. 13 : */
  14. 14 : class Slider extends Component {
  15. 15 :
  16. 16 : /**
  17. 17 : * Create an instance of this class
  18. 18 : *
  19. 19 : * @param {Player} 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 : constructor(player, options) {
  26. 26 : super(player, options);
  27. 27 :
  28. 28 : // Set property names to bar to match with the child Slider class is looking for
  29. 29 : this.bar = this.getChild(this.options_.barName);
  30. 30 :
  31. 31 : // Set a horizontal or vertical class on the slider depending on the slider type
  32. 32 : this.vertical(!!this.options_.vertical);
  33. 33 :
  34. 34 : this.on('mousedown', this.handleMouseDown);
  35. 35 : this.on('touchstart', this.handleMouseDown);
  36. 36 : this.on('focus', this.handleFocus);
  37. 37 : this.on('blur', this.handleBlur);
  38. 38 : this.on('click', this.handleClick);
  39. 39 :
  40. 40 : this.on(player, 'controlsvisible', this.update);
  41. 41 : this.on(player, this.playerEvent, this.update);
  42. 42 : }
  43. 43 :
  44. 44 : /**
  45. 45 : * Create the `Button`s DOM element.
  46. 46 : *
  47. 47 : * @param {string} type
  48. 48 : * Type of element to create.
  49. 49 : *
  50. 50 : * @param {Object} [props={}]
  51. 51 : * List of properties in Object form.
  52. 52 : *
  53. 53 : * @param {Object} [attributes={}]
  54. 54 : * list of attributes in Object form.
  55. 55 : *
  56. 56 : * @return {Element}
  57. 57 : * The element that gets created.
  58. 58 : */
  59. 59 : createEl(type, props = {}, attributes = {}) {
  60. 60 : // Add the slider element class to all sub classes
  61. 61 : props.className = props.className + ' vjs-slider';
  62. 62 : props = assign({
  63. 63 : tabIndex: 0
  64. 64 : }, props);
  65. 65 :
  66. 66 : attributes = assign({
  67. 67 : 'role': 'slider',
  68. 68 : 'aria-valuenow': 0,
  69. 69 : 'aria-valuemin': 0,
  70. 70 : 'aria-valuemax': 100,
  71. 71 : 'tabIndex': 0
  72. 72 : }, attributes);
  73. 73 :
  74. 74 : return super.createEl(type, props, attributes);
  75. 75 : }
  76. 76 :
  77. 77 : /**
  78. 78 : * Handle `mousedown` or `touchstart` events on the `Slider`.
  79. 79 : *
  80. 80 : * @param {EventTarget~Event} event
  81. 81 : * `mousedown` or `touchstart` event that triggered this function
  82. 82 : *
  83. 83 : * @listens mousedown
  84. 84 : * @listens touchstart
  85. 85 : * @fires Slider#slideractive
  86. 86 : */
  87. 87 : handleMouseDown(event) {
  88. 88 : const doc = this.bar.el_.ownerDocument;
  89. 89 :
  90. 90 : event.preventDefault();
  91. 91 : Dom.blockTextSelection();
  92. 92 :
  93. 93 : this.addClass('vjs-sliding');
  94. 94 : /**
  95. 95 : * Triggered when the slider is in an active state
  96. 96 : *
  97. 97 : * @event Slider#slideractive
  98. 98 : * @type {EventTarget~Event}
  99. 99 : */
  100. 100 : this.trigger('slideractive');
  101. 101 :
  102. 102 : this.on(doc, 'mousemove', this.handleMouseMove);
  103. 103 : this.on(doc, 'mouseup', this.handleMouseUp);
  104. 104 : this.on(doc, 'touchmove', this.handleMouseMove);
  105. 105 : this.on(doc, 'touchend', this.handleMouseUp);
  106. 106 :
  107. 107 : this.handleMouseMove(event);
  108. 108 : }
  109. 109 :
  110. 110 : /**
  111. 111 : * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
  112. 112 : * The `mousemove` and `touchmove` events will only only trigger this function during
  113. 113 : * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
  114. 114 : * {@link Slider#handleMouseUp}.
  115. 115 : *
  116. 116 : * @param {EventTarget~Event} event
  117. 117 : * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
  118. 118 : * this function
  119. 119 : *
  120. 120 : * @listens mousemove
  121. 121 : * @listens touchmove
  122. 122 : */
  123. 123 : handleMouseMove(event) {}
  124. 124 :
  125. 125 : /**
  126. 126 : * Handle `mouseup` or `touchend` events on the `Slider`.
  127. 127 : *
  128. 128 : * @param {EventTarget~Event} event
  129. 129 : * `mouseup` or `touchend` event that triggered this function.
  130. 130 : *
  131. 131 : * @listens touchend
  132. 132 : * @listens mouseup
  133. 133 : * @fires Slider#sliderinactive
  134. 134 : */
  135. 135 : handleMouseUp() {
  136. 136 : const doc = this.bar.el_.ownerDocument;
  137. 137 :
  138. 138 : Dom.unblockTextSelection();
  139. 139 :
  140. 140 : this.removeClass('vjs-sliding');
  141. 141 : /**
  142. 142 : * Triggered when the slider is no longer in an active state.
  143. 143 : *
  144. 144 : * @event Slider#sliderinactive
  145. 145 : * @type {EventTarget~Event}
  146. 146 : */
  147. 147 : this.trigger('sliderinactive');
  148. 148 :
  149. 149 : this.off(doc, 'mousemove', this.handleMouseMove);
  150. 150 : this.off(doc, 'mouseup', this.handleMouseUp);
  151. 151 : this.off(doc, 'touchmove', this.handleMouseMove);
  152. 152 : this.off(doc, 'touchend', this.handleMouseUp);
  153. 153 :
  154. 154 : this.update();
  155. 155 : }
  156. 156 :
  157. 157 : /**
  158. 158 : * Update the progress bar of the `Slider`.
  159. 159 : */
  160. 160 : update() {
  161. 161 : // In VolumeBar init we have a setTimeout for update that pops and update to the end of the
  162. 162 : // execution stack. The player is destroyed before then update will cause an error
  163. 163 : if (!this.el_) {
  164. 164 : return;
  165. 165 : }
  166. 166 :
  167. 167 : // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
  168. 168 : // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
  169. 169 : // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  170. 170 : let progress = this.getPercent();
  171. 171 : const bar = this.bar;
  172. 172 :
  173. 173 : // If there's no bar...
  174. 174 : if (!bar) {
  175. 175 : return;
  176. 176 : }
  177. 177 :
  178. 178 : // Protect against no duration and other division issues
  179. 179 : if (typeof progress !== 'number' ||
  180. 180 : progress !== progress ||
  181. 181 : progress < 0 ||
  182. 182 : progress === Infinity) {
  183. 183 : progress = 0;
  184. 184 : }
  185. 185 :
  186. 186 : // Convert to a percentage for setting
  187. 187 : const percentage = (progress * 100).toFixed(2) + '%';
  188. 188 :
  189. 189 : // Set the new bar width or height
  190. 190 : if (this.vertical()) {
  191. 191 : bar.el().style.height = percentage;
  192. 192 : } else {
  193. 193 : bar.el().style.width = percentage;
  194. 194 : }
  195. 195 : }
  196. 196 :
  197. 197 : /**
  198. 198 : * Calculate distance for slider
  199. 199 : *
  200. 200 : * @param {EventTarget~Event} event
  201. 201 : * The event that caused this function to run.
  202. 202 : *
  203. 203 : * @return {number}
  204. 204 : * The current position of the Slider.
  205. 205 : * - postition.x for vertical `Slider`s
  206. 206 : * - postition.y for horizontal `Slider`s
  207. 207 : */
  208. 208 : calculateDistance(event) {
  209. 209 : const position = Dom.getPointerPosition(this.el_, event);
  210. 210 :
  211. 211 : if (this.vertical()) {
  212. 212 : return position.y;
  213. 213 : }
  214. 214 : return position.x;
  215. 215 : }
  216. 216 :
  217. 217 : /**
  218. 218 : * Handle a `focus` event on this `Slider`.
  219. 219 : *
  220. 220 : * @param {EventTarget~Event} event
  221. 221 : * The `focus` event that caused this function to run.
  222. 222 : *
  223. 223 : * @listens focus
  224. 224 : */
  225. 225 : handleFocus() {
  226. 226 : this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  227. 227 : }
  228. 228 :
  229. 229 : /**
  230. 230 : * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
  231. 231 : * arrow keys. This function will only be called when the slider has focus. See
  232. 232 : * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
  233. 233 : *
  234. 234 : * @param {EventTarget~Event} event
  235. 235 : * the `keydown` event that caused this function to run.
  236. 236 : *
  237. 237 : * @listens keydown
  238. 238 : */
  239. 239 : handleKeyPress(event) {
  240. 240 : // Left and Down Arrows
  241. 241 : if (event.which === 37 || event.which === 40) {
  242. 242 : event.preventDefault();
  243. 243 : this.stepBack();
  244. 244 :
  245. 245 : // Up and Right Arrows
  246. 246 : } else if (event.which === 38 || event.which === 39) {
  247. 247 : event.preventDefault();
  248. 248 : this.stepForward();
  249. 249 : }
  250. 250 : }
  251. 251 :
  252. 252 : /**
  253. 253 : * Handle a `blur` event on this `Slider`.
  254. 254 : *
  255. 255 : * @param {EventTarget~Event} event
  256. 256 : * The `blur` event that caused this function to run.
  257. 257 : *
  258. 258 : * @listens blur
  259. 259 : */
  260. 260 :
  261. 261 : handleBlur() {
  262. 262 : this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  263. 263 : }
  264. 264 :
  265. 265 : /**
  266. 266 : * Listener for click events on slider, used to prevent clicks
  267. 267 : * from bubbling up to parent elements like button menus.
  268. 268 : *
  269. 269 : * @param {Object} event
  270. 270 : * Event that caused this object to run
  271. 271 : */
  272. 272 : handleClick(event) {
  273. 273 : event.stopImmediatePropagation();
  274. 274 : event.preventDefault();
  275. 275 : }
  276. 276 :
  277. 277 : /**
  278. 278 : * Get/set if slider is horizontal for vertical
  279. 279 : *
  280. 280 : * @param {boolean} [bool]
  281. 281 : * - true if slider is vertical,
  282. 282 : * - false is horizontal
  283. 283 : *
  284. 284 : * @return {boolean|Slider}
  285. 285 : * - true if slider is vertical, and getting
  286. 286 : * - false is horizontal, and getting
  287. 287 : * - a reference to this object when setting
  288. 288 : */
  289. 289 : vertical(bool) {
  290. 290 : if (bool === undefined) {
  291. 291 : return this.vertical_ || false;
  292. 292 : }
  293. 293 :
  294. 294 : this.vertical_ = !!bool;
  295. 295 :
  296. 296 : if (this.vertical_) {
  297. 297 : this.addClass('vjs-slider-vertical');
  298. 298 : } else {
  299. 299 : this.addClass('vjs-slider-horizontal');
  300. 300 : }
  301. 301 :
  302. 302 : return this;
  303. 303 : }
  304. 304 : }
  305. 305 :
  306. 306 : Component.registerComponent('Slider', Slider);
  307. 307 : export default Slider;