您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Don't like the youtube autoplay suggestion? This script can create a queue with videos you want to play after your current video has finished!
当前为
// ==UserScript== // @name Youtube Play Next Queue // @version 1.1.2 // @description Don't like the youtube autoplay suggestion? This script can create a queue with videos you want to play after your current video has finished! // @author Cpt_mathix // @include https://www.youtube.com* // @license GPL version 2 or any later version; http://www.gnu.org/licenses/gpl-2.0.txt // @require https://cdnjs.cloudflare.com/ajax/libs/JavaScript-autoComplete/1.0.4/auto-complete.min.js // @namespace https://gf.qytechs.cn/users/16080 // @grant none // @noframes // ==/UserScript== (function() { var script = { ytplayer: null, playnext: true, queue: new Queue(), version: '1.1.2', search_timeout: null, search_suggestions: [], debug: false }; // callback function for search results window.search_callback = search_callback; // youtube search parameters const GeoLocation = window.yt.config_.INNERTUBE_CONTEXT_GL; const HostLanguage = window.yt.config_.INNERTUBE_CONTEXT_HL; // reload script on page change using youtube spf events (http://youtube.github.io/js/documentation/events/) window.addEventListener("spfdone", function(e) { if (script.debug) console.log("new page loaded"); clearSearchRequests(); if (isPlayerAvailable()) { startScript(2); } }); main(); function main() { initGlobalScrollListener(); addCSS(); if (isPlayerAvailable()) { if (script.debug) console.log("player available"); startScript(5); } else { if (script.debug) console.log("player unavailable"); } } function startScript(retry) { script.ytplayer = getVideoPlayer(); if (script.debug) console.log(script.ytplayer); if (script.ytplayer) { if (getVideoInfoFromUrl(document.location.href, "t") == "0s") script.ytplayer.seekTo(0); if (script.debug) console.log("initialising queue"); initQueue(); if (script.debug) console.log("initialising search"); initSearch(); if (script.debug) console.log("initialising video state listener"); initVideoStateListener(); if (script.debug) console.log("initialising queue buttons"); initQueueButtons(); } else if (retry > 0) { // fix conflict with Youtube+ script setTimeout( function() { startScript(retry--); }.bind(retry), 1000); } } // *** LISTENERS *** // function initVideoStateListener() { // play next video in queue if current video is finished playing (state equal to 0) script.ytplayer.addEventListener("onStateChange", function(videoState) { if (script.debug) console.log("state changed", videoState); const FINISHED_STATE = 0; if (videoState === FINISHED_STATE && script.playnext === true && !script.queue.isEmpty()) { script.playnext = false; var next = script.queue.dequeue(); playNextVideo(next.id); } else if (videoState !== FINISHED_STATE) { script.playnext = true; } }); } // Did new content load? Triggered everytime you scroll function initGlobalScrollListener() { document.addEventListener("scroll", function scroll(event) { try { if (isPlayerAvailable()) { if (script.ytplayer === null) { script.ytplayer = getVideoPlayer(); startScript(0); } else { initQueueButtons(); } } } catch(error) { console.error("Couldn't initialize add to queue buttons \n" + error.message); } event.currentTarget.removeEventListener(event.type, scroll); if (script.debug) console.log("scroll"); setTimeout( function() { initGlobalScrollListener(); }, 1000); }); } // *** OBJECTS *** // // video object function ytVideo(name, id, html, anchor) { this.name = name; this.id = id; this.html = html; this.buttonAnchor = anchor; } // extended video object function extendedYtVideo(name, id, html, anchor, channelHTML, time, stats, thumb) { this.name = name; this.id = id; this.html = html; this.channelHTML = channelHTML; this.time = time; this.stats = stats; this.buttonAnchor = anchor; this.thumb = thumb; } // Queue object function Queue() { var queue = []; this.get = function() { return queue; }; this.set = function(newQueue) { queue = newQueue; setCache("queue", this.get()); }; this.isEmpty = function() { return 0 === queue.length; }; this.reset = function() { queue = []; this.update(0); }; this.enqueue = function(item) { queue.push(item); this.update(500); }; this.dequeue = function() { var item = queue.shift(); this.update(0); return item; }; this.remove = function(index) { queue.splice(index, 1); this.update(250); }; this.playNext = function(index) { var video = queue.splice(index, 1); queue.unshift(video[0]); this.update(0); }; this.playNow = function(index) { var video = queue.splice(index, 1); this.update(0); playNextVideo(video[0].id); }; this.showQueue = function() { var html = ""; queue.forEach( function(item) { html += item.html; }); return html; }; this.update = function(delay) { setCache("queue", this.get()); if (script.debug) console.log(this.get().slice()); setTimeout(function() {displayQueue();}, delay); }; } // *** VIDEO & PLAYER *** // // play next video behavior depending on if you're watching fullscreen function playNextVideo(nextVideoId) { if (script.debug) console.log("playing next song"); if (isPlayerFullscreen()) { script.ytplayer.loadVideoById(nextVideoId, 0); } else { window.spf.navigate("https://www.youtube.com/watch?v=" + nextVideoId + "&t=0s"); } } function getVideoPlayer() { return document.getElementById("movie_player"); } function isPlayerAvailable() { return /https:\/\/www\.youtube\.com\/watch\?v=.*/.test(document.location.href) && !getVideoInfoFromUrl(document.location.href, "list") && document.getElementById("live-chat-iframe") === null; } function isPlayerFullscreen() { return (script.ytplayer.classList.contains("ytp-fullscreen")); } function getVideoInfoFromUrl(url, info) { if (url.indexOf('?') === -1) return null; var urlVariables = url.split('?')[1].split('&'), varName; for (var i = 0; i < urlVariables.length; i++) { varName = urlVariables[i].split('='); if (varName[0] === info) { return varName[1] === undefined ? null : varName[1]; } } } // extracting video information and creating a video object (that can be added to the queue) function findVideoInformation(video, selector) { var anchor = video.querySelector(selector + " .yt-uix-sessionlink:not(.related-playlist)"); if (anchor) { var videoTitle = video.querySelector("span.title").textContent.trim(); var id = getVideoInfoFromUrl(video.querySelector("a.yt-uix-sessionlink").href, "v"); var newVidObject = new ytVideo(videoTitle, id, video.outerHTML, anchor); return newVidObject; } return null; } // *** QUEUE *** // function initQueue() { var cachedQueue = getCache('queue'); if (cachedQueue) { script.queue.set(cachedQueue); } else { setCache('queue', script.queue.get()); } // prepare html for queue var queue = document.getElementsByClassName("autoplay-bar")[0]; queue.classList.add("video-list"); queue.id = "watch-queue"; queue.setAttribute("style", "list-style:none"); // add class to suggestion video so it doesn't get queue related buttons var suggestion = queue.getElementsByClassName("related-list-item")[0]; suggestion.classList.add("suggestion"); // show the queue if not empty if (!script.queue.isEmpty()) { if (script.debug) console.log("showing queue"); if (script.debug) console.log(script.queue.get()); displayQueue(); } } function displayQueue() { var html = script.queue.showQueue(); var queue = document.querySelector(".autoplay-bar"); var anchor = document.querySelector(".watch-sidebar-head"); // cleanup current queue var li = document.querySelectorAll(".autoplay-bar > li.video-list-item"); if (li) { for (var i = li.length - 1; i >= 0; i--) { li[i].remove(); } } // display new queue if (html !== null) { anchor.insertAdjacentHTML("afterend", html); // add remove buttons var items = queue.querySelectorAll(".related-list-item:not(.suggestion)"); for (var z = 0; z < items.length; z++) { var video = findVideoInformation(items[z], "#watch-queue"); // remove addbutton if there is one var addedButton = items[z].querySelector(".queue-add"); if (addedButton) addedButton.parentNode.remove(); if (video) { if (z > 0) { playNextButton(video, z); } else { playNowButton(video, z); } removeButton(video, z); } } // replace autoplay options with remove queue button var autoplay = queue.querySelector(".checkbox-on-off"); if (autoplay && !script.queue.isEmpty()) { removeQueueButton(autoplay); } // add queue button to suggestion video var suggestion = queue.querySelector(".suggestion:not(.processed)"); if (suggestion && !script.queue.isEmpty()) { var suggestionVideo = findVideoInformation(suggestion, "#watch-queue"); suggestion.classList.add("processed"); suggestionAddButton(suggestionVideo, suggestion); } // triggering lazyload window.scrollTo(window.scrollX, window.scrollY + 1); window.scrollTo(window.scrollX, window.scrollY - 1); } // remove not interested menu var menu = queue.getElementsByClassName("yt-uix-menu-trigger"); for (var j = menu.length - 1; j >= 0; j--) { menu[j].remove(); } } // *** BUTTONS *** // // finding video's and adding the queue buttons function initQueueButtons() { var videos = document.querySelectorAll(".related-list-item:not(.processed-buttons)"); for (var j = 0; j < videos.length; j++) { try { var video = findVideoInformation(videos[j], "#watch-related"); videos[j].classList.add("processed-buttons"); if (video) { addButton(video); } } catch(error) { console.error("Couldn't initialize \"Add to queue\" button on a video \n" + error.message); } } } // The "add to queue" button function addButton(video) { var anchor = video.buttonAnchor; var html = '<div class="queue-button yt-uix-button yt-uix-button-default yt-uix-button-size-default"><button class="yt-uix-button-content queue-add">Add to queue</button></div>'; anchor.insertAdjacentHTML('beforeend', html); anchor.getElementsByClassName("queue-add")[0].addEventListener("click", function handler(e) { e.preventDefault(); this.textContent = "Added!"; script.queue.enqueue(video); e.currentTarget.removeEventListener(e.type, handler); this.addEventListener("click", function (e) { e.preventDefault(); }); }); } // The "add to queue" button for the suggestion video function suggestionAddButton(video, suggestion) { var anchor = video.buttonAnchor; var html = '<div class="queue-button yt-uix-button yt-uix-button-default yt-uix-button-size-default"><button class="yt-uix-button-content queue-add">Add to queue</button></div>'; anchor.insertAdjacentHTML('beforeend', html); anchor.getElementsByClassName("queue-add")[0].addEventListener("click", function handler(e) { e.preventDefault(); this.textContent = "Added!"; suggestion.classList.remove("suggestion"); video.html = suggestion.outerHTML; script.queue.enqueue(video); e.currentTarget.removeEventListener(e.type, handler); suggestion.parentNode.removeChild(suggestion); }); } // The "remove from queue" button function removeButton(video, nb) { var anchor = video.buttonAnchor; var html = '<div class="queue-button yt-uix-button yt-uix-button-default yt-uix-button-size-default" style="margin-left:3px"><button class="yt-uix-button-content queue-remove">Remove</button></div>'; anchor.insertAdjacentHTML("beforeend", html); anchor.getElementsByClassName("queue-remove")[0].addEventListener('click', function handler(e) { e.preventDefault(); this.textContent = "Removed!"; script.queue.remove(nb); restoreAddButton(video.id); e.currentTarget.removeEventListener(e.type, handler); this.addEventListener("click", function (e) { e.preventDefault(); }); }); } // The "play next" button function playNextButton(video, nb) { var anchor = video.buttonAnchor; var html = '<div class="queue-button yt-uix-button yt-uix-button-default yt-uix-button-size-default"><button class="yt-uix-button-content queue-next">Play Next</button></div>'; anchor.insertAdjacentHTML("beforeend", html); anchor.getElementsByClassName("queue-next")[0].addEventListener('click', function handler(e) { e.preventDefault(); this.textContent = "To the top!"; script.queue.playNext(nb); e.currentTarget.removeEventListener(e.type, handler); this.addEventListener("click", function (e) { e.preventDefault(); }); }); } // The "play now" button function playNowButton(video, nb) { var anchor = video.buttonAnchor; var html = '<div class="queue-button yt-uix-button yt-uix-button-default yt-uix-button-size-default"><button class="yt-uix-button-content queue-now">Play Now</button></div>'; anchor.insertAdjacentHTML("beforeend", html); anchor.getElementsByClassName("queue-now")[0].addEventListener("click", function handler(e) { e.preventDefault(); this.textContent = "Playing!"; script.queue.playNow(nb); e.currentTarget.removeEventListener(e.type, handler); this.addEventListener("click", function (e) { e.preventDefault(); }); }); } // The "remove queue and all its videos" button function removeQueueButton(anchor) { var html = '<div class="queue-button yt-uix-button yt-uix-button-default yt-uix-button-size-default" style="margin:0px"><button class="yt-uix-button-content remove-queue">Remove Queue</button></div>'; anchor.innerHTML = html; anchor.getElementsByClassName("remove-queue")[0].addEventListener("click", function handler(e) { e.preventDefault(); this.textContent = "Removed!"; script.queue.reset(); restoreAddButton("*"); // restore all e.currentTarget.removeEventListener(e.type, handler); this.addEventListener("click", function (e) { e.preventDefault(); }); }); } function restoreAddButton(id) { var videos = document.querySelectorAll(".related-list-item"); for (var j = 0; j < videos.length; j++) { if (id === "*" || id === getVideoInfoFromUrl(videos[j].querySelector("a.yt-uix-sessionlink").href, "v")) { // remove current addbutton if there is one var addedButton = videos[j].querySelector(".queue-add"); if (addedButton) addedButton.parentNode.remove(); // make new addbutton var video = findVideoInformation(videos[j], "#watch-related"); if (video) { addButton(video); } } } } // *** SEARCH *** // // initialize search function initSearch() { var anchor = document.querySelector("#watch7-sidebar-modules > div:nth-child(2)"); var html = '<input id="masthead-queueSearch" class="search-term yt-uix-form-input-bidi" type="text" placeholder="Search" style="outline: none; width:95%; padding: 5px 5px; margin: 0 4px">'; anchor.insertAdjacentHTML('afterbegin', html); var input = document.getElementById("masthead-queueSearch"); // suggestion dropdown init new autoComplete({ selector: '#masthead-queueSearch', minChars: 1, delay: 250, source: function(term, suggest) { suggest(script.search_suggestions); }, onSelect: function(event, term, item) { sendSearchRequest(term); } }); input.addEventListener('keydown', function(event) { if (script.debug) console.log(e); const ENTER = 13; const BACKSPACE = 8; if (this.value !== "" && event.keyCode === ENTER) { sendSearchRequest(this.value); } else if (this.value !== "" && event.keyCode === BACKSPACE) { searchSuggestions(this.value); } else { searchSuggestions(this.value + event.key); } }); input.addEventListener('click', function(event) { this.select(); }); } // callback from search suggestions attached to window function search_callback(data) { var raw = data[1]; // extract relevant data from json script.search_suggestions = raw.map(function(array) { return array[0]; // change 2D array to 1D array with only suggestions }); if (script.debug) console.log(script.search_suggestions); } // get search suggestions function searchSuggestions(value) { if (script.search_timeout !== null) clearTimeout(script.search_timeout); // only allow 1 search request every 100 milliseconds script.search_timeout = setTimeout( function() { if (script.debug) console.log("search request send"); var scriptElement = document.createElement('script'); scriptElement.type = 'text/javascript'; scriptElement.className = 'search-request'; scriptElement.src = 'https://clients1.google.com/complete/search?client=youtube&hl=' + HostLanguage + '&gl=' + GeoLocation + '&gs_ri=youtube&ds=yt&q=' + encodeURIComponent(value) + '&callback=search_callback'; document.head.appendChild(scriptElement); }.bind(value), 100); } // send search request function sendSearchRequest(value) { if (script.debug) console.log("searching for " + value); document.getElementById("masthead-queueSearch").blur(); // close search suggestions dropdown var nextPage = document.getElementById("watch-more-related-button"); if (nextPage !== null) nextPage.parentNode.removeChild(nextPage); // removing the "More Suggestions" link script.search_suggestions = []; // clearing the search suggestions var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { var container = document.implementation.createHTMLDocument().documentElement; container.innerHTML = xmlHttp.responseText; processSearch(container); } }; xmlHttp.open('GET', 'https://www.youtube.com/results?q=' + encodeURIComponent(value), true); // true for asynchronous xmlHttp.send(null); } function clearSearchRequests() { var requests = document.getElementsByClassName("search-request"); if (requests) { for (var i = requests.length - 1; i >= 0; i--) { requests[i].remove(); } } } // process search request function processSearch(value) { var videoList = value.getElementsByClassName("item-section")[0]; // remove current videos (and replace with searched videos later) var ul = document.getElementById("watch-related"); var li = ul.querySelectorAll("li.video-list-item"); if (li) { for (var i = li.length - 1; i >= 0; i--) { li[i].remove(); } } // insert searched videos var videos = videoList.querySelectorAll('.yt-lockup-video'); for (var j = videos.length - 1; j >= 0; j--) { var video = videos[j]; try { var videoId = video.dataset.contextItemId; var videoTitle = video.querySelector('.yt-lockup-title > a').title; var videoStats = video.querySelector('.yt-lockup-meta').innerHTML; var videoTime = video.querySelector('.video-time').textContent; var videoChannelHTML = video.querySelector('.yt-lockup-byline'); var videoThumb = video.querySelector('div.yt-lockup-thumbnail > a > div > span > img'); if (videoThumb && videoThumb.hasAttribute("data-thumb")) { videoThumb = videoThumb.dataset.thumb; } else if (videoThumb) { videoThumb = videoThumb.src; } if (videoChannelHTML) { videoChannelHTML = videoChannelHTML.textContent; } else if (video.querySelector('.yt-lockup-description')) { videoChannelHTML = "<a href=\"" + window.location.href + "\" class=\"spf-link\">" + video.querySelector('.yt-lockup-description').firstChild.textContent + "</a>"; } var videoObject = new extendedYtVideo(videoTitle, videoId, null, null, videoChannelHTML, videoTime, videoStats, videoThumb); if (script.debug) console.log(videoObject); ul.insertAdjacentHTML("afterbegin", videoQueueHTML(videoObject).html); } catch (error) { console.error("failed to process video", video); } } initQueueButtons(); } // *** LOCALSTORAGE *** // function getCache(key) { return JSON.parse(localStorage.getItem("YTQUEUE#" + script.version + '#' + key)); } function deleteCache(key) { localStorage.removeItem("YTQUEUE#" + script.version + '#' + key); } function setCache(key, value) { localStorage.setItem("YTQUEUE#" + script.version + '#' + key, JSON.stringify(value)); } // *** HTML & CSS *** // function videoQueueHTML(video) { var strVar=""; strVar += "<li class=\"video-list-item related-list-item show-video-time related-list-item-compact-video\">"; strVar += " <div class=\"related-item-dismissable\">"; strVar += " <div class=\"content-wrapper\">"; strVar += " <a href=\"\/watch?v=" + video.id + "\" class=\"yt-uix-sessionlink content-link spf-link spf-link\" rel=\"spf-prefetch\" title=\"" + video.name + "\">"; strVar += " <span dir=\"ltr\" class=\"title\">" + video.name + "<\/span>"; strVar += " <span class=\"stat\">" + video.channelHTML + "<\/span>"; strVar += " <div class=\"yt-lockup-meta stat\">" + video.stats + "<\/div>"; strVar += " <\/a>"; strVar += " <\/div>"; strVar += " <div class=\"thumb-wrapper\">"; strVar += " <a href=\"\/watch?v=" + video.id + "\" class=\"yt-uix-sessionlink thumb-link spf-link spf-link\" rel=\"spf-prefetch\" tabindex=\"-1\" aria-hidden=\"true\">"; strVar += " <span class=\"yt-uix-simple-thumb-wrap yt-uix-simple-thumb-related\" tabindex=\"0\" data-vid=\"" + video.id + "\"><img aria-hidden=\"true\" style=\"top: 0px\" width=\"168\" height=\"94\" alt=\"\" src=\"" + video.thumb + "\"><\/span>"; strVar += " <\/a>"; strVar += " <span class=\"video-time\">"+ video.time +"<\/span>"; strVar += " <button class=\"yt-uix-button yt-uix-button-size-small yt-uix-button-default yt-uix-button-empty yt-uix-button-has-icon no-icon-markup addto-button video-actions spf-nolink hide-until-delayloaded addto-watch-later-button yt-uix-tooltip\" type=\"button\" onclick=\";return false;\" title=\"Watch Later\" role=\"button\" data-video-ids=\"" + video.id + "\" data-tooltip-text=\"Watch Later\"><\/button>"; strVar += " <\/div>"; strVar += " <\/div>"; strVar += "<\/li>"; video.html = strVar; return video; } function addCSS() { var css = ` .autocomplete-suggestions { text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1); position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box; } .autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; } .autocomplete-suggestion b { font-weight: normal; color: #b31217; } .autocomplete-suggestion.selected { background: #f0f0f0; } #watch-related .yt-uix-button-size-default { display: none; } #watch-related .processed-buttons:hover .yt-uix-button-size-default { display: inline-block; } .queue-button { height: 15px; padding: 0.1em 0.4em 0.3em 0.4em; margin: 2px 0; } .related-list-item span.title { max-height: 2.4em; } `; var style = document.createElement('style'); style.type = 'text/css'; if (style.styleSheet){ style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } document.documentElement.appendChild(style); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址