您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
// ==UserScript==
// @name Youtube - Search While Watching Video
// @version 1.0.0
// @description Search YouTube without interrupting the video, by loading the search results in the related video bar
// @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,
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() {
injectCSS();
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("ytplayer: ", script.ytplayer); }
if (script.ytplayer) {
if (script.debug) { console.log("initializing search"); }
initSearch();
} else if (retry > 0) { // fix conflict with Youtube+ script
setTimeout( function() {
startScript(retry--);
}.bind(retry), 1000);
}
}
// *** VIDEO & PLAYER *** //
// video object
function ytVideo(title, id, html, anchor, author, time, stats, thumb) {
this.title = title;
this.id = id;
this.html = html;
this.buttonAnchor = anchor;
this.author = author;
this.time = time;
this.stats = stats;
this.iurlhq = thumb;
this.iurlmq = thumb;
}
function getVideoPlayer() {
return document.getElementById('movie_player');
}
function isPlayerAvailable() { // available on video pages without playlist or live chat
return /https:\/\/www\.youtube\.com\/watch\?v=.*/.test(document.location.href) && !getVideoInfoFromUrl(document.location.href, "list") && document.getElementById('live-chat-iframe') === null;
}
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];
}
}
}
// *** SEARCH *** //
// initialize search
function initSearch() {
if (!document.getElementById('masthead-queue-search')) {
var anchor = document.querySelector('#watch7-sidebar-modules > div:nth-child(2)');
var html = "<input id=\"masthead-queue-search\" class=\"search-term yt-uix-form-input-bidi\" type=\"text\" placeholder=\"Search\">";
anchor.insertAdjacentHTML("afterbegin", html);
var input = document.getElementById('masthead-queue-search');
// suggestion dropdown init
new autoComplete({
selector: '#masthead-queue-search',
minChars: 1,
delay: 250,
source: function(term, suggest) {
suggest(script.search_suggestions);
},
onSelect: function(event, term, item) {
sendSearchRequest(term);
}
});
input.addEventListener("keydown", function(event) {
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
});
}
// 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-queue-search').blur(); // close search suggestions dropdown
var nextPage = document.getElementById('watch-more-related-button');
if (nextPage !== null) { nextPage.remove(); } // 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];
if (video.querySelector('.yt-badge-live') === null) {
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') ? video.querySelector('.video-time').textContent : "0";
var author = video.querySelector('.yt-lockup-byline') ? video.querySelector('.yt-lockup-byline').textContent : "";
var videoThumb = video.querySelector('div.yt-lockup-thumbnail img').dataset.thumb || video.querySelector('div.yt-lockup-thumbnail img').src;
var videoObject = new ytVideo(videoTitle, videoId, null, null, author, videoTime, videoStats, videoThumb);
if (script.debug) { console.log(videoObject); }
ul.insertAdjacentHTML("afterbegin", videoQueueHTML(videoObject).html);
} catch (error) {
console.error("failed to process video " + error.message, video);
}
}
}
}
// *** 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.title + "\">";
strVar += " <span dir=\"ltr\" class=\"title\">" + video.title + "<\/span>";
strVar += " <span class=\"stat author\">" + video.author + "<\/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 + "\">";
strVar += " <img aria-hidden=\"true\" alt=\"\" src=\"" + video.iurlhq + "\">";
strVar += " <\/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;
}
// injecting css
function injectCSS() {
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; }
.yt-uix-simple-thumb-wrap > img { top: 0px; width: 168px; height: 94px; }
#masthead-queue-search { outline: none; width: 95%; padding: 5px 5px; margin: 0 4px; }
`;
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);
}
})();