Greasy Fork

Greasy Fork is available in English.

国开大学视频网课,一网一平台自动化脚本

国开大学网课一网一平台视频自动化播放,快进,解放你的双手🔥支持最高16倍倍速播放,播完后自动播放下一集

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
})();