mirror of
https://github.com/iv-org/invidious.git
synced 2025-08-06 04:38:31 +00:00
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
This commit is contained in:
parent
dcff1ec25f
commit
a7cd2e5155
@ -38,69 +38,7 @@ var shareOptions = {
|
|||||||
embedCode: "<iframe id='ivplayer' type='text/html' width='640' height='360' src='" + embed_url + "' frameborder='0'></iframe>"
|
embedCode: "<iframe id='ivplayer' type='text/html' width='640' height='360' src='" + embed_url + "' frameborder='0'></iframe>"
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = videojs('player', options, function () {
|
var player = videojs('player', options);
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (location.pathname.startsWith('/embed/')) {
|
if (location.pathname.startsWith('/embed/')) {
|
||||||
player.overlay({
|
player.overlay({
|
||||||
@ -254,5 +192,228 @@ if (!video_data.params.listen && video_data.params.annotations) {
|
|||||||
xhr.send();
|
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);
|
||||||
|
|
||||||
// Since videojs-share can sometimes be blocked, we defer it until last
|
// Since videojs-share can sometimes be blocked, we defer it until last
|
||||||
player.share(shareOptions);
|
player.share(shareOptions);
|
||||||
|
@ -73,29 +73,33 @@ if (continue_button) {
|
|||||||
continue_button.onclick = continue_autoplay;
|
continue_button.onclick = continue_autoplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.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.local !== video_data.preferences.local) {
|
||||||
|
url.searchParams.set('local', video_data.params.local);
|
||||||
|
}
|
||||||
|
|
||||||
|
url.searchParams.set('continue', '1');
|
||||||
|
location.assign(url.pathname + url.search);
|
||||||
|
}
|
||||||
|
|
||||||
function continue_autoplay(event) {
|
function continue_autoplay(event) {
|
||||||
if (event.target.checked) {
|
if (event.target.checked) {
|
||||||
player.on('ended', function () {
|
player.on('ended', function () {
|
||||||
var url = new URL('https://example.com/watch?v=' + video_data.next_video);
|
next_video();
|
||||||
|
|
||||||
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.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
url.searchParams.set('continue', '1');
|
|
||||||
location.assign(url.pathname + url.search);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
player.off('ended');
|
player.off('ended');
|
||||||
|
Loading…
Reference in New Issue
Block a user