Vimeo Download Button

Adds a download button to the HTML5 Vimeo Player (embeded or not)

目前为 2017-05-13 提交的版本。查看 最新版本

// ==UserScript==
// @name         Vimeo Download Button
// @namespace    larochematthias
// @version      1.0.0
// @description  Adds a download button to the HTML5 Vimeo Player (embeded or not)
// @author       Matthias Laroche
// @license      https://creativecommons.org/licenses/by/4.0/
// @include      *//vimeo.com/*
// @include      *//player.vimeo.com/video/*
// @run-at       document-start
// @grant        unsafeWindow
// @compatible   Tampermonkey
// @incompatible Greasemonkey
// ==/UserScript==
(function() {
    'use strict';

    // Syntactic sugar for getting a single node with XPath
    function getSingleNode(node, xpath) {
        var doc = node.ownerDocument || node;
        return doc.evaluate(xpath, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    }

    // Syntactic sugar for XMLHttpRequest
    function ajax(url, postData, onSuccess, onError) {
        var xhr = new XMLHttpRequest();
        xhr.open(postData ? 'POST' : 'GET', url);
        if (onSuccess || onError) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState != 4) return;
                if (xhr.status == 200 && onSuccess) onSuccess(xhr);
                if (xhr.status != 200 && onError) onError(xhr);
            };
        }
        xhr.send(postData || null);
    }

    // Wraps a property with a callback that convert the value each time the property is set
    function wrapProperty(o, propName, callback) {
        if (o.hasOwnProperty(propName)) return;
        var value;
        Object.defineProperty(o, propName, {
            get: function() {
                return value;
            },
            set: function(newValue) {
                value = callback(newValue);
            },
            enumerable: true,
            configurable: false
        });
    }

    // SVG icon of the download button
    // Icon made by Elegant Themes from www.flaticon.com
    // Icon pack: http://www.flaticon.com/packs/elegant-font
    // Published by: https://www.elegantthemes.com/
    // License: https://creativecommons.org/licenses/by/3.0/
    var downloadIcon =
        '<svg viewBox="0 0 455.992 455.992" width="14px" height="14px">' +
        '<polygon class="fill" points="227.996,334.394 379.993,182.397 288.795,182.397 288.795,0 167.197,0 167.744,182.397 75.999,182.397" />' +
        '<polygon class="fill" points="349.594,334.394 349.594,395.193 106.398,395.193 106.398,334.394 45.599,334.394 45.599,395.193 45.599,455.992 410.393,455.992 410.393,334.394" />' +
        '</svg>';

    // Create HTML div element to add to the controls bar
    function createButton(document, link) {
        var div = document.createElement('div');
        div.style.marginLeft = '7px';
        div.style.marginTop = '-1px';
        div.setAttribute('class', 'download');
        var a = document.createElement('a');
        a.setAttribute('href', link.url);
        a.setAttribute('download', (link.title || '').replace(/[\x00-\x1F"*\/:<>?\\|]+/g, ''));
        a.setAttribute('target', '_blank');
        a.setAttribute('title', 'Download ' + link.quality);
        a.setAttribute('aria-label', 'Download');
        a.setAttribute('referrerpolicy', 'origin');
        a.innerHTML = downloadIcon;
        div.appendChild(a);
        return div;
    }

    // Get url, quality and title of the video from the config parameter of the player
    function getVideoLink(config) {
        var title = config && config.video && config.video.title || '';
        var progressive = config && config.request && config.request.files && config.request.files.progressive;
        if (progressive) {
            var file = progressive.reduce(function(a, b) {
                return (b.width || 0) > a.width ? b : a;
            }, {
                width: -1
            });
            if (file.url) {
                return { url:file.url, title: title, quality: file.quality};
            }
        }
        return null;
    }

    // Called by the MutationObserver, keep adding the download button to the DOM if it disappears
    function showVideoLink(controls, link) {
        var download = getSingleNode(controls, "//div[@class = 'download']");
        if (download) {
            if (download.nextSibling) {
                download.parentNode.appendChild(download);
            }
            return;
        }
        var playBar = getSingleNode(controls, "//div[contains(@class, 'play-bar')]");
        if (!playBar) return;
        download = createButton(controls.ownerDocument, link);
        playBar.appendChild(download);
    }

    // Get the config if it's a URL, then get the link
    // and set a MutationObserver to add the download button when the controls are ready
    function tryShowVideoLink(player, config) {
        if (!player || !config) return;
        if (typeof(config) === 'string') {
            ajax(config, null, function(xhr) {
                var result = JSON.parse(xhr.responseText);
                tryShowVideoLink(player, result);
            });
            return;
        }
        var link = getVideoLink(config);
        if (!link) return;
        var controls = getSingleNode(player, "//div[@class = 'controls-wrapper']//div[@class = 'controls']");
        if (!controls) return;
        var observer = new MutationObserver(function() {
            showVideoLink(controls, link);
        });
        observer.observe(controls, {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false
        });
        showVideoLink(controls, link);
    }

    // Wrap the VimeoPlayer constructor and intercepts the arguments needed to install the download button
    wrapProperty(unsafeWindow, 'VimeoPlayer', function(newValue) {
        return newValue && function() {
            var player = arguments && arguments[0];
            var config = arguments && arguments[1];
            var result = newValue.apply(this, arguments);
            if (player && config) {
                tryShowVideoLink(player, config);
            }
            return result;
        };
    });
})();