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.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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.
// @description set config.defaultPlaybackRate to your preferred youtube PlaybackRate (1.5 by default).
// @description set config.customStyles = true, to hide search bar by default. It will appear by mouse hover.
// @description set config.customStylesDarkTheme = true, to boost Youtube dark theme. You should turn on Dark Theme in Youtube settings first!
// @author [email protected]
// @license MIT
// @version 0.13
// @include https://www.youtube.com/*
// @namespace https://greasyfork.org/users/174246
// ==/UserScript==
// https://greasyfork.org/ru/scripts/39372-youtube-player-keyboard-controls
/* globals AudioContext, ytplayer, KeyboardEvent, HTMLMediaElement, unescape, decodeURIComponent */
;(function(window) {
    const config = {// Set your preferred value. It will be saved in localStorage.
        playbackRateControls: undefined,// boolean, [default = true]
        defaultPlaybackRate: 2,// number, [default = 1.5]
        // Some custom styles to Youtube, including hiding search bar (It will appear by mouse hover).
        // Set it to true, to turn it on.
        customStyles: true,// boolean, [default = false]
        // Boost Youtube Dark Theme. Set it to true, to turn it on.
        customStylesDarkTheme: true,// boolean, [default = false]
        customStylesDarkTheme_bottomContentOpacity: 0,// number, [default = 0.1]
    };

    append(config, _loadConfigLS('youtube_player_keyboard_controls_config'));

    _saveConfigLS('youtube_player_keyboard_controls_config', config);

    append(config, {// Default config
        playbackRateControls: true,
        defaultPlaybackRate: 1.5,
        // Some custom styles to Youtube, including hiding search bar (It will appear by mouse hover).
        customStyles: false,
        // Boost Youtube Dark Theme
        customStylesDarkTheme: false,
        customStylesDarkTheme_bottomContentOpacity: 0.1,
        // download doesn't work, after the latest youtube changes
        downloadLinks: false,
    });

    if (config.defaultPlaybackRate) {
        if (config.defaultPlaybackRate === 1) {
            sessionStorage.removeItem("yt-player-playback-rate");
            localStorage.removeItem("yt-player-playback-rate");
        }
        else {
            const now = Date.now();
            sessionStorage["yt-player-playback-rate"] = `{"data":"${config.defaultPlaybackRate}","creation":${now}}`;
            localStorage["yt-player-playback-rate"] = `{"data":"${config.defaultPlaybackRate}","creation":${now}}`;
        }
    }

    const defaultYoutubeMoviePlayerId = 'movie_player';
    let youtubeMoviePlayerId;

    /* TODO:: Volume more then 100%
    let audioContext;
    let audioContextSource;
    let audioContextGain;

    function getVolumeMoreThen100(videoElement, volumeValue) {
        // TODO:: доделать
        volumeValue = Math.max(10, volumeValue);

        if (!audioContext) {
            audioContext = new AudioContext();
        }

        audioContextSource = audioContext.createMediaElementSource(videoElement);

        // create a gain node
        audioContextGain = audioContext.createGain();
        audioContextGain.gain.value = volumeValue; // double the volume
        audioContextSource.connect(audioContextGain);

        // connect the gain node to an output destination
        audioContextGain.connect(audioContext.destination);
    }
    */
	const randStr = () => (Math.random() * 9e7 | 0).toString(36);
    const getYoutubeMoviePlayerId = () => {
        let youtubeMoviePlayerId = defaultYoutubeMoviePlayerId;

        try {
            if ( typeof ytplayer !== 'undefined' ) {
                youtubeMoviePlayerId = ytplayer.config.attrs.id || defaultYoutubeMoviePlayerId;
            }
        }
        catch(e) {
            youtubeMoviePlayerId = defaultYoutubeMoviePlayerId;
        }

        return youtubeMoviePlayerId;
    };
	const isElemVisible = function($el) {
		if (!$el) {
			return false;
		}
		
		// check element visibility
		if (typeof $el.getClientRects === 'function') {
			const res = $el.getClientRects();

			return !!(res && res.length);
		}
		
		// maybe visible
		return true;
	};
	
	const detectYoutubePlayer = function() {
		const visiblePlayersContainers = Array.from(document.querySelectorAll('#player')).filter(isElemVisible);
		
		for (const $container of visiblePlayersContainers) {
			const $player = $container.querySelector('[aria-label="YouTube Video Player"]');
			
			if (isElemVisible($player)) {
				return $player;
			}
		}
	};
	const checkAndResetMoviePlayer = function($moviePlayer) {
		if (!$moviePlayer) {
			youtubeMoviePlayerId = defaultYoutubeMoviePlayerId;
			
			return document.getElementById(youtubeMoviePlayerId);
		}
		else if (!isElemVisible($moviePlayer)) {
			if (youtubeMoviePlayerId === defaultYoutubeMoviePlayerId) {
				$moviePlayer = detectYoutubePlayer();
				
				if ($moviePlayer && $moviePlayer.id) {
					youtubeMoviePlayerId = $moviePlayer.id;
				}
			}
			else {
				youtubeMoviePlayerId = defaultYoutubeMoviePlayerId;	
			
				return document.getElementById(youtubeMoviePlayerId);
			}
		}
		
		return $moviePlayer;
	};

    const getYoutubePlayer = (getVideoEl = true) => {
        if ( youtubeMoviePlayerId === void 0 ) {
            youtubeMoviePlayerId = getYoutubeMoviePlayerId();
        }
		
		let $moviePlayer = checkAndResetMoviePlayer(document.getElementById(youtubeMoviePlayerId));
		if ( !$moviePlayer ) {
			if ( youtubeMoviePlayerId !== defaultYoutubeMoviePlayerId ) {
				$moviePlayer = document.getElementById(defaultYoutubeMoviePlayerId);
			}

			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 removeElement = htmlElement => {
        if (!htmlElement) {
            return;
        }

        if (typeof htmlElement.remove === 'function') {
            htmlElement.remove();

            return;
        }

        if (htmlElement.parentNode) {
            htmlElement.parentNode.removeChild(htmlElement);
        }
    };

    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 => {
        if (!config.playbackRateControls) {
            return false;
        }

        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 ) {
                /*jshint bitwise: false*/
                playbackRateElId = 'playbackRateText' + randStr();
                /*jshint bitwise: true*/

                $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;
    };

    let prevVideoElementUrl;
    const fixPauseVideo = ($moviePlayer, $videoElement) => {
        const youtubePlayerControls = $moviePlayer.querySelector('.ytp-chrome-controls');

        if ( youtubePlayerControls && prevVideoElementUrl !== $videoElement.src ) {
            prevVideoElementUrl = $videoElement.src;
            youtubePlayerControls.click();
        }
    };

    const sDoNotHandle = typeof Symbol === 'undefined' ? '__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 ( !isVideoFullscreenElement(target) && (/*target == $moviePlayer || */isEditable(target)) ) {
                //console.log(' return ', 1)
                return;
            }

            const $videoElement = getYoutubePlayer(true);
            if ( !$videoElement ) {
                // something went wrong
                console.warn('onKey: cant find youtube video element');
                return;
            }

            if ( isAdvVideoPlayer($moviePlayer) ) {
                // Проигрывается реклама
                // TODO:: нужно сделать кастомную перемотку вперёд-назад и кнопку "Пропустить"
                console.log('Youtube Adw mode');
            }

            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){}

            if ( $videoElement.paused && isNeedMagicActionsForYoutubeFix() ) {
                fixPauseVideo($moviePlayer, $videoElement);
            }

            newEvent[sDoNotHandle] = true;

            //console.log(' dispatchEvent ', 2, newEvent, event);
            $videoElement.dispatchEvent(newEvent);

            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;

    if (config.customStyles) {
        if (config.customStylesDarkTheme) {
			const autoBottomContentOpacityClassName = 'custom_abco_' + randStr();
            const {customStylesDarkTheme_bottomContentOpacity = 1} = config;
			
			if (customStylesDarkTheme_bottomContentOpacity !== 1) {
				const toggleDocClass = () => {
					document.documentElement.classList.toggle(autoBottomContentOpacityClassName, document.documentElement.scrollTop <= 0);
				};
				
				toggleDocClass();
				document.addEventListener('scroll', toggleDocClass, { passive: true });
			}
			
            const styleText = `html, body, #content {
    background: black !important;
}
ytd-app {
    --yt-app-background: black !important;
    --yt-spec-general-background-a: black !important;
}
#masthead-container.ytd-app {
    background: black !important;
}
#container.ytd-masthead {
    background: black !important;
}
#primary, #columns {
    background: black !important;

    --yt-endpoint-visited-color: #9c9c9c;
    --yt-spec-call-to-action: #9c9c9c;
    --ytd-video-primary-info-renderer-title-color: #9c9c9c;
    --ytd-comment-text-color: #9c9c9c;
    --yt-spec-text-primary: #9c9c9c;
    
    --yt-spec-general-background-b: #1a1a1a !important;
    --yt-spec-text-primary: #9c9c9c !important;
}
ytd-toggle-button-renderer.style-default-active[is-icon-button] {
    color: #34624b;
}
#guide {
    --app-drawer-content-container_-_background-color: #1a1a1a;
    --yt-spec-brand-background-solid: #1a1a1a;
    --yt-spec-text-primary: #9c9c9c;
}

.ytp-gradient-bottom,
.ytp-large-play-button {
    display: none;
}
.ytp-cued-thumbnail-overlay {
    display: none;
}
#watch-action-panels {
    display: none;
}
.ytp-chrome-bottom {
    opacity: 0;
    transition: opacity .5s;
}
.ytp-chrome-bottom:hover {
    opacity: 1;
}

ytd-watch-flexy[theater] #columns {
    opacity: 1;
    transition: opacity .5s;
}
.${autoBottomContentOpacityClassName} ytd-watch-flexy[theater] #columns {
    opacity: ${customStylesDarkTheme_bottomContentOpacity};
}
.${autoBottomContentOpacityClassName} ytd-watch-flexy[theater] #columns:hover {
    opacity: 1;
}
ytd-masthead {
    opacity: 0;
    transition: opacity .5s;
}
ytd-masthead:hover {
    opacity: 1;
}

#masthead-positioner {
    opacity: 0;
    position: fixed;
    z-index: 9999;
}
#masthead-positioner:hover {
    opacity: 1;
}`;
            const $parentEl = document.head || document.body || document.documentElement;

            if ($parentEl) {
                $parentEl.insertAdjacentHTML('afterbegin', `<style name="youtube-player-keyboard-controls_custom-styles">${styleText}</style>`);
            }
        }
        else {
            const styleText = `#watch-action-panels {
    display: none;
}
.ytp-chrome-bottom {
    opacity: 0;
}
.ytp-chrome-bottom:hover {
    opacity: 1;
}
ytd-masthead {
    opacity: 0;
}
ytd-masthead:hover {
    opacity: 1;
    transition: opacity .5s;
}

#masthead-positioner {
    opacity: 0;
    position: fixed;
    z-index: 9999;
}
#masthead-positioner:hover {
    opacity: 1;
}
`;
            const $parentEl = document.head || document.body || document.documentElement;

            if ($parentEl) {
                $parentEl.insertAdjacentHTML('afterbegin', `<style name="youtube-player-keyboard-controls_custom-styles-simple">${styleText}</style>`);
            }
        }
    }

    function initDownloadLinks() {
        let youTuubeDownloadInterval;
        function setIntervalYD(interval = 5000) {
            if (youTuubeDownloadInterval) {
                clearInterval(youTuubeDownloadInterval);
            }

            youTuubeDownloadInterval = setInterval(function() {
                const $moreActionsBtn = getMoreActionsBtn();

                if ($moreActionsBtn) {
                    clearInterval(youTuubeDownloadInterval);
                    youTuubeDownloadInterval = void 0;

                    $moreActionsBtn.addEventListener('click', function() {
                        setTimeout(function() {
                            youtubedownloader_main(true);
                        }, 500);

                        setTimeout(function() {
                            youtubedownloader_main(true);
                        }, 5000);
                    }, false);
                }
                else {
                    youtubedownloader_main();
                }
            }, interval);
        }

        function getMoreActionsBtn() {
            const $menuContainer = document.querySelector('#menu-container');
            let $moreActionsBtn = null;

            if ($menuContainer) {
                $moreActionsBtn = $menuContainer.querySelector('[aria-label="More actions"]')
                    || $menuContainer.querySelector('yt-icon-button > button')
                    || $menuContainer.querySelector('#button')
                    || $menuContainer.querySelector('.yt-icon-button')
                    || null
                ;
            }

            return $moreActionsBtn;
        }

        let $historyManager;
        let $dataHost;

        function getYoutubeplayerConfig() {
            let ytplayerConfig = null;
            const historyState = history.state;
            const historyEntryTime = historyState && historyState.entryTime;

            if (!$historyManager) {
                $historyManager = document.querySelector('#historyManager') || document.querySelector('yt-history-manager');
            }
            if (!$dataHost) {
                $dataHost = $historyManager && $historyManager.dataHost || document.querySelector('ytd-app[is-watch-page]');
            }

            if ($dataHost && $dataHost.data) {
                ytplayerConfig = $dataHost.data.player;
            }

            if (!ytplayerConfig) {
                if ($historyManager && historyEntryTime && $historyManager["historySnapshotCache_"]) {
                    try {
                        const current_ytplayerConfig = $historyManager["USE_HISTORY_SNAPSHOT_CACHE_"]
                            ? $historyManager["historySnapshotCache_"].get(historyEntryTime)
                            : $historyManager["historyEntryTimeToDataMap_"][a.entryTime]
                        ;

                        if (current_ytplayerConfig) {
                            ytplayerConfig = current_ytplayerConfig;
                        }
                    }
                    catch(err) {
                        console.error(err);
                    }
                }
            }


            return ytplayerConfig
                || (typeof ytplayer !== 'undefined'
                        ? ((ytplayer || {})["config"] || {})
                        : { "args": {} }
                )
                ;
        }

        function getListElement() {
            return document.querySelector('paper-listbox#items');
        }

        function createElement(tagName, attributes, append) {
            const element = document.createElement(tagName);

            for (const attributeName in attributes) {
                if (attributes.hasOwnProperty(attributeName)) {
                    if (typeof element[attributeName] !== 'undefined' && attributeName !== 'style' && attributeName !== 'type'/*FIX IE*/) {
                        element[attributeName] = attributes[attributeName];
                    }
                    else {
                        element.setAttribute(attributeName, attributes[attributeName]);
                    }
                }
            }

            if (append && append.nodeType === 1/*append instanceof Element*/) {
                append.appendChild(element);
            }

            return element;
        }

        function youtubedownloader_main(forceAttacheToMoreActionsBtn = false) {
            const ytplayerConfig = getYoutubeplayerConfig();
            const ytplayerVideoId = ytplayerConfig["args"] && ytplayerConfig["args"]["video_id"];

            if (ytplayerVideoId) {
                console.log("YouTube Downloader: Start");

                const title = ytplayerConfig["args"]["title"];
                const video = {
                    title,
                    title_encoded: encodeURIComponent(title.replace(/[\\~#%&*{}\/:<>?|"-]/g, "_")),
                    fmt: ytplayerConfig["args"]["url_encoded_fmt_stream_map"] + "," + ytplayerConfig["args"]["adaptive_fmts"],
                    id: ytplayerVideoId,
                };
                const textlang = {
                    button_text: "Download",
                    button_hover_text: "Download this video!",
                };

                removeElement(document.getElementById('watch-download-links'));

                const linksContainer = createElement("div", { id: "watch-download-links" });
                const links = video.fmt.split(",");
                let useSignature = false;

                console.log("YouTube Downloader: " + (links.length) + " links collected");

                const parseParamFromLink = function(link, re) {
                    return (link.match(re) || [, ''])[1];
                };

                for (let i = 0 ; i < links.length ; i++) {
                    const link = links[i];

                    if (!link || link === 'undefined') {
                        continue;
                    }

                    const itag = parseInt(parseParamFromLink(link, /itag=([0-9]*)(?:&|$)/) || '0', 10) || void 0;
                    // TODO:: use signature instead of sig? // const signature = parseParamFromLink(link, /(?:^|&)signature=(.*?)(?:&|$)/);
                    const sig = parseParamFromLink(link, /(?:^|&)s=(.*?)(?:&|$)/);
                    const quality_label = parseParamFromLink(link, /quality_label=(.*?)(?:&|$)/) || parseParamFromLink(link, /(?:^|&)quality=(.*?)(?:&|$)/);
                    const size = parseParamFromLink(link, /(?:^|&)size=(.*?)(&|$)/);
                    const projection_type = parseParamFromLink(link, /(?:^|&)projection_type=(.*?)(&|$)/);
                    const url = /*unescape*/decodeURIComponent(parseParamFromLink(link, /url=(.*?)(?:&|$)/)) + (sig ? "&signature=" + sig : "") + "&title=" + video.title_encoded;

                    /*
    (ytplayerConfig = ytplayer.config),(ytplayerConfig["args"]["url_encoded_fmt_stream_map"] + "," + ytplayerConfig["args"]["adaptive_fmts"]).split(",").map(link => { const parse = re=>(link.match(re)||[,''])[1];  let itag = parseInt(parse(/itag=([0-9]*)(?:&|$)/)||'0');let sig = parse(/(?:^|&)s=(.*?)(?:&|$)/);let type = decodeURIComponent(parse(/(?:^|&)type=(.*?)(&|$)/)); let mime = decodeURIComponent(parse(/(?:^|&)mime=(.*?)(&|$)/)); let quality_label = parse(/quality_label=(.*?)(?:&|$)/) || parse(/(?:^|&)quality=(.*?)(?:&|$)/); let size = parse(/(?:^|&)size=(.*?)(&|$)/); let projection_type = parse(/(?:^|&)projection_type=(.*?)(&|$)/); return {itag, sig, type, mime, quality_label, size, projection_type}; });
                    */

                    if (sig) {
                        console.log("YouTube Downloader: Signature detected; aborting!", sig);

                        useSignature = true;
                        //linksContainer.innerHTML = "";
                    }

                    //console.log("links[" + i + "]: itag=" + itag + ";sig=" + sig + ';url=' + url);

                    let nAtext = "";

                    switch (itag) {
                        case 38: nAtext = "2160p(4K,MP4)"; break;
                        case 46: nAtext = "1080p(WebM)"; break;
                        case 37: nAtext = "1080p(MP4)"; break;
                        case 45: nAtext = "720p (WebM)"; break;
                        case 102: /*nAtext = "720p (WebM,3D)";*/ break;
                        case 22: nAtext = "720p (MP4)"; break;
                        case 84: /*nAtext = "720p (MP4,3D)";*/ break;
                        case 85: /*nAtext = "520p (MP4,3D)";*/ break;
                        case 35: nAtext = "480p (FLV)"; break;
                        case 44: nAtext = "480p (WebM)"; break;
                        case 34: nAtext = "360p (FLV)"; break;
                        case 18: nAtext = "360p (MP4)"; break;
                        case 82: /*nAtext = "360p (MP4,3D)";*/ break;
                        case 100: /*nAtext = "360p (WebM,3D)";*/ break;
                        case 101: /*nAtext = "360p (WebM,3D)";*/ break;
                        case 43: nAtext = "360p (WebM)"; break;
                        case 6: nAtext = "270p (FLV)"; break;
                        case 83: /*nAtext = "240p (MP4,3D)";*/ break;
                        case 36: nAtext = "240p (3GP)"; break;
                        case 5: nAtext = "240p (FLV)"; break;
                        case 395: /*nAtext = "240p (MP4, Video only)";*/ break;
                        case 17: nAtext = "144p (3GP)"; break;
                        case 278: /*nAtext = "144p (WebM,Video only)";*/ break;
                        case 264: /*nAtext = "4k(MP4,Video only)";*/ break;
                        case 138: /*nAtext = "4k+(MP4,Video only)";*/ break;
                        case 137: /*nAtext = "1080p(MP4,Video only)";*/ break;
                        case 136: /*nAtext = "720p(MP4,Video only)";*/ break;
                        case 135: /*nAtext = "480p(MP4,Video only)";*/ break;
                        case 134: /*nAtext = "360p(MP4,Video only)";*/ break;
                        case 133: /*nAtext = "240p(MP4,Video only)";*/ break;
                        case 160: /*nAtext = "144p(MP4,Video only)";*/ break;
                        case 394: /*nAtext = "144p(MP4,Video only ?)";*/ break;
                        case 248: /*nAtext = "1080p(WemM,Video only)";*/ break;
                        case 247: /*nAtext = "720p(WemM,Video only)";*/ break;
                        case 397: /*nAtext = "480p(?)";*/ break;
                        case 244: /*nAtext = "480p(WemM,Video only)";*/ break;
                        case 243: /*nAtext = "360p(WemM,Video only)";*/ break;
                        case 396: /*nAtext = "360p(?)";*/ break;
                        case 242: /*nAtext = "240p(WemM,Video only)";*/ break;
                        case 140: nAtext = "Audio(M4A)"; break;
                        case 249: /*nAtext = "Audio(WebM ?)";*/ break;
                        case 250: /*nAtext = "Audio(WebM ?)";*/ break;
                        case 251: /*nAtext = "Audio(WebM ?)";*/ break;
                        case 171: /*nAtext = "Audio(OGG)";*/ break;
                        case 298: /*nAtext = "720p60(MP4,Video only)";*/ break;
                        case 398: /*nAtext = "720p50(?)";*/ break;
                        case 299: /*nAtext = "1080p60(MP4,Video only)";*/ break;
                        case 302: /*nAtext = "720p60(WemM,Video only)";*/ break;
                        case 303: /*nAtext = "1080p60(WemM,Video only)";*/ break;
                        case 266: /*nAtext = "2160p(4K,MP4,Video only)";*/ break;
                        case 271: /*nAtext = "1440p(WemM,Video only)";*/ break;
                        case 272: /*nAtext = "2160p(4K,WemM,Video only)";*/ break;
                        case 313: /*nAtext = "2160p(4K,WemM,Video only)";*/ break;
                        default: {
                            console.warn("YouTube Downloader: Unknown itag: " + itag);

                            if (!itag) {
                                console.warn("YouTube Downloader: link: " + link);
                            }

                            break;
                        }

                    }

                    if (nAtext && !sig) {
                        createElement("a", {
                            href: url,
                            target: '_blank',
                            download: video.title_encoded,
                            innerHTML: nAtext,
                        }, linksContainer);
                    }
                }


                if(linksContainer.childNodes.length === 0){
                    createElement("a", {
                        href: "javascript:alert('This video cannot be downloaded')",
                        innerHTML: "Video download unavailable"
                    }, linksContainer);
                }

                let renderArea;

                if (!forceAttacheToMoreActionsBtn && (renderArea = document.getElementById("watch7-secondary-actions"))) {//document.cookie="VISITOR_INFO1_LIVE=jZNC3DCddAk; path=/; domain=.youtube.com";window.location.reload();
                    createElement("div", { id: "action-panel-download" }, document.getElementById("watch7-action-panels"));

                    const dlb = createElement("button", {
                        className: "action-panel-trigger yt-uix-button",
                        type: "button",
                        onclick(e) {
                            const wdl = document.getElementById("watch-download-links").style;

                            if (wdl.display !== "block") {
                                wdl.display = "block";
                                this.classList.add("yt-uix-button-toggled");
                            }
                            else {
                                wdl.display = "none";
                                this.classList.remove("yt-uix-button-toggled");
                            }

                            wdl.top = (this.offsetTop + this.clientHeight + 7) + "px";
                            wdl.left = (this.offsetLeft + 1) + "px";

                            e.stopPropagation();
                        },
                        innerHTML: `<span class="yt-uix-button-content">${textlang.button_text}</span>`
                    });

                    const W7B0 = document.querySelector('#watch7-secondary-actions button:first-child');

                    W7B0.parentNode.insertBefore(dlb, W7B0.nextSibling);
                    document.getElementById("watch7-content").appendChild(linksContainer);
                }
                else if (!forceAttacheToMoreActionsBtn && (renderArea = document.getElementById("watch8-secondary-actions"))) {
                    createElement("div", { id: "action-panel-download" }, renderArea.parentNode);

                    const dls = createElement("span", { className : "ytd-dls" });
                    createElement("button", {
                        className: "yt-uix-button yt-uix-button-size-default yt-uix-button-opacity yt-uix-button-has-icon no-icon-markup action-panel-trigger yt-uix-tooltip",
                        type: "button",
                        title: textlang.button_hover_text,
                        "data-tooltip-text": textlang.button_hover_text,
                        onclick(e) {
                            const wdl = document.getElementById("watch-download-links").style;
                            if (wdl.display !== "block") {
                                wdl.display = "block";
                                this.classList.add("yt-uix-button-toggled");
                            }
                            else {
                                wdl.display = "none";
                                this.classList.remove("yt-uix-button-toggled");
                            }

                            wdl.top = (this.offsetTop + this.clientHeight + 7) + "px";
                            wdl.left = (this.offsetLeft + 1) + "px";

                            e.stopPropagation();
                        },
                        innerHTML: `<span class="yt-uix-button-content">${textlang.button_text}</span>`
                    }, dls);

                    const W7B0 = document.querySelector('#watch8-secondary-actions .yt-uix-menu');

                    W7B0.parentNode.insertBefore(dls, W7B0);
                    renderArea.appendChild(linksContainer);
                }
                else if (renderArea = getListElement()) {
                    renderArea.appendChild(linksContainer);
                    linksContainer.style.display = 'block';

                    linksContainer.childNodes.forEach(el => el.style.display = 'block');
                }
                else {
                    console.log("YouTube Downloader: Sem renderArea");
                }

                /*
                //insert css
                var ncss = document.createElement("style");
                ncss.appendChild(document.createTextNode("#watch-download-links{width:90px;box-shadow:#999 0px 0px 3px 0px; border-bottom-left-radius:2px; border-bottom-right-radius:2px; background:#EBEBEB; overflow:hidden}#watch-download-links a{height:25px; width:82px;background:#FFF!important; line-height:25px; font-size:12px; padding:0 0 0 8px!important; display:inline-block; color:#000!important; text-decoration:none!important;}#watch-download-links a:hover{background:#EBEBEB!important;}#dl{float:left; padding:4px}#wdl{background:#FFF;border-radius:5px; border:1px solid #EAEAEA; font-family:Arial, Helvetica, sans-serif; font-size:12px; line-height:14px; margin:0px auto 10px auto; overflow:hidden; display:none; height:auto}#wdl a{padding:5px; color:#000; text-decoration:none; display:inline-block}#wdl a:hover{background:#333; color:#FFF}#watch-actions{height:auto!important}#watch-sidebar iframe{width:234px; height:60px; margin:10px 25px; border-radius:2px; box-shadow:0 0 20px rgba(0,0,0,.2)}"));
                document.head.appendChild(ncss);
                */
            }
        }

        setIntervalYD();
    }

    if (config.downloadLinks) {
        initDownloadLinks();
    }

    function _loadConfigLS(key) {
        const value = localStorage.getItem(key);

        if (value) {
            try {
                return JSON.parse(value);
            }
            catch(e) {
                //
            }
        }

        return void 0;
    }

    function _saveConfigLS(key, value) {
        if (value && typeof value === 'object') {
            const _value = append({}, value);

            if (Object.keys(_value).length > 0) {
                localStorage.setItem(key, JSON.stringify(value));
            }
        }
    }

    function append(target, ...sources) {
        if (target === void 0 || target === null) {
            throw new TypeError('Cannot convert undefined or null to object');
        }

        const _target = Object(target);

        for (let j = 0, len2 = sources.length ; j < len2 ; j++ ) {
            const source = sources[j];

            if (!source) {
                continue;
            }

            const keys = Object.keys(source);

            for (let i = 0, len = keys.length ; i < len ; i++) {
                const key = keys[i];
                const sourceValue = source[key];

                if (_target[key] === void 0 && sourceValue !== void 0) {
                    _target[key] = sourceValue;
                }
            }
        }

        return _target;
    }
})(window);