// ==UserScript==
// @name Youtube player keyboard controls
// @description Use play/pause/ArrowRight/ArrowLeft keyboard btns anywhere, but not only when youtube player is in focus. Additionaly use '[' and ']' keys to change playbackRate
// @author [email protected]
// @license MIT
// @version 0.1
// @include https://www.youtube.com/*
// @namespace https://gf.zukizuki.org/users/174246
// ==/UserScript==
;(function(window, undefined) {
const defaultYoutubeMoviePlayerId = 'movie_player';
let youtubeMoviePlayerId;
const getYoutubeMoviePlayerId = () => {
let youtubeMoviePlayerId = defaultYoutubeMoviePlayerId;
try {
if ( typeof ytplayer !== 'undefined' ) {
youtubeMoviePlayerId = ytplayer.config.attrs.id || defaultYoutubeMoviePlayerId;
}
}
catch(e) {
youtubeMoviePlayerId = defaultYoutubeMoviePlayerId;
}
return youtubeMoviePlayerId;
};
const getYoutubePlayer = (getVideoEl = true) => {
if ( youtubeMoviePlayerId === void 0 ) {
youtubeMoviePlayerId = getYoutubeMoviePlayerId();
}
const $moviePlayer = document.getElementById(youtubeMoviePlayerId);
if ( !$moviePlayer ) {
return null;
}
return getVideoEl
? $moviePlayer.querySelector('video')
: $moviePlayer
;
};
const getParrentByClassName = ($el, className, maxTopEls = 15) => {
if ( !$el ) {
return null;
}
let i = 0;
let child = $el;
for ( ; i < maxTopEls ; i++ ) {
if ( child.classList.contains(className) ) {
return child;
}
if ( !child.parentElement ) {
return null;
}
child = child.parentElement;
}
return null;
};
const isAdvVideoPlayer = $moviePlayer => {
if ( !$moviePlayer ) {
return false;
}
const $moviePlayerWrapper = getParrentByClassName($moviePlayer, 'html5-video-player', 5);
if ( $moviePlayerWrapper ) {
return $moviePlayerWrapper.classList.contains('ad-showing');
}
return false;
};
const isEditable = $el => {
return $el.tagName === 'INPUT'
|| $el.tagName === 'TEXTAREA'
|| ($el.hasAttribute('contenteditable') && $el.contentEditable !== 'false' && $el.contentEditable !== 'inherit');
};
const isVideoFullscreenElement = $el => {
const fullScreenElement = document.mozFullScreenElement || document.webkitFullscreenElement || document.webkitCurrentFullScreenElement || document.fullscreenElement;
const videoEl = fullScreenElement && fullScreenElement.querySelector('video');
return videoEl && $el.querySelector('video') !== videoEl && videoEl.tagName === 'VIDEO';
};
const _checkParent = $el => {
if ( !$el ) {
return false;
}
let i = 0;
let hasParent = true;
let child = $el;
for ( ; i < 20 ; i++ ) {
if ( !child.parentElement ) {
hasParent = false;
break;
}
child = child.parentElement;
}
return hasParent;
};
let playbackTimer;
let playbackRateElId;
let $elPlaybackRate;
let playbackRateOnKeyDown = event => {
const $moviePlayer = getYoutubePlayer(false);
if ( !$moviePlayer ) {
return false;
}
const {code, target} = event;
if ( code === 'BracketRight' || code === 'BracketLeft' ) {
if ( isEditable(target) ) {
return;
}
/*if ( !isVideoFullscreenElement(target) && (target == $moviePlayer || isEditable(target)) ) {
//console.log(' return ', 1)
return;
}
*/
if ( !playbackRateElId && $moviePlayer ) {
playbackRateElId = 'playbackRateText' + (Math.random() * 9e7 | 0).toString(36);
$moviePlayer.insertAdjacentHTML('afterbegin', `<div id="${playbackRateElId}" style="position: absolute;
z-index: 9999999;
right: 20px;
top: 20px;
pointer-events: none;
display: block;
transition: opacity .5s;
opacity: 0;
color: yellow;
width: auto;
height: 48px;
line-height: 48px;
font-size: 48px;
text-align: center;
text-shadow: 1px 1px 4px #000;"></div>`);
$elPlaybackRate = document.getElementById(playbackRateElId);
}
else if ( !_checkParent($elPlaybackRate) ) {
// Unattachment element
$moviePlayer.insertAdjacentElement('afterbegin', $elPlaybackRate);
}
const $video = $moviePlayer.querySelector('video');
const {playbackRate} = $video;
let newPlaybackRate;
{
let delta = code === 'BracketLeft' ? -0.25 : 0.25;
if ( delta < 0 ) {
if ( playbackRate > 2 || playbackRate <= 1 ) {
delta = -0.1;
}
}
else {
if ( playbackRate >= 2 || playbackRate < 1 ) {
delta = 0.1;
}
}
newPlaybackRate = playbackRate + delta;
if ( newPlaybackRate < 0.5 ) {
newPlaybackRate = 0.5;
}
else if ( newPlaybackRate > 3.5 ) {
newPlaybackRate = 3.5;
}
// Округление до 2го знака после запятой
newPlaybackRate = parseFloat(newPlaybackRate.toFixed(2));
}
$video.playbackRate = newPlaybackRate;
$elPlaybackRate.textContent = 'x' + newPlaybackRate;
$elPlaybackRate.style.opacity = 1;
if ( playbackTimer ) {
clearTimeout(playbackTimer);
}
playbackTimer = setTimeout(() => {
playbackTimer = void 0;
$elPlaybackRate.style.opacity = 0;
}, 500);
return true;
}
};
if ( window.__onKey__ ) {
document.removeEventListener('keyup', window.__onKey__, true);
document.removeEventListener('keydown', window.__onKey__, true);
window.__onKey__ = void 0;
}
const isNeedMagicActionsForYoutubeFix = () => {
return String(HTMLMediaElement.prototype.play).indexOf('pauseVideo') !== -1;
};
const sDoNotHandle = typeof Symbol === 'undefine' ? '__sDoNotHandle__' : Symbol('sDoNotHandle');
const onKey = event => {
if ( event[sDoNotHandle] ) {
return;
}
const $moviePlayer = getYoutubePlayer(false);
if ( !$moviePlayer ) {
return;
}
const {code, target, keyCode, charCode, which} = event;
if ( code === 'Space' || code === 'ArrowRight' || code === 'ArrowLeft' ) {
if ( isAdvVideoPlayer($moviePlayer) ) {
// Проигрывается реклама
// TODO:: нужно сделать кастомную перемотку вперёд-назад и кнопку "Пропустить"
console.log('Youtube Adw mode');
}
if ( !isVideoFullscreenElement(target) && (/*target == $moviePlayer || */isEditable(target)) ) {
//console.log(' return ', 1)
return;
}
const newEvent = new KeyboardEvent(event.type, event);
try {
if ( newEvent.keyCode !== keyCode ) {
Object.defineProperty(newEvent, 'keyCode', {value: keyCode, configurable: true, enumerable: true, writable: false});
}
}
catch(e){}
try {
if ( newEvent.charCode !== charCode ) {
Object.defineProperty(newEvent, 'charCode', {value: charCode, configurable: true, enumerable: true, writable: false});
}
}
catch(e){}
try {
if ( newEvent.which !== which ) {
Object.defineProperty(newEvent, 'which', {value: which, configurable: true, enumerable: true, writable: false});
}
}
catch(e){}
newEvent[sDoNotHandle] = true;
{
const $videoElement = getYoutubePlayer(true);
let magicActionsForYoutubeFix_pauseVideo = void 0;
if ( isNeedMagicActionsForYoutubeFix() ) {
try {
magicActionsForYoutubeFix_pauseVideo = $videoElement.parentElement.parentElement.pauseVideo;
if ( magicActionsForYoutubeFix_pauseVideo ) {
$videoElement.parentElement.parentElement.pauseVideo = void 0;
}
}
catch(e) {
magicActionsForYoutubeFix_pauseVideo = void 0;
}
}
//console.log(' dispatchEvent ', 2, newEvent, event);
$videoElement.dispatchEvent(newEvent);
if ( magicActionsForYoutubeFix_pauseVideo ) {
$videoElement.parentElement.parentElement.pauseVideo = magicActionsForYoutubeFix_pauseVideo;
magicActionsForYoutubeFix_pauseVideo = void 0;
}
}
event.stopPropagation();
event.preventDefault();
}
else if ( event.type === 'keydown' ) {
if ( playbackRateOnKeyDown(event) ) {
event.stopPropagation();
event.preventDefault();
}
}
};
document.addEventListener('keyup', onKey, true);
document.addEventListener('keydown', onKey, true);
window.__onKey__ = onKey;
})(window);