Universal <video> Fixer - bring back the seek bar, enable download, PiP, etc

Makes <video> more usable across the web by enabling all controls, downloads, picture-in-picture, etc. May cause usability issues on some sites but is generally an improvment.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Universal <video> Fixer - bring back the seek bar, enable download, PiP, etc
// @namespace    http://tampermonkey.net/
// @version      0.1.1
// @description  Makes <video> more usable across the web by enabling all controls, downloads, picture-in-picture, etc. May cause usability issues on some sites but is generally an improvment.
// @author       @varenc
// @match        *://*/*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAANIaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjE8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6UGhvdG9tZXRyaWNJbnRlcnByZXRhdGlvbj4yPC90aWZmOlBob3RvbWV0cmljSW50ZXJwcmV0YXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xMDI0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT4xPC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4xMDI0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Ck65VAAAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAYUExURSQkJjQ1NwMDA/Hz7gwMD9DSz6KlpXF0dUWDpyEAAAADdFJOUwT1ipnZE8QAAAKySURBVEjHrZVNj5swEIbTan/ApuuQe9U/UFmGa11NzBkZJ+cIQ84rPvbvd2aMExdnbzuHKIQnL8N8vN7tviBeNDwPrQOgBXxG/GWBPXwMT+MGwIA4Du55XPUfTmFx9mLlJspOKicYqHunxNsWaGv88K8EFM42usgAGKVsKEsoUED3W0ABSlQMLLaBwsohvd0HiZKBmQVK6JIMwbJEqRlgAS/GB1DpE0usgCcBXadpTCxhSmDgwgILlUI5Zx8SywpYtwqoG7ZFL3cJFwG5ClwB9nsAQxKAEuoOBIErHBfnzu90jyXkHWjFTFdH6qFTV3rjCt7uAF6RwLSnBgPWZaKHTHMCyMFi4sstzMnMRStdClDnjud1jsB6Eyv6ABQUPo7aiUq9BSo9PIbRTmMGtKYBbaoAvLWnDJgOHgHsEIUp6wyAWXANJyZUSCIBFKeAgOIt6aZuA5T1JQD4I8boPwfkhbMcN0BVNxGQWI9TO28B8wAUAYcM+H0HmqdAXUWAvhz8nCVZpm8xZ4DSaq0DV2oW29eUoguVDC3tIQP86AUCDd+vL7XNunnC14jdNK2x+TxIGqZ16KY566aEsY3zUp+hz4HWqGh3s6+3I8dL0q0ZFGf0DKlSoKIK+ZobCcd+4jXpEqAVfGXk+fYxuCsJ8HLegVIbHgSD5njGFSUByvORg+fS4nYf3+kZJGD+26wgEfzhA++TwGO7yzFKSEfrbWUQaOwKYPqrROJRvVXiFD2KrGCVWF2OBFooVsBNmh6X2GCpUQDqPjoMu8kttcGBBJZoYk4KlFDpiaBYwFUMoEteUv3o9mhoDQNUkemQnxe9s54BTRXJjwMUQJenA4VL6WRGYKWAjqTvgF7uyKMVU/g1fDo0NTqSdr8EGvCzgHAsosSn8RqO3m8/fsbgv1Hs8bfd18Q/+m3BBdhR+FsAAAAASUVORK5CYII=
// @license      MIT
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    function fixVideos() {
        // Process all videos on the page, making them downloadable and fully functional
        document.querySelectorAll("video").forEach((video, index) => {
            console.log(`VideoFixer: Processing <video> #${index + 1}:`, video);

            video.style.position = "relative";
            video.style.zIndex = "999999"; // <-- this is the real trick, forcing the native <video> controls to the front
            video.controls = true;
            video.style.pointerEvents = "auto";

            const removedAttributes = [];
            const removedControlsListItems = [];

            // Remove attributes that restrict functionality
            ["disablePictureInPicture", "disableRemotePlayback"].forEach((attr) => {
                if (video.hasAttribute(attr)) {
                    removedAttributes.push(attr);
                    video.removeAttribute(attr);
                }
            });

            // Remove controlsList restrictions
            if (video.hasAttribute("controlsList")) {
                removedControlsListItems.push(...video.getAttribute("controlsList").split(/\s+/));
                video.removeAttribute("controlsList");
            }

            if (removedAttributes.length > 0 || removedControlsListItems.length > 0) {
                console.log(`VideoFixer: Removed restrictions from video #${index + 1}:`, {
                    removedAttributes,
                    removedControlsListItems
                });
            }
        });

        console.log("VideoFixer: All videos processed and fixed!");
    }

    // Set up a MutationObserver to detect new videos being added to the page
    function setupVideoObserver() {
        const videoObserver = new MutationObserver((mutations) => {
            let shouldProcess = false;

            // Check for new/modified <video>
            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    const addedVideos = Array.from(mutation.addedNodes).filter(node =>
                        node.nodeName === 'VIDEO' ||
                        (node.nodeType === Node.ELEMENT_NODE && node.querySelector('video'))
                    );

                    if (addedVideos.length > 0) {
                        shouldProcess = true;
                        break;
                    }
                } else if (mutation.type === 'attributes' &&
                          mutation.target.nodeName === 'VIDEO') {
                    shouldProcess = true;
                    break;
                }
            }

            if (shouldProcess) {
                fixVideos();
            }
        });

        videoObserver.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['src', 'controlsList', 'disablePictureInPicture', 'disableRemotePlayback'],
            attributeOldValue: true
        });

        return videoObserver;
    }

    function initialize() {
        console.log("Universal Video Fixer activated!");
        fixVideos();
        setupVideoObserver();
    }

    // Wait for the DOM to be fully loaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }

    // Run a check after a short delay to catch videos that load after initial page load, but were missed by the mutation observer
    setTimeout(fixVideos, 2000);
})();