Greasy Fork is available in English.
国开大学网课一网一平台视频自动化播放,快进,解放你的双手🔥支持最高16倍倍速播放,播完后自动播放下一集
// ==UserScript==
// @name 国开大学视频网课,一网一平台自动化脚本
// @namespace https://github.com/ijinfeng/jp_video
// @version 1.1
// @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
// ==/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 = `
<p class="jp-title">国开网课脚本</p>
<label class="jp-rate-label">
播放速率:
<input type="text" value="16" class="jp-rate-input">
</label>
<button class="jp-play-btn" id="jp-play1">从最新项开始播放</button>
<button class="jp-play-btn" id="jp-play2">从选中项开始播放</button>
<button class="jp-play-btn" id="jp-play-stop">停止运行脚本</button>
`
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();
})();