diff --git a/static/videojs/videojs.hotkeys.js b/static/videojs/videojs.hotkeys.js new file mode 100644 index 0000000000000000000000000000000000000000..4ba43b1523b06f17235a733fc0be6c34d0c72c45 --- /dev/null +++ b/static/videojs/videojs.hotkeys.js @@ -0,0 +1,355 @@ +/* + * Video.js Hotkeys + * https://github.com/ctd1500/videojs-hotkeys + * + * Copyright (c) 2015 Chris Dougherty + * Licensed under the Apache-2.0 license. + */ + +;(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory.bind(this, root, root.videojs)); + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = factory(root, root.videojs); + } else { + factory(root, root.videojs); + } + +})(window, function(window, videojs) { + "use strict"; + window['videojs_hotkeys'] = { version: "0.2.16" }; + + var hotkeys = function(options) { + var player = this; + var pEl = player.el(); + var doc = document; + var def_options = { + volumeStep: 0.1, + seekStep: 5, + enableMute: true, + enableVolumeScroll: true, + enableFullscreen: true, + enableNumbers: true, + enableJogStyle: false, + alwaysCaptureHotkeys: false, + enableModifiersForNumbers: true, + playPauseKey: playPauseKey, + rewindKey: rewindKey, + forwardKey: forwardKey, + volumeUpKey: volumeUpKey, + volumeDownKey: volumeDownKey, + muteKey: muteKey, + fullscreenKey: fullscreenKey, + customKeys: {} + }; + + var cPlay = 1, + cRewind = 2, + cForward = 3, + cVolumeUp = 4, + cVolumeDown = 5, + cMute = 6, + cFullscreen = 7; + + // Use built-in merge function from Video.js v5.0+ or v4.4.0+ + var mergeOptions = videojs.mergeOptions || videojs.util.mergeOptions; + options = mergeOptions(def_options, options || {}); + + var volumeStep = options.volumeStep, + seekStep = options.seekStep, + enableMute = options.enableMute, + enableVolumeScroll = options.enableVolumeScroll, + enableFull = options.enableFullscreen, + enableNumbers = options.enableNumbers, + enableJogStyle = options.enableJogStyle, + alwaysCaptureHotkeys = options.alwaysCaptureHotkeys, + enableModifiersForNumbers = options.enableModifiersForNumbers; + + // Set default player tabindex to handle keydown and doubleclick events + if (!pEl.hasAttribute('tabIndex')) { + pEl.setAttribute('tabIndex', '-1'); + } + + // Remove player outline to fix video performance issue + pEl.style.outline = "none"; + + if (alwaysCaptureHotkeys || !player.autoplay()) { + player.one('play', function() { + pEl.focus(); // Fixes the .vjs-big-play-button handing focus back to body instead of the player + }); + } + + player.on('userinactive', function() { + // When the control bar fades, re-apply focus to the player if last focus was a control button + var cancelFocusingPlayer = function() { + clearTimeout(focusingPlayerTimeout); + }; + var focusingPlayerTimeout = setTimeout(function() { + player.off('useractive', cancelFocusingPlayer); + if (doc.activeElement.parentElement == pEl.querySelector('.vjs-control-bar')) { + pEl.focus(); + } + }, 10); + + player.one('useractive', cancelFocusingPlayer); + }); + + player.on('play', function() { + // Fix allowing the YouTube plugin to have hotkey support. + var ifblocker = pEl.querySelector('.iframeblocker'); + if (ifblocker && ifblocker.style.display === '') { + ifblocker.style.display = "block"; + ifblocker.style.bottom = "39px"; + } + }); + + var keyDown = function keyDown(event) { + var ewhich = event.which, curTime; + var ePreventDefault = event.preventDefault; + // When controls are disabled, hotkeys will be disabled as well + if (player.controls()) { + + // Don't catch keys if any control buttons are focused, unless alwaysCaptureHotkeys is true + var activeEl = doc.activeElement; + if (alwaysCaptureHotkeys || + activeEl == pEl || + activeEl == pEl.querySelector('.vjs-tech') || + activeEl == pEl.querySelector('.vjs-control-bar') || + activeEl == pEl.querySelector('.iframeblocker')) { + + switch (checkKeys(event, player)) { + // Spacebar toggles play/pause + case cPlay: + ePreventDefault(); + if (alwaysCaptureHotkeys) { + // Prevent control activation with space + event.stopPropagation(); + } + + if (player.paused()) { + player.play(); + } else { + player.pause(); + } + break; + + // Seeking with the left/right arrow keys + case cRewind: // Seek Backward + ePreventDefault(); + curTime = player.currentTime() - seekStep; + // The flash player tech will allow you to seek into negative + // numbers and break the seekbar, so try to prevent that. + if (player.currentTime() <= seekStep) { + curTime = 0; + } + player.currentTime(curTime); + break; + case cForward: // Seek Forward + ePreventDefault(); + player.currentTime(player.currentTime() + seekStep); + break; + + // Volume control with the up/down arrow keys + case cVolumeDown: + ePreventDefault(); + if (!enableJogStyle) { + player.volume(player.volume() - volumeStep); + } else { + curTime = player.currentTime() - 1; + if (player.currentTime() <= 1) { + curTime = 0; + } + player.currentTime(curTime); + } + break; + case cVolumeUp: + ePreventDefault(); + if (!enableJogStyle) { + player.volume(player.volume() + volumeStep); + } else { + player.currentTime(player.currentTime() + 1); + } + break; + + // Toggle Mute with the M key + case cMute: + if (enableMute) { + player.muted(!player.muted()); + } + break; + + // Toggle Fullscreen with the F key + case cFullscreen: + if (enableFull) { + if (player.isFullscreen()) { + player.exitFullscreen(); + } else { + player.requestFullscreen(); + } + } + break; + + default: + // Number keys from 0-9 skip to a percentage of the video. 0 is 0% and 9 is 90% + if ((ewhich > 47 && ewhich < 59) || (ewhich > 95 && ewhich < 106)) { + // Do not handle if enableModifiersForNumbers set to false and keys are Ctrl, Cmd or Alt + if (enableModifiersForNumbers || !(event.metaKey || event.ctrlKey || event.altKey)) { + if (enableNumbers) { + var sub = 48; + if (ewhich > 95) { + sub = 96; + } + var number = ewhich - sub; + ePreventDefault(); + player.currentTime(player.duration() * number * 0.1); + } + } + } + + // Handle any custom hotkeys + for (var customKey in options.customKeys) { + var customHotkey = options.customKeys[customKey]; + // Check for well formed custom keys + if (customHotkey && customHotkey.key && customHotkey.handler) { + // Check if the custom key's condition matches + if (customHotkey.key(event)) { + ePreventDefault(); + customHotkey.handler(player, options); + } + } + } + } + } + } + }; + + var doubleClick = function doubleClick(event) { + // When controls are disabled, hotkeys will be disabled as well + if (player.controls()) { + + // Don't catch clicks if any control buttons are focused + var activeEl = event.relatedTarget || event.toElement || doc.activeElement; + if (activeEl == pEl || + activeEl == pEl.querySelector('.vjs-tech') || + activeEl == pEl.querySelector('.iframeblocker')) { + + if (enableFull) { + if (player.isFullscreen()) { + player.exitFullscreen(); + } else { + player.requestFullscreen(); + } + } + } + } + }; + + var mouseScroll = function mouseScroll(event) { + // When controls are disabled, hotkeys will be disabled as well + if (player.controls()) { + var activeEl = event.relatedTarget || event.toElement || doc.activeElement; + if (alwaysCaptureHotkeys || + activeEl == pEl || + activeEl == pEl.querySelector('.vjs-tech') || + activeEl == pEl.querySelector('.iframeblocker') || + activeEl == pEl.querySelector('.vjs-control-bar')) { + + if (enableVolumeScroll) { + event = window.event || event; + var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail))); + event.preventDefault(); + + if (delta == 1) { + player.volume(player.volume() + volumeStep); + } else if (delta == -1) { + player.volume(player.volume() - volumeStep); + } + } + } + } + }; + + var checkKeys = function checkKeys(e, player) { + // Allow some modularity in defining custom hotkeys + + // Play/Pause check + if (options.playPauseKey(e, player)) { + return cPlay; + } + + // Seek Backward check + if (options.rewindKey(e, player)) { + return cRewind; + } + + // Seek Forward check + if (options.forwardKey(e, player)) { + return cForward; + } + + // Volume Up check + if (options.volumeUpKey(e, player)) { + return cVolumeUp; + } + + // Volume Down check + if (options.volumeDownKey(e, player)) { + return cVolumeDown; + } + + // Mute check + if (options.muteKey(e, player)) { + return cMute; + } + + // Fullscreen check + if (options.fullscreenKey(e, player)) { + return cFullscreen; + } + }; + + function playPauseKey(e) { + // Space bar or MediaPlayPause + return (e.which === 32 || e.which === 179); + } + + function rewindKey(e) { + // Left Arrow or MediaRewind + return (e.which === 37 || e.which === 177); + } + + function forwardKey(e) { + // Right Arrow or MediaForward + return (e.which === 39 || e.which === 176); + } + + function volumeUpKey(e) { + // Up Arrow + return (e.which === 38); + } + + function volumeDownKey(e) { + // Down Arrow + return (e.which === 40); + } + + function muteKey(e) { + // M key + return (e.which === 77); + } + + function fullscreenKey(e) { + // F key + return (e.which === 70); + } + + player.on('keydown', keyDown); + player.on('dblclick', doubleClick); + player.on('mousewheel', mouseScroll); + player.on("DOMMouseScroll", mouseScroll); + + return this; + }; + + videojs.plugin('hotkeys', hotkeys); +}); diff --git a/templates/base.html b/templates/base.html index 662b27d8f621e136ce72e996a2ceb943015075e4..ca484fa6979b7c178db030cc39206b582d5d036d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -17,7 +17,7 @@ <link href="{{url_for('static', filename='bootstrap/bootstrap.css')}}" rel="stylesheet"> <link rel="stylesheet" type="text/css" href="{{url_for('static', filename='style.css')}}"> - <link rel="stylesheet" href="{{url_for('static', filename='font-awesome/css/font-awesome.min.css')}}"> + <link rel="stylesheet" href="{{url_for('static', filename='font-awesome/css/font-awesome.css')}}"> <link rel="stylesheet" type="text/css" href="{{url_for('static', filename='videojs/video-js.css')}}"> <link rel="stylesheet" type="text/css" href="{{url_for('static', filename='videojs/videojs-resolution-switcher.css')}}"> @@ -29,6 +29,7 @@ <script src="{{url_for('static', filename='videojs/ie8/videojs-ie8.js')}}"></script> <script src="{{url_for('static', filename='videojs/videojs-resolution-switcher.js')}}"></script> <script src="{{url_for('static', filename='videojs/videojs-contrib-hls.js')}}"></script> + <script src="{{url_for('static', filename='videojs/videojs.hotkeys.js')}}"></script> </head> <body> diff --git a/templates/macros.html b/templates/macros.html index 8dc435fa19767e365eac73d1b998e8e08bb8125e..12876d77174f4aee38e0448aec7eab70998d7e93 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -43,7 +43,7 @@ {% endmacro %} {% macro player(lecture, videos) %} -<video id="videoplayer" style="width: 100%" class="video-js vjs-default-skin vjs-big-play-centered" width="640" height="320" controls data-setup='{ "plugins" : {"videoJsResolutionSwitcher": { "ui": true, "default": "720p", "dynamicLabel": true } }, "customControlsOnMobile": true, "playbackRates": [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4] }'> +<video id="videoplayer" style="width: 100%" class="video-js vjs-default-skin vjs-big-play-centered" width="640" height="320" controls data-setup='{ "plugins" : {"hotkeys": {"seekStep": 15, "alwaysCaptureHotkeys": true}, "videoJsResolutionSwitcher": { "ui": true, "default": "720p", "dynamicLabel": true } }, "customControlsOnMobile": true, "playbackRates": [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4] }'> {% for v in videos|sort(attribute='player_prio', reverse=True) %} <source type="video/mp4" src="{{ config.VIDEOPREFIX }}/{{ v.path }}" label="{{ v.format_description }}"/> {% endfor %}