运用篡改猴执行脚本,监控网页中视频播放状态

在某些场景下,不得不将视频播放完,然后继续播放下一个视频,但是又不想一直在视频播放的页面,需要将视频播放页面最小化,干其他的事情,如果不能够及时掌握页面中视频播放状态,很容易在视频播放结束后很长时间才会想起来,那么这样就会浪费一些时间。这里分享一个扩展工具,篡改猴(Tampermonkey)这是一款浏览器扩展程序,可以运行自己编写的脚本。
第一步:首先在浏览器中,找到这个管理扩展,然后进入。
image
第二步:进入之后,点击获取扩展
image
第三步:在搜索框中搜索篡改猴,选择第一个,然后进行安装
image
第四步:安装完成后,回到第二步的页面,将“开发人员模式”打开
第五步:点击下图框选的选项,进入脚本添加的页面
image
第六步:填写运行脚本,点击这个+号,然后在脚本页面中粘贴需要运行的脚本
image
第七步:将下面的脚本粘贴后,然后保存。

// UserScript
// @name 增强版视频播放提醒(支持多种播放器)
// @namespace http://tampermonkey.net/
// @version v2.0
// @description 增强版视频播放提醒,支持更多网站和播放器类型,准确识别视频播放状态
// @author YourName
// @match 😕//*
// @grant GM_notification
// @run-at document-end
// /UserScript

