Greasy Fork is available in English.

优化斗鱼web播放器

douyu优化斗鱼web播放器,通过关闭直播间全屏时的背景虚化效果来解决闪屏卡顿的问题,屏蔽独立直播间的弹幕显示,移除文字水印,添加了一些快捷键。暴力隐藏页面元素,获得纯净观看体验

// ==UserScript==
// @name         优化斗鱼web播放器
// @namespace    https://www.liebev.site/monkey/better-douyu
// @version      2.2
// @description  douyu优化斗鱼web播放器,通过关闭直播间全屏时的背景虚化效果来解决闪屏卡顿的问题,屏蔽独立直播间的弹幕显示,移除文字水印,添加了一些快捷键。暴力隐藏页面元素,获得纯净观看体验
// @author       LiebeV
// @license      MIT: Copyright (c) 2023 LiebeV
// @match        https://www.douyu.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=douyu.com
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

"use strict";

//更新日志,v2.2,临时添加了更好的多路观看模式(ALT+ M),这只是个实验性内容,没有对应的控件,且会暴力覆写网页原有结构(!慎用)
//**NOTE**:之于页面上其他不想要看到的东西,请搭配其他例如AdBlock之类的专业广告屏蔽器使用,本脚本仅提供暴力隐藏的功能
//**NOTE**:暴力隐藏无法撤销,请刷新网页以恢复
//已知问题,暴力重写导致的各种问题
//更新计划,完整重写(请关注主页新项目--“全等级弹幕屏蔽”,“优化斗鱼web鱼吧”)

// 定义一些let变量
let roomIds = GM_getValue("roomIds", []);
const userRoomIds = roomIds;
// let isFull = 0;
// let isWide = 0;
var IntervalId;
const target_xpath = "/html/body/section/main/div[4]/div[1]/div[4]";
let IntervalRun = false;
const listenerMap = new Map([
    ["l", rewrite],
    ["u", full],
    ["w", wide],
    ["k", hide],
    ["m", multi],
]);

// 屏蔽虚化背景以及文字水印的css
async function blur() {
    console.debug("已经创建blur样式表");
    return `._1Osm4fzGmcuRK9M8IVy3u6,.watermark-442a18,.is-ybHotDebate,.view-67255d.zoomIn-0f4645{visibility: hidden !important;}`;
}

// 屏蔽右侧弹幕区及飘屏弹幕区的css
// 保留礼物展示
async function danmu() {
    const url = window.location.href;

    if (userRoomIds.some((roomId) => url.includes(roomId))) {
        console.debug("已经创建danmu样式表");
        return `.Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: hidden !important;}`;
    } else {
        console.debug("无需更新danmu样式表");
        return `.Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: unset !important;}`;
    }
}

// 用于合并其他本人制作的脚本中生成的css
async function addStyle(css) {
    const liebev = document.getElementById("LiebeV");
    // console.debug(liebev);
    if (liebev) {
        liebev.innerHTML = css;
        console.debug("原css已更新");
    } else {
        // 此脚本单独运作时创建liebev标签
        const style = document.createElement("style");
        style.id = "LiebeV";
        style.type = "text/css";

        style.appendChild(document.createTextNode(css));
        document.head.appendChild(style);
        console.debug("新css已插入");
    }
}

async function showSettings() {
    let message = "当前设置的房间号列表:\n";
    const idList = GM_getValue("roomIds", []);
    for (let i = 0; i < idList.length; i++) {
        message += `${i + 1}. ${idList[i]}\n`;
    }
    const roomIdInput = prompt(`${message}\n请输入要移除(恢复)弹幕的房间号房间号:`);
    if (roomIdInput) {
        updateRoomId(roomIdInput);
    }
}

GM_registerMenuCommand("房间号设置", showSettings);

// 接受用户输入,进入判断分支
async function updateRoomId(roomId) {
    if (!roomIds.includes(roomId)) {
        roomIds.push(roomId);
        console.debug(`已添加房间号 ${roomId}`);
        GM_setValue("roomIds", roomIds);
        await DMbye();
    } else {
        const index = roomIds.indexOf(roomId);
        if (index !== -1) {
            roomIds.splice(index, 1);
            console.debug(`已移除房间号 ${roomId}`);
            GM_setValue("roomIds", roomIds);
            await DMback();
        } else {
            console.debug(`输入不合法`);
        }
    }
}

