// ==UserScript== // @name 泰州师说 · 视频自动续播(防暂停) // @namespace local.tzss.autoplay // @version 1.0 // @description 视频被暂停后自动续播;切到后台标签页不暂停;自动关闭"请确认仍在学习"类弹窗。视频真实播放,时长真实累计。 // @author local // @match http://jspx.tze.cn/* // @match https://jspx.tze.cn/* // @match http://www.tze.cn/* // @match https://www.tze.cn/* // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; // ============================================================ // 1. 让页面"以为"自己始终可见 // 多数培训平台的播放器会监听 visibilitychange / 读取 document.hidden, // 发现你切了标签页就暂停视频。这里把这几个属性伪装成"永远可见"。 // 注意:@noframes 已去掉,脚本会在每个 iframe 内各自生效。 // ============================================================ try { Object.defineProperty(document, "hidden", { get: function () { return false; }, configurable: true, }); Object.defineProperty(document, "visibilityState", { get: function () { return "visible"; }, configurable: true, }); Object.defineProperty(document, "webkitHidden", { get: function () { return false; }, configurable: true, }); Object.defineProperty(document, "webkitVisibilityState", { get: function () { return "visible"; }, configurable: true, }); } catch (e) { console.log("[自动续播] visibility 伪装失败(可能已被页面占用):", e); } // ============================================================ // 2. 暂停自动续播(带防"暂停-播放"死循环保护) // 如果平台反复强行暂停(可能是反挂机检测),短时间内续播次数 // 过多就自动收手,避免被平台判定为异常学习行为。 // ============================================================ var RESUME_DELAY_MS = 300; // 暂停后多少毫秒内续播 var MAX_RESUMES = 10; // 30 秒窗口内最多自动续播次数 var resumeTimes = []; // 最近续播的时间戳 function resumeAllowed() { var now = Date.now(); resumeTimes = resumeTimes.filter(function (t) { return now - t < 30000; }); return resumeTimes.length < MAX_RESUMES; } function bindVideo(v) { if (v.__autoPlayBound) return; v.__autoPlayBound = true; console.log("[自动续播] 已绑定 video 元素", v); v.addEventListener("pause", function () { if (v.ended) return; // 正常播完不处理 if (!resumeAllowed()) { console.log("[自动续播] 短时间内暂停过于频繁,已停止续播(疑似反挂机检测)"); return; } resumeTimes.push(Date.now()); setTimeout(function () { if (!v.ended && v.paused) { var p = v.play(); if (p && p.catch) p.catch(function () {}); } }, RESUME_DELAY_MS); }); // 页面加载完尝试直接开始播放 var p = v.play(); if (p && p.catch) p.catch(function () {}); } function scanVideos() { var list = document.querySelectorAll("video"); for (var i = 0; i < list.length; i++) bindVideo(list[i]); } // ============================================================ // 3. 自动关闭"请确认仍在学习"类弹窗 // 只点击【可见弹层内部】且文案匹配白名单的按钮, // 避免误点页面上的普通按钮(如测验的提交)。 // ============================================================ var DIALOG_SELECTORS = [ ".el-dialog", // Element UI ".ant-modal", // Ant Design ".modal", ".modal-dialog", // Bootstrap / 通用 "[class*='dialog']", "[class*='Dialog']", "[class*='popup']", "[class*='Popup']", "[class*='confirm']", "[class*='Confirm']", ".layui-layer" // Layui ]; var BTN_TEXT_WHITELIST = [ "继续学习", "继续播放", "继续", "我在", "仍在", "确定", "好的", "我知道了", "知道了", "是" ]; function isElementVisible(el) { if (!el) return false; var rect = el.getBoundingClientRect(); if (rect.width <= 0 || rect.height <= 0) return false; var style = window.getComputedStyle(el); return style.display !== "none" && style.visibility !== "hidden" && parseFloat(style.opacity || "1") > 0.05; } function dismissDialogs() { for (var d = 0; d < DIALOG_SELECTORS.length; d++) { var dialogs = document.querySelectorAll(DIALOG_SELECTORS[d]); for (var i = 0; i < dialogs.length; i++) { var dlg = dialogs[i]; if (!isElementVisible(dlg)) continue; var btns = dlg.querySelectorAll("button, .el-button, .ant-btn, .layui-layer-btn a, a.btn"); for (var j = 0; j < btns.length; j++) { var btn = btns[j]; if (!isElementVisible(btn)) continue; var text = (btn.textContent || "").trim(); var hit = false; for (var k = 0; k < BTN_TEXT_WHITELIST.length; k++) { if (text && text.indexOf(BTN_TEXT_WHITELIST[k]) >= 0 && text.length <= 8) { hit = true; break; } } if (hit) { console.log("[自动续播] 自动点击弹窗按钮:", text); btn.click(); return; // 每轮只点一个,避免连锁误操作 } } } } } // ============================================================ // 4. 启动:扫描视频 + 定时处理弹窗 // ============================================================ scanVideos(); setInterval(scanVideos, 2000); // 新出现的 video(换课时)自动接管 setInterval(dismissDialogs, 2500); // 定时关闭确认弹窗 // 动态插入的 video(SPA 页面切换章节)也兜底接管 try { var mo = new MutationObserver(function () { scanVideos(); }); mo.observe(document.documentElement, { childList: true, subtree: true }); } catch (e) {} console.log("[自动续播] 脚本已启动:视频暂停会自动续播,切后台不暂停"); })();