Vimeo Download Button

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

当前为 2017-05-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Vimeo Download Button
  3. // @namespace larochematthias
  4. // @version 1.0.0
  5. // @description Adds a download button to the HTML5 Vimeo Player (embeded or not)
  6. // @author Matthias Laroche
  7. // @license https://creativecommons.org/licenses/by/4.0/
  8. // @include *//vimeo.com/*
  9. // @include *//player.vimeo.com/video/*
  10. // @run-at document-start
  11. // @grant unsafeWindow
  12. // @compatible Tampermonkey
  13. // @incompatible Greasemonkey
  14. // ==/UserScript==
  15. (function() {
  16. 'use strict';
  17.  
  18. // Syntactic sugar for getting a single node with XPath
  19. function getSingleNode(node, xpath) {
  20. var doc = node.ownerDocument || node;
  21. return doc.evaluate(xpath, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  22. }
  23.  
  24. // Syntactic sugar for XMLHttpRequest
  25. function ajax(url, postData, onSuccess, onError) {
  26. var xhr = new XMLHttpRequest();
  27. xhr.open(postData ? 'POST' : 'GET', url);
  28. if (onSuccess || onError) {
  29. xhr.onreadystatechange = function() {
  30. if (xhr.readyState != 4) return;
  31. if (xhr.status == 200 && onSuccess) onSuccess(xhr);
  32. if (xhr.status != 200 && onError) onError(xhr);
  33. };
  34. }
  35. xhr.send(postData || null);
  36. }
  37.  
  38. // Wraps a property with a callback that convert the value each time the property is set
  39. function wrapProperty(o, propName, callback) {
  40. if (o.hasOwnProperty(propName)) return;
  41. var value;
  42. Object.defineProperty(o, propName, {
  43. get: function() {
  44. return value;
  45. },
  46. set: function(newValue) {
  47. value = callback(newValue);
  48. },
  49. enumerable: true,
  50. configurable: false
  51. });
  52. }
  53.  
  54. // SVG icon of the download button
  55. // Icon made by Elegant Themes from www.flaticon.com
  56. // Icon pack: http://www.flaticon.com/packs/elegant-font
  57. // Published by: https://www.elegantthemes.com/
  58. // License: https://creativecommons.org/licenses/by/3.0/
  59. var downloadIcon =
  60. '<svg viewBox="0 0 455.992 455.992" width="14px" height="14px">' +
  61. '<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" />' +
  62. '<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" />' +
  63. '</svg>';
  64.  
  65. // Create HTML div element to add to the controls bar
  66. function createButton(document, link) {
  67. var div = document.createElement('div');
  68. div.style.marginLeft = '7px';
  69. div.style.marginTop = '-1px';
  70. div.setAttribute('class', 'download');
  71. var a = document.createElement('a');
  72. a.setAttribute('href', link.url);
  73. a.setAttribute('download', (link.title || '').replace(/[\x00-\x1F"*\/:<>?\\|]+/g, ''));
  74. a.setAttribute('target', '_blank');
  75. a.setAttribute('title', 'Download ' + link.quality);
  76. a.setAttribute('aria-label', 'Download');
  77. a.setAttribute('referrerpolicy', 'origin');
  78. a.innerHTML = downloadIcon;
  79. div.appendChild(a);
  80. return div;
  81. }
  82.  
  83. // Get url, quality and title of the video from the config parameter of the player
  84. function getVideoLink(config) {
  85. var title = config && config.video && config.video.title || '';
  86. var progressive = config && config.request && config.request.files && config.request.files.progressive;
  87. if (progressive) {
  88. var file = progressive.reduce(function(a, b) {
  89. return (b.width || 0) > a.width ? b : a;
  90. }, {
  91. width: -1
  92. });
  93. if (file.url) {
  94. return { url:file.url, title: title, quality: file.quality};
  95. }
  96. }
  97. return null;
  98. }
  99.  
  100. // Called by the MutationObserver, keep adding the download button to the DOM if it disappears
  101. function showVideoLink(controls, link) {
  102. var download = getSingleNode(controls, "//div[@class = 'download']");
  103. if (download) {
  104. if (download.nextSibling) {
  105. download.parentNode.appendChild(download);
  106. }
  107. return;
  108. }
  109. var playBar = getSingleNode(controls, "//div[contains(@class, 'play-bar')]");
  110. if (!playBar) return;
  111. download = createButton(controls.ownerDocument, link);
  112. playBar.appendChild(download);
  113. }
  114.  
  115. // Get the config if it's a URL, then get the link
  116. // and set a MutationObserver to add the download button when the controls are ready
  117. function tryShowVideoLink(player, config) {
  118. if (!player || !config) return;
  119. if (typeof(config) === 'string') {
  120. ajax(config, null, function(xhr) {
  121. var result = JSON.parse(xhr.responseText);
  122. tryShowVideoLink(player, result);
  123. });
  124. return;
  125. }
  126. var link = getVideoLink(config);
  127. if (!link) return;
  128. var controls = getSingleNode(player, "//div[@class = 'controls-wrapper']//div[@class = 'controls']");
  129. if (!controls) return;
  130. var observer = new MutationObserver(function() {
  131. showVideoLink(controls, link);
  132. });
  133. observer.observe(controls, {
  134. childList: true,
  135. subtree: true,
  136. attributes: false,
  137. characterData: false
  138. });
  139. showVideoLink(controls, link);
  140. }
  141.  
  142. // Wrap the VimeoPlayer constructor and intercepts the arguments needed to install the download button
  143. wrapProperty(unsafeWindow, 'VimeoPlayer', function(newValue) {
  144. return newValue && function() {
  145. var player = arguments && arguments[0];
  146. var config = arguments && arguments[1];
  147. var result = newValue.apply(this, arguments);
  148. if (player && config) {
  149. tryShowVideoLink(player, config);
  150. }
  151. return result;
  152. };
  153. });
  154. })();