您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
优化上海交通大学 Canvas 平台课程视频播放器的功能
当前为
// ==UserScript== // @name 上海交通大学 Canvas 平台课程视频播放器至尊版焕然一新插件 // @namespace http://tampermonkey.net/ // @version 1.7.5 // @description 优化上海交通大学 Canvas 平台课程视频播放器的功能 // @author danyang685 // @match https://oc.sjtu.edu.cn/* // @match https://courses.sjtu.edu.cn/* // @match https://vshare.sjtu.edu.cn/play/* // @icon data:image/png;base64,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 // @grant GM_info // @grant GM_addStyle // @grant unsafeWindow // @license MIT // ==/UserScript== /*----------------------------------------------- 本项目主页:http://greasyfork.icu/zh-CN/scripts/432918 -----------------------------------------------*/ (function () { 'use strict'; let script_version = GM_info.script.version; // 本脚本的版本号 let window = unsafeWindow; // 脚本中使用GM函数后,必须使用 unsafeWindow 才可覆盖原有 window 事件回调函数 // 登录页面,要求选 jAccount 或校外用户登录 let is_canvas_login = window.location.href == "https://oc.sjtu.edu.cn/login/canvas"; // 点播页面,包括 Canvas 内置的和 courses.sjtu 网站上的 let is_canvas_vod_page = window.location.href.startsWith("https://courses.sjtu.edu.cn/lti/app/lti/vodVideo/playPage") || window.location.href.startsWith("https://courses.sjtu.edu.cn/app/vodvideo/vodVideoPlay.d2j"); // 直播页面,Canvas 内置的 let is_canvas_live_page = window.location.href.startsWith("https://courses.sjtu.edu.cn/lti/app/lti/liveVideo/index.d2j"); // 课程视频 LTI 插件页面 let is_canvas_lti162_page = new RegExp("https://oc\.sjtu\.edu\.cn/courses/\\d*/external_tools/162").test(window.location.href); // vshare 视频页面 let is_vshare_page = window.location.href.startsWith("https://vshare.sjtu.edu.cn/play/") // 处于 iframe 内 let is_iframe = (self != top); // 检查是否为安卓设备 function isAndroid() { return navigator.userAgent.includes("Android"); } // 允许进一步缩放 if (isAndroid()) { // 怎么会一点也不起作用呢?一定是哪里出了问题,不应当不应当! // document.getElementById("viewport").setAttribute("content", "height=520, initial-scale=0, minimum-scale=0.25, maximum-scale=1.0, user-scalable=yes"); } // 到达 Canvas 登录页时,自动跳转到 jAccount 登录页 if (is_canvas_login) { window.location.replace("https://oc.sjtu.edu.cn/login/openid_connect"); } // LTI插件页面 else if (is_canvas_lti162_page) { $(document).ready(function () { // 去除了页面中多余的滚动条 let clear_resize_event_interval = setInterval(function () { $(window).off("resize"); //删除疑似画蛇添足的resize事件,瞎删除2百次就行吧 $(".tool_content_wrapper").attr("style", ""); $("#tool_content") .css("height", "510px") .css("width", "1020px"); }, 50); setTimeout(() => clearInterval(clear_resize_event_interval), 10000); // 去除了左侧的空白区域 $(".ic-Layout-contentMain") .css("height", "100%") .css("padding", "0"); $("#tool_content") .css("min-height", "600px"); // 关灯纯色元素 $(".ic-Layout-columns").append($('<div class="light-turn-off"></div>')) $(".light-turn-off") .css("position", "fixed") .css("inset", "0") .css("background-color", "black") .css("z-index", "101") .css("display", "none"); // 页面最高z-index为100 $("#tool_content") .css("position", "fixed") .css("z-index", "102"); // 页面最高z-index为100 // 自动更新cookie,防止页面会话失效 function help_refresh_session() { $("body").append('<iframe src="' + window.location.href + '" style="display:none; height:0;" id="session_updater_iframe">'); } let pending_request_refresh = false; // 将相关重要跨域传递给子页面 $(window).on("message", function (event) { let origin = event.origin || event.originalEvent.origin; let data = event.data || event.originalEvent.data; if (origin == "https://courses.sjtu.edu.cn") { console.log("收到子页面的message", data); switch (data) { case "online!": { document.getElementById("tool_content").contentWindow.postMessage( JSON.stringify({ "message_type": "config_tranfer", "course_canvasid": window.location.href.match(new RegExp("courses/(\\d*)/"))[1], "course_name": $("#context_title").attr("value"), "course_fullname": $("#context_label").attr("value"), "user_name": $("#lis_person_name_full").attr("value"), "user_id": $("#custom_canvas_user_id").attr("value") }), "https://courses.sjtu.edu.cn"); break; } case "help!": { help_refresh_session(); break; } case "helpr!": { help_refresh_session(); pending_request_refresh = true; break; } case "done!": { // 新创建的附属页面完成了一次登录,会话已更新 $("#session_updater_iframe").remove(); if (pending_request_refresh) { // 需要发送刷新指令 pending_request_refresh = false; document.getElementById("tool_content").contentWindow.postMessage( JSON.stringify({ "message_type": "request_refresh" }), "https://courses.sjtu.edu.cn"); console.log("子页面要求在更新会话后刷新,刷新。"); } break; } case "lightoff!": { // 修复了乱改z-index导致的某些场景中的界面样式错乱 $(".ic-Layout-columns").css("z-index", "101"); $(".light-turn-off").show(); // 搞渐变不好整啊,这finish和queue咋用啊。。。 break; } case "lighton!": { // 修复了乱改z-index导致的某些场景中的界面样式错乱 $(".ic-Layout-columns").css("z-index", ""); $(".light-turn-off").hide(); break; } } } }) }) } // 课程视频页面 else if (is_canvas_live_page || is_canvas_vod_page) { console.log("%cCanvas课程视频加强!%c当前链接:%c" + window.location.href, "color:blue;", "color:green;", "color:#0FF;"); let auto_refresh_flag = false, allow_long_live_flag = false, // 嘿嘿,嘿嘿 allow_live_earlier = false, vlist_ajax_called_flag = true; canvas_live_vod_enhance_pre(); let video_list_loaded_flag = false; // 通过捕获 getVideoInfoById 的调用来进行视频加载状态的检查 { let raw_getVideoInfoById = getVideoInfoById; getVideoInfoById = function (id) { let vlist_ajax_called_flag_waiting_interval = setInterval(function () { if (!vlist_ajax_called_flag) { return; // 哦原来它没有返回值啊,那太好了,可以用俺的土办法 } clearInterval(vlist_ajax_called_flag_waiting_interval); let data = undefined; let newfunc_getVideoInfoById = undefined; eval("newfunc_getVideoInfoById = " + raw_getVideoInfoById.toString().replace("getVideoInfoById", "").replace("noStart();\n\t\t\t\t\treturn;", "")); /* while(!vlist_ajax_called_flag){ (async () => await new Promise(resolve => setTimeout(resolve, 10)))(); // 抄来的高级代码啊哈哈哈哈,嘤嘤嘤根本等不来那个ajax callback console.log("waiting for vlist_ajax_called_flag"); } */ if (allow_live_earlier) { raw_getVideoInfoById = newfunc_getVideoInfoById; console.log("allow_live"); } raw_getVideoInfoById(id); video_list_loaded_flag = true; // 如果没有视频的时候,它就不会被调用了emmmm。。。 console.log("loaded!"); }, 10); } } let onload_check_interval = setInterval(function () { if (!video_list_loaded_flag) { // 或许用 $("body .loading").css("display")!="none" 判断的话会有极小概率出现线程间不同步(或许??) console.log("loading..."); return; } clearInterval(onload_check_interval); canvas_live_vod_enhance(); }, 10); function canvas_live_vod_enhance_pre() { if (is_canvas_vod_page) { // 阻止向服务器回报观看日志 if (window.addVodPlayLog) addVodPlayLog = function () {}; else { console.log("%c日志阻断失败", "color:#0FF;"); location.reload(); } if (window.updateLiveCount) updateLiveCount = function () {}; else { console.log("%c日志阻断失败", "color:#0FF;"); location.reload(); } if (window.updateVodPlayLog) updateVodPlayLog = function () {}; else { console.log("%c日志阻断失败", "color:#0FF;"); location.reload(); } //dateVodPlayLog = updateLiveCount = addVodPlayLog = function () {}; } else { if (window.updateLivePlayLog) updateLivePlayLog = function () {}; if (window.updateLiveCount) updateLiveCount = function () {}; if (window.addLivePlayLog) addLivePlayLog = function () {}; //dateLivePlayLog=updateLiveCount=addLivePlayLog = function () {}; } // 允许通过点击顶部当前标签页重新载入当前网页 $(".tab-item--active").click(function () { window.location.replace(window.location.href); }); if (is_canvas_live_page) { // 将直播中同一场课程的多个节次合并显示 let old_creatCourseList_func = creatCourseList; creatCourseList = function (lst) { let joint_live_list = Array.from(function* getJointLiveItemList(lst) { // 注意一会别忘了写长度为1的判定。……啊哈根本不需要写哈哈哈哈哈 for (let i = 1; i < lst.length; i++) { let is_continious = Date.parse(lst[i].courBeginTime) - Date.parse(lst[i - 1].courEndTime) <= 1000 * 60 * 20, // 间隔小于20分钟 is_same_room = lst[i].clroName == lst[i - 1].clroName; // 在同一教室 if (is_continious && is_same_room) { // 视为同一课程安排 lst[i - 1].courEndTime = lst[i].courEndTime; lst[i] = lst[i - 1]; console.log("joint!") } else { yield lst[i - 1]; // 之前那个 } } yield lst[lst.length - 1]; // 最后一个 }(lst)); old_creatCourseList_func(joint_live_list); } } $(document).ajaxComplete(function (event, xhr, settings) { // woc jQuery好厉害,但它不够快。明天再改。。。。。。。 switch (settings.url) { case "/lti/liveVideo/findLiveList": { //console.log("ajax done!!!",event,xhr,settings); if (xhr.status != 200) { // 这是因为后台页面正在进行登录 location.reload(); } let live_list = xhr.responseJSON.body.list; if (live_list.length) { let this_id = (new URLSearchParams(window.location.search).get("id") || live_list[0].id).replaceAll(" ", "+"); // 为什么加号会变成空格?? console.log("活跃的视频ID:" + this_id); live_list = live_list.filter(live_item => live_item.id == this_id); if (live_list.length) { let course_start_time_span = (Date.parse(live_list[0].courBeginTime) - new Date().getTime()) / 1000 / 60; // 允许在课前25分钟即开始观看课程直播 if (course_start_time_span < 25 || allow_long_live_flag) { allow_live_earlier = true; vlist_ajax_called_flag = true; // 否则是来不及的 console.log("try_allow_live"); } } } else { video_list_loaded_flag = true; // 直接置位视频列表已加载的标志 } break; } case "/lti/vodVideo/findVodVideoList": { //console.log("ajax done!!!",event,xhr,settings); if (xhr.status != 200) { // 这是因为后台页面正在进行登录 location.reload(); } let vod_list = xhr.responseJSON.body.list; if (vod_list.length == 0) { video_list_loaded_flag = true; // 直接置位视频列表已加载的标志 } break; } default: { break; } } }); } function canvas_live_vod_enhance() { let is_from_default_lti_entry = document.referrer == "https://oc.sjtu.edu.cn/", video_count = $(".lti-list .item-text").length || $(".lti-list .item-infos").length, is_live_playing = $(".lti-list .live-course-item--avtive .icon-play").length, no_live_video_played = false; let is_single_video = undefined, use_storage_flag = true; // 在视频列表顶部文字中显示视频总数 $(".lti-list>.list-title").append($('<span style="font-size: 50%;">(' + video_count + '条视频)</span>')); function setStorage(k, v) { if (use_storage_flag) { localStorage.setItem("canvasnb_plugin_" + k, v); } } function getStorage(k) { if (use_storage_flag) { return localStorage.getItem("canvasnb_plugin_" + k); } else { return null; } } // 首先获取本页面的课程ID let course_id = undefined; { // 本科生课程0-9A-Z,研究生课程额外含有a-z和-,感谢@icebreak的反馈 let ma = document.getElementsByTagName('html')[0].outerHTML.match(new RegExp("canvasCourseId = '([0-9A-Za-z\\-]*)';")); if (ma) { course_id = ma[1]; } } console.log("课程ID:" + course_id); if (!course_id) { // august反馈,他的页面一直在疯狂刷新(研究生课程),故对该情况禁用记忆功能,反正啥也记不住 // window.location.reload(); use_storage_flag = false; course_id = "UNKNOWN_COURSE" } // 再获取本视频的视频ID let video_id = $(".lti-list .list-item--active").attr("id") || $(".lti-list .live-course-item--avtive").attr("id"); // 修复了右侧视频栏中已激活的视频在部分场景下仍然可点击的bug if (video_id && video_id.length > 10) { // 10是我瞎写的 let net_url = window.location.origin + window.location.pathname + "?id=" + video_id; console.log("raw url: " + window.location.href + " , push new url: " + net_url); window.history.pushState('data', document.title, net_url); // 点播用的这种实现 if (is_canvas_live_page) { urlId = video_id; // 直播用的这种实现 } } let userid = getStorage("course_" + course_id + "_last_userid"); let username = getStorage("course_" + course_id + "_last_username"); // 跳转到合适的直播页面 function redirectToVodPage() { // 修复了自动跳转到点播页面时,需要二次跳转到上次视频的bug let last_play = getStorage(userid + "_course_" + course_id + "_lastplay_video_id"); if (last_play != null) { window.location.replace("https://courses.sjtu.edu.cn/lti/app/lti/vodVideo/playPage?id=" + last_play); } } if (is_canvas_live_page) { let should_redirect_flag = false; if (video_count != 0) { let item_date = $(".live-course-item--avtive .item-infos p:nth-child(2) span:nth-child(2)").text(), item_time = $(".live-course-item--avtive .item-infos p:nth-child(3) span:nth-child(2)").text().split("~")[0], next_date_span_minute = (Date.parse(item_date + " " + item_time) - new Date().getTime()) / 1000 / 60; // 距离正式开始直播的分钟数 // 当距离上课还有超过40分钟时,访问【课程视频】自动切换到点播页面 if (next_date_span_minute > 40 && !allow_long_live_flag) { should_redirect_flag = true; } } else { should_redirect_flag = true; } if (is_from_default_lti_entry) { if (window.innerHeight == 0) { // 我是用于更新会话的工具iframe console.log("会话更新已完成!课程ID:%c" + course_id, "color:#0FF;"); top.postMessage("done!", "https://oc.sjtu.edu.cn"); return; } if (should_redirect_flag) { redirectToVodPage(); } } } // 从canvas内直接打开【视频点播】时,自动切换到上次观看的视频 if (is_canvas_vod_page) { if (!window.location.href.includes("?id=")) { redirectToVodPage(); } if (video_id != undefined) { setStorage(userid + "_course_" + course_id + "_lastplay_video_id", video_id); } } console.log("视频ID:" + video_id); if (is_iframe && (document.referrer == "https://courses.sjtu.edu.cn/lti/app/lti/vodVideo/playPage" || document.referrer == "https://oc.sjtu.edu.cn/")) { console.log("发送message:online!"); top.window.postMessage("online!", "https://oc.sjtu.edu.cn"); } console.log("配置message"); $(window).on("message", function (event) { let origin = event.origin || event.originalEvent.origin; let data = event.data || event.originalEvent.data; if (is_iframe && origin == "https://oc.sjtu.edu.cn") { // oc.sjtu仅会向iframe内的course.sjtu发送消息 console.log("收到父页面的message", data); let message = JSON.parse(data); if (message["message_type"] == "config_tranfer") { setStorage("course_" + course_id + "_realname", message["course_name"]); setStorage("course_" + course_id + "_canvasid", message["course_canvasid"]); setStorage("course_" + course_id + "_fullname", message["course_fullname"]); setStorage("course_" + course_id + "_last_userid", message["user_id"]); setStorage("course_" + course_id + "_last_username", message["user_name"]); userid = message["user_id"]; username = message["user_name"]; console.log("config_message: ", message); } else if (message["message_type"] == "request_refresh") { location.reload(); // 刷新页面 } } }); // 覆盖已有设定,有一说一,光屏蔽F12有用吗,Chrome还可以 Ctrl+Shift+I 呢。 window.onkeydown = window.onkeyup = window.onkeypress = window.oncontextmenu = undefined; // 清空了视频下方的说明文字 $(".course-details").empty(); // 移除直播视频上的两个无意义图标 $(".live-review-icon").remove(); $(".icon-play").remove(); // 将全局右键屏蔽改为仅应用至视频区域 $("#rtcMain").bind('contextmenu', function (event) { // console.log("contextmenu"); event.preventDefault(); return false; }); // 在右上角增加【在新标签页播放】按钮 if (is_iframe) { // 【在新标签页播放】时不再显示【在新标签页播放】按钮 $(".lti-page-tab").append($('<button class="tab-help" onclick="window.open(window.location.href)" id="btn_play_in_new_tab">在新标签页播放</button>')); } if (is_canvas_vod_page) { $("#btn_play_in_new_tab").on("contextmenu", function (event) { // 右击能在新标签页打开有意思的东西 let totalX = this.clientWidth, clickX = event.offsetX; if (!is_single_video) { if (clickX < totalX / 2) { // 打开左边的视频 window.open($(".cont-item-1 #kmd-video-player").attr("src")); } else { // 打开右边的视频 window.open($(".cont-item-2 #kmd-video-player").attr("src")); } } else { // 只有一个视频 window.open($(".cont-item-1 #kmd-video-player").attr("src")); } event.preventDefault(); return false; }) } function work_all() { // 隐藏画面上的音量控制 $(".voice-icon").hide(); // 特殊判断只有一个视频流的点播视频 is_single_video = $(".cont-item-2").length == 0; // 加快状态栏功能弹窗隐藏速度 function new_hoverleave_callback(cld, prt) { let timer = null; prt.on('mouseover', function () { clearTimeout(timer); cld.show(); }) prt.on("mouseout", function () { timer = setTimeout(function () { cld.hide(); }, 100); }); } new_hoverleave_callback($(".voice-volume"), $("#voiceContorl")); new_hoverleave_callback($(".split-select"), $("#splitContorl")); new_hoverleave_callback($("#timesContorl>ul"), $("#timesContorl")); // 增加画面参数调整选项,可进行亮度、对比度、透明度的调节 $('<div id="effectControl" class="tool-bar-item tool-btn__times" style="position: relative; z-index: 20;"><span>画面</span><div><p>画面设定</p></div></div>') .insertAfter("#splitContorl"); $("#effectControl") .css("position", "relative") .css("z-index", "20"); $("#effectControl>div") .css("position", "absolute") .css("display", "none") .css("bottom", "25px") .css("left", "-58px") .css("height", "300px") .css("width", "150px") .css("border-radius", "3px") .css("background-color", "rgba(28, 32, 44, .9)"); $("#effectControl>div>p") .css("font-size", "150%") .css("font-weight", "bold"); let effect_control_list = [{ for: "opacity", label: "小窗透明度", default: 0.85, min: 0.2, max: 1.0 }, { for: "brightnesssA", label: "现场亮度", default: 1, min: 0.7, max: 1.5 }, { for: "brightnesssB", label: "电脑亮度", default: 1, min: 0.7, max: 1.5 }, { for: "contrastA", label: "现场对比度", default: 1, min: 0.8, max: 2.5 }, { for: "contrastB", label: "电脑对比度", default: 1, min: 0.8, max: 2.5 }, { for: "blurA", label: "现场清晰度", default: 0.3, min: 1.5, max: 0 }, { for: "blurB", label: "电脑清晰度", default: 0.3, min: 2, max: 0 }] let effect_setting = JSON.parse(getStorage(userid + "_effect")); if (!effect_setting) { effect_setting = Object.fromEntries(effect_control_list.map(x => [x.for, x.default])); // 哦越来越熟练了呢 } setStorage(userid + "_effect", JSON.stringify(effect_setting)); effect_control_list.forEach(function (v) { if (effect_setting[v.for] == undefined) { effect_setting[v.for] = v.default; // 其实上面那句可以删了,嘿但我就不删,看着多高级啊 } $("#effectControl>div").append($('<div id="effect_container_' + v.for+'"></div')); let container = $("#effectControl>div>#effect_container_" + v.for); container.append($("<label>" + v.label + "</label>")); container.append($('<input type="range" id="effect_slider_' + v.for+'" min="0" max="100">')); //嘤嘤嘤slider的默认样式好美啊 let range_slider = $("#effectControl>div #effect_slider_" + v.for); range_slider[0].value = parseInt(100 * (effect_setting[v.for] - v.min) / (v.max - v.min)); range_slider.on("input", function (event) { let new_value = event.target.value / 100; effect_setting[v.for] = v.min + (new_value * (v.max - v.min)); setStorage(userid + "_effect", JSON.stringify(effect_setting)); }); }) $("#effectControl>div").append('<input type="button" id="btn_reset_effect" value="还原默认设定">') $("#btn_reset_effect").on("click", function () { effect_control_list.forEach(function (v) { effect_setting[v.for] = v.default; let range_slider = $("#effectControl>div #effect_slider_" + v.for); range_slider[0].value = parseInt(100 * (effect_setting[v.for] - v.min) / (v.max - v.min)); }) // 哦豁忘记这个了 setStorage(userid + "_effect", JSON.stringify(Object.fromEntries(effect_control_list.map(x => [x.for, x.default])))); // 可以清理废弃字段 }); new_hoverleave_callback($("#effectControl>div"), $("#effectControl")); // 有两路视频时,能够通过设定参考点的方式进行画面同步 if (!is_single_video) { $('<div id="syncControl" class="tool-bar-item tool-btn__times" style="position: relative; z-index: 20;"><span>同步</span><div><p>设定参考点以进行同步</p><input type="button" value="现场画面参考"><input type="button" value="电脑画面参考"><p>还原</p><input type="button" value="还原默认"></div></div>') .insertAfter("#timesContorl"); $("#syncControl") .css("position", "relative") .css("z-index", "20"); $("#syncControl>div") .css("position", "absolute") .css("display", "none") .css("bottom", "25px") .css("left", "-58px") .css("height", "100px") .css("width", "150px") .css("border-radius", "3px") .css("background-color", "rgba(28, 32, 44, .9)"); let sync_ref_scene = -1; let sync_ref_computer = -1; function clear_pending_sync_control_button() { $("#syncControl :button").each(function (idx, elem) { if (elem.value.includes("撤销")) { elem.value = elem.value.substr(2, elem.value.length - 1); } }); } $("#syncControl :button").on("click", function (event) { switch (event.target.value) { case "现场画面参考": { sync_ref_scene = kmplayer.allInstance.type1.currentTime(); if (sync_ref_computer == -1) { putText("ref_scene: " + timeSec2Text(sync_ref_scene) + ", waiting for ref_computer..."); event.target.value = "撤销现场画面参考"; } else { setTimeDelta(sync_ref_computer - sync_ref_scene); putText("delta: " + time_sync_delta.toFixed(3) + "s"); sync_ref_scene = -1; sync_ref_computer = -1; clear_pending_sync_control_button(); } break; } case "电脑画面参考": { sync_ref_computer = kmplayer.allInstance.type2.currentTime(); if (sync_ref_scene == -1) { putText("ref_computer: " + timeSec2Text(sync_ref_computer) + ", waiting for ref_scene..."); event.target.value = "撤销电脑画面参考"; } else { setTimeDelta(sync_ref_computer - sync_ref_scene); putText("delta: " + time_sync_delta.toFixed(3) + "s"); sync_ref_scene = -1; sync_ref_computer = -1; clear_pending_sync_control_button(); } break; } case "撤销现场画面参考": { sync_ref_scene = -1; event.target.value = "现场画面参考"; break; } case "撤销电脑画面参考": { sync_ref_computer = -1; event.target.value = "电脑画面参考"; break; } case "还原默认": { sync_ref_scene = -1; sync_ref_computer = -1; putText("resetted!"); clear_pending_sync_control_button(); setTimeDelta(0); break; } } }); new_hoverleave_callback($("#syncControl>div"), $("#syncControl")); } // 在播放控制栏增加了开关灯按钮 $('<div id="lightControl" class="tool-bar-item tool-btn__times" style="position: relative; z-index: 20;"><span>关灯</span></div>') .insertAfter("#splitContorl"); $("#lightControl").click(function () { switch ($("#lightControl>span").text()) { case "关灯": { $("#lightControl>span").text("开灯") $(".light-turn-off").show(); // 搞渐变不好整啊,这finish和queue咋用啊。。。 /* .hide().finish().fadeIn({ duration: 200, easing: "linear", queue: false }); */ if (is_iframe) { // 父页面也关灯 top.postMessage("lightoff!", "https://oc.sjtu.edu.cn"); } break; } case "开灯": { $("#lightControl>span").text("关灯") $(".light-turn-off").hide() if (is_iframe) { top.postMessage("lighton!", "https://oc.sjtu.edu.cn"); } break; } } }) let progressBar_operating_flag = false, // 手动拖动进度条的过程中,不应覆写进度条状态 current_time_speed = 1.0, default_time_speed = 1.0, current_zoom_ratio = parseInt(getStorage(userid + "_zoom_ratio")) || 45; // 重新打开时,记忆上次的小画面尺寸 // 使右侧视频列表的内容更紧凑了,并增加了教室显示 let this_teacher; $(".lti-list .item-text").each(function (idx, elem) { elem = $(elem); elem.children("p:first").attr("title", ""); // 清空课程名title elem.children(".classroom-name").css("display", ""); let course_name = elem.children("p:first").html(); let teacher_name = elem.children(".item-contour").html(); teacher_name = teacher_name.substr(3, teacher_name.length - 1).trim(); elem.children("p:first").html(course_name + "(" + teacher_name + ")"); // 课程名加教师名 elem.children(".item-contour").remove(); // 删除教师 let course_time = elem.children("p:last-child").html(); elem.children("p:last-child").html(course_time.trim().substr(0, course_time.length - 3)); if (elem.attr("class").includes("list-item--active")) { this_teacher = teacher_name; } // 使用配色糟糕的标签突出显示未观看过的视频 // 微调了右边视频列表中未观看视频的红绿字位置,避免遮挡其他文字 let elem_video_id = elem.parent().attr("id"); if (getStorage(userid + "_video_" + elem_video_id + "_position") == null && video_id != elem_video_id) { let terrible_label = '<div style="position: relative;top: -40px;left: 160px;width: 0;height:0;"><span style="text-align: center; position: absolute; background-color: #00ac18; border-radius: 10px; height: 30px; line-height: 30px; width: 30px; font-size: 20px; font-weight: bold; color: red;">新</span></div>'; elem.append($(terrible_label)); } }); // 将右侧视频列表改为升序了 // $(".list-main").html($(".list-main>.list-item").get()); // 列表好长好长好长的时候,自动将滚动条定位到当前视频条目 if (is_canvas_vod_page) { $(".list-main")[0].scrollTop = $(".lti-list .list-item--active")[0].offsetTop - 250; //$(".lti-list .list-item--active")[0].scrollIntoView({block: "center"}); // 这个不太行,把整个页面都给滚没了 } // 是否正在播放/暂停 function isPlaying() { return $(".tool-btn__play").css("display") == "none"; } // 设置播放/暂停 function setPlay(status, quiet = false) { if (status) { console.log("请求播放!"); kmplayer.play("play"); navigator.mediaSession.playbackState = "playing"; if (!quiet) { putText("状态:播放"); } } else { console.log("请求暂停!"); kmplayer.play("pause"); navigator.mediaSession.playbackState = "paused"; if (!quiet) { putText("状态:暂停"); } } } // 切换播放/暂停 function togglePlay() { setPlay(!isPlaying()); } function getMuted() { return $(".tool-btn__voice").length == 0; } function setMuted(muted) { kmplayer.voice((muted) ? "voice" : "muted"); } // 设定播放速度 function setSpeed(speed) { console.log("设定速度:" + speed); for (let i = 0; i < kmplayer.ids.length; i++) { kmplayer.allInstance['type' + (i + 1)].playbackRate(speed); } $("#timesContorl>span").html(speed); $("#timesContorl>ul>li").each(function (idx, elem) { if (elem.id == speed) { $(elem).attr("class", "times-active"); } else { $(elem).attr("class", ""); } }); } // 读取播放速度 function getSpeed() { return $("#player-00001 #kmd-video-player")[0].playbackRate; } // 重写原生全屏函数 { let newfullscreenFunc = undefined; eval(kmplayer.scrren.toString() .replace("scrren()", "newfullscreenFunc=function()") // 修复了缩放后播放控制栏自动弹出的响应范围随之变化的bug .replace("window.screen.height * 0.5", '($("#kmd-video-player").height()*0.95-32)')) // 缩小播放控制栏自动弹出的响应范围,使体验更加顺滑 kmplayer.scrren = newfullscreenFunc; } function toggleFullscreen() { kmplayer.scrren(); // 这都能拼错? } $('#rtcContent').on("mousemove", function (event) { console.log(event) }) // 设定播放位置 function setTime(time) { $("#player-00001 #kmd-video-player")[0].currentTime = time; $("#player-00002 #kmd-video-player")[0].currentTime = time + getTimeDelta(); } // 读取播放位置 function getTime() { return $("#player-00001 #kmd-video-player")[0].currentTime; } // 重写状态栏播放状态 function updateTimeText() { kmplayer.setViewSenTime(getTime()); // 时间显示同步 kmplayer.addSpeedRate(getTime()); // 进度条同步 } // 设定音量 let scene_audio_ratio = 1.00; let computer_audio_ratio = 1.00; function setVolume(volume_ratio, idx) { volume_ratio = volume_ratio > 1 ? 1 : volume_ratio; volume_ratio = volume_ratio < 0 ? 0 : volume_ratio; volume_ratio *= 100; switch (idx) { case 0: kmplayer.volume = volume_ratio; kmplayer.setVolume(volume_ratio); rewriteVolume(); $(".voice0-rate").height(volume_ratio); setMuted(volume_ratio == 0); break; case 1: $(".voice" + idx + "-rate").height(volume_ratio); scene_audio_ratio = volume_ratio / 100; break; case 2: $(".voice" + idx + "-rate").height(volume_ratio); computer_audio_ratio = volume_ratio / 100; break; default: break; } } // 为两路声音设置均衡 function rewriteVolume() { if (kmplayer.allInstance.type1.volume()) { kmplayer.allInstance.type1.volume(kmplayer.volume * scene_audio_ratio); } if (kmplayer.allInstance.type2.volume()) { kmplayer.allInstance.type2.volume(kmplayer.volume * computer_audio_ratio); } } // 为两路画面设置同步 function syncTime() { let delta = kmplayer.allInstance.type2.currentTime() - kmplayer.allInstance.type1.currentTime() - getTimeDelta(); if (Math.abs(delta) > 0.2) { kmplayer.allInstance.type2.currentTime(getTime() + getTimeDelta()); } } // 设定小画面的尺寸 function setSmallVideoSize(size) { setStorage(userid + "_zoom_ratio", size); // 重新打开时,记忆上次的小画面尺寸 let num2 = parseInt(size), num1 = 100 - num2; $("style").each(function (idx, elem) { if (elem.innerHTML.includes(".style-type-2-1 .cont-item-2 {top: ")) { $(elem).remove(); } }) GM_addStyle(".style-type-2-1 .cont-item-2 {top: " + num1 + "%;left: " + num1 + "%; width: " + num2 + "%; height: " + num2 + "%;}"); } setSmallVideoSize(current_zoom_ratio); // 首先设置一遍 let time_sync_delta = 0; let last_time_sync = getStorage("video_" + video_id + "_timedelta"); if (last_time_sync != null) { time_sync_delta = parseFloat(last_time_sync); } function setTimeDelta(time_delta) { time_sync_delta = time_delta; setStorage("video_" + video_id + "_timedelta", time_delta); } function getTimeDelta() { return time_sync_delta; } // 读取音量 function getVolume() { return kmplayer.volume / 100; } function getDuration() { return kmplayer.durationSec; } // 输出左上角渐隐提示文本 function putText(text) { $("#custom-status-text>span").html(text); $("#custom-status-text").finish().show().delay(1000).fadeOut(1000); // 完成前stoptruetrue的话,会导致delay不生效,为什么呢? } // 将时间转换为分:秒.毫秒 function timeSec2Text(time) { let time_s = parseInt(time); let time_ms = parseInt((time - parseInt(time)) * 1000); let time_min = parseInt(time_s / 60); let time_sec = parseInt(time_s % 60); return ("00" + parseInt(time_min)).slice(time_min >= 100 ? -3 : -2) + ":" + ("0" + parseInt(time_sec)).slice(-2) + "." + ("00" + parseInt(time_ms)).slice(-3); } // 创建截图 function makeScreenshot(video_id, no_download = false) { let se = $(".cont-item-" + video_id + " #kmd-video-player"); if (se) { let el = se[0]; console.log(el); let t = el.currentTime; // 修正了此前自以为是的画面尺寸都是720p的想法 let w = el.videoWidth; let h = el.videoHeight; // 抄的jAccount验证码识别那个插件 $("body").append($('<canvas width="' + w + '" height="' + h + '" style="display: none;" id="screenshot_canvas"></canvas>')); let canvas_el = document.getElementById("screenshot_canvas"), current_time = timeSec2Text(t).replaceAll(":", "_").replaceAll(".", "_"); canvas_el.getContext("2d").drawImage(el, 0, 0); let screenshot_img = canvas_el.toDataURL("image/jpeg"); $("#screenshot_canvas").remove(); if (no_download) { // 在新标签页打开,而不是下载 //console.log(screenshot_img); window.open("", null, 'width=' + (w + 50) + ',height=' + (h + 50) + ',status=yes,toolbar=no,menubar=no,location=no').document.body.innerHTML = '<img src="' + screenshot_img + '">'; } else { // 下载 $("body").append($('<a href="' + screenshot_img + '" id="download_link" download="canvas_screenshot_' + current_time + '.jpg">下载</a>')); $("#download_link")[0].click(); // 哦是我之前在浏览器里阻止了下载 $("#download_link").remove(); } } } function onStartPlay() { if (is_canvas_vod_page) { // 打开后默认不自动播放视频 setPlay(false, true); setTimeout(function () { // 打开视频后,自动跳转到上次观看的进度 let last_playback = getStorage(userid + "_video_" + video_id + "_position") if (last_playback != null) { last_playback = parseFloat(last_playback); if (last_playback > 10) { // 仅调整观看超过10秒的视频 let restore_time_interval = setInterval(function () { kmplayer.allInstance.type1.currentTime(parseFloat(last_playback)); if (kmplayer.allInstance.type1.currentTime() >= last_playback - 2) { clearInterval(restore_time_interval); putText("已恢复到上次的播放进度:" + ("0" + parseInt(last_playback / 60)).slice(-2) + ":" + ("0" + parseInt(last_playback % 60)).slice(-2)); } }, 1); } } setTimeout(function () { setPlay(false, true); }, 1); // 打开视频后,自动载入上次的默认播放速度 if (getStorage(userid + "_default_speed_val") !== null) { default_time_speed = parseFloat(getStorage(userid + "_default_speed_val")); // 传入字符串时,无效 console.log("load default speed: " + default_time_speed); current_time_speed = default_time_speed; // 应用记忆中的播放速度 setSpeed(current_time_speed); } }, 1); } // 修改默认音量设定为不静音,并移除静音说明 setMuted(false); $(".mute-tip").hide(); } let play_started_check_interval = setInterval(function () { if (getTime() > 0) { // 这个判据不合适吗?为什么有时候会无法成功暂停? clearInterval(play_started_check_interval); onStartPlay(); } }, 20); // 双击画面全屏 $("video").on('dblclick', function (event) { toggleFullscreen(); putText("切换全屏"); event.preventDefault(); // 阻断默认全屏行为 return false; }); // 单击画面暂停/播放 $("video").on('click', function (event) { togglePlay(); }); // 修正视频变形问题(但导致了空边) $("video").css("object-fit", "contain"); // 将黑边修改为空白边 $(".kmd-app-container").css("background-color", "transparent"); $(".kmd-app").css("background-color", "transparent"); $(".kmd-container").css("background-color", "transparent"); $(".kmd-player").css("background-color", "transparent"); $("#rtcContent").css("background-color", "transparent"); if (is_canvas_vod_page) { $("#rtcMain").css("background-color", "transparent"); $(".video-box").css("background-color", "black"); } else { $("#rtcMain").css("background-color", "black"); } $("#rtcMain").focus(); // 说不定有点用? // 支持注册 MediaSession 从而通过系统控制视频的播放与暂停 if ('mediaSession' in navigator && is_canvas_vod_page) { let elem = $(".list-main .list-item--active>.item-text")[0]; let media_title = $(elem).children("p:first").html(); let media_artist = this_teacher; navigator.mediaSession.metadata = new MediaMetadata({ title: media_title, artist: media_artist, }); navigator.mediaSession.setActionHandler('play', function () { setPlay(true) console.log("MediaSession请求播放!"); }); navigator.mediaSession.setActionHandler('pause', function () { setPlay(false) console.log("MediaSession请求暂停!"); }); navigator.mediaSession.setActionHandler('stop', function () { kmplayer.stop("stop") console.log("MediaSession请求停止!"); }); navigator.mediaSession.playbackState = "paused"; } // 当视频链接过期时,自动刷新链接 function refreshVideoLink() { // 本函数效果不理想,更改视频源后,播放进度将自动复位,倒不如刷新整个页面 $.ajax({ type: "POST", url: "/lti/vodVideo/getVodVideoInfos", async: true, traditional: true, dataType: "json", data: { playTypeHls: true, id: video_id, isAudit: true }, success: function (data) { let video_link_array = data.body.videoPlayResponseVoList; let current_time = getTime(); $(".kmd-wrapper video#kmd-video-player").each(function (idx, elem) { elem.src = video_link_array[idx].rtmpUrlHdv; }) setTime(current_time); console.log(video_link_array); } }); } $(".kmd-wrapper video#kmd-video-player").each(function (idx, elem) { elem.onerror = function () { console.log("video error: " + elem.error.code + "; details: " + elem.error.message); if (is_iframe) { refresh_session(true); // 然后在onmessage里刷新页面 } else { // 独立窗口中无法自动更新会话,切回大窗口 window.location.href("https://oc.sjtu.edu.cn/courses/" + getStorage("course_" + course_id + "_canvasid") + "/external_tools/162"); } } }) let periodic_job_interval = setInterval(periodic_job, 25); // 25ms 的定时任务 function periodic_job() { if (!kmplayer.allInstance.type1) { // 检查直播状态是否已经结束(否则会导致下方过程中出错) clearInterval(periodic_job_interval); } // 状态栏时间显示帧率优化(由1秒刷新加速为0.05秒刷新) updateTimeText(); { let screen_switched = $(".cont-item-1 .kmd-app-container").attr("id") != "player-00001"; let bA = screen_switched ? effect_setting.brightnesssA : effect_setting.brightnesssB; let bB = !screen_switched ? effect_setting.brightnesssA : effect_setting.brightnesssB; let cA = screen_switched ? effect_setting.contrastA : effect_setting.contrastB; let cB = !screen_switched ? effect_setting.contrastA : effect_setting.contrastB; let bl1 = effect_setting.blurA * $("#player-00001 #kmd-video-player")[0].offsetHeight / 1000; let bl2 = effect_setting.blurB * $("#player-00002 #kmd-video-player")[0].offsetHeight / 1000; let blA = screen_switched ? bl1 : bl2; // 这个居然和画面大小也有关。。。 let blB = !screen_switched ? bl1 : bl2; let op = effect_setting.opacity; $(".cont-item-1 #kmd-video-player").css("filter", "brightness(" + bB + ") contrast(" + cB + ") blur(" + blB + "px)"); $(".cont-item-2 #kmd-video-player").css("filter", "brightness(" + bA + ") contrast(" + cA + ") blur(" + blA + "px) opacity(" + op + ")"); } // 有两路视频时 if (!is_single_video) { rewriteVolume(); // 为两路声音设置均衡 if (is_canvas_vod_page) { // 否则会导致直播视频加载异常 syncTime(); // 开启画面同步控制功能 if ($(".kmd-wrapper #kmd-video-player")[0].paused != $(".kmd-wrapper #kmd-video-player")[1].paused) { setPlay(false, true); // 修复禁用启动后自动播放时的冲突导致的可能的暂停不彻底 } } } // 支持注册 MediaSession 从而通过系统控制视频的播放与暂停 // 禁用了MediaSession if (is_canvas_vod_page && 'mediaSession' in navigator && 0) { navigator.mediaSession.setPositionState({ duration: getDuration(), playbackRate: parseFloat($("#timesContorl>span").html()) || 1, position: getTime() }); } // 记录当前进度 if (is_canvas_vod_page && isPlaying()) { if (getStorage(userid + "_video_" + video_id + "_position") && getTime() < 5) { // 本次根本没播放 } else { setStorage(userid + "_video_" + video_id + "_position", getTime()); } } } // 倍速列表选项优化(0.5-16倍) let speed_choice = [0.5, 0.75, 0.9, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 3, 3.5, 4, 5, 6, 8, 16]; // 16是该播放器支持的最高倍速 speed_choice.sort(function (a, b) { return a - b; }); let speed_choice_text = speed_choice.map(function (num) { return '<li id="' + num + '">' + num + '倍</li>'; }).join(""); $("#timesContorl>ul").html(speed_choice_text); $("#timesContorl>ul>#1").attr("class", "times-active"); // 默认1倍高亮 // 在调整倍速后自动关闭菜单 $("#timesContorl").bind("click", function (event) { if (event.target.tagName == "LI") { // 状态栏显示倍速 $("#timesContorl>span").html(event.target.id); $("#timesContorl>ul").css("display", "none"); current_time_speed = parseFloat(event.target.id); putText("设定倍速:" + current_time_speed); } return true; }); if (is_canvas_live_page) { // 移除了直播页面中自欺欺人的【暂停】按钮(看来不容易直接移除。。算了) // 移除了直播页面中的播放时间显示 $(".tool-view-time").hide(); } // 移除了点播页面缺乏使用场景、直播页面没有作用的【停止播放】按钮 $(".tool-btn__stop").remove(); // 移除了直播页面中没有任何作用的【画质】按钮 $(".tool-btn__sharp").remove(); // 增加了画中画模式,并允许从设置中直接选择所需画面 let split_choice_id = ["split_scene_only", "split_computer_only", "split_big_small", "split_pip"]; let split_choice_desc = ["仅现场画面", "仅电脑画面", "一大一小", "进入画中画"]; let split_choice_class = ["style-type-1-1", "style-type-1-1", "style-type-2-1", "style-type-2-1 style-type-2-1-pip"]; let split_choice_text = split_choice_id.map(function (id, idx) { return '<li id="' + id + '" class="' + split_choice_class[idx] + '">' + split_choice_desc[idx] + '</li>'; }).join(""); $("#splitContorl>ul").html(split_choice_text); $("#splitContorl>ul>#split_big_small").css("background", "white").css("color", "#0a0a0a"); // 默认一大一小 // 仅有一路视频时,禁用各类画面布局功能 if (is_single_video) { $("#splitContorl").remove(); } // 移动端似乎不支持画中画 if (isAndroid()) { $("#splitContorl>ul>#split_pip").remove(); } // 有两路视频时,开启子音量控制功能 $(".voice-volume .voice-max").attr("class", "voice-max voice0-max"); $(".voice-volume .voice-rate").attr("class", "voice-rate voice0-rate"); if (!is_single_video) { $(".voice-volume").css("width", "80px").css("text-align", "center"); $(".voice-volume").append($('<div class="voice-max voice1-max" style="width: 8px;"><div class="voice-rate voice1-rate voice-rate-child" style="height: 100%;"><div class="tool-rate-tip"></div></div></div>')); $(".voice-volume").append($('<div class="voice-max voice2-max" style="width: 8px;"><div class="voice-rate voice2-rate voice-rate-child" style="height: 100%;"><div class="tool-rate-tip"></div></div></div>')); $(".voice-volume>div").css("display", "inline-block").css("margin", "0 9px"); $(".voice-volume").css("transform", "translateX(-35px)"); $(".voice-volume").append($('<span class="voice-desc voice0-desc">总控</span>')); $(".voice-volume").append($('<span class="voice-desc voice1-desc">现场</span>')); $(".voice-volume").append($('<span class="voice-desc voice2-desc">电脑</span>')); $(".voice-volume>span").css("position", "absolute") .css("top", "50%") .css("font-size", "12px") .css("width", "50px"); $(".voice-volume>.voice0-desc").css("transform", "translateX(-87px) translateY(50px)"); $(".voice-volume>.voice1-desc").css("transform", "translateX(-62px) translateY(50px)"); $(".voice-volume>.voice2-desc").css("transform", "translateX(-37px) translateY(50px)"); } // 切换画中画设定 function enable_PiP(flag) { if (flag) { let video2 = $(".cont-item-2 #kmd-video-player"); // 右下角视频 video2.on('enterpictureinpicture', function () { $(".cont-item-2").hide(); // 进入画中画,隐藏原小窗 $("#split_pip").html("退出画中画"); }); video2.on('leavepictureinpicture', function () { $(".cont-item-1").css("display", ""); // 退出画中画,恢复原小窗 $(".cont-item-2").css("display", ""); // 退出画中画,恢复原小窗 $("#split_pip").html("进入画中画"); $("#split_big_small").click(); }); video2[0].requestPictureInPicture(); } else { document.exitPictureInPicture(); } } let split_replaying_flag = false, split_changed = 0; $("#splitContorl").bind("click", function (event) { if (event.target.tagName == "LI") { split_changed++; $(event.target).css("background", "white").css("color", "#0a0a0a"); $(event.target).siblings().each(function (idx, elem) { $(elem).css("background", "").css("color", ""); }); let changed_flag = false; if ($("#split_pip").html().includes("退出")) { enable_PiP(false); $("#split_pip").html("进入画中画"); changed_flag = true; } switch ($(event.target).attr("id")) { case "split_computer_only": // 仅电脑画面 $("#player-00002").parent(".rtc-cont-item").attr("class", "rtc-cont-item cont-item-1"); $("#player-00001").parent(".rtc-cont-item").attr("class", "rtc-cont-item cont-item-2"); if (!split_replaying_flag) { split_replaying_flag = true; setTimeout(function () { $(event.target).click(); split_replaying_flag = false; }, 1); } break; case "split_scene_only": // 仅现场画面 $("#player-00001").parent(".rtc-cont-item").attr("class", "rtc-cont-item cont-item-1"); $("#player-00002").parent(".rtc-cont-item").attr("class", "rtc-cont-item cont-item-2"); if (!split_replaying_flag) { split_replaying_flag = true; setTimeout(function () { $(event.target).click(); split_replaying_flag = false; }, 1); } break; case "split_pip": if ($(event.target).html().includes("进入") && !changed_flag) { enable_PiP(true); // $(event.target).html("退出画中画"); } break; default: break; } $("#rtcContent").attr("class", $(event.target).attr("class")); // 虽然网页里已经定义过这个事件,但是好像有些晚,所以我在这重复操作一遍。 } }); // 直播中没有电脑视频流时,以【仅现场画面】模式启动 if (is_canvas_live_page) { $("#split_scene_only").click(); let computer_video = $(".cont-item-2 #kmd-video-player"); computer_video.one("playing", function () { console.log("playing....", 321); if (split_changed == 2) { $("#split_big_small").click(); // 切换回一大一小模式 } }); if (computer_video[0].currentTime > 0) { //防止刚才趁程序不注意就开始了播放 computer_video.trigger("playing"); } } // 鼠标位于进度条上或拖动进度条时浮窗显示时刻 $("#rtcContent").append('<div id="custom-progress-hover"><svg viewBox="-3 -3 37.6 23"> <polygon points="17.3,20 0,0 34.6,0" style="fill:#0092E9AA; stroke:black; stroke-width:3;"/></svg><span>12:00</span></div>'); $("#custom-progress-hover") // 进度条,百万大制作 .css("position", "absolute") .css("text-align", "center") .css("display", "none") .css("background-color", "#0092E9AA") .css("border", "2px solid black") .css("border-radius", "10px") .css("box-shadow", "#AAA 0px 0px 10px") .css("margin", "8px 8px") .css("z-index", "11") .css("padding", "5px 5px;") .css("width", "60px") .css("height", "25px"); $("#custom-progress-hover span") .css("user-select", "none") .css("position", "absolute") .css("top", "50%") .css("transform", "translateX(-50%) translateY(-50%)") .css("font-weight", "bold") .css("font-size", "14px"); $("#custom-progress-hover svg") .css("position", "absolute") .css("top", "50%") .css("width", "15px") .css("transform", "translateX(-50%) translateY(140%)"); // 浮窗显示 $(".tool-progress").on("mousemove", function (event) { let progress_ratio = (event.pageX - $(".tool-progress").offset().left) / $(".tool-progress").width(); progress_ratio = progress_ratio < 0 ? 0 : progress_ratio; progress_ratio = progress_ratio > 1 ? 1 : progress_ratio; $("#custom-progress-hover").css("display", "block"); $("#custom-progress-hover").css("top", $("#rtcTool").position().top - 48); $("#custom-progress-hover").css("left", event.pageX - $("#rtcTool").offset().left - 40); let progress_sec = parseInt(progress_ratio * getDuration()); $("#custom-progress-hover>span").html(parseInt(progress_sec / 60) + ":" + ("0" + parseInt(progress_sec % 60)).slice(-2)); if (progressBar_operating_flag) { setTime(progress_sec); console.log(getTime()); } }); $(".tool-progress").on("mouseleave", function (event) { $("#custom-progress-hover").css("display", "none"); }); // 扩展进度条的可点击范围高度,便于操作 GM_addStyle(".tool-progress {z-index: 12;}") GM_addStyle(".tool-progress > div {height:4px !important; position:absolute !important;}") $(".tool-progress").append('<div style="width: 100%; height: 25px !important; background-color: transparent; transform: translateY(-16px); cursor:auto; z-index: 11;" class="tool-progress"></div>'); // 在画面左上角显示自动渐隐的文本,显示状态变化 $("#rtcContent").append('<div id="custom-status-text"><span></span></div>'); $("#custom-status-text") .css("position", "absolute") .css("text-align", "center") .css("display", "none") .css("background-color", "#CCCA") .css("margin", "5px 5px") .css("z-index", "11") .css("padding", "0px 2px") // 嘤嘤嘤因为这里不小心加了分号,竟然一直没生效过 .css("height", "20px") .css("top", 0) .css("left", 0); $("#custom-status-text>span") .css("font-size", "16px") .css("line-height", "20px") .css("user-select", "none") // 禁止选中 .css("color", "blue"); // 修复了暂停状态下改变进度条导致意外继续播放且进度条不再刷新的问题 function repair_pause_playing() { if (!isPlaying()) { setTimeout(function () { if (!isPlaying()) { setPlay(false, true); // 这种情况下不展示消息提醒 } }, 1); } } $("body").on("mouseup", function (event) { // console.log("mouseup",event.originalEvent.button); if (event.originalEvent.button != 0) { // 仅响应鼠标左键 return; } let target_el_class = $(event.target).attr("class"); // 使用按钮进行播放和暂停也可以显示文字提示了 if (Object.prototype.toString.call(target_el_class) === "[object String]") { if ($(event.target).attr("class").includes("tool-btn__play")) { putText("状态:播放"); } else if ($(event.target).attr("class").includes("tool-btn__pause")) { putText("状态:暂停"); } } progressBar_operating_flag = false; repair_pause_playing(); //return true; }); $("body").on("mousedown", function (event) { // console.log("mousedown",event.originalEvent.button); if (event.originalEvent.button != 0) { // 仅响应鼠标左键 return; } if (!$(event.target).attr("class")) { } else if ($(event.target).attr("class").startsWith("tool-progress")) { // 避免鼠标操作进度条时自动刷新进度条 progressBar_operating_flag = true; } else if ($(event.target).attr("class").startsWith("voice-")) { // 允许鼠标点击音量条任意位置设定音量并解除静音 let click_height = event.pageY; let clicked_idx_match = $(event.target).attr("class").match(new RegExp("voice(\\d)-")); // 这里改成另一个格式的RegExp之后要转义反斜杠了 if (clicked_idx_match) { let clicked_idx = parseInt(clicked_idx_match[1]); let voicebar_height = $(".voice0-max").height(); let voicebar_base = $(".voice0-max").offset().top; let volume_ratio = 1 - (click_height - voicebar_base) / voicebar_height; setVolume(volume_ratio, clicked_idx); } } //return true; }); let in_ultra_slow_mode = false, is_mute_before_ultra_slow_mode; // 在进入超慢速模式前是否静音,一定要静音否则声音太怪了 $("body").on("keyup", function (event) { let is_using_default_speed = (Math.abs(getSpeed() - default_time_speed) < 0.005); // console.log("keyup:",event.target); switch (event.keyCode) { case 37: // 方向键左 case 39: // 方向键右 case 68: // 字母D,上一帧 case 70: { // 字母F,下一帧 repair_pause_playing(); break; } case 190: { // .键 // 使用.键进入超慢速定位模式,方便寻找合适的时间点(替代不易实现的下一帧功能) if (is_using_default_speed) { setSpeed(default_time_speed); // 恢复到之前的播放速度 } else { setSpeed(current_time_speed); } setPlay(false, true); in_ultra_slow_mode = false; if (!is_mute_before_ultra_slow_mode) { setMuted(false); } break } default: break; } }); $("body").on("keydown", function (event) { // console.log("keydown:",event.target,event.keyCode); // 左右方向键进行时移 // Ctrl+左右方向键进行快速时移 // Ctrl+Shift+左右方向键进行超快速时移 const SMALL_TIME_STEP = 3; const MIDDLE_TIME_STEP = 15; const BIG_TIME_STEP = 59; let step = SMALL_TIME_STEP; if (event.ctrlKey && event.shiftKey) { step = BIG_TIME_STEP; } else if (event.ctrlKey) { step = MIDDLE_TIME_STEP; } let is_using_default_speed = (Math.abs(getSpeed() - default_time_speed) < 0.005); switch (event.keyCode) { case 32: { // 空格,暂停/播放 if (!is_canvas_vod_page) break; //仅针对点播 togglePlay(); break; } case 49: { // 数字1,左屏幕截图 makeScreenshot(1, event.shiftKey); break; } case 50: { // 数字2,右屏幕截图 makeScreenshot(2, event.shiftKey); break; } case 37: { // 方向键左,快退 if (!is_canvas_vod_page) break; //仅针对点播 let target_time = getTime() - step; target_time = target_time < 0 ? 0 : target_time; setTime(target_time); putText("快退"); // console.log("minus:", step, "to:", target_time); break; } case 39: { // 方向键右,快进 if (!is_canvas_vod_page) break; //仅针对点播 let target_time = getTime() + step; target_time = target_time > getDuration() ? getDuration() : target_time; setTime(target_time); putText("快进"); // console.log("add:", step, "to:", target_time); break; } case 68: { // 字母D,上一帧 if (!is_canvas_vod_page) break; //仅针对点播 if (isPlaying()) setPlay(false, true); // 暂停 let target_time = getTime() - 1 / 30; target_time = target_time < 0 ? 0 : target_time; setTime(target_time); putText("上一帧 当前时刻:" + timeSec2Text(getTime())); // console.log("add:", step, "to:", target_time); break; } case 70: { // 字母F,下一帧 if (!is_canvas_vod_page) break; //仅针对点播 if (isPlaying()) setPlay(false, true); // 保持暂停状态 let target_time = getTime() + 1 / 30; target_time = target_time > getDuration() ? getDuration() : target_time; setTime(target_time); putText("下一帧 当前时刻:" + timeSec2Text(getTime())); // console.log("minus:", step, "to:", target_time); break; } case 190: { // .键 // 使用.键进入超慢速定位模式,方便寻找合适的时间点(替代不易实现的下一帧功能) if (!is_canvas_vod_page) break; //仅针对点播 if (!in_ultra_slow_mode) { in_ultra_slow_mode = true; is_mute_before_ultra_slow_mode = getMuted(); if (!is_mute_before_ultra_slow_mode) { setMuted(true); } setSpeed(0.3); // 0.3倍速似乎较为合适 if (!isPlaying()) { setPlay(true, true); } } putText("超慢速播放中"); break; } case 67: { // 字母C if (!is_canvas_vod_page) break; //仅针对点播 current_time_speed = Math.round(current_time_speed * 10 + 1) / 10; if (current_time_speed > 16) { current_time_speed = 16; } putText("增加倍速:" + current_time_speed.toFixed(1)); setSpeed(current_time_speed); break; } case 88: { // 字母X if (!is_canvas_vod_page) break; //仅针对点播 current_time_speed = Math.round(current_time_speed * 10 - 1) / 10; if (current_time_speed < 0.1) { current_time_speed = 0.1; } putText("减小倍速:" + current_time_speed.toFixed(1)); setSpeed(current_time_speed); break; } case 90: { // 字母Z if (!is_canvas_vod_page) break; //仅针对点播 if (is_using_default_speed) { setSpeed(current_time_speed); putText("恢复倍速:" + current_time_speed.toFixed(1)); } else { setSpeed(default_time_speed); putText("暂停倍速:" + default_time_speed.toFixed(1)); } break; } case 65: { // 字母A if (!is_canvas_vod_page) break; //仅针对点播 default_time_speed = Math.round(default_time_speed * 10 - 1) / 10; // 不然会有那么那么多零。。。 if (default_time_speed < 0.1) { default_time_speed = 0.1; } putText("减小默认倍速:" + default_time_speed.toFixed(1)); if (is_using_default_speed) { setSpeed(default_time_speed); } setStorage(userid + "_default_speed_val", default_time_speed); break; } case 83: { // 字母S if (!is_canvas_vod_page) break; //仅针对点播 default_time_speed = Math.round(default_time_speed * 10 + 1) / 10; // 不然会有那么那么多零。。。 if (default_time_speed > 16) { default_time_speed = 16; } putText("增加默认倍速:" + default_time_speed.toFixed(1)); if (is_using_default_speed) { setSpeed(default_time_speed); } setStorage(userid + "_default_speed_val", default_time_speed); break; } case 13: { // 使用Enter键切换全屏 toggleFullscreen(); putText("切换全屏"); break; } default: return true; } event.preventDefault(); return false; }); $("body").on("mousewheel", function (event) { event.preventDefault(); event.stopPropagation(); return false; }); $("body").on("wheel", function (event) { // console.log("wheel",event.originalEvent.deltaY); event.preventDefault(); event.stopPropagation(); // 仅在视频上使用滚轮生效 if (event.target.tagName != "VIDEO") { return; } // 可以在小画面上使用鼠标滚轮缩放画面 if (event.target == $(".cont-item-2 #kmd-video-player")[0]) { let max_ratio = 80, min_ratio = 15; current_zoom_ratio -= event.originalEvent.deltaY / 25; current_zoom_ratio = current_zoom_ratio > max_ratio ? max_ratio : current_zoom_ratio; current_zoom_ratio = current_zoom_ratio < min_ratio ? min_ratio : current_zoom_ratio; setSmallVideoSize(current_zoom_ratio); } else { let volDelta = event.originalEvent.deltaY / 2500; setVolume(getVolume() - volDelta, 0); // 使用滚轮调节音量时,会在画面左上角显示自动渐隐的文本,显示音量变化 putText("Volume: " + parseInt(getVolume() * 100)); } return false; }); } let video_check_interval = setInterval(function () { if ($("video").length && kmplayer.ids.length && $(".kmd-wrapper #kmd-video-player")[0]) { // 直到video元素和#timesContorl元素和视频生成,大概能表明播放器完全加载出来了 clearInterval(video_check_interval); work_all(); } else { if ($(".not-data").length || no_live_video_played) { clearInterval(video_check_interval); console.log("无直播视频,退出检测"); } console.log("loading..."); } }, 50); function refresh_session(andRefresh = false) { if (andRefresh) { top.postMessage("helpr!", "https://oc.sjtu.edu.cn"); } else { top.postMessage("help!", "https://oc.sjtu.edu.cn"); } } if (is_iframe) { $(document).on('visibilitychange', function (event) { //从外面回来时 if (!document.hidden && auto_refresh_flag) { refresh_session(); } }) setInterval(refresh_session, 20 * 60 * 1000); // 20分钟更新一次,防止页面失效 } // 禁止标题文字被选中,改善体验……算了不改了,没改善。 // $(".list-title").css("user-select","none"); // 本插件顺利生效时,顶部标签卡颜色为金色 $(".tab-item--active").css("color", "gold"); $(".tab-item--active").css("border-bottom", "2px solid gold"); // 将画面背景色由廉价的灰色改为圣洁白 $(".lti-page").css("background-color", "transparent"); // 将部分白色背景色改为透明色 $(".lti-video").css("background-color", "transparent"); $(".list-title").css("background-color", "transparent"); $(".list-item").css("background-color", "transparent"); $(".lti-page-tab").css("background-color", "transparent"); $(".list-item").css("color", "gray"); $(".list-item--active").children().css("color", "white"); // 关灯纯色元素 $(".loading").css("z-index", "103"); // 这个元素要始终能够遮盖整个画面 $("#rtcMain") .css("position", "absolute") .css("z-index", "102"); // 父框架最高z-index为100,遮罩为101 $("body").append($('<div class="light-turn-off"></div>')) $(".light-turn-off") .css("position", "fixed") .css("inset", "0") .css("background-color", "black") .css("z-index", "101") .css("display", "none"); // 父框架最高z-index为100,遮罩为101 // 通过增加边框使右侧视频列表更有质感 $(".lti-list").css("border", "2px solid black") .css("border-radius", "10px"); $(".list-title").css("border-bottom", "1px solid black"); $(".lti-list>div").css("background-color", "transparent"); $(".lti-list").height("460px"); $(".list-main").height("400px"); // 移除了底部课程信息区域,压缩页面高度 $(".course-details").height(0); // 将播放控制栏底色由廉价的灰色改为至尊黑 GM_addStyle("#rtcTool .tool-bar {background-color: black;}"); GM_addStyle("#rtcContent {background-color: black;}"); $(".tool-bar").click() // 随便点一下,不知道有没有用 console.log("%c施工完毕,辛苦了!", "color:#0FF;"); } } // 将 vshare 网站的视频播放器替换为浏览器内置播放器 else if (is_vshare_page) { if ($(".video-wrapper").length) { let wait_video_interval = setInterval(function () { if (!$("#video-share_html5_api").attr("src")) { return; } clearInterval(wait_video_interval); // 恢复页面对基本操作的响应 let document = window.document; document.onkeydown = document.oncontextmenu = undefined; document.body.oncontextmenu = document.body.onselectstart = undefined; // 替换播放器 let v_src = $("#video-share_html5_api").attr("src"); $("#video-share_html5_api")[0].pause(); // 终止原视频的播放 $("#video-share_html5_api")[0].src = ""; // 终止原视频的播放 $(".video-wrapper").replaceWith($('<video src="' + v_src + '"id="new_player" controls></video>')); $("#new_player").css("height", "100%").css("width", "100%"); }, 10); } } })();