videojs.hotkeys.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. /*
  2. * Video.js Hotkeys
  3. * https://github.com/ctd1500/videojs-hotkeys
  4. *
  5. * Copyright (c) 2015 Chris Dougherty
  6. * Licensed under the Apache-2.0 license.
  7. */
  8. ;(function(root, factory) {
  9. if (typeof window !== 'undefined' && window.videojs) {
  10. factory(window.videojs);
  11. } else if (typeof define === 'function' && define.amd) {
  12. define('videojs-hotkeys', ['video.js'], function (module) {
  13. return factory(module.default || module);
  14. });
  15. } else if (typeof module !== 'undefined' && module.exports) {
  16. var videojs = require('video.js');
  17. module.exports = factory(videojs.default || videojs);
  18. }
  19. }(this, function (videojs) {
  20. "use strict";
  21. if (typeof window !== 'undefined') {
  22. window['videojs_hotkeys'] = { version: "0.2.28" };
  23. }
  24. var hotkeys = function(options) {
  25. var player = this;
  26. var pEl = player.el();
  27. var doc = document;
  28. var def_options = {
  29. volumeStep: 0.1,
  30. seekStep: 5,
  31. enableMute: true,
  32. enableVolumeScroll: true,
  33. enableHoverScroll: false,
  34. enableFullscreen: true,
  35. enableNumbers: true,
  36. enableJogStyle: false,
  37. alwaysCaptureHotkeys: false,
  38. captureDocumentHotkeys: false,
  39. documentHotkeysFocusElementFilter: function () { return false },
  40. enableModifiersForNumbers: true,
  41. enableInactiveFocus: true,
  42. skipInitialFocus: false,
  43. playPauseKey: playPauseKey,
  44. rewindKey: rewindKey,
  45. forwardKey: forwardKey,
  46. volumeUpKey: volumeUpKey,
  47. volumeDownKey: volumeDownKey,
  48. muteKey: muteKey,
  49. fullscreenKey: fullscreenKey,
  50. customKeys: {}
  51. };
  52. var cPlay = 1,
  53. cRewind = 2,
  54. cForward = 3,
  55. cVolumeUp = 4,
  56. cVolumeDown = 5,
  57. cMute = 6,
  58. cFullscreen = 7;
  59. // Use built-in merge function from Video.js v5.0+ or v4.4.0+
  60. var mergeOptions = videojs.mergeOptions || videojs.util.mergeOptions;
  61. options = mergeOptions(def_options, options || {});
  62. var volumeStep = options.volumeStep,
  63. seekStep = options.seekStep,
  64. enableMute = options.enableMute,
  65. enableVolumeScroll = options.enableVolumeScroll,
  66. enableHoverScroll = options.enableHoverScroll,
  67. enableFull = options.enableFullscreen,
  68. enableNumbers = options.enableNumbers,
  69. enableJogStyle = options.enableJogStyle,
  70. alwaysCaptureHotkeys = options.alwaysCaptureHotkeys,
  71. captureDocumentHotkeys = options.captureDocumentHotkeys,
  72. documentHotkeysFocusElementFilter = options.documentHotkeysFocusElementFilter,
  73. enableModifiersForNumbers = options.enableModifiersForNumbers,
  74. enableInactiveFocus = options.enableInactiveFocus,
  75. skipInitialFocus = options.skipInitialFocus;
  76. var videojsVer = videojs.VERSION;
  77. // Set default player tabindex to handle keydown and doubleclick events
  78. if (!pEl.hasAttribute('tabIndex')) {
  79. pEl.setAttribute('tabIndex', '-1');
  80. }
  81. // Remove player outline to fix video performance issue
  82. pEl.style.outline = "none";
  83. if (alwaysCaptureHotkeys || !player.autoplay()) {
  84. if (!skipInitialFocus) {
  85. player.one('play', function() {
  86. pEl.focus(); // Fixes the .vjs-big-play-button handing focus back to body instead of the player
  87. });
  88. }
  89. }
  90. if (enableInactiveFocus) {
  91. player.on('userinactive', function() {
  92. // When the control bar fades, re-apply focus to the player if last focus was a control button
  93. var cancelFocusingPlayer = function() {
  94. clearTimeout(focusingPlayerTimeout);
  95. };
  96. var focusingPlayerTimeout = setTimeout(function() {
  97. player.off('useractive', cancelFocusingPlayer);
  98. var activeElement = doc.activeElement;
  99. var controlBar = pEl.querySelector('.vjs-control-bar');
  100. if (activeElement && activeElement.parentElement == controlBar) {
  101. pEl.focus();
  102. }
  103. }, 10);
  104. player.one('useractive', cancelFocusingPlayer);
  105. });
  106. }
  107. player.on('play', function() {
  108. // Fix allowing the YouTube plugin to have hotkey support.
  109. var ifblocker = pEl.querySelector('.iframeblocker');
  110. if (ifblocker && ifblocker.style.display === '') {
  111. ifblocker.style.display = "block";
  112. ifblocker.style.bottom = "39px";
  113. }
  114. });
  115. var keyDown = function keyDown(event) {
  116. var ewhich = event.which, wasPlaying, seekTime;
  117. var ePreventDefault = event.preventDefault.bind(event);
  118. var duration = player.duration();
  119. // When controls are disabled, hotkeys will be disabled as well
  120. if (player.controls()) {
  121. // Don't catch keys if any control buttons are focused, unless alwaysCaptureHotkeys is true
  122. var activeEl = doc.activeElement;
  123. if (
  124. alwaysCaptureHotkeys ||
  125. (captureDocumentHotkeys && documentHotkeysFocusElementFilter(activeEl)) ||
  126. activeEl == pEl ||
  127. activeEl == pEl.querySelector('.vjs-tech') ||
  128. activeEl == pEl.querySelector('.vjs-control-bar') ||
  129. activeEl == pEl.querySelector('.iframeblocker')
  130. ) {
  131. switch (checkKeys(event, player)) {
  132. // Spacebar toggles play/pause
  133. case cPlay:
  134. ePreventDefault();
  135. if (alwaysCaptureHotkeys || captureDocumentHotkeys) {
  136. // Prevent control activation with space
  137. event.stopPropagation();
  138. }
  139. if (player.paused()) {
  140. silencePromise(player.play());
  141. } else {
  142. player.pause();
  143. }
  144. break;
  145. // Seeking with the left/right arrow keys
  146. case cRewind: // Seek Backward
  147. wasPlaying = !player.paused();
  148. ePreventDefault();
  149. if (wasPlaying) {
  150. player.pause();
  151. }
  152. seekTime = player.currentTime() - seekStepD(event);
  153. // The flash player tech will allow you to seek into negative
  154. // numbers and break the seekbar, so try to prevent that.
  155. if (seekTime <= 0) {
  156. seekTime = 0;
  157. }
  158. player.currentTime(seekTime);
  159. if (wasPlaying) {
  160. silencePromise(player.play());
  161. }
  162. break;
  163. case cForward: // Seek Forward
  164. wasPlaying = !player.paused();
  165. ePreventDefault();
  166. if (wasPlaying) {
  167. player.pause();
  168. }
  169. seekTime = player.currentTime() + seekStepD(event);
  170. // Fixes the player not sending the end event if you
  171. // try to seek past the duration on the seekbar.
  172. if (seekTime >= duration) {
  173. seekTime = wasPlaying ? duration - .001 : duration;
  174. }
  175. player.currentTime(seekTime);
  176. if (wasPlaying) {
  177. silencePromise(player.play());
  178. }
  179. break;
  180. // Volume control with the up/down arrow keys
  181. case cVolumeDown:
  182. ePreventDefault();
  183. if (!enableJogStyle) {
  184. player.volume(player.volume() - volumeStep);
  185. } else {
  186. seekTime = player.currentTime() - 1;
  187. if (player.currentTime() <= 1) {
  188. seekTime = 0;
  189. }
  190. player.currentTime(seekTime);
  191. }
  192. break;
  193. case cVolumeUp:
  194. ePreventDefault();
  195. if (!enableJogStyle) {
  196. player.volume(player.volume() + volumeStep);
  197. } else {
  198. seekTime = player.currentTime() + 1;
  199. if (seekTime >= duration) {
  200. seekTime = duration;
  201. }
  202. player.currentTime(seekTime);
  203. }
  204. break;
  205. // Toggle Mute with the M key
  206. case cMute:
  207. if (enableMute) {
  208. player.muted(!player.muted());
  209. }
  210. break;
  211. // Toggle Fullscreen with the F key
  212. case cFullscreen:
  213. if (enableFull) {
  214. if (player.isFullscreen()) {
  215. player.exitFullscreen();
  216. } else {
  217. player.requestFullscreen();
  218. }
  219. }
  220. break;
  221. default:
  222. // Number keys from 0-9 skip to a percentage of the video. 0 is 0% and 9 is 90%
  223. if ((ewhich > 47 && ewhich < 59) || (ewhich > 95 && ewhich < 106)) {
  224. // Do not handle if enableModifiersForNumbers set to false and keys are Ctrl, Cmd or Alt
  225. if (enableModifiersForNumbers || !(event.metaKey || event.ctrlKey || event.altKey)) {
  226. if (enableNumbers) {
  227. var sub = 48;
  228. if (ewhich > 95) {
  229. sub = 96;
  230. }
  231. var number = ewhich - sub;
  232. ePreventDefault();
  233. player.currentTime(player.duration() * number * 0.1);
  234. }
  235. }
  236. }
  237. // Handle any custom hotkeys
  238. for (var customKey in options.customKeys) {
  239. var customHotkey = options.customKeys[customKey];
  240. // Check for well formed custom keys
  241. if (customHotkey && customHotkey.key && customHotkey.handler) {
  242. // Check if the custom key's condition matches
  243. if (customHotkey.key(event)) {
  244. ePreventDefault();
  245. customHotkey.handler(player, options, event);
  246. }
  247. }
  248. }
  249. }
  250. }
  251. }
  252. };
  253. var doubleClick = function doubleClick(event) {
  254. // Video.js added double-click fullscreen in 7.1.0
  255. if (videojsVer != null && videojsVer <= "7.1.0") {
  256. // When controls are disabled, hotkeys will be disabled as well
  257. if (player.controls()) {
  258. // Don't catch clicks if any control buttons are focused
  259. var activeEl = event.relatedTarget || event.toElement || doc.activeElement;
  260. if (activeEl == pEl ||
  261. activeEl == pEl.querySelector('.vjs-tech') ||
  262. activeEl == pEl.querySelector('.iframeblocker')) {
  263. if (enableFull) {
  264. if (player.isFullscreen()) {
  265. player.exitFullscreen();
  266. } else {
  267. player.requestFullscreen();
  268. }
  269. }
  270. }
  271. }
  272. }
  273. };
  274. var volumeHover = false;
  275. var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel');
  276. if (volumeSelector != null) {
  277. volumeSelector.onmouseover = function() { volumeHover = true; };
  278. volumeSelector.onmouseout = function() { volumeHover = false; };
  279. }
  280. var mouseScroll = function mouseScroll(event) {
  281. if (enableHoverScroll) {
  282. // If we leave this undefined then it can match non-existent elements below
  283. var activeEl = 0;
  284. } else {
  285. var activeEl = doc.activeElement;
  286. }
  287. // When controls are disabled, hotkeys will be disabled as well
  288. if (player.controls()) {
  289. if (alwaysCaptureHotkeys ||
  290. activeEl == pEl ||
  291. activeEl == pEl.querySelector('.vjs-tech') ||
  292. activeEl == pEl.querySelector('.iframeblocker') ||
  293. activeEl == pEl.querySelector('.vjs-control-bar') ||
  294. volumeHover) {
  295. if (enableVolumeScroll) {
  296. event = window.event || event;
  297. var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
  298. event.preventDefault();
  299. if (delta == 1) {
  300. player.volume(player.volume() + volumeStep);
  301. } else if (delta == -1) {
  302. player.volume(player.volume() - volumeStep);
  303. }
  304. }
  305. }
  306. }
  307. };
  308. var checkKeys = function checkKeys(e, player) {
  309. // Allow some modularity in defining custom hotkeys
  310. // Play/Pause check
  311. if (options.playPauseKey(e, player)) {
  312. return cPlay;
  313. }
  314. // Seek Backward check
  315. if (options.rewindKey(e, player)) {
  316. return cRewind;
  317. }
  318. // Seek Forward check
  319. if (options.forwardKey(e, player)) {
  320. return cForward;
  321. }
  322. // Volume Up check
  323. if (options.volumeUpKey(e, player)) {
  324. return cVolumeUp;
  325. }
  326. // Volume Down check
  327. if (options.volumeDownKey(e, player)) {
  328. return cVolumeDown;
  329. }
  330. // Mute check
  331. if (options.muteKey(e, player)) {
  332. return cMute;
  333. }
  334. // Fullscreen check
  335. if (options.fullscreenKey(e, player)) {
  336. return cFullscreen;
  337. }
  338. };
  339. function playPauseKey(e) {
  340. // Space bar or MediaPlayPause
  341. return (e.which === 32 || e.which === 179);
  342. }
  343. function rewindKey(e) {
  344. // Left Arrow or MediaRewind
  345. return (e.which === 37 || e.which === 177);
  346. }
  347. function forwardKey(e) {
  348. // Right Arrow or MediaForward
  349. return (e.which === 39 || e.which === 176);
  350. }
  351. function volumeUpKey(e) {
  352. // Up Arrow
  353. return (e.which === 38);
  354. }
  355. function volumeDownKey(e) {
  356. // Down Arrow
  357. return (e.which === 40);
  358. }
  359. function muteKey(e) {
  360. // M key
  361. return (e.which === 77);
  362. }
  363. function fullscreenKey(e) {
  364. // F key
  365. return (e.which === 70);
  366. }
  367. function seekStepD(e) {
  368. // SeekStep caller, returns an int, or a function returning an int
  369. return (typeof seekStep === "function" ? seekStep(e) : seekStep);
  370. }
  371. function silencePromise(value) {
  372. if (value != null && typeof value.then === 'function') {
  373. value.then(null, function(e) {});
  374. }
  375. }
  376. if (captureDocumentHotkeys) {
  377. var capDocHK = function (event) { keyDown(event) };
  378. document.addEventListener('keydown', capDocHK);
  379. this.dispose = function () {
  380. document.removeEventListener('keydown', capDocHK);
  381. }
  382. } else {
  383. player.on('keydown', keyDown);
  384. }
  385. player.on('dblclick', doubleClick);
  386. player.on('mousewheel', mouseScroll);
  387. player.on("DOMMouseScroll", mouseScroll);
  388. return this;
  389. };
  390. var registerPlugin = videojs.registerPlugin || videojs.plugin;
  391. registerPlugin('hotkeys', hotkeys);
  392. }));