// ==UserScript== // @name 企学宝 · 防切屏暂停 + 自动起播 + 自动下一节 // @namespace local.qixuebao // @version 1.1.0 // @description 课程播放页保持播放、自动起播、播完自动点「下一节」 // @match https://*.lmschina.net/static/admin/* // @run-at document-start // @grant none // ==/UserScript== (function () { 'use strict'; /* ================== 配置 ================== */ var CFG = { keepPlaying: true, autoNext: true, autoPlay: true, nextDelay: 2500, debug: true }; var log = CFG.debug ? function () { console.log.apply(console, ['[qxb]'].concat([].slice.call(arguments))); } : function () {}; function onPlayPage() { return location.hash.indexOf('#/course/play/') === 0; } function inFrame() { try { return window.top !== window; } catch (e) { return true; } } function guardOn() { return onPlayPage() || inFrame(); } /* ========== 1. 防切屏暂停 ========== */ if (CFG.keepPlaying) { var hiddenDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'hidden'); var visDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'visibilityState'); var nativeHasFocus = Document.prototype.hasFocus; var fakeHasFocus = function () { return onPlayPage() ? true : nativeHasFocus.call(document); }; try { Document.prototype.hasFocus = fakeHasFocus; document.hasFocus = fakeHasFocus; } catch (e) { log('hasFocus 伪装失败', e); } try { Object.defineProperty(document, 'hidden', { configurable: true, get: function () { return onPlayPage() ? false : hiddenDesc.get.call(document); } }); Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { return onPlayPage() ? 'visible' : visDesc.get.call(document); } }); } catch (e) { log('visibility 伪装失败', e); } var swallow = function (e) { if (onPlayPage()) e.stopImmediatePropagation(); }; document.addEventListener('visibilitychange', swallow, true); document.addEventListener('webkitvisibilitychange', swallow, true); window.addEventListener('blur', swallow, true); log('keepPlaying 已启用'); } /* ========== 2. 播完自动下一节 ========== */ if (CFG.autoNext) { var scheduled = false; function overlayOpen() { var boxes = document.querySelectorAll('.el-message-box__wrapper, .el-dialog__wrapper'); for (var i = 0; i < boxes.length; i++) { if (boxes[i].offsetParent !== null) return true; } return false; } function nextButton() { var end = document.querySelector('.end-page'); if (!end || !end.querySelector('p')) return null; var btns = end.querySelectorAll('button.el-button--primary'); for (var i = 0; i < btns.length; i++) { if (!btns[i].closest('a') && !btns[i].disabled) return btns[i]; } return null; } function tick() { if (!onPlayPage()) { scheduled = false; return; } if (!document.querySelector('.end-page')) { scheduled = false; return; } if (scheduled) return; scheduled = true; setTimeout(function () { scheduled = false; if (overlayOpen()) { log('有弹窗,暂不切下一节'); return; } var b = nextButton(); if (b) { log('点击「下一节」'); b.click(); } }, CFG.nextDelay); } setInterval(tick, 800); log('autoNext 已启用'); } /* ========== 3. 自动起播 ========== */ if (CFG.autoPlay) { var lastTry = new WeakMap(); function tryPlay(v) { var wasMuted = v.muted; if (!wasMuted) v.muted = true; var p; try { p = v.play(); } catch (e) { log('play() 抛错', e); return; } if (!p || !p.then) return; p.then(function () { log('自动起播成功'); if (!wasMuted) { setTimeout(function () { v.muted = false; if (v.paused) v.play().catch(function () { v.muted = true; v.play().catch(function () {}); }); }, 300); } }).catch(function (e) { log('自动起播被拒绝:', e && e.name); }); } setInterval(function () { if (!guardOn()) return; var vids = document.querySelectorAll('video'); for (var i = 0; i < vids.length; i++) { var v = vids[i]; if (!v.paused || v.ended) continue; if (v.played && v.played.length > 0) continue; if (v.readyState < 2) continue; var now = Date.now(); if (now - (lastTry.get(v) || 0) < 3000) continue; lastTry.set(v, now); tryPlay(v); } }, 1200); log('autoPlay 已启用'); } })();