// DMbye和back是setting修改时调用的函数
async function DMbye() {
    const css = `._1Osm4fzGmcuRK9M8IVy3u6,.watermark-442a18,.is-ybHotDebate.view-67255d.zoomIn-0f4645{visibility: hidden !important;}.Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: hidden !important;}`;
    addStyle(css);
}

async function DMback() {
    const css = `._1Osm4fzGmcuRK9M8IVy3u6,.watermark-442a18,.is-ybHotDebate.view-67255d.zoomIn-0f4645{visibility: hidden !important;}.Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: unset !important;}`;
    addStyle(css);
}

async function listener() {
    for (const [key, func] of listenerMap) {
        document.addEventListener("keydown", function (event) {
            if ((event.getModifierState("Alt") || event.getModifierState("AltGraph")) && event.key.toLowerCase() === key) {
                func();
            }
        });
    }
    document.addEventListener("keydown", function (event) {
        if ((event.getModifierState("Alt") || event.getModifierState("AltGraph")) && event.key >= "1" && event.key <= "5") {
            // console.debug("RES快捷键触发");
            res(parseInt(event.key));
        }
    });
    document.addEventListener("keydown", function (event) {
        if ((event.getModifierState("Alt") || event.getModifierState("AltGraph")) && event.key >= "6" && event.key <= "9") {
            // console.debug("LINE快捷键触发");
            line(parseInt(event.key));
        }
    });
}

// 类似DMbye和back,但是由快捷键触发
async function rewrite() {
    const liebev = document.getElementById("LiebeV");
    const checker = liebev.innerHTML;
    var css;
    if (checker.indexOf(".Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: unset !important;}") !== -1) {
        css = checker.replace(".Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: unset !important;}", ".Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: hidden !important;}");
    } else {
        css = checker.replace(".Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: hidden !important;}", ".Barrage-main,.Barrage-topFloater,.comment-37342a {visibility: unset !important;}");
    }
    // console.debug(css);
    addStyle(css);
}

// 发送浏览器全屏/退出全屏事件
// 无需等待控件加载完成
async function full() {
    if (!document.fullscreenElement) {
        document.querySelector("#js-player-video-case").requestFullscreen();
    } else {
        document.exitFullscreen();
    }
}

// 模拟双击".video-container-dbc7dc"
// 因为网页原本就提供了双击播放器来切换的功能,故不做修改
async function wide() {
    let evt = new MouseEvent("dblclick", {
        bubbles: true,
        cancelable: true,
    });
    document.querySelector(".video-container-dbc7dc").dispatchEvent(evt);
}

// 点击原播放器控件来调整清晰度
// 最多只见过5个选项。原画/8M/4M/超清/高清。溢出也不会影响
async function res(numberkey) {
    const resinput = document.querySelector(".tipItem-898596 input[value='画质 ']");
    const resul = resinput.nextSibling;
    let reslist = [];
    resul.childNodes.forEach(function (li) {
        reslist.push(li);
    });
    // console.debug(reslist);
    const choice = numberkey - 1;
    reslist[choice].click();
}

// 找到了可以用于切换线路可点击的标签
// 最多只见过4个选项。溢出也不会影响
async function line(numberkey) {
    const lineinput = document.querySelectorAll("ul.menu-da2a9e li");
    const alllines = Array.from(lineinput).filter((li) => li.textContent.includes("线路"));
    let linelist = [];
    alllines.forEach(function (li) {
        linelist.push(li);
    });
    const choice = numberkey - 6;
    linelist[choice].click();
}

// *********************暴力************待优化****************
const videos = () => {
    return document.querySelectorAll("video");
};

function createElement() {
    const container = document.createElement("div");
    container.classList.add("liebev-multi");

    videos().forEach((video) => {
        container.appendChild(video);
    });

    document.body.appendChild(container);
}

function adjust_size(target) {
    let isIn = false;

    target.addEventListener("mouseover", (event) => {
        isIn = true;
    });
    target.addEventListener("mouseout", (event) => {
        isIn = false;
    });

    target.addEventListener("wheel", (event) => {
        if (event.deltaY && isIn) {
            const adjustment = event.deltaY / -10;
            const currentWidth = target.offsetWidth;
            const currentHeight = target.offsetHeight;

            target.style.width = `${currentWidth + adjustment}px`;
            target.style.height = `${currentHeight + adjustment}px`;
        }
    });
}

