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

Version au 24/03/2020. Voir la dernière version.

// ==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.8
// @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 = {
    downloadLinks: true,
    playbackRateControls: true,
    customStyles: true,
    defaultPlaybackRate: 2,
};

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 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();
    }

    let $moviePlayer = 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' + (Math.random() * 9e7 | 0).toString(36);
            /*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) {
    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;
}

#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>${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();
}

})(window);