您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a download button to the HTML5 Vimeo Player (embeded or not)
// ==UserScript== // @name Vimeo Download Button // @namespace larochematthias // @version 1.2.1 // @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 none // ==/UserScript== (function() { 'use strict'; // 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>'; function updateButton(button, link) { if (!link) { button.div.style.visibility = 'hidden'; return; } button.a.setAttribute('href', link.url); button.a.setAttribute('download', (link.title || '').replace(/[\x00-\x1F"*\/:<>?\\|]+/g, '')); button.a.setAttribute('title', 'Download ' + link.quality + "\n" + link.title); button.div.style.visibility = 'visible'; button.div.style.display = 'block'; } // Create HTML div element to add to the controls bar function createButton(document) { var div = document.createElement('div'); div.style.marginLeft = '7px'; div.style.marginTop = '-1px'; div.style.display = 'none'; div.setAttribute('class', 'download'); var a = document.createElement('a'); a.setAttribute('target', '_blank'); a.setAttribute('aria-label', 'Download'); a.setAttribute('referrerpolicy', 'origin'); a.style.outlineStyle = 'none'; a.innerHTML = downloadIcon; div.appendChild(a); return { div: div, a: a }; } // 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); } // Get url, quality and title of the video from the config of the player // and then update the download button accordingly function updateVideoLink(button, config) { if (config && typeof(config) === 'object') { 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) { updateButton(button, { url: file.url, title: title, quality: file.quality }); return; } } } updateButton(button, null); if (config && typeof(config) === 'string') { // config is an URL -> download with ajax and update again with the result ajax(config, null, function(xhr) { updateVideoLink(button, JSON.parse(xhr.responseText)); }); } } // Called by the MutationObserver, keep adding the download button to the DOM if it disappears function showVideoLink(button, controls) { var playBar = getSingleNode(controls, "//div[contains(@class, 'play-bar')]"); if (playBar && (button.div.parentNode != playBar || button.div.nextSibling)) { playBar.appendChild(button.div); } } // Create a link and update it with the config of the player, // set a MutationObserver to add the download button when the controls are ready // and wrap the player to intercept the loadVideo method and update the link function wrapVimeoPlayer(player, container, config) { if (!player || !container) return player; var controls = getSingleNode(container, "//div[@class = 'controls-wrapper']//div[@class = 'controls']"); if (!controls) return player; var button = createButton(container.ownerDocument); var observer = new MutationObserver(function() { showVideoLink(button, controls); }); observer.observe(controls, { childList: true, subtree: true, attributes: false, characterData: false }); updateVideoLink(button, config); showVideoLink(button, controls); return Object.create(player, { ready: { writable: true, enumerable: true, configurable: true, value: player.ready }, loadVideo: { enumerable: true, configurable: false, get: function() { return player.loadVideo && function() { updateVideoLink(button, arguments && arguments[0]); return player.loadVideo.apply(this, arguments); }; } } }); } // 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)) { console.log('Vimeo Download Button : Unable to wrap property ' + propName + '.'); return; } var value; Object.defineProperty(o, propName, { get: function() { return value; }, set: function(newValue) { value = callback(newValue); }, enumerable: true, configurable: false }); console.log('Vimeo Download Button : Property ' + propName + ' wrapped successfully.'); } // Wrap the VimeoPlayer constructor and intercepts the arguments needed to install the download button wrapProperty(window, 'VimeoPlayer', function(ctr) { return ctr && function() { var container = arguments && arguments[0]; var config = arguments && arguments[1]; var player = ctr.apply(this, arguments); return wrapVimeoPlayer(player, container, config); }; }); })();