summaryrefslogtreecommitdiffstats
path: root/assets
diff options
context:
space:
mode:
authorleonklingele <5585491+leonklingele@users.noreply.github.com>2019-08-16 23:01:14 +0200
committerOmar Roth <omarroth@protonmail.com>2019-08-16 16:01:14 -0500
commite6b4e1268945777c5d07dfca4362a1af23f6d970 (patch)
treed2ec039c99ef9ed956e486fc8b7e29cab428e998 /assets
parent7eaac995bd549b03f53930fbbfc5d77e2b051362 (diff)
downloadinvidious-e6b4e1268945777c5d07dfca4362a1af23f6d970.tar.gz
invidious-e6b4e1268945777c5d07dfca4362a1af23f6d970.tar.bz2
invidious-e6b4e1268945777c5d07dfca4362a1af23f6d970.zip
js: add support for keydown events (#678)
* js: add support for keydown events This will modify the player behavior even if the player element is unfocused. Based on the YouTube key bindings, allow to - toggle playback with space and 'k' key - increase and decrease player volume with up / down arrow key - mute and unmute player with 'm' key - jump forwards and backwards by 5 seconds with right / left arrow key - jump forwards and backwards by 10 seconds with 'l' / 'j' key - set video progress with number keys 0–9 - toggle captions with 'c' key - toggle fullscreen mode with 'f' key - play next video with 'N' key - increase and decrease playback speed with '>' / '<' key * js: remove unused dependency 'videojs.hotkeys.min.js' Support for controlling the player volume by scrolling over it is still retained by copying over the relevant code part from the aforementioned library.
Diffstat (limited to 'assets')
-rw-r--r--assets/js/player.js332
-rw-r--r--assets/js/videojs.hotkeys.min.js2
-rw-r--r--assets/js/watch.js40
3 files changed, 291 insertions, 83 deletions
diff --git a/assets/js/player.js b/assets/js/player.js
index 25cbb18b..4a61258c 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -38,69 +38,7 @@ var shareOptions = {
embedCode: "<iframe id='ivplayer' type='text/html' width='640' height='360' src='" + embed_url + "' frameborder='0'></iframe>"
}
-var player = videojs('player', options, function () {
- this.hotkeys({
- volumeStep: 0.1,
- seekStep: 5,
- enableModifiersForNumbers: false,
- enableHoverScroll: true,
- customKeys: {
- // Toggle play with K Key
- play: {
- key: function (e) {
- return e.which === 75;
- },
- handler: function (player, options, e) {
- if (player.paused()) {
- player.play();
- } else {
- player.pause();
- }
- }
- },
- // Go backward 10 seconds
- backward: {
- key: function (e) {
- return e.which === 74;
- },
- handler: function (player, options, e) {
- player.currentTime(player.currentTime() - 10);
- }
- },
- // Go forward 10 seconds
- forward: {
- key: function (e) {
- return e.which === 76;
- },
- handler: function (player, options, e) {
- player.currentTime(player.currentTime() + 10);
- }
- },
- // Increase speed
- increase_speed: {
- key: function (e) {
- return (e.which === 190 && e.shiftKey);
- },
- handler: function (player, _, e) {
- size = options.playbackRates.length;
- index = options.playbackRates.indexOf(player.playbackRate());
- player.playbackRate(options.playbackRates[(index + 1) % size]);
- }
- },
- // Decrease speed
- decrease_speed: {
- key: function (e) {
- return (e.which === 188 && e.shiftKey);
- },
- handler: function (player, _, e) {
- size = options.playbackRates.length;
- index = options.playbackRates.indexOf(player.playbackRate());
- player.playbackRate(options.playbackRates[(size + index - 1) % size]);
- }
- }
- }
- });
-});
+var player = videojs('player', options);
if (location.pathname.startsWith('/embed/')) {
player.overlay({
@@ -254,5 +192,273 @@ if (!video_data.params.listen && video_data.params.annotations) {
xhr.send();
}
+function increase_volume(delta) {
+ const curVolume = player.volume();
+ let newVolume = curVolume + delta;
+ if (newVolume > 1) {
+ newVolume = 1;
+ } else if (newVolume < 0) {
+ newVolume = 0;
+ }
+ player.volume(newVolume);
+}
+
+function toggle_muted() {
+ const isMuted = player.muted();
+ player.muted(!isMuted);
+}
+
+function skip_seconds(delta) {
+ const duration = player.duration();
+ const curTime = player.currentTime();
+ let newTime = curTime + delta;
+ if (newTime > duration) {
+ newTime = duration;
+ } else if (newTime < 0) {
+ newTime = 0;
+ }
+ player.currentTime(newTime);
+}
+
+function set_time_percent(percent) {
+ const duration = player.duration();
+ const newTime = duration * (percent / 100);
+ player.currentTime(newTime);
+}
+
+function toggle_play() {
+ if (player.paused()) {
+ player.play();
+ } else {
+ player.pause();
+ }
+}
+
+const toggle_captions = (function() {
+ let toggledTrack = null;
+ const onChange = function(e) {
+ toggledTrack = null;
+ };
+ const bindChange = function(onOrOff) {
+ player.textTracks()[onOrOff]('change', onChange);
+ };
+ // Wrapper function to ignore our own emitted events and only listen
+ // to events emitted by Video.js on click on the captions menu items.
+ const setMode = function(track, mode) {
+ bindChange('off');
+ track.mode = mode;
+ window.setTimeout(function() {
+ bindChange('on');
+ }, 0);
+ };
+ bindChange('on');
+ return function() {
+ if (toggledTrack !== null) {
+ if (toggledTrack.mode !== 'showing') {
+ setMode(toggledTrack, 'showing');
+ } else {
+ setMode(toggledTrack, 'disabled');
+ }
+ toggledTrack = null;
+ return;
+ }
+
+ // Used as a fallback if no captions are currently active.
+ // TODO: Make this more intelligent by e.g. relying on browser language.
+ let fallbackCaptionsTrack = null;
+
+ const tracks = player.textTracks();
+ for (let i = 0; i < tracks.length; i++) {
+ const track = tracks[i];
+ if (track.kind !== 'captions') {
+ continue;
+ }
+
+ if (fallbackCaptionsTrack === null) {
+ fallbackCaptionsTrack = track;
+ }
+ if (track.mode === 'showing') {
+ setMode(track, 'disabled');
+ toggledTrack = track;
+ return;
+ }
+ }
+
+ // Fallback if no captions are currently active.
+ if (fallbackCaptionsTrack !== null) {
+ setMode(fallbackCaptionsTrack, 'showing');
+ toggledTrack = fallbackCaptionsTrack;
+ }
+ };
+})();
+
+function toggle_fullscreen() {
+ if (player.isFullscreen()) {
+ player.exitFullscreen();
+ } else {
+ player.requestFullscreen();
+ }
+}
+
+function increase_playback_rate(steps) {
+ const maxIndex = options.playbackRates.length - 1;
+ const curIndex = options.playbackRates.indexOf(player.playbackRate());
+ let newIndex = curIndex + steps;
+ if (newIndex > maxIndex) {
+ newIndex = maxIndex;
+ } else if (newIndex < 0) {
+ newIndex = 0;
+ }
+ player.playbackRate(options.playbackRates[newIndex]);
+}
+
+window.addEventListener('keydown', e => {
+ if (e.target.tagName.toLowerCase() === 'input') {
+ // Ignore input when focus is on certain elements, e.g. form fields.
+ return;
+ }
+ // See https://github.com/ctd1500/videojs-hotkeys/blob/bb4a158b2e214ccab87c2e7b95f42bc45c6bfd87/videojs.hotkeys.js#L310-L313
+ const isPlayerFocused = false
+ || e.target === document.querySelector('.video-js')
+ || e.target === document.querySelector('.vjs-tech')
+ || e.target === document.querySelector('.iframeblocker')
+ || e.target === document.querySelector('.vjs-control-bar')
+ ;
+ let action = null;
+
+ const code = e.keyCode;
+ const key = e.key;
+ switch (key) {
+ case ' ':
+ case 'k':
+ action = toggle_play;
+ break;
+
+ case 'ArrowUp':
+ if (isPlayerFocused) {
+ action = increase_volume.bind(this, 0.1);
+ }
+ break;
+ case 'ArrowDown':
+ if (isPlayerFocused) {
+ action = increase_volume.bind(this, -0.1);
+ }
+ break;
+
+ case 'm':
+ action = toggle_muted;
+ break;
+
+ case 'ArrowRight':
+ action = skip_seconds.bind(this, 5);
+ break;
+ case 'ArrowLeft':
+ action = skip_seconds.bind(this, -5);
+ break;
+ case 'l':
+ action = skip_seconds.bind(this, 10);
+ break;
+ case 'j':
+ action = skip_seconds.bind(this, -10);
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ const percent = (code - 48) * 10;
+ action = set_time_percent.bind(this, percent);
+ break;
+
+ case 'c':
+ action = toggle_captions;
+ break;
+ case 'f':
+ action = toggle_fullscreen;
+ break;
+
+ case 'N':
+ action = next_video;
+ break;
+ case 'P':
+ // TODO: Add support to play back previous video.
+ break;
+
+ case '.':
+ // TODO: Add support for next-frame-stepping.
+ break;
+ case ',':
+ // TODO: Add support for previous-frame-stepping.
+ break;
+
+ case '>':
+ action = increase_playback_rate.bind(this, 1);
+ break;
+ case '<':
+ action = increase_playback_rate.bind(this, -1);
+ break;
+
+ default:
+ console.info('Unhandled key down event: %s:', key, e);
+ break;
+ }
+
+ if (action) {
+ e.preventDefault();
+ action();
+ }
+}, false);
+
+// Add support for controlling the player volume by scrolling over it. Adapted from
+// https://github.com/ctd1500/videojs-hotkeys/blob/bb4a158b2e214ccab87c2e7b95f42bc45c6bfd87/videojs.hotkeys.js#L292-L328
+(function() {
+ const volumeStep = 0.05;
+ const enableVolumeScroll = true;
+ const enableHoverScroll = true;
+ const doc = document;
+ const pEl = document.getElementById('player');
+
+ var volumeHover = false;
+ var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel');
+ if (volumeSelector != null) {
+ volumeSelector.onmouseover = function() { volumeHover = true; };
+ volumeSelector.onmouseout = function() { volumeHover = false; };
+ }
+
+ var mouseScroll = function mouseScroll(event) {
+ var activeEl = doc.activeElement;
+ if (enableHoverScroll) {
+ // If we leave this undefined then it can match non-existent elements below
+ activeEl = 0;
+ }
+
+ // When controls are disabled, hotkeys will be disabled as well
+ if (player.controls()) {
+ if (volumeHover) {
+ if (enableVolumeScroll) {
+ event = window.event || event;
+ var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
+ event.preventDefault();
+
+ if (delta == 1) {
+ increase_volume(volumeStep);
+ } else if (delta == -1) {
+ increase_volume(-volumeStep);
+ }
+ }
+ }
+ }
+ };
+
+ player.on('mousewheel', mouseScroll);
+ player.on("DOMMouseScroll", mouseScroll);
+}());
+
// Since videojs-share can sometimes be blocked, we defer it until last
player.share(shareOptions);
diff --git a/assets/js/videojs.hotkeys.min.js b/assets/js/videojs.hotkeys.min.js
deleted file mode 100644
index a6cfe6e2..00000000
--- a/assets/js/videojs.hotkeys.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/* videojs-hotkeys v0.2.25 - https://github.com/ctd1500/videojs-hotkeys */
-!function(e,n){"undefined"!=typeof window&&window.videojs?n(window.videojs):"function"==typeof define&&define.amd?define("videojs-hotkeys",["video.js"],function(e){return n(e.default||e)}):"undefined"!=typeof module&&module.exports&&(module.exports=n(require("video.js")))}(0,function(e){"use strict";"undefined"!=typeof window&&(window.videojs_hotkeys={version:"0.2.25"});(e.registerPlugin||e.plugin)("hotkeys",function(n){function t(e){return"function"==typeof s?s(e):s}function r(e){null!=e&&"function"==typeof e.then&&e.then(null,function(e){})}var o=this,u=o.el(),l=document,i={volumeStep:.1,seekStep:5,enableMute:!0,enableVolumeScroll:!0,enableHoverScroll:!1,enableFullscreen:!0,enableNumbers:!0,enableJogStyle:!1,alwaysCaptureHotkeys:!1,enableModifiersForNumbers:!0,enableInactiveFocus:!0,skipInitialFocus:!1,playPauseKey:function(e){return 32===e.which||179===e.which},rewindKey:function(e){return 37===e.which||177===e.which},forwardKey:function(e){return 39===e.which||176===e.which},volumeUpKey:function(e){return 38===e.which},volumeDownKey:function(e){return 40===e.which},muteKey:function(e){return 77===e.which},fullscreenKey:function(e){return 70===e.which},customKeys:{}},c=e.mergeOptions||e.util.mergeOptions,a=(n=c(i,n||{})).volumeStep,s=n.seekStep,m=n.enableMute,f=n.enableVolumeScroll,y=n.enableHoverScroll,v=n.enableFullscreen,d=n.enableNumbers,p=n.enableJogStyle,b=n.alwaysCaptureHotkeys,h=n.enableModifiersForNumbers,w=n.enableInactiveFocus,k=n.skipInitialFocus,S=e.VERSION;u.hasAttribute("tabIndex")||u.setAttribute("tabIndex","-1"),u.style.outline="none",!b&&o.autoplay()||k||o.one("play",function(){u.focus()}),w&&o.on("userinactive",function(){var e=function(){clearTimeout(n)},n=setTimeout(function(){o.off("useractive",e);var n=l.activeElement,t=u.querySelector(".vjs-control-bar");n&&n.parentElement==t&&u.focus()},10);o.one("useractive",e)}),o.on("play",function(){var e=u.querySelector(".iframeblocker");e&&""===e.style.display&&(e.style.display="block",e.style.bottom="39px")});var K=!1,q=u.querySelector(".vjs-volume-menu-button")||u.querySelector(".vjs-volume-panel");null!=q&&(q.onmouseover=function(){K=!0},q.onmouseout=function(){K=!1});var j=function(e){if(y)n=0;else var n=l.activeElement;if(o.controls()&&(b||n==u||n==u.querySelector(".vjs-tech")||n==u.querySelector(".iframeblocker")||n==u.querySelector(".vjs-control-bar")||K)&&f){e=window.event||e;var t=Math.max(-1,Math.min(1,e.wheelDelta||-e.detail));e.preventDefault(),1==t?o.volume(o.volume()+a):-1==t&&o.volume(o.volume()-a)}},F=function(e,t){return n.playPauseKey(e,t)?1:n.rewindKey(e,t)?2:n.forwardKey(e,t)?3:n.volumeUpKey(e,t)?4:n.volumeDownKey(e,t)?5:n.muteKey(e,t)?6:n.fullscreenKey(e,t)?7:void 0};return o.on("keydown",function(e){var i,c,s=e.which,f=e.preventDefault,y=o.duration();if(o.controls()){var w=l.activeElement;if(b||w==u||w==u.querySelector(".vjs-tech")||w==u.querySelector(".vjs-control-bar")||w==u.querySelector(".iframeblocker"))switch(F(e,o)){case 1:f(),b&&e.stopPropagation(),o.paused()?r(o.play()):o.pause();break;case 2:i=!o.paused(),f(),i&&o.pause(),(c=o.currentTime()-t(e))<=0&&(c=0),o.currentTime(c),i&&r(o.play());break;case 3:i=!o.paused(),f(),i&&o.pause(),(c=o.currentTime()+t(e))>=y&&(c=i?y-.001:y),o.currentTime(c),i&&r(o.play());break;case 5:f(),p?(c=o.currentTime()-1,o.currentTime()<=1&&(c=0),o.currentTime(c)):o.volume(o.volume()-a);break;case 4:f(),p?((c=o.currentTime()+1)>=y&&(c=y),o.currentTime(c)):o.volume(o.volume()+a);break;case 6:m&&o.muted(!o.muted());break;case 7:v&&(o.isFullscreen()?o.exitFullscreen():o.requestFullscreen());break;default:if((s>47&&s<59||s>95&&s<106)&&(h||!(e.metaKey||e.ctrlKey||e.altKey))&&d){var k=48;s>95&&(k=96);var S=s-k;f(),o.currentTime(o.duration()*S*.1)}for(var K in n.customKeys){var q=n.customKeys[K];q&&q.key&&q.handler&&q.key(e)&&(f(),q.handler(o,n,e))}}}}),o.on("dblclick",function(e){if(null!=S&&S<="7.1.0"&&o.controls()){var n=e.relatedTarget||e.toElement||l.activeElement;n!=u&&n!=u.querySelector(".vjs-tech")&&n!=u.querySelector(".iframeblocker")||v&&(o.isFullscreen()?o.exitFullscreen():o.requestFullscreen())}}),o.on("mousewheel",j),o.on("DOMMouseScroll",j),this})}); \ No newline at end of file
diff --git a/assets/js/watch.js b/assets/js/watch.js
index 05e3b7e2..0f3e8123 100644
--- a/assets/js/watch.js
+++ b/assets/js/watch.js
@@ -73,29 +73,33 @@ if (continue_button) {
continue_button.onclick = continue_autoplay;
}
-function continue_autoplay(event) {
- if (event.target.checked) {
- player.on('ended', function () {
- var url = new URL('https://example.com/watch?v=' + video_data.next_video);
+function next_video() {
+ var url = new URL('https://example.com/watch?v=' + video_data.next_video);
- if (video_data.params.autoplay || video_data.params.continue_autoplay) {
- url.searchParams.set('autoplay', '1');
- }
+ if (video_data.params.autoplay || video_data.params.continue_autoplay) {
+ url.searchParams.set('autoplay', '1');
+ }
- if (video_data.params.listen !== video_data.preferences.listen) {
- url.searchParams.set('listen', video_data.params.listen);
- }
+ if (video_data.params.listen !== video_data.preferences.listen) {
+ url.searchParams.set('listen', video_data.params.listen);
+ }
- if (video_data.params.speed !== video_data.preferences.speed) {
- url.searchParams.set('speed', video_data.params.speed);
- }
+ if (video_data.params.speed !== video_data.preferences.speed) {
+ url.searchParams.set('speed', video_data.params.speed);
+ }
- if (video_data.params.local !== video_data.preferences.local) {
- url.searchParams.set('local', video_data.params.local);
- }
+ if (video_data.params.local !== video_data.preferences.local) {
+ url.searchParams.set('local', video_data.params.local);
+ }
+
+ url.searchParams.set('continue', '1');
+ location.assign(url.pathname + url.search);
+}
- url.searchParams.set('continue', '1');
- location.assign(url.pathname + url.search);
+function continue_autoplay(event) {
+ if (event.target.checked) {
+ player.on('ended', function () {
+ next_video();
});
} else {
player.off('ended');