您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
mouse-over video thumbnail to preview video by cycling through the storyboard
// ==UserScript== // @name Youtube Storyboard // @namespace hbb_works // @description mouse-over video thumbnail to preview video by cycling through the storyboard // @version 1.3.1r1605101704 // @include http://*youtube.com* // @include https://*youtube.com* // @grant none // ==/UserScript== const INTERVAL = 200; // default interval in milliseconds var div; var loader; var animator; var mousePos = { x: 0, y: 0 }; function dbg() { //console.log.apply(console, arguments); } function getPos(element) { var ref = document.body.getBoundingClientRect(); var rel = element.getBoundingClientRect(); return { left: rel.left - ref.left, top: rel.top - ref.top }; } function setMousePos(event) { mousePos = { x: event.pageX, y: event.pageY }; } (function init() { dbg("init youtube storyboard"); document.addEventListener('mouseover', onMouseOver, false); loader = document.createElement('img'); div = document.createElement('div'); div.id = 'storyboardPlayer'; div.style.display = 'none'; div.style.position = 'absolute'; div.style.zIndex = 99; var a = document.createElement('a'); var innerDiv = document.createElement('div'); innerDiv.style.height = '100%'; innerDiv.style.width = '100%'; a.appendChild(innerDiv); div.appendChild(a); document.getElementById('page') .appendChild(div); div.addEventListener('mouseout', function () { animator.stop(); }); document.addEventListener('mousemove', function (event) { setMousePos(event); }); })(); function onMouseOver(event) { setMousePos(event); var target = event.target; var src = target.src; if (target.nodeName == 'IMG' && src && (/default\.jpg/.exec(src) || /0\.jpg/.exec(src))) { if (animator) { animator.stop(); } animator = new Animator(target); animator.getStoryboardSpecs(function () { animator.start(); }); } } var Animator = function (target) { this.id = Math.floor(Math.random() * 10000); dbg("new " + this.id); this.target = target; this.frame = 0; this.sheet = 0; this.interval = INTERVAL; this.src = target.src // get video id this.v = (/vi\/(.*?)\//.exec(this.src) || []) [1]; if (this.v) { this.width = target.width; this.height = target.height; } var p = getPos(target); div.style.top = p.top + 'px'; div.style.left = p.left + 'px'; div.style.width = this.width + 'px'; div.style.height = this.height + 'px'; this.x = p.left; this.y = p.top; // adjustments for small thumbnails in list of related videos if (target.parentElement.classList.contains('yt-uix-simple-thumb-wrap')) { var spanHeight = target.parentElement.getBoundingClientRect().height; div.style.top = p.top + ((target.height - spanHeight) / 2) + 'px'; div.style.height = spanHeight + 'px'; this.height = spanHeight; } var parent = target.parentElement; while (parent.tagName != 'A') { parent = parent.parentElement; } div.children[0].href = parent.href; div.style.backgroundRepeat = 'no-repeat'; div.style.backgroundPosition = 'center'; div.style.backgroundSize = null; // loading indicator div.style.backgroundImage ='url(\'data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==\')'; div.style.display = null; }; Animator.prototype.parseSpec = function (storyboard_spec) { dbg("parseSpec " + this.id); var lines = storyboard_spec.split('|'); var q; if (!this.quality) { this.quality = - 1; do { this.quality++; q = lines[this.quality + 1].split('#'); } while (parseInt(q[0], 10) < this.width && this.quality + 2 < lines.length); } var q = lines[this.quality + 1].split('#'); var s = { url: lines[0].replace('$L', this.quality) .replace('$N', q[6]), width: parseInt(q[0], 10), height: parseInt(q[1], 10), count: parseInt(q[2], 10), cols: parseInt(q[3], 10), rows: parseInt(q[4], 10), sigh: q[7] }; s.sheetSize = s.cols * s.rows; s.sheetCount = ((s.count / s.sheetSize) | 0) + 1; // bitwise OR to loose decimals s.countOnLastSheet = ((s.count - 1) % s.sheetSize) + 1; return this.spec = s; }; Animator.prototype.loadImage = function (callback) { dbg("loadImage " + this.id); var onLoad = (function () { div.style.backgroundImage = 'url(' + loader.src + ')'; loader.removeEventListener('load', onLoad); callback.apply(this); }).bind(this); loader.addEventListener('load', onLoad); loader.src = this.spec.url.replace('$M', this.sheet) + '?sigh=' + this.spec.sigh; }; Animator.prototype.getStoryboardSpecs = function (callback, fromWatch) { dbg("getStoryboardSpecs (fromWatch: " + fromWatch + ") " + this.id); this.xhr = new XMLHttpRequest(); this.xhr.onload = (function () { if (this.isMouseOver()) { if (fromWatch) { var spec = (/"storyboard_spec":\s*(".*?")/.exec(this.xhr.responseText) || []) [1]; } else { var spec = (/&storyboard_spec=(.*?)&/.exec(this.xhr.responseText) || []) [1]; } if (spec) { if (fromWatch) { spec = eval(spec); // remove backslashes } this.parseSpec(decodeURIComponent(spec)); callback.apply(this); } else if (!fromWatch) { this.getStoryboardSpecs(callback, true); } else { div.style.background = null; } } else { this.stop(); } }).bind(this); if (fromWatch) { this.xhr.open('GET', '/watch?v=' + this.v, true); } else { this.xhr.open('GET', '/get_video_info?video_id=' + this.v, true); } this.xhr.send(); }; Animator.prototype.start = function () { dbg("start " + this.id); this.frame = 0; this.sheet = 0; var lastTick; var next = function () { dbg("next " + this.id + " token " + this.token); if (this.isMouseOver()) { if (this.spec.fit == 'height') { var w = this.spec.width * this.height / this.spec.height; var offset = (this.width - w) / 2; div.style.backgroundPosition = -((this.frame % this.spec.cols) * w - offset) + 'px ' + -(((this.frame / this.spec.cols) | 0) * this.height) + 'px'; // bitwise OR with 0 to loose decimals } else { var h = this.spec.height * this.width / this.spec.width; var offset = (this.height - h) / 2; div.style.backgroundPosition = -((this.frame % this.spec.cols) * this.width) + 'px ' + -(((this.frame / this.spec.cols) | 0) * h - offset) + 'px'; } this.frame++; if ((this.frame == this.spec.sheetSize) || (this.sheet == this.spec.sheetCount - 1 && this.frame == this.spec.countOnLastSheet)) { clearInterval(this.token); dbg("clear " + this.id + " token " + this.token); this.loadImage(function () { this.frame = 0; this.sheet = (this.sheet + 1) % this.spec.sheetCount; this.token = setInterval(next, this.interval); dbg("set " + this.id + " token " + this.token); }); } } else { this.stop(); } }.bind(this); this.loadImage(function () { if (this.isMouseOver()) { if ((this.width / this.height) <= (this.spec.width / this.spec.height)) { this.spec.fit = 'height'; div.style.backgroundSize = ((this.spec.width * this.height / this.spec.height) * this.spec.cols) + 'px ' + (this.height * this.spec.rows) + 'px'; } else { this.spec.fit = 'width'; div.style.backgroundSize = (this.width * this.spec.cols) + 'px ' + ((this.spec.height * this.width / this.spec.width) * this.spec.rows) + 'px'; } this.token = setInterval(next, this.interval); dbg("set " + id + " token " + this.token); } else { this.stop(); } }); }; Animator.prototype.stop = function () { dbg("stop " + this.id); if (!this.stopped) { clearInterval(this.token); dbg("clear " + this.id + " token " + this.token); div.style.display = 'none'; } this.stopped = true; }; Animator.prototype.isMouseOver = function () { return mousePos.x >= this.x && mousePos.x <= this.x + this.width && mousePos.y >= this.y && mousePos.y <= this.y + this.height; };