您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Use play/pause/ArrowRight/ArrowLeft keyboard btns anywhere, but not only when youtube player is in focus. Additionaly use '[' and ']' keys to change playbackRate
当前为
// ==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.7 // @include https://www.youtube.com/* // @namespace https://gf.zukizuki.org/users/174246 // ==/UserScript== // https://gf.zukizuki.org/ru/scripts/39372-youtube-player-keyboard-controls /* globals AudioContext, ytplayer, KeyboardEvent, HTMLMediaElement, unescape, decodeURIComponent */ ;(function(window) { const config = { downloadLinks: true, playbackRate: true, customStyles: true, }; 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.playbackRate) { 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);