(function() {
'use strict';

let currentVideo = null;
let hasPreEndNotified = false;
let activeNotification = null;
let observer = null;
let mutationObserver = null;
let checkInterval = null;

/**
 * 查找页面中的视频元素(增强版)
 */
function findVideoElement() {
    // 标准video元素
    let videos = Array.from(document.querySelectorAll('video'));

    // 常见的视频播放器类名
    const playerSelectors = [
        '.video-player video',
        '.player video',
        '[class*="video"] video',
        '.jw-video',
        '.mejs-video',
        '.plyr__video-wrapper video',
        '.video-js video',
        '.fluid-width-video-wrapper video',
        '.video-container video',
        '.youtube-player video',
        '.vjs-tech',
        '.html5-video-container video',
        '.video-element video'
    ];

    for (const selector of playerSelectors) {
        videos = videos.concat(Array.from(document.querySelectorAll(selector)));
    }

    // 检查iframe中的视频
    const iframes = document.querySelectorAll('iframe');
    for (const iframe of iframes) {
        try {
            const iframeVideos = iframe.contentDocument?.querySelectorAll('video');
            if (iframeVideos) {
                videos = videos.concat(Array.from(iframeVideos));
            }
        } catch (e) {
            // 跨域iframe无法访问,跳过
            continue;
        }
    }

    // 检查shadow DOM中的视频元素
    const shadowRoots = getAllShadowRoots();
    for (const root of shadowRoots) {
        const shadowVideos = root.querySelectorAll('video');
        if (shadowVideos.length > 0) {
            videos = videos.concat(Array.from(shadowVideos));
        }
    }

    // 返回第一个非空的视频元素
    for (const video of videos) {
        if (video && video.duration > 0) {
            return video;
        }
    }

    // 如果没找到,返回第一个video元素(即使duration为0)
    if (videos.length > 0) {
        return videos[0];
    }

    return null;
}

/**
 * 获取所有shadow roots
 */
function getAllShadowRoots() {
    const roots = [];

    // 查找带有shadow root的元素
    const elementsWithShadow = document.querySelectorAll('*');
    for (const element of elementsWithShadow) {
        if (element.shadowRoot) {
            roots.push(element.shadowRoot);
            // 递归查找子shadow roots
            roots.push(...getAllShadowRootsInRoot(element.shadowRoot));
        }
    }

    return roots;
}

/**
 * 在特定root中递归查找shadow roots
 */
function getAllShadowRootsInRoot(root) {
    const roots = [];
    const elementsWithShadow = root.querySelectorAll('*');
    for (const element of elementsWithShadow) {
        if (element.shadowRoot) {
            roots.push(element.shadowRoot);
            roots.push(...getAllShadowRootsInRoot(element.shadowRoot));
        }
    }
    return roots;
}

/**
 * 格式化时间为 MM:SS 字符串
 */
function formatTime(seconds) {
    if (!isFinite(seconds) || seconds < 0) return '--:--';
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}

/**
 * [核心新增] 发送视频播放结束通知
 */
function sendEndNotification() {
    const title = "视频播放结束";
    const message = `当前视频已播放完毕。`;

    // 优先使用浏览器原生Notification API
    if ('Notification' in window && Notification.permission === 'granted') {
        const options = {
            body: message,
            icon: 'https://cdn.jsdelivr.net/npm/[email protected]/assets/72x72/1f3a5.png' // 使用电影胶片emoji
        };
        const notification = new Notification(title, options);

        // 点击通知可聚焦回页面
        notification.onclick = function() {
            window.focus();
            this.close();
        };

        // 设置自动关闭时间(5秒后)
        setTimeout(() => {
            if(notification.close) {
                notification.close();
            }
        }, 5000);

        console.log('[视频提醒] 已发送播放结束通知 (浏览器API)');

    } else if (typeof GM_notification !== 'undefined') {
        // 降级使用油猴API
        GM_notification({
            text: message,
            title: title,
            silent: false,
            timeout: 5000, // 结束通知5秒后自动关闭
            onclick: function() {
                window.focus();
            }
        });
        console.log('[视频提醒] 已发送播放结束通知 (油猴API)');
    }
}

/**
 * 发送需要手动关闭的提前通知(原功能)
 */
function sendManualNotification() {
    // 检查页面是否处于隐藏状态
    if (!document.hidden && document.visibilityState !== 'hidden') {
        console.log('[视频提醒] 页面处于前台,不发送提前通知。');
        return;
    }

    // 检查是否已经发送过提前通知
    if (hasPreEndNotified) {
        return;
    }

    if (!currentVideo || !currentVideo.duration) {
        return;
    }

    const totalTime = currentVideo.duration;
    const remainingTime = totalTime - currentVideo.currentTime;

    const title = "视频即将结束提醒";
    const message = `视频将在约3分钟内结束\n剩余时间:${formatTime(remainingTime)} / 总时长:${formatTime(totalTime)}`;

    if ('Notification' in window && Notification.permission === 'granted') {
        const options = {
            body: message,
            requireInteraction: true, // 需要手动关闭
            icon: 'https://cdn.jsdelivr.net/npm/[email protected]/assets/72x72/1f4fa.png' // 电视机emoji
        };

        const notification = new Notification(title, options);
        activeNotification = notification; // 存储当前通知引用

        notification.onclose = () => {
            console.log('[视频提醒] 提前通知已关闭');
            hasPreEndNotified = false; // 重置标记
            activeNotification = null;
        };

        hasPreEndNotified = true;
        console.log('[视频提醒] 已发送需要手动关闭的提前通知');

    } else if (typeof GM_notification !== 'undefined') {
        GM_notification({
            text: message,
            title: title,
            silent: false,
            timeout: 0, // 0表示不自动关闭
            onclick: function() {
                this.close();
                hasPreEndNotified = false;
            }
        });
        hasPreEndNotified = true;
        console.log('[视频提醒] 已发送油猴提前通知(需手动关闭)');
    }
}

/**
 * 检查是否需要发送提前通知(原功能逻辑)
 */
function checkPreEndNotification() {
    if (!currentVideo || currentVideo.ended || !currentVideo.duration) {
        return;
    }

    const totalTime = currentVideo.duration;
    const currentTime = currentVideo.currentTime;
    const remainingTime = totalTime - currentTime;

    // [修改] 如果剩余时间在3分钟(180秒)以内,且还没有发送过提前通知
    if (remainingTime <= 180 && remainingTime > 0 && !hasPreEndNotified) {
        sendManualNotification();
    }
}

/**
 * 检查视频播放状态(备用方法)
 */
function checkVideoStatus() {
    if (!currentVideo) {
        currentVideo = findVideoElement();
        if (currentVideo) {
            initVideoEventListeners();
        }
    }

    if (currentVideo && currentVideo.duration > 0) {
        // 检查视频是否已结束(备用检测)
        if (currentVideo.currentTime >= currentVideo.duration && !hasPreEndNotified) {
            console.log('[视频提醒] 通过定时器检测到视频播放结束');
            hasPreEndNotified = false;
            sendEndNotification();
        }
    }
}

/**
 * 初始化视频事件监听器
 */
function initVideoEventListeners() {
    if (!currentVideo) return;

    // 移除旧的监听器
    if (checkInterval) {
        clearInterval(checkInterval);
    }

    // 添加新的监听器
    currentVideo.addEventListener('timeupdate', checkPreEndNotification, { passive: true });
    currentVideo.addEventListener('ended', function() {
        console.log('[视频提醒] 视频播放结束事件触发');
        hasPreEndNotified = false;
        sendEndNotification();

        // 页面最小化时的备用通知
        if ((document.hidden || document.visibilityState === 'hidden') && typeof GM_notification !== 'undefined') {
            GM_notification({
                text: "视频播放已结束",
                title: "播放完成",
                timeout: 5000
            });
        }
    }, { passive: true });

    // 启动备用检查定时器(每5秒检查一次)
    if (checkInterval) {
        clearInterval(checkInterval);
    }
    checkInterval = setInterval(checkVideoStatus, 5000);

    console.log('[视频提醒] 视频事件监听器已初始化');
}

/**
 * 监控DOM变化以发现新插入的视频元素
 */
function initMutationObserver() {
    if (mutationObserver) {
        mutationObserver.disconnect();
    }

    mutationObserver = new MutationObserver(function(mutations) {
        let shouldCheck = false;

        for (const mutation of mutations) {
            if (mutation.type === 'childList') {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1) { // Element node
                        if (node.tagName === 'VIDEO' || node.querySelector('video')) {
                            shouldCheck = true;
                            break;
                        }

                        // 检查新添加的元素是否包含视频相关的类名
                        const videoRelatedClasses = ['video', 'player'];
                        for (const className of videoRelatedClasses) {
                            if (node.classList && node.classList.contains(className)) {
                                shouldCheck = true;
                                break;
                            }
                        }
                    }
                }
            }
        }

        if (shouldCheck && !currentVideo) {
            // 延迟一点时间,让元素完全加载
            setTimeout(() => {
                currentVideo = findVideoElement();
                if (currentVideo) {
                    initVideoEventListeners();
                    console.log('[视频提醒] 发现新视频元素并初始化监听');
                }
            }, 1000);
        }
    });

    mutationObserver.observe(document.body, {
        childList: true,
        subtree: true
    });

    console.log('[视频提醒] DOM变化监听器已启动');
}

