// ==UserScript== // @name 云班课高效助手 // @name:zh-CN 云班课高效助手 // @author bellamy.n.h // @namespace http://tampermonkey.net/ // @version 1.88 // @description 【高效再升级😃!高效使用云班课,一个脚本就够了!😎】 【🧡视频倍速:新增视频倍速控件(支持 倍速递加、递减;倍速重置;一键最佳倍速;视频快进、快退)】、【💛视频连播:新版视频连播功能,支持从当前视频开始连播(配合视频控件,体验更佳)】、【💙快捷键:新增快捷键系统,常用功能都已加入,高效更进一步】、【💚资源处理:批量点击、下载、批处理】 // @match https://www.mosoteach.cn/web/index.php* // @include *://www.mosoteach.cn/web/index.php* // @note Version 1.88 修复视频播放界面点击连播相关按钮无效;修复对全局键盘的频繁操作误判,改为只判断快捷键的频繁操作;修改视频快进、视频快退的速度为一次前进或后退5s; // @note Version 1.85 —— 1.87 修复连播视频时数量错误BUG;重构快捷键视图生成代码,降冗余;Add Statistical Analysis System;限制对快捷键的频繁操作;特殊处理部分高频使用的快捷键;Fix Some Bugs。 // @note Version 1.80 😁【新增视频倍速控件(支持 倍速递加、递减;倍速重置;一键最佳倍速;视频快进、快退)】、【新版视频连播功能,支持从当前视频开始连播(配合视频控件,可达到极度自由)】、【新增快捷键系统,常用功能已都加入,高效更进一步】、【修复模拟点击/下载失效Bug】、【限制全部连播最大速度为8倍】 // @note Version 1.70 视频最高16倍速连播;调用系统通知,反馈更佳; // @note Version 1.65 偷偷改了些小Bug 🤭,使连播更顺畅。下个版本上16倍速连播喽😊 // @note Version 1.60 新增测试功能,支持 连续播放所有视频、 立即看完当前视频(测试阶段,还请反馈) // @note Version 1.50 加强对输入值约束; 支持多栏处理; chrome浏览器自动打开 设置页面地址更改; 其他Bug修复。 // @note Version 1.40 优化代码; 新增浏览器类型判断,支持chrome浏览器自动打开 设置页面。 // @note Version 1.32 优化操作反馈 (可以重置已选择的资源栏数) // @note Version 1.31 修复可能存在的Bug (页面无法自动关闭) // @icon https://s1.ax1x.com/2020/05/18/Yf6Kcd.png // @require https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/layer/2.3/layer.js // @require https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js // @require https://cdn.jsdelivr.net/npm/qs@6.9.4/dist/qs.min.js // @grant GM_openInTab // @grant GM_notification // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_listValues // @downloadURL https://update.greasyfork.icu/scripts/390978/%E4%BA%91%E7%8F%AD%E8%AF%BE%E9%AB%98%E6%95%88%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/390978/%E4%BA%91%E7%8F%AD%E8%AF%BE%E9%AB%98%E6%95%88%E5%8A%A9%E6%89%8B.meta.js // ==/UserScript== $(function () { 'use strict'; var config = { isCRX: false, notificationTitle: "云班课高效助手", icon128: "https://s1.ax1x.com/2020/05/18/Yf6pp4.png", icon48: "https://s1.ax1x.com/2020/05/18/Yf6Kcd.png", icon32: "https://s1.ax1x.com/2020/05/18/Yf6BBq.png", icon16: 'https://s1.ax1x.com/2020/05/18/Yfg71e.png', layer_css: "https://cdn.jsdelivr.net/npm/layui-layer@1.0.9/layer.min.css", layer_js: "https://cdnjs.cloudflare.com/ajax/libs/layer/2.3/layer.js", jquery_js: "https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js", layui_js: "https://cdn.jsdelivr.net/npm/layui-src@2.5.5/dist/layui.min.js", fontawesome_css: "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/css/all.min.css", base: "https://mst.bellamy.top:8443", }; var openInTab; var setVal; var getVal; var notification; var delVal; var listVals; var qs = Qs; if (config.isCRX) { console.log("in CRX"); /** * *********** * For CRX Begin * *********** * * *********** * Override the following apis provided by TamperMonkey * these apis can only work well in TamperMonkey Script * but they can not work in CRX */ /** * [GM_openInTab : send message to bg.js to create New Tab according to these following parameters] * @param {[String]} forWhat [onDownload or offDownload] * @param {[String]} _url [new tab] * @param {[Boolean]} _option [is or not active] */ function GM_openInTab(_url, _option, forWhat) { chrome.runtime.sendMessage({ createTab: forWhat, url: _url, option: "active" === _option, }); } function GM_setValue(name, value) { } function GM_getValue(name, defaultValue) { } /** * send message to chrome API * chrome.notifications.create(string notificationId, NotificationOptions options, function callback) * @param {[type]} notificationDetails [description] * @param {Function} callback [description] */ function GM_notification(notificationDetails, callback) { chrome.runtime.sendMessage({ notifDetails: { details: notificationDetails, callbackFunc: callback } }); } function GM_deleteValue(name) { } function GM_listValues() { } /** * *********** * For CRX End * *********** */ openInTab = GM_openInTab; //GM_openInTab(url, option); setVal = GM_setValue; // GM_setValue(name, value) getVal = GM_getValue; // GM_getValue(name, defaultValue) notification = GM_notification; // GM_notification(text, title, image, onclick) delVal = GM_deleteValue; // GM_deleteValue(name) listVals = GM_listValues; // GM_listValues() // inject layer.css $("") .attr({ rel: "stylesheet", type: "text/css", href: config.fontawesome_css }) .appendTo("head"); } else { console.log("in Script "); openInTab = GM_openInTab; //GM_openInTab(url, option); setVal = GM_setValue; // GM_setValue(name, value) getVal = GM_getValue; // GM_getValue(name, defaultValue) notification = GM_notification; // GM_notification(text, title, image, onclick) delVal = GM_deleteValue; // GM_deleteValue(name) listVals = GM_listValues; // GM_listValues() // inject layer.css $("") .attr({ rel: "stylesheet", type: "text/css", href: config.layer_css }) .appendTo("head"); // inject layer.css $("") .attr({ rel: "stylesheet", type: "text/css", href: config.fontawesome_css }) .appendTo("head"); } /** * For notification function * * text - the text of the notification (required unless highlight is set) * title - the notificaton title * image - the image * highlight - a boolean flag whether to highlight the tab that sends the notfication (required unless text is set) * silent - a boolean flag whether to not play a sound * timeout - the time after that the notification will be hidden (0 = disabled) * ondone - called when the notification is closed (no matter if this was triggered by a timeout or a click) or the tab was highlighted * onclick - called in case the user clicks the notification */ function getNotificationDetails(_text, _timeout, _title, _image, _highlight, _silent, _ondone, _onclick) { let details = { text: _text === undefined ? '' : _text, title: _title === undefined || _title === null ? config.notificationTitle : _title, image: _image === undefined || _image === null ? config.icon48 : _image, highlight: _highlight === undefined || _highlight === null ? true : _highlight, silent: _silent === undefined || _silent === null ? false : _silent, timeout: _timeout === undefined || _timeout === null ? 6000 : _timeout, ondone: _ondone === undefined || _ondone === null ? null : _ondone, onclick: _onclick === undefined || _onclick === null ? null : _onclick }; return details; }; /** * Determine the browser type */ function browserType() { var userAgent = navigator.userAgent; //get browser userAgent string var isOpera = userAgent.indexOf("Opera") > -1; if (isOpera) { return "Opera" }; //is Opera or not if (userAgent.indexOf("Firefox") > -1) { return "FF"; } //is Firefox or not if (userAgent.indexOf("Chrome") > -1) { return "Chrome"; } if (userAgent.indexOf("Safari") > -1) { return "Safari"; } //is Safari or not if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera) { return "IE"; }; //is IE or not } /** * sleep function * @param numberMillis -- 要睡眠的毫秒数 */ function sleep(numberMillis) { var now = new Date(); var exitTime = now.getTime() + numberMillis; while (true) { now = new Date(); if (now.getTime() > exitTime) return; } } /** * Remove duplicate value in array */ function removeDuplicate(arr) { let x = new Set(arr); return [...x]; } /** * download resources function */ function download(name, href) { var a = document.createElement("a"), //创建a标签 e = document.createEvent("MouseEvents"); //创建鼠标事件对象 e.initEvent("click", false, false); //初始化事件对象 a.href = href; //设置下载地址 a.download = name; //设置下载文件名 a.dispatchEvent(e); //给指定的元素,执行事件click事件 } /** * Refresh page tips */ function refreshPage() { alert("操作完成,请小可爱刷新页面查看结果!!!"); } /** * cancel action */ function cancel() { alert("已取消操作!"); } /** * 点击和下载前以弹窗二次确认 * modeName * return boolean **/ function popupComfirm(modeName) { let conf_str = false; conf_str = confirm("小可爱,你即将执行“" + modeName + "”操作!!!" + "\n\n" + "根据选择资源数量的不同,会打开相应数量的页面,如果数量较多,请不要惊慌,因为这些页面会自动关闭的哦!!!" + "\n\n" + "你是否按照上一个提示,进行了相应的操作?" + "\n\n" + "如果是,你是否要开始执行本次操作?"); return conf_str; } /** * cleaning data 数据清洗 inputString -> idsArr */ function cleanData(inputString) { //去除字符串中的所有空格 inputString.replace(/\s*/g, ""); //去掉首尾的 - 字符 if (inputString.charAt(0) == "-") { if (inputString.charAt(inputString.length - 1) == "-") inputString = inputString.substring(1, inputString.length - 1); else inputString = inputString.substring(1, inputString.length); } else if (inputString.charAt(inputString.length - 1) == "-") { inputString = inputString.substring(0, inputString.length - 1); } //console.log(inputString + "/" + inputString.charAt(0) +"/" + inputString.charAt(inputString.length-1)); //分割出要点击的栏号,存入数组,用于映射出对应的资源栏id let idsArr = inputString.split("-"); //去重并排序 idsArr = removeDuplicate(idsArr).sort(); //去除超出资源栏总数的无效值 let temp = []; for (let i = 0; i < idsArr.length; i++) { // console.log("srcBarSum is" + srcBarSum); if (idsArr[i] <= srcBarSum && idsArr[i] > 0) { temp.push(idsArr[i]); // console.log("temp is" + temp); } } // console.log("idsArr is" + idsArr); return idsArr = temp; } /** * 根据模式名执行对应的批量处理操作 * * 点击确认按钮弹出确认弹窗, * 如果确认执行,则执行点击操作, * 否则执行 取消操作 */ function batchForMoreSrcBars(modeName, ids) { if (ids.length == 0) ids.push(".res-row-box"); let isDownloadMode = modeName == "模拟点击" ? "false" : (modeName == "批量下载" ? "true" : "其他"); if (popupComfirm(modeName)) { try { // console.log(chosenIDs); let startIndex = $("#head").val(); let endIndex = $("#tail").val(); for (let id of ids) { // console.log(thisID); try { batch(isDownloadMode, id, startIndex, endIndex); } catch (e) { console.log(id + "该栏执行异常,跳过执行"); continue; } } } finally { //点击完成,提示刷新页面 setTimeout(refreshPage, 0); //置空栏号输入框 $(".indexNum").val(""); } } else { cancel(); } } /** * Click or download in bulk according to * isDownload : true -> Download Mode ; false -> Click Mode * thisBarID : 此次要执行的资源栏 id * startIndex : 此次资源栏中执行的开始资源编号 * endIndex : 此次资源栏中执行的结束资源编号 * */ function batch(isDownload, thisBarID, startIndex, endIndex) { //let isDownloadMesg = isDownload == "false" ? "模拟点击" : "批量下载"; // 以下五个等价,实现相同功能,但写法是逐步优化 // var list = document.getElementsByClassName("res-row-open-enable"); // var list = $(".res-row-open-enable"); // var list = $(".hide-div").children(); // var list = $(".res-row-box").children(".hide-div").children(); let list = $(thisBarID).children(".hide-div").children(); let succNum = 0; let failNum = 0; let tempUrl; let win; let actualStartIndex = startIndex <= list.length && startIndex > 0 ? startIndex : (startIndex <= 0 ? 1 : list.length); //小于0则为 1 ; 大于 最大值 则为 最大值 let actualEndIndex = endIndex <= list.length && endIndex > 0 ? endIndex : (endIndex <= 0 ? 1 : list.length); //输入值超出资源总数的值,则将输入值置为总数的值 if (actualStartIndex > actualEndIndex) { //console.log("here"); alert("小可爱😀,你的起始结束值写反了哟!"); return; } // console.log("actualStartIndex: " + actualStartIndex); // console.log("actualEndIndex: " + actualEndIndex); // list 存在并不为空 if (null == list || list.length == 0) { console.log(thisBarID + "对应的资源栏为空"); } else { for (let i = actualStartIndex - 1; i < actualEndIndex; i++) { // console.log(i); // console.log(list); // console.log(list[i]); try { tempUrl = list[i].getAttribute("data-href"); if (null == tempUrl || tempUrl == "") { console.log("资源栏:" + thisBarId + "的第 " + (i + 1) + " 条资源未获取到URL"); } else { win = window.open(tempUrl); if (isDownload == 'false') { sleep(100); //睡眠,是为了确保每个资源都被正常获取 win.close(); } succNum++; // console.log(tempUrl); } } catch (e) { console.log(e.name + ": " + e.message); console.log("资源栏:" + thisBarId + "的第 " + (i + 1) + " 条未成功执行 ;URL : " + list[i].getAttribute("data-href")); failNum++; continue; } } } console.log("共检索到 " + list.length + "条; 成功执行 " + succNum + " 次! 失败 " + failNum + " 次! 操作范围:从第 " + actualStartIndex + " 条 至 第 " + actualEndIndex + " 条。"); } /** * click all resources in two ways according to 'isPositive' */ function clickAll(isPositive) { let isPositiveMesg = isPositive == "true" ? "正序点击" : "倒序点击"; let conf_str = false; conf_str = confirm("小可爱,你即将执行“" + isPositiveMesg + "全部资源”操作,如果资源量较大(> 1000),耗时就会较久,打开的页面也会较多哦!不过都会自动关闭的哦!!!" + "\n\n" + "小可爱,资源较多时,还请三思啊!!!" + "\n\n" + "你是否要执行?"); if (conf_str) { let list = document.getElementsByClassName("res-row-open-enable"); let succNum = 0; let failNum = 0; let tempUrl; let win; if (isPositive == "true") { for (let i = 0; i < list.length; i++) { try { tempUrl = list[i].getAttribute("data-href"); win = window.open(tempUrl); sleep(100); //睡眠,是为了确保每个资源都被正常获取 win.close(); succNum++; // console.log(tempUrl); } catch (e) { console.log(e.name + ": " + e.message); console.log("该条未成功执行 ;URL : " + list[i].getAttribute("data-href")); failNum++; continue; } } } else { for (let i = list.length - 1; i >= 0; i--) { try { tempUrl = list[i].getAttribute("data-href"); win = window.open(tempUrl); sleep(100); //睡眠,是为了确保每个资源都被正常获取 win.close(); succNum++; // console.log(tempUrl); } catch (e) { console.log(e.name + ": " + e.message); console.log("该条未成功执行 ;URL : " + list[i].getAttribute("data-href")); failNum++; continue; } } } console.log(isPositiveMesg + ": 共检索到 " + list.length + "条; 成功执行 " + succNum + " 次! 失败 " + failNum + " 次!"); setTimeout(refreshPage, 0); } else { alert("已取消操作!"); } } /** * open a new tab according the url and execute callback function */ function newTabAlert(forWhat, url, option, callback) { if (config.isCRX) openInTab(url, option, forWhat); else openInTab(url, option); if (typeof callback === "function") { callback(); } } /****************************************** * play videos * */ let playVideoConfig = { isContinuous: false, isPlayAll: false, isPlayPart: false, videoDuration: '', }; /********************************************** * Play all videos continuously */ let arr = []; let count = 0; let interval; let timeout; let intervalTime = 4000; //millisecond let isContinuousPaly = false; let rate = 1; // <=10 let weight = 1000 / rate; let currentVideoIndex = 0; let nextVideoIndex = 0; //当前第几个视频 let bufferTime = 10000; // millisecond let maxRate = 8; let log = ''; //将所有视频资源存入数组,以作点击使用 let a = $("div[data-mime='video']"); let tempArr = Object.keys(a); let tpArr = tempArr.slice(0, tempArr.length - 2); tpArr.forEach((key) => { //console.log(key, a[key]); arr.push(a[key]); }); //console.log(arr.length); // for (let a in arr) { // console.log(a); // } // playVideoConfig.videoDuration = $('.video-duration'); function onContinuousPlayFunc() { if (playVideoConfig.isContinuous) { layer.msg('【无效操作】 : 连播功能已开启'); return; } playVideoConfig.isContinuous = true; if (typeof ($("#continuousPlay").attr("class")) != "undefined") { let text = "连续播放已开启,无需重复开启"; //alert(text); notification(getNotificationDetails(text), null); //layer.msg("test"); return; } alert("请先关闭 【 Win10 专注助手 】 再使用,否则无法正常提示信息 \n\n 提示:在通知托盘中关闭"); // $('
\ // \ //
\ //
\ // \ //
\ //
\ // \ //
\ //
\ // \ //
\ // ').insertAfter(".mejs__fullscreen-button"); $('
\ \ \ \ \
').insertBefore("#preview-video"); //For all $("#continuousPlayAll").click(() => { startContinuousPlayAll(); }); $("#stopContinuousPlayAll").click(() => { stopContinuousPlayAll(); }); //For part $("#continuousPlayPart").click(() => { startContinuousPlayForPart(); }); $("#stopContinuousPlayPart").click(() => { stopContinuousPlayForPart(); }); let txt = "连续播放已开启,请到视频播放页面使用"; notification(getNotificationDetails(txt), null); } /** * close continuous play * */ function offContinuousPlayFunc() { if (!playVideoConfig.isContinuous) { layer.msg('【无效操作】 : 连播功能已关闭'); return; } $('#continuousplayAll, #stopContinusPlayAll, #continuousPlayPart, #stopContinusPlayPart').unbind(); $('#helper-btn').remove(); if (playVideoConfig.isPlayAll) { stopContinuousPlayAll(); } if (playVideoConfig.isPlayPart) { stopContinuousPlayForPart(); } playVideoConfig.isContinuous = false; layer.msg('连播功能已关闭!'); } /** * For all * steps that must be taken when start playing continuously */ function startContinuousPlayAll() { if (playVideoConfig.isPlayPart) { layer.msg('【无效操作】 : 正常连播进行中...'); return; } if (isContinuousPaly) { layer.msg("【无效操作】:全部连播进行中..."); return; } if (playBySpecifiedRateAndNotify()) { $('.video-duration').remove(); playVideoConfig.isPlayAll = true; isContinuousPaly = true; layer.msg('禁用进度条', function () { clickDiv(); }); } else { notification(getNotificationDetails("已取消本次操作!"), null); } } /** * For all * steps that must be taken when stopping playing continuously */ function stopContinuousPlayAll() { if (playVideoConfig.isPlayPart) { stopContinuousPlayForPart(); playVideoConfig.isPlayPart = false; } if (!isContinuousPaly) { layer.msg("【无效操作】:全部连播未执行"); return; } // console.log("llllllllll:"+$('.video-duration') ); if ($('.video-duration').length == 0) { $(playVideoConfig.videoDuration).insertAfter('#mep_0'); } isContinuousPaly = false; playVideoConfig.isPlayAll = false; //停掉当前还未执行完的 interval timeout clearInterval(interval); clearTimeout(timeout); layer.msg('全部连播已关闭'); let stopContinusPlayText = "已退出连续播放模式,但保留了关闭视频即可看完功能;\n下一次连续播放从第 " + (nextVideoIndex + 1) + " 个视频开始。"; //alert(stopContinusPlayText); notification(getNotificationDetails(stopContinusPlayText), null); } /** * For part * start playing continuously part of all the specified videos */ function startContinuousPlayForPart() { if (playVideoConfig.isPlayAll) { layer.msg('【无效操作】 : 全部连播进行中...'); return; } if (videoConfig.isContinuousPaly) { layer.msg("【无效操作】:正常连播进行中..."); return; } playVideoConfig.isPlayPart = true; videoConfig.isContinuousPaly = true; play(videoConfig.currentVideoDivs); } //部分连播 //开始时不需要设置播放速度, //结束时不需要提示下次播放位置 //只需开始/结束提即可 /** * For part * stop playing continuously part of all the specified videos */ function stopContinuousPlayForPart() { if (playVideoConfig.isPlayAll) { stopContinuousPlayAll(); playVideoConfig.isPlayAll = false; } if (!videoConfig.isContinuousPaly) { layer.msg("【无效操作】:正常连播未执行"); return; } videoConfig.isContinuousPaly = false; playVideoConfig.isPlayPart = false; let video = document.querySelector('video'); let isPaused = video.paused; if (isPaused) { video.play(); video.pause(); } else { video.pause(); video.play(); } } /** * unlock progress bar and click this div * return the index of layer */ function unlockBarAndClickDiv(div, func) { layer.msg('解锁进度条中...', { time: 1500, }, function () { let info = '未上锁'; if ($(div).attr('data-drag') == 'N') { $(div).attr('data-drag', 'Y'); info = '已解锁!'; } layer.msg(info, { time: 1500 }, function () { $(div).trigger('click'); if (typeof func === "function") { func(); } }); }); } /** * is or not a number * @param {[type]} val [description] * @return {Boolean} [description] */ function isNumber(val) { var regPos = /^\d+(\.\d+)?$/; //非负浮点数 var regNeg = /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/; //负浮点数 if (regPos.test(val) || regNeg.test(val)) { return true; } else { return false; } } /** * play next videos according to the specified rate inputed by user and notify user */ function playBySpecifiedRateAndNotify() { let inputRate = prompt("以几倍速度进行连续播放呀🧐(最高" + maxRate + "倍哦!)建议1.8倍最佳🤭"); //console.log(inputRate); if (inputRate == null) { return false; } if (!isNumber(inputRate)) { let text = "啥❓ 你输入了啥,那是数字吗?\n 再输一次吧,别输出咯!😀"; notification(getNotificationDetails(text), null); return false; } rate = inputRate <= 0 ? 1 : (inputRate > maxRate ? maxRate : inputRate); weight = 1000 / rate; let text = "连续播放已开始!\n将以 " + rate + " 倍速 播放 " + (arr.length - nextVideoIndex) + " 个视频。"; //console.log(text); notification(getNotificationDetails(text), null); return true; } /** * edit those value about duration before sending ajax */ function send() { $.ajaxSetup({ beforeSend: function () { let argsData = arguments[1].data let falseArgsData = ""; let falseVal; for (let k in argsData) { if (k.includes("watch_to")) { //console.log("before: " + k + " : " + argsData[k]); falseVal = argsData.duration; //console.log("after: " + k + " : " + falseVal); } else { falseVal = argsData[k]; } falseArgsData = falseArgsData + "&" + k + "=" + falseVal; } arguments[1].data = falseArgsData.substring(1, falseArgsData.length); }, processData: false, complete: function () { console.log("send completed"); } }); } /** * trigger the click action of the current DIV * */ function clickDiv() { currentVideoIndex = count++; nextVideoIndex = currentVideoIndex + 1; if (isContinuousPaly == false) { //console.log("在播放第 " + (nextVideoIndex) + " 个视频时退出了连续播放"); return; } //第一次使用连续播放开启 关闭即可看完 if (currentVideoIndex == 0) { send(); } if (currentVideoIndex < arr.length) { // $(arr[currentVideoIndex]).trigger("click"); unlockBarAndClickDiv(arr[currentVideoIndex], playThisVideo); // setTimeout(function() {layer.close(index)}, videoConfig.loadingTime); //console.log(currentVideoIndex + " : " + arr[currentVideoIndex]); // playThisVideo(); } else { setTimeout(() => { clearInterval(interval); }, 0); //console.log("currentVideoIndex: " + currentVideoIndex); //关掉最后一个视频 $(".close-window").trigger("click"); alert("连续播放结束, 共连续播放了 " + arr.length + " 个视频,即将刷新页面"); location.reload(); } } /** * play the current video until it is over and play the next video */ function playThisVideo() { if (currentVideoIndex >= arr.length) { return; } let duration; let currentTime; setTimeout(() => { let video = document.querySelector('video'); let duration = video.duration; let currentTime = video.currentTime; let isPaused = video.paused; //console.log(isPaused ? "暂停" : "未停"); // if video has paused then play the video if (isPaused) { $(".mejs__replay").trigger("click"); //console.log("开始播放"); } setTimeout(() => { //console.log(document.querySelector('video').paused ? "暂停" : "未停"); }, 500); video.playbackRate = rate; //second --> millisecond let remain = (duration - currentTime) * weight; console.log("该视频剩余播放时长 :" + remain + " 毫秒"); //停掉上一个interval timeout clearInterval(interval); clearTimeout(timeout); //is NaN if (duration != duration || currentTime != currentTime || remain != remain) { stopContinuousPlayAll(); notification(getNotificationDetails("执行异常,已停止本次连播。" + "\n下一次连续播放从第 " + (nextVideoIndex + 1) + " 个视频开始。", 10000), null); return; } interval = setInterval(clickDiv, remain + intervalTime); timeout = setTimeout(() => { //console.log("当前视频播放到:" + document.querySelector('video').currentTime); $(".close-window").trigger("click"); //console.log("关闭第" + nextVideoIndex + "个视频"); //console.log(intervalTime + " 毫秒后播放下一个视频"); }, remain); }, bufferTime); } /** * play all the videos since this video */ let videoConfig = { videoSum: 0, currentVideoId: '', currentVideoDivs: arr, isContinuousPaly: false, loadingTime: 6000, rate: 1, } $("div[data-mime='video']").each(function (i, e) { let ts = $(this); ts.attr('id', 'vdoId_' + i); ts.bind('click', function (event) { /* Act on the event */ let id = videoConfig.currentVideoId = ts.attr('id'); let split = id.split('_'); let newFirstIndex = Number(split[1]); videoConfig.currentVideoDivs = arr.slice(newFirstIndex); //console.log(videoConfig.currentVideoDivs); }); videoConfig.videoSum = ++i; }); //每拿一个阻塞一次, function play(videosArr) { layer.msg( "连播开始!(共" + videosArr.length + "个)", { time: 3000 }, async function () { let isOver = true; for (let i = 0; isOver && i < videosArr.length; i++) { //console.log("time:" + i); isOver = await playOne(videosArr[i]); } //console.log("Done all"); videoConfig.isContinuousPaly = false; layer.msg("连播结束!"); } ); } function playOne(div) { unlockBarAndClickDiv(div); let index = layer.load(); // if(document.querySelector('video').readyState == 4){ // layer.msg("OK"); // }; return new Promise(resolve => { setTimeout(() => { //close load layer.close(index); let video = document.querySelector('video'); let onPause = function () { let a = video.currentTime == 0 || video.currentTime == video.duration; let b = videoConfig.isContinuousPaly; if (b && a) { //视频播完会回到开头如果没有回到开头应该在结尾 resolve(true); //console.log("连播&结束"); } else if (!b && !a) { //如果按下暂停前关闭了连续播放 == 结束本次列表循环 resolve(false); //console.log("play stopped"); } else if (b && !a) { //如果还在连续播放但是按下暂停 == 暂停 ,什么也不做 //console.log("play blocked"); } else if (!b && a) { //不再连播但播放结束 resolve(false); //console.log("不连播&结束"); } } video.removeEventListener('pause', onPause, false); // let duration = video.duration; // let currentTime = video.currentTime; let isPaused = video.paused; //console.log(isPaused ? "本是暂停" : "本是播放"); // if video has paused then play the video if (isPaused) { $(".mejs__replay").trigger("click"); //console.log("暂停-》开始播放"); } setTimeout(() => { //console.log(document.querySelector('video').paused ? "依旧暂停" : "已打开播放"); }, 500); video.playbackRate = keyboardEvent.currentSpeed; // video.addEventListener("ended", function() { // resolve(true); // console.log("this over"); // }); video.addEventListener('pause', onPause); }, videoConfig.loadingTime); }); } /********************************************** * keyMap module */ let keyboardEvent = { keyBindings: [], speedStep: 0, rewindTime: 0, advanceTime: 0, fastSpeed: 0, slowerKeyCode: 0, fasterKeyCode: 0, rewindKeyCode: 0, advanceKeyCode: 0, resetKeyCode: 0, fasterKeyCode: 0, currentSpeed: 1.0, functionKey: { keyMap: 0, playAll: 0, stopPlayAll: 0, playPart: 0, stopPlayPart: 0, onContinuousPlayFunc: 0, offContinuousPlayFunc: 0, showTips: 0 }, keyMapInfo: ``, keyMapDetail: [] }; // for video keyboardEvent.keyBindings.push({ action: "slower", key: Number(keyboardEvent.slowerKeyCode) || 83, value: Number(keyboardEvent.speedStep) || 0.1, force: false, predefined: true }); // default S keyboardEvent.keyBindings.push({ action: "faster", key: Number(keyboardEvent.fasterKeyCode) || 87, value: Number(keyboardEvent.speedStep) || 0.1, force: false, predefined: true }); // default: W keyboardEvent.keyBindings.push({ action: "rewind", key: Number(keyboardEvent.rewindKeyCode) || 65, value: Number(keyboardEvent.rewindTime) || 5, force: false, predefined: true }); // default: A keyboardEvent.keyBindings.push({ action: "advance", key: Number(keyboardEvent.advanceKeyCode) || 68, value: Number(keyboardEvent.advanceTime) || 5, force: false, predefined: true }); // default: D keyboardEvent.keyBindings.push({ action: "reset", key: Number(keyboardEvent.resetKeyCode) || 82, value: 1.0, force: false, predefined: true }); // default: R keyboardEvent.keyBindings.push({ action: "fast", key: Number(keyboardEvent.fastKeyCode) || 71, value: Number(keyboardEvent.fastSpeed) || 1.8, force: false, predefined: true }); // default: G // for functions keyboardEvent.keyBindings.push({ action: 'keyMap', key: Number(keyboardEvent.functionKey.keyMap) || 77 }); // M keyboardEvent.keyBindings.push({ action: 'playAll', key: Number(keyboardEvent.functionKey.playAll) || 90 }); // Z keyboardEvent.keyBindings.push({ action: 'stopPlayAll', key: Number(keyboardEvent.functionKey.stopPlayAll) || 88 }); // X keyboardEvent.keyBindings.push({ action: 'playPart', key: Number(keyboardEvent.functionKey.playPart) || 67 }); // C keyboardEvent.keyBindings.push({ action: 'stopPlayPart', key: Number(keyboardEvent.functionKey.stopPlayPart) || 86 }); // V keyboardEvent.keyBindings.push({ action: 'onContinuousPlayFunc', key: Number(keyboardEvent.functionKey.onContinuousPlayFunc) || 66 }); // B keyboardEvent.keyBindings.push({ action: 'offContinuousPlayFunc', key: Number(keyboardEvent.functionKey.offContinuousPlayFunc) || 78 }); // N keyboardEvent.keyBindings.push({ action: 'showTips', key: Number(keyboardEvent.functionKey.showTips) || 84 }); // T /** * get the content of the action specified * the action bound to some event * @return json */ function getKeyBindingsByAction(action) { let item = keyboardEvent.keyBindings.find(item => item.action === action); return item; } /** * get the value by specified action and keyname * @param {string} action [the action bound to some event] * @param {string} keyname * @return {[type]} */ function getValueByActionAndKeyname(action, keyname) { return getKeyBindingsByAction(action)[keyname]; } /** * [get all values by specified keyname ] * @return {[array]} [all values] */ function getAllValuesByKeyname(keyname) { let all = []; let arr = keyboardEvent.keyBindings; for (let i in arr) { let x = arr[i]; /** * access value by variable key * x.keyname ==> x[keyname] */ all.push(x[keyname]); } // console.log('all:'+ all); return all; } function changeKeycode(keycodeArr, toLowercase) { let arr = []; for (let i in keycodeArr) { // if (toLowercase) { // arr.push(keycodeArr[i] + 32); // }else{ // arr.push(keycodeArr[i] - 32); // } toLowercase == true ? arr.push(keycodeArr[i] + 32) : arr.push(keycodeArr[i] - 32); } //console.log(keycodeArr + '****' + arr); return arr; } /** * initialize keyboardEvent: keyMapInfo keyMapDetail * @type {[type]} */ keyboardEvent.keyMapDetail = [ ['强制关闭Chrome', 'Alt + F4'], ['查看快捷键', 'shift + m'], ['弹出提示', 'shift + t'], [`视频加速 (+${getKeyBindingsByAction('faster').value})`, 'W'], [`视频减速 (-${getKeyBindingsByAction('slower').value})`, 'S'], [`视频快退 ${getKeyBindingsByAction('rewind').value}s`, 'A'], [`视频快进 ${getKeyBindingsByAction('advance').value}s`, 'D'], [`最佳倍速 (${getKeyBindingsByAction('fast').value})`, 'G'], [`重置倍速 (${getKeyBindingsByAction('reset').value})`, 'R'], ['开启连播', 'shift + b'], ['关闭连播', 'shift + n'], ['开始正常连播', 'shift + c'], ['结束正常连播', 'shift + v'], ['开始全部连播', 'shift + z'], ['结束全部连播', 'shift + x'] ]; //获取 快捷键列表 function getKeyMapView() { let viewArr = keyboardEvent.keyMapDetail.map((item) => { return `

