针对油管移动端,点击视频新标签页打开,记忆播放速度,突破播放速度限制
// ==UserScript== // @name Youtube Mobile Enhance 油管移动端增强 // @namespace http://tampermonkey.net/ // @version 2.9.2 // @author zyronon // @description 针对油管移动端,点击视频新标签页打开,记忆播放速度,突破播放速度限制 // @license GPL License // @icon https://v2next.netlify.app/favicon.ico // @homepage https://github.com/zyronon/web-scripts // @homepageURL https://github.com/zyronon/web-scripts // @supportURL https://update.greasyfork.org/scripts/487013/Youtube%20Mobile%20Enhance%20%E6%B2%B9%E7%AE%A1%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%A2%9E%E5%BC%BA.user.js // @match https://m.youtube.com/* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js // @require https://cdn.jsdelivr.net/npm/[email protected]/eruda.js // @grant GM_addStyle // @grant GM_openInTab // @grant unsafeWindow // @run-at document-start // ==/UserScript== (t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const e=document.createElement("style");e.textContent=t,document.head.append(e)})(" html{font-size:12px!important}.ytb-next{font-size:1.4rem;display:flex;position:fixed;top:0;right:10px;width:calc(22vw - 10px);z-index:99999;background:black}.ytb-next .btn{flex:1;color:#f1f1f1;background-color:#ffffff1a;padding:5px 0;height:36px;font-size:14px;line-height:36px;text-align:center;border:1px solid rgba(0,0,0,.8)}.msg{position:fixed;z-index:999;font-size:3rem;left:0;top:0;color:#000;background:white;padding:1rem 2rem}@media (min-width: 1280px) and (orientation: landscape){.player-container,.player-container.sticky-player{right:22vw!important;top:0!important;z-index:999!important}ytm-watch{margin-right:22vw!important}ytm-engagement-panel,.related-items-container{width:22vw!important}lazy-list .feed-item{width:100%!important}lazy-list .feed-item ytm-media-item{width:100%!important}.playlist-entrypoint-background-protection,.slide-in-animation-entry-point{width:22vw!important}ytm-single-column-watch-next-results-renderer [section-identifier=related-items],ytm-single-column-watch-next-results-renderer>ytm-playlist{width:22vw!important;padding:0 0 8px 8px;box-sizing:border-box}ytm-single-column-watch-next-results-renderer .playlist-content{width:22vw!important}} "); (function (vue, eruda) { 'use strict'; function _interopNamespaceDefault(e) { const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } }); if (e) { for (const k in e) { if (k !== 'default') { const d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: () => e[k] }); } } } n.default = e; return Object.freeze(n); } const eruda__namespace = /*#__PURE__*/_interopNamespaceDefault(eruda); var _GM_openInTab = /* @__PURE__ */ (() => typeof GM_openInTab != "undefined" ? GM_openInTab : void 0)(); var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); const _hoisted_2 = { key: 1, class: "msg" }; const _sfc_main = /* @__PURE__ */ vue.defineComponent({ __name: "App", setup(__props) { let refVideo = vue.ref(null); let rate = vue.ref(1); let lastRate = vue.ref(1); let pageType = vue.ref(""); let msg = vue.reactive({ show: false, content: "", timer: -1 }); function stop(e) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); return true; } function openNewTab(href, active = false) { _GM_openInTab(href, { active }); } function getBrowserType() { let userAgent = navigator.userAgent; if (userAgent.indexOf("Opera") > -1) { return "Opera"; } if (userAgent.indexOf("Firefox") > -1) { return "FF"; } if (userAgent.indexOf("Chrome") > -1) { return "Chrome"; } if (userAgent.indexOf("Safari") > -1) { return "Safari"; } if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera) { return "IE"; } } function initStyle(type) { let style2 = ` :root { --color-scrollbar: rgb(147, 173, 227); } html[darker-dark-theme] { --color-scrollbar: rgb(92, 93, 94); } ${type === "FF" ? `/* 火狐美化滚动条 */ * { scrollbar-color: var(--color-scrollbar); /* 滑块颜色 滚动条背景颜色 */ scrollbar-width: thin; /* 滚动条宽度有三种:thin、auto、none */ }` : ` ::-webkit-scrollbar { width: 1rem; height: 1rem; } ::-webkit-scrollbar-track { background: transparent; border-radius: .2rem; } ::-webkit-scrollbar-thumb { background: var(--color-scrollbar); border-radius: 1rem; }`} `; let addStyle2 = document.createElement("style"); addStyle2.rel = "stylesheet"; addStyle2.type = "text/css"; addStyle2.innerHTML = style2; window.document.head.append(addStyle2); } function findA(target, e) { let parentNode = target.parentNode; let count = 0; while (parentNode.tagName !== "A" && count < 10) { count++; parentNode = parentNode.parentNode; } console.log(parentNode); openNewTab(parentNode.href, true); return stop(e); } function checkPageType() { if (location.pathname === "/watch") { pageType.value = "watch"; } if (location.pathname === "/") { pageType.value = "home"; } if (location.pathname.startsWith("/@")) { pageType.value = "user"; } } function checkVideo() { let v = document.querySelector("video"); if (v) { v.playbackRate = rate.value; refVideo.value = v; window.funs.checkWatchPageDiv(); return true; } } function playbackRateToggle() { checkVideo(); if (refVideo.value) { if (refVideo.value.playbackRate !== 1) { lastRate.value = rate.value; rate.value = refVideo.value.playbackRate = 1; showMsg("播放速度: 1"); } else { rate.value = refVideo.value.playbackRate = lastRate.value === 1 ? 2 : lastRate.value; showMsg("播放速度: " + rate.value); } } } function toggle() { checkVideo(); if (refVideo.value) { if (refVideo.value.paused) { refVideo.value.play(); } else { refVideo.value.pause(); } } } function setPlaybackRate(val) { checkVideo(); if (refVideo.value) { rate.value = refVideo.value.playbackRate = Number(val.toFixed(1)); showMsg("播放速度: " + rate.value); } } function showMsg(text) { if (msg.show) { msg.show = false; clearTimeout(msg.timer); } msg.show = true; msg.content = text; msg.timer = setTimeout(() => { msg.show = false; }, 3e3); } function checkOptionButtons() { let dom = document.querySelector(".ytb-next"); if (dom) return; dom = document.createElement("div"); dom.classList.add("ytb-next"); dom.innerHTML = ` <div class="btn" onclick="window.cb('playbackRateToggle')">切</div> <div class="btn" onclick="window.cb('addRate')"> + </div> <div class="btn" onclick="window.cb('removeRate')"> - </div> <div class="btn" onclick="window.cb('playbackRateToggle1')"> 1 </div> <div class="btn" onclick="window.cb('playbackRateToggle2')"> 2 </div> <div class="btn" onclick="window.cb('playbackRateToggle25')"> 2.5 </div> <div class="btn" onclick="window.cb('playbackRateToggle3')"> 3 </div> `; document.body.append(dom); } function checkIsWatchPage() { checkPageType(); return pageType.value === "watch"; } function checkA(e) { let target = e.target; let tagName = target.tagName; let classList = target.classList; if (tagName === "IMG" && Array.from(classList).some((v) => v.includes("yt-core-image"))) { console.log("封面"); if (checkIsWatchPage()) return; return findA(target, e); } if (tagName === "SPAN" && Array.from(classList).some((v) => v.includes("yt-core-attributed-string"))) { console.log("标题"); if (checkIsWatchPage()) return; return findA(target, e); } if (tagName === "BUTTON" && Array.from(classList).some((v) => v.includes("ytp-large-play-button"))) { console.log("播放按钮"); if (checkIsWatchPage()) return; } if (tagName === "DIV" && Array.from(classList).some((v) => v.includes("ytp-cued-thumbnail-overlay-image"))) { console.log("播放按钮"); if (checkIsWatchPage()) return; } } vue.watch(rate, (value) => { localStorage.setItem("youtube-rate", value); window.rate = value; }); vue.onMounted(() => { console.log("Youtube Next start"); setTimeout(() => { let browserType = getBrowserType(); initStyle(browserType); }, 500); let youtubeRate = localStorage.getItem("youtube-rate"); if (youtubeRate) { rate.value = Number(youtubeRate); } _unsafeWindow.cb = (type) => { console.log("type", type); switch (type) { case "toggle": toggle(); break; case "playbackRateToggle": playbackRateToggle(); break; case "playbackRateToggle1": setPlaybackRate(1); break; case "playbackRateToggle2": setPlaybackRate(2); break; case "playbackRateToggle25": setPlaybackRate(2.5); break; case "playbackRateToggle3": setPlaybackRate(3); break; case "addRate": setPlaybackRate(rate.value + 0.1); break; case "removeRate": setPlaybackRate(rate.value - 0.1); break; } }; if (checkIsWatchPage()) { setTimeout(() => { checkOptionButtons(); checkVideo(); if (refVideo.value) { refVideo.value.muted = false; refVideo.value.playbackRate = rate.value; showMsg("播放速度: " + rate.value); } }, 1e3); } window.addEventListener("click", checkA, true); window.addEventListener("visibilitychange", stop, true); }); vue.onUnmounted(() => { window.removeEventListener("click", checkA, true); window.removeEventListener("visibilitychange", stop, true); }); return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [ vue.createCommentVNode("", true), vue.unref(msg).show ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2, vue.toDisplayString(vue.unref(msg).content), 1)) : vue.createCommentVNode("", true) ], 64); }; } }); try { if (window.trustedTypes && window.trustedTypes.createPolicy) { window.trustedTypes.createPolicy("default", { createHTML: (string) => string, createScriptURL: (string) => string, createScript: (string) => string }); } } catch (e) { console.error("trustedTypes"); } try { setTimeout(() => { var _a; (_a = window.eruda) == null ? void 0 : _a.init(); eruda__namespace == null ? void 0 : eruda__namespace.init(); }, 0); } catch (e) { } window.videoEl = null; window.rate = 1; window.funs = { checkWatchPageDiv() { let header = document.querySelector("#header-bar"); let stickyPlayer = document.querySelector("#app.sticky-player"); if (header) header.style["display"] = "none"; if (stickyPlayer) stickyPlayer.style["padding-top"] = "0"; } }; async function sleep(time) { return new Promise((resolve) => { setTimeout(resolve, time); }); } function proxyHTMLMediaElementEvent() { if (HTMLMediaElement.prototype._rawAddEventListener_) { return false; } HTMLMediaElement.prototype._rawAddEventListener_ = HTMLMediaElement.prototype.addEventListener; HTMLMediaElement.prototype._rawRemoveEventListener_ = HTMLMediaElement.prototype.removeEventListener; HTMLMediaElement.prototype.addEventListener = new Proxy(HTMLMediaElement.prototype.addEventListener, { apply(target, ctx, args) { const eventName = args[0]; const listener = args[1]; if (listener instanceof Function && eventName === "ratechange") { args[1] = new Proxy(listener, { apply(target2, ctx2, args2) { if (ctx2) { if (ctx2.playbackRate && eventName === "ratechange") { if (ctx2._hasBlockRatechangeEvent_) { return true; } const oldRate = ctx2.playbackRate; const startTime = Date.now(); const result = target2.apply(ctx2, args2); const blockRatechangeBehave1 = oldRate !== ctx2.playbackRate || Date.now() - startTime > 1e3; const blockRatechangeBehave2 = ctx2._setPlaybackRate_ && ctx2._setPlaybackRate_.value !== ctx2.playbackRate; if (blockRatechangeBehave1 || blockRatechangeBehave2) { debug.info(`[execVideoEvent][${eventName}]检测到可能存在阻止调速的行为,已禁止${eventName}事件的执行`, listener); ctx2._hasBlockRatechangeEvent_ = true; return true; } else { return result; } } } try { return target2.apply(ctx2, args2); } catch (e) { debug.error(`[proxyPlayerEvent][${eventName}]`, listener, e); } } }); } if (listener instanceof Function && eventName === "play") { args[1] = new Proxy(listener, { apply(target2, ctx2, args2) { console.log("play", window.rate); ctx2.playbackRate = window.rate; window.funs.checkWatchPageDiv(); try { return target2.apply(ctx2, args2); } catch (e) { debug.error(`[proxyPlayerEvent][${eventName}]`, listener, e); } } }); } return target.apply(ctx, args); } }); } proxyHTMLMediaElementEvent(); async function init() { let $section = document.createElement("section"); $section.id = "vue-app"; let count = 0; if (document.body) { document.body.append($section); } else { while (!document.body && count < 50) { await sleep(100); count++; } document.body.append($section); } let vueApp = vue.createApp(_sfc_main); vueApp.config.unwrapInjectedRef = true; vueApp.mount($section); } init(); })(Vue, Vue);