Pornhub 增强

《也许同类型中最好用?》系列 - Pornhub 视频替换 Artplayer,不显示视频广告,支持进度条热力图,显示视频多分辨率 m3u8/mp4 下载链接,可直接下载 免费/付费/禁下 的视频

  1. // ==UserScript==
  2. // @name Pornhub 增强
  3. // @icon data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAABorAAACAwAAwP8AAG+5AAA0VgAAUYkAAInkAAA/aQAAMFEAAJf/AABZlAAAmv8AAGSnAAAaLAAAERwAAKP/AABHZAAABAcAAC0xAACp/wAArP8AALL/AABDcAAAbowAAI/uAAAKEAAAht4AALv/AAAtSwAAiOEAAI3sAACV/wAAmP8AAJv/AAABAQAAnv8AAHrHAABlpwAA//8AAND/AACh/wAAiOIAAC9PAABqsgAApP8AAEVkAABJegAAp/8AADFSAACq/wAAGSoAAAECAACt/wAAM1UAAIjjAAA+aAAAfcgAALb/AAAdMAAAT4MAADpjAABAawAAuf8AAAsQAAC//wAAAAYAAML/AAADBgAACQ4AAMX/AACW/wAAmf8AADdcAACc/wAAn/8AABAcAABRhwAAov8AAIXdAADX/wAAqP8AAKv/AACQ8AAAWI0AAC5NAACx/wAAIEMAALT/AACF3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISAxISEhISCIiSEgMIkhICkpWLSBIIUg1MEcgEDUiCk4ZCAQUSCQiMTwUNQ0xHxBSTwAvQ0sQSgAzWBw4AFkyUSoABjpOPykANihQCwA3UVEHAjRCRVQyD0wuETs0B1FRNyNVGAkAJh0CGkASAgdRUR4ALCdOAD0BVUFGTQA3UVIbAEQTAAA5AA4/Az4AWTJOUxcFV0klNSs8FDUNMR8QCkoWFhZYNSEVMEcgEDUiCkhIDAwMSEhIIiJISAwiSEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
  4. // @namespace github.com/hmjz100
  5. // @version 0.1.7
  6. // @description 《也许同类型中最好用?》系列 - Pornhub 视频替换 Artplayer,不显示视频广告,支持进度条热力图,显示视频多分辨率 m3u8/mp4 下载链接,可直接下载 免费/付费/禁下 的视频
  7. // @author hmjz100,liuwanlin
  8. // @license MIT
  9. // @match *://*.pornhub.org/*
  10. // @match *://*.pornhub.com/*
  11. // @match *://*.pornhubpremium.com/*
  12. // @match *://*/*/hvtrs8%2F-cl.ropnju%60.aoo%2Ftigw%5Dvkdgo%2Cpjp%3Dvkeukgy%3F*
  13. // @grant unsafeWindow
  14. // @grant GM_addStyle
  15. // @grant GM_setClipboard
  16. // @grant GM_openInTab
  17. // @grant GM_xmlHttpRequest
  18. // @grant GM.xmlHttpRequest
  19. // @connect pornhub.org
  20. // @connect pornhub.com
  21. // @connect pornhubpremium.com
  22. // @connect phncdn.com
  23. // @require https://unpkg.com/jquery@3.6.3/dist/jquery.min.js
  24. // @require https://unpkg.com/hls.js@1.5.10/dist/hls.min.js
  25. // @require https://unpkg.com/artplayer@5.1.6/dist/artplayer.legacy.js
  26. // ==/UserScript==
  27.  
  28. (function () {
  29. Artplayer.LOG_VERSION = false;
  30. 'use strict';
  31. let url = new URL(unsafeWindow.location.href)
  32.  
  33. // 空白间距
  34. waitForKeyElements("li.emptyBlockSpace", function (element) {
  35. if (element.attr('removed')) return;
  36. element.attr('removed', true);
  37. element.fadeOut()
  38. })
  39.  
  40. // 顶栏广告
  41. waitForKeyElements("#headerUpgradePremiumBtn", function (element) {
  42. if (element.parent().attr('removed')) return;
  43. element.parent().attr('removed', true);
  44. element.parent().fadeOut()
  45. })
  46.  
  47. // 年龄认证
  48. waitForKeyElements('[id^="age-verification"]', function (element) {
  49. if (element.attr('removed')) return;
  50. element.attr('removed', true);
  51. element.fadeOut()
  52. })
  53.  
  54. // 删除页面 iframe 广告
  55. waitForKeyElements('iframe[allowtransparency][data-embeddedads][data-spot-id], ins[class*="adsby"]', function (element) {
  56. function findParentWithClass(el) {
  57. if (el.length > 0) {
  58. if (el.parent().attr("class")) {
  59. if (el.parent().attr('removed') || el.parent().parent().parent().attr('removed')) return;
  60. if (el.parent().parent().parent().prop('tagName') === "LI") {
  61. el.parent().parent().parent().attr('removed', true);
  62. console.log(`【Pornhub增强】AD\n`, el.parent().parent().parent().attr("class"), "有class且有li,隐藏!")
  63. el.parent().parent().parent().fadeOut();
  64. } else {
  65. el.parent().attr('removed', true);
  66. console.log(`【Pornhub增强】AD\n`, el.parent().attr("class"), "有class,隐藏!")
  67. el.parent().fadeOut();
  68. }
  69. } else {
  70. console.log(`【Pornhub增强】AD\n`, "没有class,继续查找...")
  71. findParentWithClass(el.parent());
  72. }
  73. }
  74. }
  75. findParentWithClass(element);
  76. })
  77.  
  78. waitForKeyElements('.bg-spice-badge', function (element) {
  79. function findParentWithClass(el) {
  80. if (el.length > 0) {
  81. if (el.parent().prop('tagName') === "LI") {
  82. if (el.parent().attr('removed')) return;
  83. el.parent().attr('removed', true);
  84. console.log(`【Pornhub增强】AD\n`, el.parent().attr("class"), "有li,隐藏!")
  85. el.parent().fadeOut();
  86. } else {
  87. console.log(`【Pornhub增强】AD\n`, "没有class,继续查找...")
  88. findParentWithClass(el.parent());
  89. }
  90. }
  91. }
  92. findParentWithClass(element);
  93. })
  94.  
  95. waitForKeyElements("body div.bottomNav, div#js-abContainterMain", function (element) {
  96. element.fadeOut()
  97. })
  98.  
  99. if (!url.pathname.includes("view_video.php") && !url.searchParams.get("viewkey") && !url.pathname.includes("vkeukgy")) return;
  100.  
  101. // 暂停原始播放器的播放,避免替换后没法暂停
  102. waitForKeyElements("div.mgp_videoWrapper video", function (element) {
  103. setInterval(() => {
  104. element[0].pause()
  105. }, 1)
  106. })
  107.  
  108. // 等待页面信息出现
  109. let flashvarsRequireTimer = setInterval(async () => {
  110. let flashvars = {};
  111. for (let prop in unsafeWindow) {
  112. if (prop.startsWith('flashvars_')) {
  113. clearInterval(flashvarsRequireTimer)
  114. flashvars = unsafeWindow[prop];
  115. } else continue
  116. }
  117. if (!flashvars.length && !flashvars.mediaDefinitions) return;
  118.  
  119. GM_addStyle(`
  120. a, button, ul li, ui, label, input, select {
  121. transition: all 0.25s !important;
  122. -webkit-transition: all 0.25s !important;
  123. }
  124. .no-scroll {
  125. overflow: hidden !important;
  126. }
  127. .download-urls {
  128. margin-top: 10px !important;
  129. }
  130. .download-urls ul {
  131. padding: 0 10px 15px;
  132. font-weight: bold;
  133. line-height: 1.5;
  134. }
  135. .download-urls ul li {
  136. display: flex;
  137. align-items: center;
  138. height: auto;
  139. }
  140. .download-url.label {
  141. width: 10%;
  142. text-align: center;
  143. }
  144. .download-url {
  145. width: 5%;
  146. text-align: center;
  147. }
  148. .download-url.download {
  149. border: none;
  150. border-radius: 5px;
  151. font-size: 12px;
  152. background: #ff9000;
  153. color: #fff;
  154. }
  155. .download-url.download:hover, .download-url.download:disabled {
  156. background: #ff9000D0;
  157. }
  158. .download-url.input {
  159. width: 80%;
  160. font-size: 12px;
  161. padding: 0 5px;
  162. border: 1px solid #fff;
  163. margin: 0 5px;
  164. }
  165. .art-contextmenu svg, .art-contextmenu img {
  166. vertical-align: top;
  167. margin-right: 5px
  168. }
  169. header {
  170. z-index: 999999999999 !important;
  171. }
  172. `);
  173.  
  174. console.log(`【Pornhub增强】info\n`, flashvars);
  175.  
  176. let data = {
  177. id: flashvars.link_url,
  178. title: flashvars.video_title,
  179. url: null,
  180. hls: [],
  181. mp4: [],
  182. title: flashvars.video_title,
  183. cover: flashvars.image_url,
  184. hotspots: flashvars.hotspots
  185. }
  186. let maxQuality = null;
  187. let promises = [];
  188.  
  189. flashvars.mediaDefinitions.forEach(item => {
  190. if (item.format === 'hls') {
  191. let quality = Number(item.quality);
  192. if (maxQuality === null || quality < maxQuality) {
  193. maxQuality = quality;
  194. }
  195. let video = {
  196. "url": item.videoUrl,
  197. "html": quality <= 1080 ? `${quality}P` : quality <= 1440 ? `2K` : quality <= 2160 ? `4K` : `${quality}P`,
  198. "quality": quality
  199. };
  200. data.hls.push(video);
  201. } else if (item.format === 'mp4') {
  202. let url = new URL(item.videoUrl)
  203. if (location.host.includes("pornhub")) url.host = location.host
  204. let promise = unsafeWindow.fetch(url.href)
  205. .then(response => response.json())
  206. .then(res => {
  207. res.forEach(item => {
  208. let quality = Number(item.quality);
  209. if (maxQuality === null || quality < maxQuality) {
  210. maxQuality = quality;
  211. }
  212. let video = {
  213. "url": item.videoUrl,
  214. "html": quality <= 1080 ? `${quality}P` : quality <= 1440 ? `2K` : quality <= 2160 ? `4K` : `${quality}P`,
  215. "quality": quality
  216. };
  217. data.mp4.push(video);
  218. });
  219. })
  220. .catch(error => {
  221. console.error('Fetch 发生错误:', error);
  222. });
  223.  
  224. promises.push(promise); // 将 Promise 存入数组
  225. }
  226. });
  227. data.hls.sort((a, b) => (b.quality || 0) - (a.quality || 0));
  228. data.hls.forEach(url => {
  229. if (url.quality === maxQuality) {
  230. url.default = true;
  231. data.url = url.url
  232. }
  233. });
  234.  
  235. waitForKeyElements("div.video-wrapper div#player", function (element) {
  236. let player = $('<div class="index-module_art-player" style="overflow: hidden; aspect-ratio: 16/9; border-radius: 10px;"></div>')
  237. element.after(player);
  238. element.fadeOut();
  239. play(data.id, data.url, data.hls, data.cover, (art) => {
  240. art.controls.add({
  241. name: 'hideList',
  242. index: 50,
  243. position: 'right',
  244. html: '<i class="art-icon"><svg width="18" height="18" viewBox="0 0 1152 1024"><path fill="#fff" d="M1075.2 0H76.8A76.8 76.8 0 0 0 0 76.8v870.4a76.8 76.8 0 0 0 76.8 76.8h998.4a76.8 76.8 0 0 0 76.8-76.8V76.8A76.8 76.8 0 0 0 1075.2 0zM1024 128v768H128V128h896zm-576"></path></svg></i>',
  245. tooltip: '宽屏模式',
  246. click: function () {
  247. $('#hd-rightColVideoPage').fadeToggle();
  248. if ($('#vpContentContainer').attr("style")) {
  249. $('#vpContentContainer').removeAttr("style");
  250. } else {
  251. $('#vpContentContainer').css({ "grid-template-columns": "auto" });
  252. }
  253. }
  254. })
  255. art.on('fullscreen', () => {
  256. $('header').fadeToggle();
  257. $(art.controls.fullscreenWeb).toggle();
  258. $(art.controls.hideList).toggle();
  259. });
  260. art.on('fullscreenWeb', () => {
  261. $(art.controls.fullscreen).toggle();
  262. $(art.controls.hideList).toggle();
  263. });
  264. if (data.hotspots) {
  265. // 平滑函数,使用简单的移动平均
  266. function smoothData(data, windowSize) {
  267. var smoothed = [];
  268. for (let i = 0; i < data.length; i++) {
  269. let sum = 0;
  270. let count = 0;
  271. for (let j = Math.max(0, i - windowSize); j <= Math.min(data.length - 1, i + windowSize); j++) {
  272. sum += data[j];
  273. count++;
  274. }
  275. smoothed.push(sum / count);
  276. }
  277. return smoothed;
  278. }
  279.  
  280. let hotspots = smoothData(data.hotspots.slice(5), 2);
  281.  
  282. var svgWidth = 1000;
  283. var svgHeight = 100;
  284. var max = Math.max(...hotspots);
  285.  
  286. let points = `0,${svgHeight} `; // 从左下角开始
  287. hotspots.forEach((value, index) => {
  288. var x = (index / (hotspots.length - 1)) * svgWidth;
  289. var y = svgHeight - (value / max) * svgHeight;
  290. points += `${x},${y} `;
  291. });
  292. points += `${svgWidth},${svgHeight}`; // 到右下角
  293. $(art.template.$layer).css({ "z-index": "61" })
  294. $(art.template.$progress).prepend(`
  295. <svg viewBox="0 0 1000 100" preserveAspectRatio="none" style="height: 30px; width: 100%; transform: translateY(5.6px); pointer-events: none;">
  296. <polygon id="heatmap" points="${points}" fill="var(--art-progress-color)" stroke="var(--art-progress-color)" />
  297. </svg>
  298. `)
  299. }
  300. });
  301. })
  302. waitForKeyElements("div.playerWrapper", function (element) {
  303. let player = $('<div class="index-module_art-player" style="overflow: hidden; aspect-ratio: 16/9; margin-top: 60px;"></div>')
  304. element.after(player);
  305. element.fadeOut();
  306. play(data.id, data.url, data.hls, data.cover, (art) => {
  307. art.on('fullscreen', () => {
  308. $('header').fadeToggle();
  309. $(art.controls.fullscreenWeb).toggle();
  310. $(art.controls.hideList).toggle();
  311. });
  312. art.on('fullscreenWeb', () => {
  313. $(art.controls.fullscreen).toggle();
  314. $(art.controls.hideList).toggle();
  315. });
  316. if (data.hotspots) {
  317. // 平滑函数,使用简单的移动平均
  318. function smoothData(data, windowSize) {
  319. var smoothed = [];
  320. for (let i = 0; i < data.length; i++) {
  321. let sum = 0;
  322. let count = 0;
  323. for (let j = Math.max(0, i - windowSize); j <= Math.min(data.length - 1, i + windowSize); j++) {
  324. sum += data[j];
  325. count++;
  326. }
  327. smoothed.push(sum / count);
  328. }
  329. return smoothed;
  330. }
  331.  
  332. let hotspots = smoothData(data.hotspots.slice(5), 2);
  333.  
  334. var svgWidth = 1000;
  335. var svgHeight = 100;
  336. var max = Math.max(...hotspots);
  337.  
  338. let points = `0,${svgHeight} `; // 从左下角开始
  339. hotspots.forEach((value, index) => {
  340. var x = (index / (hotspots.length - 1)) * svgWidth;
  341. var y = svgHeight - (value / max) * svgHeight;
  342. points += `${x},${y} `;
  343. });
  344. points += `${svgWidth},${svgHeight}`; // 到右下角
  345. $(art.template.$progress).prepend(`
  346. <svg viewBox="0 0 1000 100" preserveAspectRatio="none" style="height: 30px; width: 100%; transform: translateY(5.1px);">
  347. <polygon id="heatmap" points="${points}" fill="var(--art-progress-color)" stroke="var(--art-progress-color)" />
  348. </svg>
  349. `)
  350. }
  351. });
  352. })
  353.  
  354. waitForKeyElements("div.video-wrapper div.video-actions-menu,div.categoryTags div.tooltipWrapper", function (element) {
  355. let downloadContent = $(`<div class="download-urls"><ul></ul><li><span style="width:100%; text-align: center;">小贴士:MP4 下载总是失败?复制 HLS 链接到猫抓插件(需单独安装)的 M3U8 解析器里再下载吧</span></li></div>`);
  356. element.after(downloadContent);
  357.  
  358. data.hls.forEach(item => {
  359. let downloadItem = $(`
  360. <li>
  361. <span class="download-url label">HLS ${item.html}</span>
  362. <input class="download-url input" value="${item.url}" />
  363. <a class="download-url copy" data-href="${item.url}" href="javascript: void(0);" style="width: 10%;">复制</a>
  364. </li>
  365. `);
  366. downloadItem.find('.download-url.copy').on('click', function (element) {
  367. element.preventDefault();
  368. element = $(this)
  369. GM_setClipboard(element.data('href'));
  370. element.text("成功")
  371. setTimeout(() => { element.text("复制") }, 1000)
  372. })
  373. element.parent().find(".download-urls ul").append(downloadItem);
  374. });
  375. Promise.all(promises).then(() => {
  376. data.mp4.sort((a, b) => (b.quality || 0) - (a.quality || 0));
  377. data.mp4.forEach(item => {
  378. let downloadItem
  379. // 对于原始站点显示下载按钮
  380. if (location.host.includes("pornhub")) {
  381. downloadItem = $(`
  382. <li>
  383. <span class="download-url label">MP4 ${item.html}</span>
  384. <input class="download-url input" value="${item.url}" />
  385. <button class="download-url download" data-href="${item.url}" data-name="${data.title} ${item.html}">下载</button>
  386. <a class="download-url copy" data-href="${item.url}" href="javascript: void(0);">复制</a>
  387. </li>
  388. `)
  389. downloadItem.find('.download-url.download').on('click', async function (event) {
  390. if (!$(this).data('href')) return;
  391. event.preventDefault();
  392.  
  393. let element = $(this);
  394. let index = element.data('index');
  395. let ins = {};
  396. let progress = {};
  397. let totalSize = 0;
  398.  
  399. element.prop('disabled', true);
  400.  
  401. ins[index] = setInterval(() => {
  402. let prog = +progress[index] || 0;
  403. element.text(prog.toFixed(1) + "%"); // 显示下载进度,保留一位小数
  404. }, 100);
  405.  
  406. const httpRequest = typeof GM_xmlhttpRequest !== "undefined" ? GM_xmlhttpRequest : GM.xmlHttpRequest;
  407.  
  408. const handleProgress = (event) => {
  409. if (event.lengthComputable) {
  410. totalSize = event.total;
  411. let currentLoaded = event.loaded;
  412. progress[index] = ((currentLoaded / totalSize) * 100).toFixed(1); // 计算百分比
  413. }
  414. };
  415.  
  416. const handleLoad = (response) => {
  417. clearInterval(ins[index]);
  418.  
  419. if (response.status !== 200) {
  420. handleError(new Error(`请求失败,状态码:${response.status}`));
  421. return;
  422. }
  423.  
  424. const arrayBuffer = response.response;
  425. progress[index] = 100;
  426. element.text("完成");
  427.  
  428. // 创建 Blob
  429. const blob = new Blob([new Uint8Array(arrayBuffer)]);
  430. const url = URL.createObjectURL(blob);
  431.  
  432. // 创建下载链接
  433. const a = document.createElement('a');
  434. a.href = url;
  435. a.download = `${element.data('name')}.mp4`;
  436. document.body.appendChild(a);
  437. a.click();
  438.  
  439. setTimeout(() => {
  440. URL.revokeObjectURL(url);
  441. document.body.removeChild(a);
  442. element.text("下载");
  443. element.prop('disabled', false);
  444. }, 1000);
  445. };
  446.  
  447. const handleError = (error) => {
  448. clearInterval(ins[index]);
  449. console.error("下载失败:", error);
  450. element.text("重试?");
  451. element.prop('disabled', false).one('click', () => {
  452. element.trigger('click');
  453. });
  454. };
  455.  
  456. try {
  457. httpRequest({
  458. method: 'GET',
  459. url: element.data('href'),
  460. headers: {
  461. "referer": "https://www.pornhub.com/",
  462. "user-agent": navigator.userAgent
  463. },
  464. responseType: 'arraybuffer',
  465. onprogress: handleProgress,
  466. onload: handleLoad,
  467. onerror: handleError
  468. });
  469. } catch (error) {
  470. handleError(error);
  471. }
  472. });
  473. } else {
  474. // 对于镜像站点显示直链按钮
  475. downloadItem = $(`
  476. <li>
  477. <span class="download-url label">MP4 ${item.html}</span>
  478. <input class="download-url input" value="${item.url}" />
  479. <a class="download-url download" href="${item.url}" target="_blank">直链</a>
  480. <a class="download-url copy" data-href="${item.url}" href="javascript: void(0);">复制</a>
  481. </li>
  482. `)
  483. }
  484. downloadItem.find('.download-url.copy').on('click', function (element) {
  485. if (!$(this).data('href')) return;
  486. element.preventDefault();
  487. element = $(this)
  488. GM_setClipboard(element.data('href'));
  489. element.text("完成")
  490. setTimeout(() => { element.text("复制") }, 1000)
  491. })
  492. element.parent().find(".download-urls ul").append(downloadItem);
  493. });
  494. })
  495. }, true);
  496. }, 1)
  497.  
  498. function play(id, url, urls, cover, actionFunction) {
  499. var art = new Artplayer({
  500. id: id,
  501. container: '.index-module_art-player',
  502. url: url,
  503. volume: 1,
  504. autoPlayback: true,
  505. theme: '#ff9000',
  506. customType: {
  507. m3u8: function (video, url, art) {
  508. if (Hls.isSupported()) {
  509. if (art.hls) art.hls.destroy();
  510. let hls = new Hls();
  511. hls.loadSource(url);
  512. hls.attachMedia(video);
  513. art.hls = hls;
  514. art.on('destroy', () => hls.destroy());
  515. } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
  516. video.src = url;
  517. } else {
  518. art.notice.show = 'Unsupported playback format: m3u8';
  519. }
  520. },
  521. },
  522. controls: [
  523. {
  524. name: 'goodRing',
  525. index: 21,
  526. position: 'left',
  527. html: '<i class="art-icon ph-icon-thumb-up"></i>',
  528. tooltip: '赏个好评',
  529. click: function () {
  530. GM_openInTab('https://greasyfork.org/scripts/501537/feedback', { active: true });
  531. },
  532. }
  533. ],
  534. icons: {
  535. state: '<svg width="100" height="100" fill="none"><g opacity=".9"><path opacity=".5" d="M50 4.167C24.687 4.167 4.167 24.687 4.167 50c0 25.314 20.52 45.834 45.833 45.834 25.314 0 45.834-20.52 45.834-45.834 0-25.313-20.52-45.833-45.834-45.833Z" fill="#000"/><path d="M69.194 53.043 43.153 70.231a3.646 3.646 0 0 1-5.653-3.043V32.816a3.646 3.646 0 0 1 5.654-3.043l26.042 17.188c2.183 1.44 2.183 4.645 0 6.086-2.184 1.44 6.51-4.3-.002-.004Z" fill="#fff"/></g></svg>',
  536. indicator: '<img width="16" heigth="16" style="border-radius:50px" src="/favicon.ico">',
  537. play: '<svg width="13" height="13" viewBox="0 0 87.5 100"><path d="m0 0v100l87.5-50z"></path></svg>',
  538. pause: '<svg width="13" height="13" viewBox="0 0 81.75 100"><path d="m56.596 100v-100h25.154v100zm-56.596-100h25.154v100h-25.154z"></path></svg>',
  539. volume: '<svg width="18" height="18" viewBox="0 0 120 100" fill="none"><path d="m30 25 39.999-25v100l-39.999-25zm-30 50h20v-50h-20zm101.32-66.32-7.4851 7.485c9.2351 8.915 14.915 20.785 14.915 33.835s-5.6752 24.92-14.915 33.83l7.4899 7.49c11.525-10.765 18.676-25.275 18.676-41.32 0-16.046-7.1498-30.55-18.68-41.32zm-2.4453 41.32c0-10.42-4.5747-19.835-11.919-26.954l-7.5146 7.515c5.0547 5.245 8.1845 12.005 8.1845 19.44 0 7.44-3.1146 14.205-8.1697 19.456l7.515 7.5147c7.34-7.12 11.904-16.544 11.904-26.97z" stroke-width="1.1995"></path></svg>',
  540. volumeClose: '<svg width="18" height="18" viewBox="0 0 120 100" fill="none"><path d="m83.949 30.656v65.176l-42.579-20.834v-3.2712zm7.9837-25.393-7.1156-5.2669-15.84 17.697-27.608 15.637v15.192l-9.1933 10.267v-25.459h-18.387v41.668h3.8749l-17.664 19.734 7.1156 5.267z" stroke-width="1.0504"></path></svg>',
  541. setting: '<svg width="18" height="18" viewBox="0 0 97.3 100"><path d="m85.822 54.9c0.19964-1.6004 0.35-3.2002 0.35-4.9002s-0.15036-3.3-0.35-4.9l10.553-8.2497c0.95018-0.75 1.2004-2.1 0.60018-3.2l-10.003-17.3c-0.60018-1.1-1.9509-1.5-3.0515-1.1l-12.453 5c-2.601-2-5.4016-3.65-8.4525-4.9l-1.901-13.25c-0.14973-1.2-1.2004-2.1-2.4506-2.1h-20.006c-1.2504 0-2.3007 0.9-2.4508 2.1l-1.9006 13.25c-3.051 1.25-5.8518 2.95-8.4525 4.9l-12.453-5c-1.1503-0.45-2.4508 0-3.051 1.1l-10.003 17.3c-0.65022 1.1-0.35013 2.45 0.60018 3.2l10.553 8.2497c-0.20008 1.6-0.35013 3.25-0.35013 4.9 0 1.65 0.15005 3.2998 0.35013 4.9002l-10.553 8.2497c-0.95031 0.75032-1.2004 2.1-0.60018 3.2002l10.003 17.3c0.60018 1.0996 1.9506 1.5 3.051 1.0996l12.453-4.9996c2.6008 1.9996 5.4016 3.6499 8.4525 4.8998l1.9006 13.25c0.15004 1.2 1.2004 2.1 2.4508 2.1h20.006c1.2503 0 2.3009-0.9 2.4506-2.1l1.901-13.25c3.0508-1.2499 5.8515-2.9501 8.4525-4.8998l12.453 4.9996c1.1505 0.45032 2.4513 0 3.0515-1.0996l10.003-17.3c0.60018-1.1002 0.35-2.4499-0.60018-3.2002zm-37.161 12.6c-9.6528 0-17.505-7.8499-17.505-17.5 0-9.6499 7.8523-17.5 17.505-17.5 9.6528 0 17.505 7.8499 17.505 17.5 0 9.6506-7.8523 17.5-17.505 17.5z"></path></svg>',
  542. },
  543. setting: true,
  544. hotkey: true,
  545. flip: true,
  546. playbackRate: true,
  547. aspectRatio: true,
  548. miniProgressBar: true,
  549. fullscreen: true,
  550. fullscreenWeb: true,
  551. fastForward: true,
  552. autoOrientation: true,
  553. lock: true,
  554. moreVideoAttr: {
  555. 'preload': 'none'
  556. },
  557. })
  558. art.on('fullscreen', () => {
  559. $('header').toggle();
  560. $(art.controls.goodRing).toggle();
  561. if (!$('body').hasClass('no-scroll')) {
  562. $('body').addClass('no-scroll');
  563. } else {
  564. $('body').removeClass('no-scroll');
  565. }
  566. });
  567. art.on('fullscreenWeb', () => {
  568. $('header').toggle();
  569. $(art.controls.goodRing).toggle();
  570. if (!$('body').hasClass('no-scroll')) {
  571. $('body').addClass('no-scroll');
  572. } else {
  573. $('body').removeClass('no-scroll');
  574. }
  575. });
  576. var contextmenuStyle = {
  577. "display": "flex",
  578. "justify-content": "center",
  579. "align-items": "center",
  580. "border-bottom": "none"
  581. }
  582. art.contextmenu.add({
  583. name: 'appTitle',
  584. index: 1,
  585. html: `<img width="16" heigth="16" style="border-radius:50px" src="/favicon.ico">Pronhub 增强`,
  586. style: contextmenuStyle,
  587. click: function () {
  588. GM_openInTab('https://greasyfork.org/scripts/501537/feedback', { active: true });
  589. },
  590. })
  591. art.contextmenu.update({
  592. name: 'version',
  593. index: 2,
  594. html: `<img width="15" heigth="15" src="https://artplayer.org/assets/img/logo.png"/>Artplayer Ultra ${Artplayer.version}`,
  595. click: function () {
  596. GM_openInTab('https://artplayer.org/', { active: true });
  597. },
  598. style: contextmenuStyle
  599. })
  600. art.contextmenu.update({
  601. name: 'info',
  602. index: 40,
  603. html: `${art.i18n.language["Video Info"]}`,
  604. style: contextmenuStyle
  605. })
  606. art.contextmenu.update({
  607. name: 'close',
  608. index: 50,
  609. html: `${art.i18n.language["Close"]}`,
  610. style: contextmenuStyle
  611. })
  612. unsafeWindow.art = art
  613. cover ? (art.poster = cover) : ""
  614. urls ? (art.quality = [...urls]) : ""
  615. $(art.template.$container).find(".icon").removeClass("icon")
  616. actionFunction ? actionFunction(art) : ""
  617. }
  618.  
  619. function waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector) {
  620. var targetbadges, btargetsFound;
  621.  
  622. if (typeof iframeSelector == "undefined")
  623. targetbadges = $(selectorTxt);
  624. else
  625. targetbadges = $(iframeSelector).contents().find(selectorTxt);
  626.  
  627. if (targetbadges && targetbadges.length > 0) {
  628. btargetsFound = true;
  629. targetbadges.each(function () {
  630. var jThis = $(this);
  631. var alreadyFound = jThis.data('alreadyFound') || false;
  632. if (!alreadyFound) {
  633. var cancelFound = actionFunction(jThis);
  634. if (cancelFound) {
  635. btargetsFound = false;
  636. } else {
  637. jThis.data('alreadyFound', true);
  638. }
  639. }
  640. });
  641. } else {
  642. btargetsFound = false;
  643. }
  644.  
  645. var controlObj = waitForKeyElements.controlObj || {};
  646. var controlKey = selectorTxt.replace(/[^\w]/g, "_");
  647. var timeControl = controlObj[controlKey];
  648.  
  649. if (btargetsFound && bWaitOnce && timeControl) {
  650. clearInterval(timeControl);
  651. delete controlObj[controlKey];
  652. } else {
  653. if (!timeControl) {
  654. timeControl = setInterval(function () {
  655. waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector);
  656. }, 1);
  657. controlObj[controlKey] = timeControl;
  658. }
  659. }
  660.  
  661. waitForKeyElements.controlObj = controlObj;
  662. }
  663. })();