// ==UserScript== // @name 国开大学视频网课,一网一平台自动化脚本 // @namespace https://github.com/ijinfeng/jp_video // @version 1.0 // @description 国开大学网课一网一平台视频自动化播放,快进,解放你的双手🔥支持最高16倍倍速播放,播完后自动播放下一集 // @author jinfeng // @match *://menhu.pt.ouchn.cn/* // @match *://lms.ouchn.cn/course/* // @license GPL License // @icon https://www.google.com/s2/favicons?sz=64&domain=baidu. // @grant GM_addStyle // @grant unsafeWindow // @downloadURL none // ==/UserScript== (function () { // 通过脚本点击开始播放 let startPlay = false; // 当前的播放对象(视频专题) let currentPlayObj = null; // 最高播放速率 const kHighestRate = 16.0; function readTitleFromItem(item) { if (!item) return null; let title = item.querySelector('.activity-title') let span = title.querySelector('span') let distitle = span.getAttribute('original-title') return distitle; } function isVideo(item) { const icon = item.getElementsByClassName('full-screen-activity-icon')[0]; const ot = icon.getAttribute('original-title'); // console.log(`- ${ot}`); return ot === '音视频教材'; } /// 寻找当前打开的专题 /// @param selected: 播放选中,否则播放最新的 function findCurrentOpenedTopic(selected) { let currentItem = null; let nextItem = null; let targetIndex = undefined; let items = document.getElementsByClassName('activity ng-scope') // 选中:activity ng-scope active // let items = document.getElementsByClassName('activity-menu-item') let videoItems = [] for (const item of items) { if (isVideo(item) == false) { continue; } videoItems.push(item) } if (videoItems.length == 0) { console.log("Can't find any video item, you should select a special topic first!"); return; } let findedTarget = false; for (let index = 0; index < videoItems.length; index++) { const item = videoItems[index]; currentItem = nextItem; nextItem = item; const isActive = item.classList.contains('active'); let distitle = readTitleFromItem(item); console.log(`+ ${distitle}|${isActive}`); if (distitle) { // find lock if (selected) { if (isActive) { findedTarget = true; targetIndex = index; break; } } else { let lock = item.querySelector('.is-locked'); if (lock) { findedTarget = true; targetIndex = index - 1; break } } } } if (findedTarget) { if (targetIndex == undefined) { targetIndex = videoItems.length - 1; } currentItem = videoItems[targetIndex]; if (targetIndex < videoItems.length - 1) { nextItem = videoItems[targetIndex+1]; } console.log(`💪 Successfully find the video at index: ${targetIndex} that you want to play!`); console.log(`Current title of video is ${readTitleFromItem(currentItem)}`); } else { console.log('Failed find the video in curren topic scope.'); return null; } if (targetIndex >= videoItems.length) { console.log('There are no more videos to play!'); } else { console.log(`The next video is ${readTitleFromItem(nextItem)}`); } return { current: currentItem, items: videoItems, index: targetIndex }; } function logCurrentPlayObj() { if (!currentPlayObj) { console.log('👀 Current video topic is empty.'); return; } console.log(`🖨️ Video items count: ${currentPlayObj.items.length}, index: ${currentPlayObj.index}, curTtitle: ${readTitleFromItem(currentPlayObj.current)}`); } function selectedTargetItemAt(index) { if (!index || !currentPlayObj) { console.log('Target item is empty!'); return; } if (index > currentPlayObj.items.length - 1) { console.log(`The target index: ${index} out of bounds: {0, ${currentPlayObj.items.length}}`); return; } currentPlayObj.index = index; const item = currentPlayObj.items[index]; currentPlayObj.current = item; let clickEvent = new Event('click'); item.querySelector('.activity-menu-item').dispatchEvent(clickEvent); console.log(`Auto select video item named: ${readTitleFromItem(item)}`); } function playEnded() { console.log(`play end - [${currentPlayObj.index}, ${currentPlayObj.items.length}]`); if (isLastOne(currentPlayObj)) { console.log('There are no more videos in playlist.'); alert('There are no more videos in playlist.'); startPlay = false; return; } // hover let nextItem = getNextPlayItem(currentPlayObj); console.log(`Ready to find next video named: ${readTitleFromItem(nextItem)}`); // 先检查下是否存在锁 const isExsitLock = exsitLock(nextItem); if (isExsitLock) { console.log('Find a lock in this video item, try remove it.'); tryRemoveLock(nextItem); console.log('🚌 Wait 1 second to continue...'); let t = setTimeout(() => { console.log('OK, start the next step!'); selectedTargetItemAt(currentPlayObj.index + 1); startPlayVideo(); logCurrentPlayObj(); clearTimeout(t); }, 1000); } else { selectedTargetItemAt(currentPlayObj.index + 1); startPlayVideo(); logCurrentPlayObj(); } } function exsitLock(item) { let lock = item.querySelector('.is-locked'); return lock != undefined || lock != null; } function startPlayVideo() { if (!currentPlayObj) return; isElementLoaded('.mvp-toggle-play').then(() => { let playBtn = document.querySelector(".mvp-toggle-play"); let clickEvent = new Event('click'); playBtn.dispatchEvent(clickEvent); isElementLoaded('video').then(() => { let video = document.querySelector('video'); video.currentTime = 0; video.playbackRate = currentPlayObj.playrate; video.play(); video.removeEventListener('ended', playEnded); video.addEventListener('ended', playEnded); }); }); } // 继续播放,这种情况一般是失去焦点导致的 function resumePlayer() { console.log('resume player'); isElementLoaded('.mvp-toggle-play').then(() => { isElementLoaded('video').then(() => { let video = document.querySelector('video'); console.log(`Find video player is paused: ${video.paused}, rate: ${video.playbackRate}`); if (!video.paused) { return; } let playBtn = document.querySelector(".mvp-toggle-play"); let clickEvent = new Event('click'); playBtn.dispatchEvent(clickEvent); if (!video.paused) { return; } video.play(); }); }); } // Element loaded const isElementLoaded = async selector => { while (document.querySelector(selector) === null) { await new Promise(resolve => requestAnimationFrame(resolve)) } return document.querySelector(selector); }; function isLastOne(playObj) { return playObj.index >= playObj.items.length - 1; } function getNextPlayItem(playObj) { if (!playObj) return null; if (isLastOne(playObj)) return null; return playObj.items[playObj.index + 1]; } function setNextPlayItemReady(playObj) { if (!playObj) return null; if (isLastOne(playObj)) return null; playObj.index++; playObj.current = playObj.items[playObj.index]; } function tryRemoveLock(item) { let locks = item.querySelectorAll('.right'); for (const lock of locks) { if (lock) { lock.innerHTML = ""; } } } /// 用户交互界面 function createUserInterface() { let box = document.createElement('div'); box.className = 'jp-box'; box.innerHTML = `
国开网课脚本
` document.body.appendChild(box); setUserCss() const playBtn1 = document.querySelector('#jp-play1'); playBtn1.onclick = userClickStartPlayByNew; const playBtn2 = document.querySelector('#jp-play2'); playBtn2.onclick = userClickStartPlayBySelected; const playStopBtn = document.querySelector('#jp-play-stop'); playStopBtn.onclick = userClickStopRunScript; } function setUserCss() { let css = ` .jp-box { position: fixed; right: 30px; bottom: 100px; width: 140px; height: 160px; display: flex; flex-direction: column; align-items: center !important; justify-content: center !important; background-color: #f4f5f6; border-radius: 8px; font-size: 12px !important; text-align: center !important; border: 1px solid #999; overflow: hidden; z-index: 10; } .jp-title { color: #333333; line-height: 30px !important; background-color: antiquewhite; width: 100%; margin: 0; text-align: center; } .jp-rate-label { margin-top: 6px; margin-bottom: 6px; } .jp-rate-input { width: 40px !important; height: 25px !important; display: inline-block !important; background-color: rgb(185, 226, 226) !important; } .jp-play-btn { font-size: 12px !important; height: 20px; line-height: 20px; padding: 0 16px; margin-bottom: 5px; } ` GM_addStyle(css) } function userClickStartPlayByNew() { const state = userReadyClickPlayVideo(); isElementLoaded('video').then(() => { console.log('Video is loaded!'); let playObj = findCurrentOpenedTopic(); if (!playObj) { userClickStopRunScript(); return; } currentPlayObj = playObj; playObj.playrate = state.rate; selectedTargetItemAt(playObj.index); startPlayVideo(); }) } function userClickStartPlayBySelected() { const state = userReadyClickPlayVideo(); isElementLoaded('video').then(() => { console.log('Video is loaded!'); let playObj = findCurrentOpenedTopic(true); if (!playObj) { userClickStopRunScript(); return; } currentPlayObj = playObj; playObj.playrate = state.rate; selectedTargetItemAt(playObj.index); startPlayVideo(); }) } function userReadyClickPlayVideo() { startPlay = true; let rate = document.querySelector('.jp-rate-input').value; if (rate > kHighestRate) { rate = kHighestRate; console.log(`The highest playback rate is ${kHighestRate}`); } console.log(`On click play video with playrate: ${rate}`); listenBlur(); return { rate: rate }; } function userClickStopRunScript() { console.log('Stop run jp_script'); startPlay = false; isElementLoaded('video').then(() => { let video = document.querySelector('video'); video.removeEventListener('ended', playEnded); }); } function listenBlur() { console.log('Start listen focus/blur events'); unsafeWindow.onblur = function () { if (startPlay) { resumePlayer(); } } } console.log('Welcome to use ef_video script!'); // 创建用户界面 createUserInterface(); })();