videojs.hotkeys.js 14 KB

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