${item[0]} ${item[1]}

` }); return viewArr.join(' '); } keyboardEvent.keyMapInfo = `

功能快捷键


${getKeyMapView()}
`; /** * bind keyboard eventListener to document */ let lastTimeStamp = 0; let isSameKey = false; let lastKeyCode = 0; let recent2KeysInterval = 0; let requiredInterval = 200; /** * 如果连续两次操作同一个快捷键的时间间隔小于要求的时间间隔,则不执行 */ function isFrequent(interval, requiredInterval){ if (isSameKey && (interval < requiredInterval) ) { layer.msg("操作过于频繁"); return; } } $(document).bind('keypress', function (event) { /* 禁止频繁操作 */ let curTimeStamp = event.timeStamp; recent2KeysInterval = curTimeStamp - lastTimeStamp; lastTimeStamp = curTimeStamp; //此处写法会导致对所有keydown生效 // if (recent2KeysInterval < 200) { // layer.msg("操作过于频繁"); // return; // } /* Act on the event */ let keyCode = event.keyCode; let altKey = event.altKey; let ctrlKey = event.ctrlKey; let shiftKey = event.shiftKey; //console.log("keyCode:" + keyCode); /* 记录最近两次按下是否为同一个 key */ isSameKey = lastKeyCode == keyCode ? true : false; lastKeyCode = keyCode; let lowercase = changeKeycode(getAllValuesByKeyname('key').slice(0, 6), true); // console.log('[119, 115, 97, 100, 114, 103]:' + lowercase); let funcKeyLowercase = changeKeycode(getAllValuesByKeyname('key').slice(6), true); // console.log("[109, 122, 120, 99, 118, 98, 110]:" + funcKeyLowercase); let funcKeyUppercase = getAllValuesByKeyname('key').slice(6); // console.log("[77, 90, 88, 67, 86, 66, 78]:" + funcKeyUppercase); let playVdoFuncKeyLowercase = getAllValuesByKeyname('key').slice(7, 11); // shift + lowercase => uppercase 小写键盘 let shiftAndLowercase = shiftKey && ((funcKeyUppercase.find(item => item === keyCode) === undefined ? false : true)); // shift + uppercase => lowercase 大写键盘 let shiftAndUppercase = shiftKey && ((funcKeyLowercase.find(item => item === keyCode) === undefined ? false : true)); let shiftAndPlayVdoLowercase = shiftKey && ((playVdoFuncKeyLowercase.find(item => item === keyCode) === undefined ? false : true)); if (!document.querySelector('video').paused) { if (lowercase.find(item => item === keyCode)) { //console.log("is pause:"+ document.querySelector('video').paused); layer.msg('请打开大写键盘 以使用 【视频控件】'); return; } if (shiftAndUppercase) { layer.msg('请关闭大写键盘 以使用完整的快捷键功能'); return; } if (shiftAndPlayVdoLowercase) { if (!playVideoConfig.isContinuous) { layer.msg('请先开启连播功能'); return; } } } else if (document.querySelector('video').paused) { if (shiftAndUppercase) { layer.msg('请关闭大写键盘!以使用完整的快捷键功能'); return; } //四个连播功能(ZXCV)在没有开启连播时,提醒开启连播功能 if (shiftAndPlayVdoLowercase) { if (!playVideoConfig.isContinuous) { layer.msg('请先开启连播功能'); return; } } if (!shiftAndLowercase) { return; } } let item = keyboardEvent.keyBindings.find(item => item.key === keyCode); if (item) { let video = document.querySelector('video'); doAction(item, video); } }); /** * [doAction description] * @param {[type]} item [that event triggered] * @param {[type]} video [description] */ function doAction(item, video) { //避免频繁的快捷键操作(只对存在的快捷键有效) //如果连续两次操作同一个快捷键的时间间隔小于要求的时间间隔,则操作无效 if (isSameKey && (recent2KeysInterval < requiredInterval) ) { layer.msg("操作过于频繁"); return; } let action = item.action; let value = item.value; let num = (video.playbackRate).toFixed(1); /** * send a record ( special Keys ) */ if (keyboardEventMap.has(action)) { if ( !(specialKeyboardEventMap.has(action) && isSameKey && recent2KeysInterval < statConfig.specialKeysInterval) ) { // console.log('not special keys') // console.log(specialKeyboardEventMap.has(action)); // console.log(isSameKey); // console.log(recent2KeysInterval); record(keyboardEventMap.get(action)) } } if (action == 'slower') { video.playbackRate -= value; num = (video.playbackRate).toFixed(1); keyboardEvent.currentSpeed = num; layer.msg(num + " 倍"); } else if (action == 'faster') { video.playbackRate += value; num = (video.playbackRate).toFixed(1); keyboardEvent.currentSpeed = num; layer.msg(num + " 倍"); } else if (action == 'rewind') { video.currentTime -= value; layer.msg("- " + value + 's'); return; } else if (action == 'advance') { video.currentTime += value; layer.msg("+ " + value + 's'); return; } else if (action == 'reset') { video.playbackRate = value; num = (video.playbackRate).toFixed(1); keyboardEvent.currentSpeed = num; layer.msg(num + " 倍"); } else if (action == 'fast') { video.playbackRate = value; num = (video.playbackRate).toFixed(1); keyboardEvent.currentSpeed = num; layer.msg(num + " 倍"); } else if (action == 'keyMap') { let i = layer.alert( keyboardEvent.keyMapInfo, { //icon: 1 anim: 2 }, function (index) { //layer.msg('操作成功!'); layer.close(index); }); layer.title('Key Map', i); return; } else if (action == 'playAll') { $("#continuousPlayAll").trigger('click'); return; } else if (action == 'stopPlayAll') { $("#stopContinuousPlayAll").trigger('click'); return; } else if (action == 'playPart') { $("#continuousPlayPart").trigger('click'); return; } else if (action == 'stopPlayPart') { $("#stopContinuousPlayPart").trigger('click'); return; } else if (action == 'onContinuousPlayFunc') { onContinuousPlayFunc(); return; } else if (action == 'offContinuousPlayFunc') { offContinuousPlayFunc(); return; } else if (action == 'showTips') { showTips(); return; } } /************************************ * tips module */ let tipsConfig = { params: { tipsMore: true, tips: 1, time: 6000 }, }; function showTips() { layer.tips('全部连播', '#continuousPlayAll', tipsConfig.params); layer.tips('终止全部连播', '#stopContinuousPlayAll', tipsConfig.params); layer.tips('正常连播', '#continuousPlayPart', tipsConfig.params); layer.tips('终止正常连播', '#stopContinuousPlayPart', tipsConfig.params); } /********************************** * statistics */ var meta = ''; $("head").prepend(meta); const clickEventMap = new Map([ ['mode-click', 1], ['mode-download', 2], ['refresh', 3], ['reset', 4], ['choose', 5], ['confirm', 7], ['downloadSrc', 8], ['download-res-btn', 9], ['forward', 10], ['reverse', 11], ['continuousPlayAll', 26], ['stopContinuousPlayAll', 27], ['continuousPlayPart', 28], ['stopContinuousPlayPart', 29], ]); const keyboardEventMap = new Map([ ['keyMap', 12], ['showTips', 13], ['faster', 14], ['slower', 15], ['rewind', 16], ['advance', 17], ['fast', 18], ['reset', 19], ['onContinuousPlayFunc', 20], ['offContinuousPlayFunc', 21], ['playPart', 22], ['stopPlayPart', 23], ['playAll', 24], ['stopPlayAll', 25] ]); // ?s 内的操作记为 1 次 有效记录 const specialKeyboardEventMap = new Map([ ['faster', 14], ['slower', 15], ['rewind', 16], ['advance', 17], ]); let statConfig = { recordURL: config.base + '/hits/saveOrUpdateUsePostWithoutCORS', //? s 内记 1 specialKeysInterval: 5000 } let record = (fcId) => { let params = { htFcId: fcId } axios({ method: 'POST', url: statConfig.recordURL, data: qs.stringify(params), headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then((response) => { //succ console.log("succ") }).catch((error) => { //err console.log("err") }); } window.addEventListener("click", (event) => { let id = event.target.id; if (clickEventMap.has(id)) { record(clickEventMap.get(id)) } }); $(document).on('click','#forward, #reverse, #download-res-btn, .download-res-button ', (event) => { let id = event.target.id; let classNames = event.target.className; if( classNames.includes('download-res-button')){ id = 'download-res-btn'; } if (clickEventMap.has(id)) { record(clickEventMap.get(id)) } }); /** * MosoteachHelper CSS */ const styleTag = ` `; $(styleTag).appendTo('head'); //为每个资源添加下载按钮 $(".res-row-open-enable").each(function () { if ($(this).find(".download-res-button").length > 0) return; //如果已经存在下载按钮(例如mp3),则不再添加 $(this).find("ul").html('
  • 下载
  • ' + $(this).find("ul").html()); // $(this).find("ul").html('
  • 正序点击
  • ' + $(this).find("ul").html()); // $(this).find("ul").html('
  • 倒序点击
  • ' + $(this).find("ul").html()); }); //单个资源下载 $(document).on('click', '#download-res-btn', function () { var resHref = $(this).parents(".res-row-open-enable").attr('data-href'); window.open(resHref); }); $('
    \
    \
    \
    \
    \ 功能区 \ Powered by \ 云班课高效助手 \ 查看快捷键 :shift + m \ \
    \
    \
    \ \ \ \
    \
    \
    \
    \
    \ 资源区 \
    \
    \ ').insertAfter("#res-view-way"); // 初始化 $("#module-1,#module-2").css("display", "none"); $("#confirm, #downloadSrc, #mode-click, #mode-download").css("display", "inline"); // change mode $(document).on('click', '#mode-click', function () { $("#module-1, #module-2").css("display", "block"); // 等价于 // document.getElementById("module-1").style.display="block"; // document.getElementById("module-2").style.display="block"; // document.getElementById('confirm').style.display = document.getElementById('confirm').style.display=="inline"?"inline":"none"; $("#downloadSrc, #mode-download").css("display", "none"); // $("#mode-click").css({"background-color":"#0BD","color":"#fff"}); $("#modeName").text("模拟点击"); if (browserType() == "Chrome") { newTabAlert("onDownload", "chrome://settings/downloads", 'active', function () { alert("操作提醒:\n" + "务必操作,否则请不要向下执行任何操作!!!\n" + "\n" + "(识别到您使用的是Chrome浏览器)" + "\n\n" + " 已自动为你打开浏览器【设置】页面" + "\n" + " 【提醒】:如果没有结果可在搜索框中搜索【保存位置】" + "\n" + " 【 打开 】 “下载前询问每个文件的保存位置” 右侧按钮"); }); } else { alert("操作提醒:\n" + "务必操作,否则请不要向下执行任何操作!!!\n" + "\n" + "(以下只是 chrome 浏览器操作步骤)" + "\n" + " 1. 新建 Tab 页\n" + " -->\n" + " 2. 地址栏输入: chrome://settings/?search=downloads\n" + " -->\n" + " 3. 打开 “下载前询问每个文件的保存位置” 右侧按钮"); } }); $(document).on('click', '#mode-download', function () { document.getElementById("module-1").style.display = "block"; $("#module-2, #confirm, #mode-click").css("display", "none"); // $("#mode-download").css({"background-color":"#0BD","color":"#fff"}); $("#modeName").text("批量下载"); if (browserType() == "Chrome") { newTabAlert("offDownload", "chrome://settings/downloads", 'active', function () { alert("操作提醒:\n" + "务必操作,否则请不要向下执行任何操作!!!\n" + "\n" + "(识别到您使用的是Chrome浏览器)" + "\n\n" + " 已自动为你打开浏览器【设置】页面" + "\n" + " 【提醒】:如果没有结果可在搜索框中搜索【保存位置】" + "\n" + " 【 关闭 】 “下载前询问每个文件的保存位置” 右侧按钮") }); } else { alert("操作提醒:\n" + "务必操作,否则请不要向下执行任何操作!!!\n" + "\n" + "(以下只是 chrome 浏览器操作步骤)" + "\n" + " 1. 新建 Tab 页\n" + " -->\n" + " 2. 地址栏输入:chrome://settings/?search=downloads\n" + " -->\n" + " 3. 关闭 “下载前询问每个文件的保存位置” 右侧按钮"); } }); $(document).on('click', '#reset', function () { $("#module-1,#module-2").css("display", "none"); $("#confirm, #downloadSrc, #mode-click, #mode-download").css("display", "inline"); // $("#mode-download, #mode-click").css({"background-color":"#fff","color":"#000"}); $("#modeName").text("未选择"); }); // 刷新 $(document).on('click', '#refresh', function () { layer.msg('即将刷新...', { time: 2500 //如果不配置,默认是3秒 }, function(){ location.reload() }); }) //资源栏总数 var srcBarSum = 0; // 给分栏添加 id 易于按栏操作 $(".res-row-box").each(function (i, e) { $(this).attr('id', 'id_' + i); srcBarSum = i + 1; }); //存储所有被选择的资源栏 id var chosenIDs = []; $(document).on('click', '#choose', function () { //获取点击时按钮值 var val = $("#choose").val(); //接受用户输入的id 字符串 let inputString = $("#bar_index").val().trim(); //inputString经过清洗后得到的有效资源栏编号 let idsArr = cleanData(inputString); if (val == "确认选择") { /** * 用户修改要选择的资源栏点击确认后 * 根据有效资源栏编号生成对应资源栏id存入数组备用 * 并显示被选择的所有有效资源栏 */ //无输入内容,选择全部栏 if (idsArr.length == 0) { chosenIDs.push(".res-row-box"); } else { //有输入内容,转化成对应的id,放入数组备用 for (let id of idsArr) { chosenIDs.push("#id_" + (id - 1)); } } //test // console.log(idsArr); //var barID = $("#bar_index").val(); let barID_str = idsArr.length == 0 ? "全选" : idsArr; //var barID_str = (barID > 0 && barID < 21) ? barID : "全选"; alert("小可爱,你已将要操作的资源栏修改为: " + barID_str); $("#barID").text(barID_str); $("#choose").val("重置"); $("#choose").css('background-color', 'rgba(204, 0, 0,0.6)'); } else { /** * 用户重置资源栏输入框 * 置空输入框 和 存储的所有资源栏id * 被选择的资源栏设为全选 */ $("#bar_index").val(""); chosenIDs = []; $("#choose").val("确认选择"); $("#choose").click(); } }); // reset bar_index $('#bar_index').bind("input propertychange", function (event) { $("#choose").val("确认选择"); $("#choose").css('background-color', 'rgba(0, 151, 179,0.7)'); }); /** * Main body * */ /** * 根据指定的所有资源栏id,进行模拟点击 */ $(document).on('click', '#confirm', function () { batchForMoreSrcBars("模拟点击", chosenIDs) }); /** * 根据指定的所有资源栏id,进行批量下载 * */ $(document).on('click', '#downloadSrc', function () { batchForMoreSrcBars("批量下载", chosenIDs) }); /** * 模拟正序点击全部资源 * */ $(document).on('click', '#forward', function () { clickAll("true") }); /** * 模拟倒序点击全部资源 * */ $(document).on('click', '#reverse', function () { clickAll("false") }); /** * Play videos continuously */ $(document).on('click', '#continuousPlayMode', () => { continuousPlay() }) });