运用篡改猴执行脚本,监控网页中视频播放状态
在某些场景下,不得不将视频播放完,然后继续播放下一个视频,但是又不想一直在视频播放的页面,需要将视频播放页面最小化,干其他的事情,如果不能够及时掌握页面中视频播放状态,很容易在视频播放结束后很长时间才会想起来,那么这样就会浪费一些时间。这里分享一个扩展工具,篡改猴(Tampermonkey)这是一款浏览器扩展程序,可以运行自己编写的脚本。
第一步:首先在浏览器中,找到这个管理扩展,然后进入。

第二步:进入之后,点击获取扩展

第三步:在搜索框中搜索篡改猴,选择第一个,然后进行安装

第四步:安装完成后,回到第二步的页面,将“开发人员模式”打开
第五步:点击下图框选的选项,进入脚本添加的页面

第六步:填写运行脚本,点击这个+号,然后在脚本页面中粘贴需要运行的脚本

第七步:将下面的脚本粘贴后,然后保存。
// 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,那么表示程序已运行成功,在视频播放结束前三分钟,和结束后,在桌面右下角弹出提示窗口。这样就可以及时掌握视频当前的播放状态了,即使最小化时也能够弹出提示。

浙公网安备 33010602011771号