/**
 * 初始化视频监控
 */
function initVideoMonitor() {
    console.log('[视频提醒] 开始初始化视频监控');

    // 首先尝试直接查找视频元素
    currentVideo = findVideoElement();

    if (currentVideo) {
        initVideoEventListeners();
        console.log('[视频提醒] 已找到视频元素,开始监控');
    } else {
        console.log('[视频提醒] 未找到视频元素,启动DOM监控');
        // 如果没找到视频元素,启动DOM观察器
        initMutationObserver();

        // 每3秒尝试一次查找视频元素
        const findVideoInterval = setInterval(() => {
            currentVideo = findVideoElement();
            if (currentVideo) {
                clearInterval(findVideoInterval);
                initVideoEventListeners();
                console.log('[视频提醒] 延迟找到视频元素,开始监控');
            }
        }, 3000);
    }
}

/**
 * 页面可见性变化处理
 */
function handleVisibilityChange() {
    console.log('[视频提醒] 页面可见性变化:', document.visibilityState);
    if (!document.hidden && activeNotification) {
        console.log('[视频提醒] 页面恢复,可以关闭手动通知');
    }
}

// 请求通知权限
function requestNotificationPermission() {
    if ('Notification' in window && Notification.permission === 'default') {
        Notification.requestPermission().then(permission => {
            console.log('[视频提醒] 通知权限状态:', permission);
            initVideoMonitor();
        });
    } else {
        initVideoMonitor();
    }
}

// 页面加载完成后初始化
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', requestNotificationPermission);
} else {
    requestNotificationPermission();
}

// 监听页面可见性变化
document.addEventListener('visibilitychange', handleVisibilityChange);

// 清理函数
window.addEventListener('beforeunload', function() {
    if (currentVideo) {
        currentVideo.removeEventListener('timeupdate', checkPreEndNotification);
    }

    if (observer) {
        observer.disconnect();
    }

    if (mutationObserver) {
        mutationObserver.disconnect();
    }

    if (checkInterval) {
        clearInterval(checkInterval);
    }

    // 关闭活动的通知
    if (activeNotification && activeNotification.close) {
        try {
            activeNotification.close();
        } catch (e) {
            console.log('[视频提醒] 关闭通知时出错:', e);
        }
    }

    console.log('[视频提醒] 清理完成');
});

// 页面焦点变化时重新检查视频元素
window.addEventListener('focus', function() {
    if (!currentVideo) {
        currentVideo = findVideoElement();
        if (currentVideo) {
            initVideoEventListeners();
            console.log('[视频提醒] 焦点变化时发现视频元素');
        }
    }
});

})();

最后一步:进入到相应的页面,刷新一下,在右上角的位置应该有一个篡改猴的扩展程序的图标,点击图标后,再点击刷新,如果篡改猴图标上显示1,那么表示程序已运行成功,在视频播放结束前三分钟,和结束后,在桌面右下角弹出提示窗口。这样就可以及时掌握视频当前的播放状态了,即使最小化时也能够弹出提示。

posted @ 2026-02-05 15:37  lishilei32  阅读(14)  评论(0)    收藏  举报