Youtube player keyboard controls

Use play/pause/ArrowRight/ArrowLeft keyboard btns anywhere, but not only when youtube player is in focus. Additionaly use '[' and ']' keys to change playbackRate

Ajankohdalta 10.3.2018. Katso uusin versio.

// ==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);