使用 mpv 播放

通過 mpv-handler 播放網頁上的視頻和歌曲

目前為 2021-07-24 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Play with mpv
  3. // @name:en-US Play with mpv
  4. // @name:zh-CN 使用 mpv 播放
  5. // @name:zh-TW 使用 mpv 播放
  6. // @description Play videos and songs on the website via mpv-handler
  7. // @description:en-US Play videos and songs on the website via mpv-handler
  8. // @description:zh-CN 通过 mpv-handler 播放网页上的视频和歌曲
  9. // @description:zh-TW 通過 mpv-handler 播放網頁上的視頻和歌曲
  10. // @namespace play-with-mpv-handler
  11. // @version 2021.07.24.1
  12. // @author Akatsuki Rui
  13. // @license MIT License
  14. // @require https://cdn.jsdelivr.net/gh/sizzlemctwizzle/GM_config@a4a49b47ecfb1d8fcd27049cc0e8114d05522a0f/gm_config.js
  15. // @grant GM_info
  16. // @grant GM_getValue
  17. // @grant GM_setValue
  18. // @grant GM_notification
  19. // @grant GM_openInTab
  20. // @compatible chrome Since Chrome 49.x
  21. // @compatible firefox Since Firefox 44.x with Violentmonky or Tampermonkey
  22. // @compatible opera Since 17.x
  23. // @run-at document-idle
  24. // @noframes
  25. // @match *://clips.twitch.tv/*
  26. // @match *://www.bilibili.com/video/*
  27. // @match *://www.twitch.tv/*
  28. // @match *://www.youtube.com/*
  29. // ==/UserScript==
  30.  
  31. "use strict";
  32.  
  33. const MPV_HANDLER_VERSION = "v0.2.7";
  34.  
  35. const MATCHERS = {
  36. "clips.twitch.tv": /clips.twitch.tv/gi,
  37. "www.bilibili.com": /www.bilibili.com\/video\/(av|bv)/gi,
  38. "www.twitch.tv":
  39. /www.twitch.tv\/(?!(directory|downloads|jobs|p|turbo)\/).+/gi,
  40. "www.youtube.com": /www.youtube.com\/(watch|playlist)\?/gi,
  41. };
  42.  
  43. const ICON_MPV =
  44. "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0\
  45. PSI2NCIgdmVyc2lvbj0iMSI+CiA8Y2lyY2xlIHN0eWxlPSJvcGFjaXR5Oi4yIiBjeD0iMzIiIGN5\
  46. PSIzMyIgcj0iMjgiLz4KIDxjaXJjbGUgc3R5bGU9ImZpbGw6IzhkMzQ4ZSIgY3g9IjMyIiBjeT0i\
  47. MzIiIHI9IjI4Ii8+CiA8Y2lyY2xlIHN0eWxlPSJvcGFjaXR5Oi4zIiBjeD0iMzQuNSIgY3k9IjI5\
  48. LjUiIHI9IjIwLjUiLz4KIDxjaXJjbGUgc3R5bGU9Im9wYWNpdHk6LjIiIGN4PSIzMiIgY3k9IjMz\
  49. IiByPSIxNCIvPgogPGNpcmNsZSBzdHlsZT0iZmlsbDojZmZmZmZmIiBjeD0iMzIiIGN5PSIzMiIg\
  50. cj0iMTQiLz4KIDxwYXRoIHN0eWxlPSJmaWxsOiM2OTFmNjkiIHRyYW5zZm9ybT0ibWF0cml4KDEu\
  51. NTE1NTQ0NSwwLDAsMS41LC0zLjY1Mzg3OSwtNC45ODczODQ4KSIgZD0ibTI3LjE1NDUxNyAyNC42\
  52. NTgyNTctMy40NjQxMDEgMi0zLjQ2NDEwMiAxLjk5OTk5OXYtNC0zLjk5OTk5OWwzLjQ2NDEwMiAy\
  53. eiIvPgogPHBhdGggc3R5bGU9ImZpbGw6I2ZmZmZmZjtvcGFjaXR5Oi4xIiBkPSJNIDMyIDQgQSAy\
  54. OCAyOCAwIDAgMCA0IDMyIEEgMjggMjggMCAwIDAgNC4wMjE0ODQ0IDMyLjU4NTkzOCBBIDI4IDI4\
  55. IDAgMCAxIDMyIDUgQSAyOCAyOCAwIDAgMSA1OS45Nzg1MTYgMzIuNDE0MDYyIEEgMjggMjggMCAw\
  56. IDAgNjAgMzIgQSAyOCAyOCAwIDAgMCAzMiA0IHoiLz4KPC9zdmc+Cg==";
  57.  
  58. const ICON_SETTINGS =
  59. "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0\
  60. PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij4KIDxkZWZzPgogIDxzdHlsZSBpZD0iY3VycmVudC1j\
  61. b2xvci1zY2hlbWUiIHR5cGU9InRleHQvY3NzIj4KICAgLkNvbG9yU2NoZW1lLVRleHQgeyBjb2xv\
  62. cjojNDQ0NDQ0OyB9IC5Db2xvclNjaGVtZS1IaWdobGlnaHQgeyBjb2xvcjojNDI4NWY0OyB9CiAg\
  63. PC9zdHlsZT4KIDwvZGVmcz4KIDxwYXRoIHN0eWxlPSJmaWxsOmN1cnJlbnRDb2xvciIgY2xhc3M9\
  64. IkNvbG9yU2NoZW1lLVRleHQiIGQ9Ik0gNi4yNSAxIEwgNi4wOTU3MDMxIDIuODQzNzUgQSA1LjUg\
  65. NS41IDAgMCAwIDQuNDg4MjgxMiAzLjc3MzQzNzUgTCAyLjgxMjUgMi45ODQzNzUgTCAxLjA2MjUg\
  66. Ni4wMTU2MjUgTCAyLjU4Mzk4NDQgNy4wNzIyNjU2IEEgNS41IDUuNSAwIDAgMCAyLjUgOCBBIDUu\
  67. NSA1LjUgMCAwIDAgMi41ODAwNzgxIDguOTMxNjQwNiBMIDEuMDYyNSA5Ljk4NDM3NSBMIDIuODEy\
  68. NSAxMy4wMTU2MjUgTCA0LjQ4NDM3NSAxMi4yMjg1MTYgQSA1LjUgNS41IDAgMCAwIDYuMDk1NzAz\
  69. MSAxMy4xNTIzNDQgTCA2LjI0NjA5MzggMTUuMDAxOTUzIEwgOS43NDYwOTM4IDE1LjAwMTk1MyBM\
  70. IDkuOTAwMzkwNiAxMy4xNTgyMDMgQSA1LjUgNS41IDAgMCAwIDExLjUwNzgxMiAxMi4yMjg1MTYg\
  71. TCAxMy4xODM1OTQgMTMuMDE3NTc4IEwgMTQuOTMzNTk0IDkuOTg2MzI4MSBMIDEzLjQxMjEwOSA4\
  72. LjkyOTY4NzUgQSA1LjUgNS41IDAgMCAwIDEzLjQ5NjA5NCA4LjAwMTk1MzEgQSA1LjUgNS41IDAg\
  73. MCAwIDEzLjQxNjAxNiA3LjA3MDMxMjUgTCAxNC45MzM1OTQgNi4wMTc1NzgxIEwgMTMuMTgzNTk0\
  74. IDIuOTg2MzI4MSBMIDExLjUxMTcxOSAzLjc3MzQzNzUgQSA1LjUgNS41IDAgMCAwIDkuOTAwMzkw\
  75. NiAyLjg0OTYwOTQgTCA5Ljc1IDEgTCA2LjI1IDEgeiBNIDggNiBBIDIgMiAwIDAgMSAxMCA4IEEg\
  76. MiAyIDAgMCAxIDggMTAgQSAyIDIgMCAwIDEgNiA4IEEgMiAyIDAgMCAxIDggNiB6IiB0cmFuc2Zv\
  77. cm09InRyYW5zbGF0ZSg0IDQpIi8+Cjwvc3ZnPgo=";
  78.  
  79. const MPV_CSS = `
  80. .pwm-play {
  81. width: 48px;
  82. height: 48px;
  83. border: 0;
  84. border-radius: 50%;
  85. background-size: 48px;
  86. background-image: url(data:image/svg+xml;base64,${ICON_MPV});
  87. background-repeat: no-repeat;
  88. }
  89. .pwm-settings {
  90. opacity: 0;
  91. visibility: hidden;
  92. transition: all 0.2s ease-in-out;
  93. display: block;
  94. position: absolute;
  95. top: -32px;
  96. width: 32px;
  97. height: 32px;
  98. margin-left: 8px;
  99. border: 0;
  100. border-radius: 50%;
  101. background-size: 32px;
  102. background-color: #eeeeee;
  103. background-image: url(data:image/svg+xml;base64,${ICON_SETTINGS});
  104. background-repeat: no-repeat;
  105. }
  106. .pwm-iframe {
  107. display: none;
  108. }
  109. .play-with-mpv {
  110. position: fixed;
  111. left: 8px;
  112. bottom: 8px;
  113. }
  114. .pwm-play:hover + .pwm-settings,
  115. .pwm-settings:hover {
  116. opacity: 1;
  117. visibility: visible;
  118. transition: all 0.2s ease-in-out;
  119. }
  120. `;
  121.  
  122. const CONFIG_ID = "play-with-mpv";
  123.  
  124. const CONFIG_CSS = `
  125. body {
  126. display: flex;
  127. }
  128. #${CONFIG_ID}_wrapper {
  129. margin: auto;
  130. }
  131. #${CONFIG_ID} .config_header {
  132. padding-bottom: 8px;
  133. }
  134. #${CONFIG_ID}_field_perferQuality {
  135. padding-top: 4px;
  136. padding-bottom: 8px;
  137. }
  138. #${CONFIG_ID} .saveclose_buttons {
  139. margin: 1px;
  140. padding: 4px 16px;
  141. }
  142. #${CONFIG_ID} .reset_holder {
  143. padding-top: 4px;
  144. }
  145. `;
  146.  
  147. const CONFIG_IFRAME_CSS = `
  148. position: fixed;
  149. z-index: 999;
  150. width: 440px;
  151. height: 240px;
  152. border: 1px solid;
  153. border-radius: 2px;
  154. `;
  155.  
  156. GM_config.init({
  157. id: `${CONFIG_ID}`,
  158. title: `${GM_info.script.name}`,
  159. fields: {
  160. perferQuality: {
  161. label: "Prefer Quality",
  162. type: "radio",
  163. options: ["Best", "2160p", "1440p", "1080p", "720p", "480p", "360p"],
  164. default: "Best",
  165. },
  166. useCookies: {
  167. label: "Try Pass Cookies",
  168. type: "radio",
  169. options: ["Yes", "No"],
  170. default: "No",
  171. },
  172. },
  173. events: {
  174. save: () => {
  175. updateButton(location.href);
  176. GM_config.close();
  177. },
  178. reset: () => {
  179. updateButton(location.href);
  180. GM_config.save();
  181. GM_config.close();
  182. },
  183. },
  184. css: CONFIG_CSS.trim(),
  185. });
  186.  
  187. function notifyUpdate() {
  188. let version = GM_getValue("mpvHandlerVersion", null);
  189.  
  190. if (version !== MPV_HANDLER_VERSION) {
  191. const UPDATE_NOTIFY = {
  192. title: `${GM_info.script.name}`,
  193. text: `mpv-handler is upgraded to ${MPV_HANDLER_VERSION}\n\nClick to check updates`,
  194. onclick: () => {
  195. GM_openInTab("https://github.com/akiirui/mpv-handler/releases/latest");
  196. GM_setValue("mpvHandlerVersion", MPV_HANDLER_VERSION);
  197. },
  198. };
  199.  
  200. GM_notification(UPDATE_NOTIFY);
  201. }
  202. }
  203.  
  204. function matchUrl(currentUrl) {
  205. return currentUrl.search(MATCHERS[location.hostname]) !== -1;
  206. }
  207.  
  208. function appendButton() {
  209. let head = document.getElementsByTagName("head")[0];
  210. let style = document.createElement("style");
  211.  
  212. if (head) {
  213. style.innerHTML = MPV_CSS.trim();
  214. head.appendChild(style);
  215. }
  216.  
  217. let body = document.getElementsByTagName("body")[0];
  218. let buttonDiv = document.createElement("div");
  219. let buttonIframe = document.createElement("iframe");
  220. let buttonPlay = document.createElement("a");
  221. let buttonSettings = document.createElement("button");
  222.  
  223. if (body) {
  224. buttonIframe.className = "pwm-iframe";
  225. buttonIframe.name = "pwm-iframe";
  226.  
  227. buttonPlay.className = "pwm-play";
  228. buttonPlay.target = "pwm-iframe";
  229. buttonPlay.style = "display: none";
  230. buttonPlay.addEventListener("click", () => {
  231. let videoElement = document.getElementsByTagName("video")[0];
  232. if (videoElement) videoElement.pause();
  233. });
  234.  
  235. buttonSettings.className = "pwm-settings";
  236. buttonSettings.addEventListener("click", () => {
  237. if (!GM_config.isOpen) {
  238. GM_config.open();
  239. GM_config.frame.style = CONFIG_IFRAME_CSS.trim();
  240. }
  241. });
  242.  
  243. buttonDiv.className = "play-with-mpv";
  244. buttonDiv.appendChild(buttonIframe);
  245. buttonDiv.appendChild(buttonPlay);
  246. buttonDiv.appendChild(buttonSettings);
  247.  
  248. body.appendChild(buttonDiv);
  249.  
  250. document.addEventListener("fullscreenchange", () => {
  251. let button = document.getElementsByClassName("pwm-play")[0];
  252.  
  253. button.style = document.fullscreenElement
  254. ? "display: none"
  255. : "display: block";
  256. });
  257. }
  258. }
  259.  
  260. function updateButton(currentUrl) {
  261. let isMatch = matchUrl(currentUrl);
  262. let button = document.getElementsByClassName("pwm-play")[0];
  263.  
  264. if (button) {
  265. let quality = GM_config.get("perferQuality").toLowerCase();
  266. let cookies = GM_config.get("useCookies").toLowerCase();
  267. let protocol = "mpv://" + btoa(currentUrl) + "/";
  268.  
  269. if (cookies === "yes") {
  270. protocol += "?cookies=" + document.location.hostname + ".txt" + "&";
  271. } else {
  272. protocol += "?";
  273. }
  274.  
  275. protocol += "downloader=mpv" + "&";
  276. protocol += "quality=" + quality;
  277.  
  278. button.style = isMatch ? "display: block" : "display: none";
  279. button.href = isMatch ? protocol : "";
  280. }
  281. }
  282.  
  283. function detectPJAX() {
  284. let previousUrl = null;
  285. let currentUrl = null;
  286.  
  287. setInterval(() => {
  288. currentUrl = location.href;
  289.  
  290. if (previousUrl !== currentUrl) {
  291. updateButton(currentUrl);
  292. previousUrl = currentUrl;
  293. }
  294. }, 500);
  295. }
  296.  
  297. notifyUpdate();
  298. appendButton();
  299. detectPJAX();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址