function adjust_position(target) {
    let isDragging = false;
    let startX = 0;
    let startY = 0;

    const moveAt = (pageX, pageY) => {
        const deltaX = pageX - startX;
        const deltaY = pageY - startY;
        const rect = target.getBoundingClientRect();

        target.style.left = rect.left + deltaX + "px";
        target.style.top = rect.top + deltaY + "px";

        startX = pageX;
        startY = pageY;
    };

    target.addEventListener("mousedown", function (event) {
        isDragging = true;
        target.style.cursor = "move";

        startX = event.pageX;
        startY = event.pageY;

        document.addEventListener("mousemove", onMouseMove);
    });

    document.addEventListener("mouseup", function () {
        isDragging = false;
        target.style.cursor = "default";

        document.removeEventListener("mousemove", onMouseMove);
    });

    const onMouseMove = function (event) {
        if (isDragging) {
            moveAt(event.pageX, event.pageY);
        }
    };
}

function addListeners(target) {
    adjust_size(target);
    adjust_position(target);
}

function multi() {
    createElement();
    const container = document.querySelector(".liebev-multi");
    container.requestFullscreen();
    videos().forEach((video) => {
        video.classList.remove("_3kBBGV3-d-EIxmN6JRZPar");

        video.style.setProperty("position", "absolute", "important");
        video.style.setProperty("left", "0", "important");
        video.style.setProperty("top", "0", "important");
        video.style.setProperty("width", "960px", "important");
        video.style.setProperty("height", "540px", "important");

        addListeners(video);
    });
}

// *********************暴力************待优化****************

// 点击原弹幕区控件来清除DOM节点(网页默认DOM上限为200)
async function rightdomremove() {
    var clearbtn = document.querySelector(".Barrage-toolbarClear");
    clearbtn.click();
    console.debug("doms been removed");
}

async function ifintervalrun() {
    if (IntervalRun == true) {
        // 定时触发清屏(60s)
        IntervalId = setInterval(rightdomremove, 60 * 1000);
        IntervalRun = false;
        console.debug("已设置定时清屏");
    } else {
        clearInterval(IntervalId);
        IntervalRun = true;
        console.debug("已关闭定时清屏");
    }
}

GM_registerMenuCommand("定时清屏右侧弹幕", function () {
    const isConfirm = confirm(
        // 因为初始化调用时更改了IntervalRun的值,所以表达式逻辑相反
        `当前定时器状态:${IntervalRun ? "禁用" : "启用"}\n是否切换定时器状态?`
    );
    if (isConfirm) {
        ifintervalrun();
    }
});

// 根据/的位置切片Xpath
async function slice() {
    const parts = target_xpath.split("/");
    let xpaths = [];
    for (let i = 1; i < parts.length; i++) {
        xpaths.push("/" + parts.slice(1, i + 1).join("/"));
    }

    console.debug(`来自**优化斗鱼web播放器**:全部的Xpath [${xpaths}]`);

    return xpaths;
}

// 选中每个元素
async function select(xpaths) {
    let elements = [];
    xpaths.forEach(function (xpath) {
        var element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
        elements.push(element);
    });

    return elements;
}

function isDescendant(parent, child) {
    var node = child.parentNode;
    while (node != null) {
        if (node === parent) {
            return true;
        }
        node = node.parentNode;
    }
    return false;
}

// 隐藏所有其他元素
function hide_all(elements, target_element) {
    document.querySelectorAll("body *").forEach(function (node) {
        let is_protected = elements.includes(node) || isDescendant(target_element, node);
        if (!is_protected) {
            node.style.display = "none";
        }
    });
}

async function force_hide(target_element) {
    const protected_xpaths = await slice();
    const protected_elements = await select(protected_xpaths);

    hide_all(protected_elements, target_element);
}

function hide() {
    if (target_xpath) {
        const target_element = document.evaluate(target_xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
        let checker;
        try {
            checker = target_element.className;
        } catch (TypeError) {
            console.debug("来自**优化斗鱼web播放器**: Error Code [hide-0] ---> 页面上没有找到播放器");
            return;
        }

        if (checker === "layout-Player-video") {
            force_hide(target_element);
        } else {
            console.debug("来自**优化斗鱼web播放器**: Error Code [hide-1] ---> Xpath描述的元素与预期不符");
        }
    }
}

(async function () {
    const blurcss = await blur();
    const danmucss = await danmu();
    const css = blurcss + danmucss;
    console.debug("样式表已合并" + css);
    addStyle(css);
    // 添加全部eventlistener
    listener();
    ifintervalrun();
})();