YouTube Preview

A userscript to play youtube videos by hovering over their thumbnails.

当前为 2016-09-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         YouTube Preview
// @author       sooqua
// @namespace    https://github.com/sooqua/
// @version      0.7
// @description  A userscript to play youtube videos by hovering over their thumbnails.
// @match        *://*.youtube.com/*
// @run-at       document-end
// @grant        none
// ==/UserScript==

var APIready = new Promise(function(resolve) {
    window.onYouTubeIframeAPIReady = resolve;
});

(function() {
    'use strict';

    function init() {
        // requesting api
        var scriptTag = document.createElement('script');
        scriptTag.src = "https://www.youtube.com/iframe_api";
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag);

        addGlobalStyle(
            ".player-api { z-index: 1 !important; } " +

            ".yt-lockup-thumbnail,.thumb-wrapper { " +
            "-webkit-transition: all 200ms ease-in !important; " +
            "-webkit-transform: scale(1) !important; " +
            "-ms-transition: all 200ms ease-in !important; " +
            "-ms-transform: scale(1) !important; " +
            "-moz-transition: all 200ms ease-in !important; " +
            "-moz-transform: scale(1) !important; " +
            "transition: all 200ms ease-in !important; " +
            "transform: scale(1) !important; " +
            "} " +

            ".yt-lockup-thumbnail:hover,.thumb-wrapper:hover { " +
            "z-index: 9999999999; " +
            "box-shadow: 0px 0px 100px #000000 !important; " +
            "-webkit-transition: all 200ms ease-in !important; " +
            "-webkit-transform: scale(2.0) !important; " +
            "-ms-transition: all 200ms ease-in !important; " +
            "-ms-transform: scale(2.0) !important; " +
            "-moz-transition: all 200ms ease-in !important; " +
            "-moz-transform: scale(2.0) !important; " +
            "transition: all 200ms ease-in !important; " +
            "transform: scale(2.0) !important; " +
            "} " +

            ".yt-thumb.video-thumb, .yt-uix-simple-thumb-wrap.yt-uix-simple-thumb-related { " +
            "background-color: black !important; " +
            "} " +

            ".xspinner { " +
            "pointer-events: none; " +
            "position: absolute; " +
            "top: 0; " +
            "right: 0; " +
            "bottom: 0; " +
            "left: 0; " +
            "background: rgba(255,255,255,0.5); " +
            "font-size: 14px; " +
            "text-align: center; " +
            "line-height: 2; " +
            "color: rgb(0,0,0); " +
            "font-weight: bold; " +
            "} ");

        initOn(document);
        var mo = new MutationObserver(function(muts) {
            muts.forEach(function(mut) {
                [].forEach.call(mut.addedNodes, function(node) {
                    if (node instanceof HTMLElement) {
                        initOn(node);
                    }
                });
            });
        });
        mo.observe(document.body, {childList: true, subtree: true});
    }

    function initOn(base) {
        [].forEach.call(base.querySelectorAll('.yt-lockup-thumbnail:not(.yt-pl-thumb) a[href^="/watch"], .thumb-wrapper a[href^="/watch"]'), function(thumbnail) {

            thumbnail.parentNode.addEventListener('mouseover', function() {
                if(thumbnail.overlocker) return;
                thumbnail.overlocker = new Promise(function (unlock) {
                    var spinner = document.createElement('div');
                    spinner.className = 'xspinner';
                    spinner.textContent = 'Loading...';

                    var childThumb = thumbnail.querySelector('.yt-thumb.video-thumb, .yt-uix-simple-thumb-wrap.yt-uix-simple-thumb-related');
                    childThumb.appendChild(spinner);

                    thumbnail.watchedContainer = [];
                    for (var el = thumbnail; el; el = el.parentElement) {
                        if (el.classList.contains('watched')) {
                            thumbnail.watchedContainer.push(el);
                            el.classList.remove('watched');
                        }
                    }
                    thumbnail.watchedBadgeContainer = [];
                    [].forEach.call(thumbnail.parentNode.querySelectorAll('.watched-badge'), function (watchedBadge) {
                        thumbnail.watchedBadgeContainer.push(watchedBadge);
                        watchedBadge.style.display = 'none';
                    });
                    thumbnail.imageContainer = [];
                    [].forEach.call(thumbnail.getElementsByTagName('img'), function(img) {
                        thumbnail.imageContainer.push(img);
                        img.style.opacity = 0;
                    });

                    var vidId = thumbnail.href.split('v=')[1];
                    loadLinks(vidId).then(function (links) {
                        // video not ciphered, creating simple html5 player
                        thumbnail.HPlayer = new Promise(function (resolve) {
                            var HPlayer = document.createElement('video');
                            HPlayer.onloadedmetadata = resolve(HPlayer);
                            HPlayer.style.position = 'absolute';
                            HPlayer.style.width = childThumb.offsetWidth + 'px';
                            HPlayer.style.height = childThumb.offsetHeight + 'px';
                            HPlayer.style.zIndex = '1';
                            HPlayer.controls = false;
                            HPlayer.autoplay = true;
                            for (var i = 0; i < links.length; i++) {
                                if (links[i].quality === "medium" && links[i].filetype === "mp4") {
                                    HPlayer.src = links[i].src;
                                    break;
                                }
                            }
                            childThumb.insertBefore(HPlayer, childThumb.firstChild);
                        });
                        thumbnail.HPlayer.then(function () {
                            childThumb.removeChild(spinner);
                            unlock();
                        });

                    }).catch(function () {
                        // video is ciphered, loading youtube player
                        thumbnail.PPlayer = new Promise(function (resolve) {
                            var playerTag = document.createElement('div');
                            playerTag.id = vidId;
                            playerTag.style.pointerEvents = 'none';
                            playerTag.style.position = 'absolute';
                            playerTag.style.zIndex = '1';
                            childThumb.insertBefore(playerTag, childThumb.firstChild);
                            APIready.then(function () {
                                var pplayer = new YT.Player(playerTag.id, {
                                    width: childThumb.offsetWidth,
                                    height: childThumb.offsetHeight,
                                    videoId: vidId,
                                    playerVars: {
                                        'enablejsapi': 1, 'autoplay': 1, 'showinfo': 0, 'controls': 0,
                                        'modestbranding': 1, 'ps': 'docs', 'iv_load_policy': 3, 'rel': 0,
                                        'vq': 'medium'
                                    },
                                    events: {
                                        'onReady': function() { resolve(pplayer); }
                                    }
                                });
                            });
                        });
                        thumbnail.PPlayer.then(function () {
                            childThumb.removeChild(spinner);
                            unlock();
                        });

                    });
                });
            });

            thumbnail.parentNode.addEventListener('mousemove', function(evt) {
                if (thumbnail.spinner) return;

                if(thumbnail.lastX !== evt.screenX) {
                    var offsetX = evt.offsetX || evt.layerX - thumbnail.offsetLeft;

                    if(thumbnail.PPlayer) {
                        thumbnail.PPlayer.then(function (PPlayer) {
                            try {
                                PPlayer.seekTo(PPlayer.getDuration() * offsetX / thumbnail.parentElement.offsetWidth, true);
                            } catch (e){}
                        });
                    }
                    else if(thumbnail.HPlayer) {
                        thumbnail.HPlayer.then(function (HPlayer) {
                            if (HPlayer.readyState !== 0) {
                                HPlayer.currentTime = HPlayer.duration * offsetX / thumbnail.parentElement.offsetWidth;
                                if (HPlayer.paused)
                                    HPlayer.play();
                            }
                        });
                    }
                }
                thumbnail.lastX = evt.screenX;
            });

            thumbnail.parentNode.addEventListener('mouseout', function(evt) {
                if(!thumbnail.overlocker) return;
                thumbnail.overlocker.then(function () {
                    if(thumbnail.contains(evt.relatedTarget)) return;
                    if(thumbnail.watchedContainer) {
                        for (var i = 0; i < thumbnail.watchedContainer.length; i++)
                            thumbnail.watchedContainer[i].classList.add('watched');
                        thumbnail.watchedContainer = null;
                    }
                    if(thumbnail.watchedBadgeContainer) {
                        for (var i = 0; i < thumbnail.watchedBadgeContainer.length; i++)
                            thumbnail.watchedBadgeContainer[i].style.display = null;
                        thumbnail.watchedBadgeContainer = null;
                    }
                    if(thumbnail.imageContainer) {
                        for (var i = 0; i < thumbnail.imageContainer.length; i++)
                            thumbnail.imageContainer[i].style.opacity = null;
                        thumbnail.imageContainer = null;
                    }

                    if(thumbnail.PPlayer) {
                        thumbnail.PPlayer.then(function(PPlayer) {
                            if(PPlayer.a.parentNode)
                                PPlayer.a.parentNode.removeChild(PPlayer.a);
                            thumbnail.PPlayer = null;
                            thumbnail.overlocker = null;
                        });
                    }
                    else if(thumbnail.HPlayer) {
                        thumbnail.HPlayer.then(function (HPlayer) {
                            HPlayer.pause();
                            HPlayer.src='';
                            HPlayer.load();
                            if(HPlayer.parentNode) {
                                HPlayer.parentNode.removeChild(HPlayer);
                            }
                            thumbnail.HPlayer = null;
                            thumbnail.overlocker = null;
                        });
                    }
                    else {
                        thumbnail.overlocker = null;
                    }
                });
            });

        });
    }

    function loadLinks(vidId) {
        return new Promise(function(resolve, reject) {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'https://www.youtube.com/get_video_info?video_id=' + vidId, true);
            xhr.addEventListener('load', function () {
                var result = [];
                var input = xhr.responseText;
                if (input.indexOf('status=fail') !== -1) {
                    reject();
                    return;
                }
                if (input.indexOf('use_cipher_signature=True') !== -1) {
                    reject();
                    return;
                }
                var token = 'url_encoded_fmt_stream_map';
                input = input.split('&');
                for (var i = 0; i < input.length; i++) {
                    if (input[i].indexOf(token) === 0) {
                        input = decodeURIComponent(input[i].substr(token.length + 1));
                        input = input && input.split(',');
                        break;
                    }
                }
                if (input) {
                    for (i = 0; i < input.length; i++) {
                        var url = input[i].split('&');
                        var obj = {};
                        for (var j = 0; j < url.length; j++) {
                            var pair = url[j].split('=');
                            switch (pair[0]) {
                                case 'quality':
                                    obj.quality = decodeURIComponent(pair[1]);
                                    break;
                                case 'type':
                                    obj.filetype = decodeURIComponent(pair[1]).split(';')[0];
                                    obj.filetype = obj.filetype && obj.filetype.replace('video/', '');
                                    break;
                                case 'url':
                                    obj.src = decodeURIComponent(pair[1]);
                                    break;
                            }
                        }
                        result.push(obj);
                    }
                }
                resolve(result);
            });
            xhr.send();
        });
    }

    function addGlobalStyle(css) {
        var head = document.getElementsByTagName('head')[0];
        if (!head) { return; }
        var style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = css;
        head.appendChild(style);
    }

    init();
})();