// ==UserScript== // @name 抖音画质调节 // @namespace https://github.com/WhiteSevs/TamperMonkeyScript // @version 1.0.0 // @author WhiteSevs(精简改造) // @description 只做画质:固定抖音网页版视频清晰度(4K / 2K / 1080P / 720P / 540P / 极速 / 智能),可解锁更高画质 // @license GPL-3.0-only // @match *://*.douyin.com/* // @match *://*.iesdouyin.com/* // @exclude *://creator.douyin.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant unsafeWindow // @run-at document-start // @noframes // ==/UserScript== (function () { "use strict"; /* ============================ 基础配置 ============================ */ var W = typeof unsafeWindow !== "undefined" ? unsafeWindow : window; // 抖音播放器读取的 sessionStorage 键(原脚本中选择清晰度的核心就是往这里写数据) var KEY_MANUAL = "MANUAL_SWITCH"; var CFG = { gear: "dy_quality_gear", unlock: "dy_quality_unlock", panel: "dy_quality_panel", pos: "dy_quality_pos", }; // 高码率清晰度映射(来自抖音播放器内部数据结构) var CLARITY_UHD = [ "adapt_lowest_4_1", "normal_1080_0", "adapt_lowest_1440_1", "low_720_0", "normal_720_0", "low_540_0", "normal_540_0", "adapt_lowest_1080_1", "lower_540_0", "adapt_low_540_0", "720_2_1", "540_2_1", "540_3_1", ]; var CLARITY_FHD = [ "normal_1080_0", "normal_1080_0", "normal_720_0", "low_720_0", "normal_540_0", "low_720_0", "low_540_0", "low_540_0", "lower_540_0", "adapt_low_540_0", "adapt_low_540_0", "adapt_lowest_1080_1", "720_2_1", "720_2_1", "720_3_1", "540_2_1", "720_3_1", "540_2_1", "720_4_1", "720_4_1", "540_3_1", "540_3_1", ]; var GEARS = [ { id: "4k", short: "4K", full: "超清 4K", payload: { done: 1, gearClarity: "20", qualityType: 72, gearName: "超清 4K", gearType: -2, clarityReal: CLARITY_UHD, }, }, { id: "2k", short: "2K", full: "超清 2K", payload: { done: 1, gearClarity: "10", qualityType: 7, gearName: "超清 2K", gearType: -1, clarityReal: CLARITY_UHD, }, }, { id: "1080", short: "1080P", full: "高清 1080P", payload: { done: 1, gearClarity: "5", gearName: "高清 1080P", gearType: 1, clarityReal: CLARITY_FHD, qualityType: 1, }, }, { id: "720", short: "720P", full: "高清 720P", payload: { done: 1, gearClarity: "4", gearName: "高清 720P", gearType: 2, clarityReal: CLARITY_FHD, qualityType: 10, }, }, { id: "540", short: "540P", full: "标清 540P", payload: { done: 1, gearClarity: "3", gearName: "标清 540P", gearType: 3, clarityReal: CLARITY_FHD, qualityType: 20, }, }, { id: "fast", short: "极速", full: "极速", payload: { done: 1, gearClarity: "2", gearName: "极速", gearType: 4, qualityType: 21, }, }, { id: "auto", short: "智能", full: "智能", payload: { done: 1, gearClarity: "0", gearName: "智能", gearType: 0, clarityReal: CLARITY_FHD, }, }, ]; /* ============================ 存储读写 ============================ */ var hasGM = typeof GM_getValue === "function" && typeof GM_setValue === "function"; function readCfg(key, fallback) { try { if (hasGM) { var v = GM_getValue(key, null); return v === null || v === undefined ? fallback : v; } var raw = localStorage.getItem(key); return raw === null ? fallback : JSON.parse(raw); } catch (e) { return fallback; } } function writeCfg(key, value) { try { if (hasGM) GM_setValue(key, value); else localStorage.setItem(key, JSON.stringify(value)); } catch (e) { /* ignore */ } } var state = { gear: readCfg(CFG.gear, "1080"), unlock: readCfg(CFG.unlock, true), panel: readCfg(CFG.panel, true), pos: readCfg(CFG.pos, null), }; if (state.gear !== "off" && !findGear(state.gear)) state.gear = "1080"; /* ============================ 画质控制 ============================ */ function findGear(id) { for (var i = 0; i < GEARS.length; i++) { if (GEARS[i].id === id) return GEARS[i]; } return null; } function labelOf(id) { if (id === "off") return "不干预"; var g = findGear(id); return g ? g.short : "1080P"; } function currentGear() { return findGear(state.gear); } // 优先使用页面上下文的 sessionStorage(沙箱环境下更保险) function pageSession() { try { return W.sessionStorage || sessionStorage; } catch (e) { return null; } } // 单次写入:把选中的画质参数写进播放器的 sessionStorage function writeOnce() { var gear = currentGear(); if (!gear) return; var ss = pageSession(); if (!ss) return; try { var raw = ss.getItem(KEY_MANUAL); if (raw) { try { if (JSON.parse(raw).gearName === gear.payload.gearName) return; } catch (e) { /* 旧值不是 JSON,直接覆盖 */ } } ss.setItem(KEY_MANUAL, JSON.stringify(gear.payload)); } catch (e) { /* ignore */ } } // 连续写入一段时间,覆盖播放器初始化时机(原脚本为 200ms x 5s) var burstTimer = 0; var burstStop = 0; function applyQuality(duration) { if (!currentGear()) return; if (state.unlock) unlockHighQuality(); // 每次应用时补一次,兼顾页面后写入的 A/B 标记 writeOnce(); clearInterval(burstTimer); clearTimeout(burstStop); burstTimer = setInterval(writeOnce, 250); burstStop = setTimeout(function () { clearInterval(burstTimer); }, duration || 4000); } // 解锁更高画质:伪装设备性能 + 改写 A/B 实验标记(原脚本 hookVideoQuality 的同源做法) function unlockHighQuality() { try { Object.defineProperty(W.navigator, "hardwareConcurrency", { get: function () { return 16; }, configurable: true, }); Object.defineProperty(W.navigator, "deviceMemory", { get: function () { return 64; }, configurable: true, }); } catch (e) { /* ignore */ } try { var ls = W.localStorage; var keys = []; for (var i = 0; i < ls.length; i++) { var k = ls.key(i); if (k && k.indexOf("__tea_sdk_ab_version_") === 0) keys.push(k); } keys.forEach(function (key) { var data = JSON.parse(ls.getItem(key) || "{}"); if (!data || typeof data !== "object" || !data.data) return; data.data.exposure_defination = { val: 1, vid: "" }; data.data.has_2k = { val: 1, vid: "" }; data.data.has_hdr = { val: 1, vid: "" }; ls.setItem(key, JSON.stringify(data)); }); } catch (e) { /* ignore */ } } function setGear(id, silent) { state.gear = id; writeCfg(CFG.gear, id); if (id === "off") { try { var ss = pageSession(); if (ss) ss.removeItem(KEY_MANUAL); } catch (e) { /* ignore */ } if (!silent) toast("已恢复默认(不干预)"); } else { applyQuality(5000); if (!silent) toast("画质:" + labelOf(id)); } renderUI(); registerMenu(); } /* ============================ 触发时机 ============================ */ var lastHref = location.href; var lastSrc = ""; function startWatcher() { setInterval(function () { // 站内路由切换(SPA) if (location.href !== lastHref) { lastHref = location.href; applyQuality(3000); } // 视频源切换(上下滑 / 下一个视频) var video = document.querySelector("video"); var src = video ? video.currentSrc || video.src || "" : ""; if (src && src !== lastSrc) { lastSrc = src; applyQuality(3000); } }, 700); } /* ============================ 悬浮面板 ============================ */ var CSS = [ ".box{position:relative;display:flex;flex-direction:column;align-items:flex-start;", 'font:12px/1.45 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;', "color:#e9e9ee;user-select:none;}", ".pill{display:flex;align-items:center;gap:6px;height:28px;padding:0 11px;border-radius:14px;", "background:rgba(25,25,30,.82);border:1px solid rgba(255,255,255,.14);", "box-shadow:0 2px 12px rgba(0,0,0,.4);cursor:grab;white-space:nowrap;backdrop-filter:blur(8px);}", ".pill:active{cursor:grabbing;}", ".dot{width:6px;height:6px;border-radius:50%;background:#22d3a5;box-shadow:0 0 6px rgba(34,211,165,.8);}", ".pill.off .dot{background:#8a8a93;box-shadow:none;}", ".name{font-size:12px;font-weight:600;letter-spacing:.3px;}", ".menu{display:none;position:absolute;left:0;bottom:34px;width:180px;padding:6px;", "border-radius:12px;background:rgba(22,22,26,.94);border:1px solid rgba(255,255,255,.12);", "box-shadow:0 10px 28px rgba(0,0,0,.55);backdrop-filter:blur(10px);}", ".menu.open{display:block;}", ".menu.down{top:34px;bottom:auto;}", ".title{padding:4px 8px 6px;font-size:11px;color:#94949c;}", ".item{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;", "border-radius:8px;cursor:pointer;}", ".item:hover{background:rgba(255,255,255,.08);}", ".item.active{background:rgba(254,44,85,.16);}", ".item .tick{font-size:11px;color:#fe2c55;visibility:hidden;}", ".item.active .tick{visibility:visible;}", ".sep{height:1px;margin:5px 6px;background:rgba(255,255,255,.08);}", ".row{display:flex;align-items:center;justify-content:space-between;padding:5px 8px;", "border-radius:8px;cursor:pointer;color:#c9c9d1;}", ".row:hover{background:rgba(255,255,255,.08);}", ".sw{position:relative;width:26px;height:14px;border-radius:7px;background:#3b3b44;", "transition:background .15s;}", ".sw i{position:absolute;top:2px;left:2px;width:10px;height:10px;border-radius:50%;", "background:#fff;transition:left .15s;}", ".sw.on{background:#fe2c55;}", ".sw.on i{left:14px;}", ".hint{padding:5px 8px 3px;font-size:10px;line-height:1.5;color:#77777f;}", ".toast{position:absolute;left:calc(100% + 8px);bottom:6px;padding:4px 9px;border-radius:7px;", "background:rgba(0,0,0,.85);color:#fff;font-size:11px;white-space:nowrap;opacity:0;", "transform:translateX(-4px);transition:opacity .18s,transform .18s;pointer-events:none;}", ".toast.show{opacity:1;transform:translateX(0);}", ].join(""); var host = null; var root = null; var pillEl = null; var nameEl = null; var menuEl = null; var toastEl = null; var swEl = null; var toastTimer = 0; function toast(text) { if (!toastEl) return; toastEl.textContent = text; toastEl.classList.add("show"); clearTimeout(toastTimer); toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 1600); } function renderUI() { if (!root) return; nameEl.textContent = labelOf(state.gear); pillEl.classList.toggle("off", state.gear === "off"); Array.prototype.forEach.call(root.querySelectorAll("[data-gear]"), function (el) { el.classList.toggle("active", el.getAttribute("data-gear") === state.gear); }); swEl.classList.toggle("on", !!state.unlock); host.style.display = state.panel ? "" : "none"; } // 菜单靠近屏幕顶部时翻转到下方显示 function fitMenu() { if (!menuEl) return; menuEl.classList.remove("down"); if (!menuEl.classList.contains("open")) return; if (menuEl.getBoundingClientRect().top < 8) menuEl.classList.add("down"); } function ensureUI() { if (host || !document.body) return; host = document.createElement("div"); host.id = "dy-quality-host"; host.style.cssText = "position:fixed;left:16px;bottom:120px;z-index:2147483000;"; root = host.attachShadow({ mode: "open" }); var items = GEARS.map(function (g) { return '