Greasy Fork

Greasy Fork is available in English.

Canvas 平台录播播放器翻天覆地彻头彻尾焕然一新加强插件

优化上海交通大学 Canvas 平台录播观看功能

当前为 2021-10-06 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Canvas 平台录播播放器翻天覆地彻头彻尾焕然一新加强插件
// @namespace    http://tampermonkey.net/
// @version      1.3.3
// @description  优化上海交通大学 Canvas 平台录播观看功能
// @author       danyang685
// @match        https://oc.sjtu.edu.cn/*
// @match        https://courses.sjtu.edu.cn/*
// @icon         data:image/png;base64,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
// @grant        GM_info
// @grant        unsafeWindow
// @license      MIT
// ==/UserScript==

// 功能清单:
// [x] 到达 Canvas 登录页时,自动跳转到 jAccount 登录页
// [x] 在 Canvas 课程页面访问【课程视频】时,默认进入【点播】功能
// [x] 禁用了打开后自动开始播放视频的特性
// [x] 倍速列表选项优化(0.5-16倍)
// [x] 将全局右键屏蔽改为仅应用至视频区域
// [x] 状态栏时间显示帧率优化(由1秒刷新加速为0.05秒刷新)
// [x] 加快状态栏功能弹窗隐藏速度
// [x] 允许鼠标点击音量条任意位置设定音量并解除静音
// [x] 可以使用鼠标滚轮调节音量了,调节时会在画面左上角显示当前音量
// [x] 隐藏了小音量控制按钮,并在音量菜单增加了两路子音量控制
// [x] 状态栏显示倍速
// [x] 在调整倍速后自动关闭菜单
// [x] 空格键或单击画面暂停/播放
// [x] 双击画面全屏
// [x] 左右方向键进行时移(3秒)
// [x] Ctrl+左右方向键进行快速时移(15秒)
// [x] Ctrl+Shift+左右方向键进行超快速时移(59秒)
// [x] 修正视频变形问题(但导致了空边)
// [x] 将黑边修改为空白边
// [x] 鼠标位于进度条上或拖动进度条时浮窗显示时刻
// [x] 修复了暂停状态下改变进度条导致意外继续播放且进度条不再刷新的问题
// [x] 为小窗视频略微增加透明度
// [x] 修改默认音量设定为不静音,并移除静音说明
// [x] 在右上角增加【在新标签页播放】按钮
// [x] 清空了视频下方的说明文字
// [x] 使右侧视频列表的内容更紧凑,并增加了教室显示,并去除了时间显示的00秒部分
// [x] 增加了画中画模式,并允许从设置中直接选择所需画面
// [x] 仅有一路视频时,禁用了画面布局按钮
// [x] 增加了双路视频手动同步功能,能够比较方便地进行画面同步
// [x] 支持注册 MediaSession 从而通过系统控制视频的播放与暂停
// [x] 阻止向服务器回报观看日志
// [x] 去除了画面中不必要的滚动条
// [x] 通过增加边框使右侧视频列表更有质感
// [x] 将画面背景色由廉价的灰色改为圣洁白
// [x] 将播放控制栏底色由廉价的灰色改为至尊黑
// [x] 本插件顺利生效时,顶部标签卡颜色为金色

(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_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 == "https://courses.sjtu.edu.cn/lti/app/lti/liveVideo/index.d2j";
    // 直录播LTI插件页面
    let is_canvas_vodnlive_lti_page = new RegExp("https://oc\.sjtu\.edu\.cn/courses/\\d*/external_tools/162").test(window.location.href);

    // 检查是否为安卓设备
    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");
    }

    // 在 Canvas 课程页面访问【课程视频】时,默认进入【点播】功能
    if (is_canvas_live_page) {
        if (document.referrer == "https://oc.sjtu.edu.cn/") {
            window.location.replace("https://courses.sjtu.edu.cn/lti/app/lti/vodVideo/playPage");
        }
    }

    // LTI插件页面也有点UI问题可以修改
    if (is_canvas_vodnlive_lti_page) {
        $(document).ready(function () {
            // 去除了画面中不必要的滚动条
            /*
            let set_height_interval = setInterval(function () {
                $(".ic-Layout-contentMain>.tool_content_wrapper")
                    .css("height", "100%");
            }, 100);
            */

            $(".screenreader-only").height(0);

            // 去除了左侧的空白区域
            $(".ic-Layout-contentMain")
                .css("height", "100%")
                .css("padding", "0");

            $("#tool_content")
                .css("min-height", "600px");
        })
    }



    // 让 Canvas 平台录播播放器翻天覆地彻头彻尾焕然一新
    if (is_vod_page) {
        console.log("Canvas录播加强!");

        $(document).ready(function () {
            let progressBar_operating_flag = false; // 手动拖动进度条的过程中,不应覆写进度条状态
            let is_single_video = undefined;

            // 是否正在播放/暂停
            function isPlaying() {
                return $(".tool-btn__play").css("display") == "none";
            }

            // 设置播放/暂停
            function setPlay(status) {
                if (status) {
                    kmplayer.play("play");
                    navigator.mediaSession.playbackState = "playing";
                } else {
                    kmplayer.play("pause");
                    navigator.mediaSession.playbackState = "paused";
                }
            }

            // 切换播放/暂停
            function togglePlay() {
                setPlay(!isPlaying());
            }

            // 设定播放位置
            function setTime(time) {
                kmplayer.timeUpdateFlag = false; // 它里面就这么写的
                kmplayer.setKMediaRate(time);
            }

            // 读取播放位置
            function getTime() {
                return kmplayer.allInstance.type1.currentTime();
            }

            // 重写状态栏播放状态
            function updateTimeText() {
                if (!kmplayer || progressBar_operating_flag) {
                    return;
                }
                // console.log("update text!");
                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);
                        kmplayer.voice("muted");
                        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());
                    // console.log("resync video2");
                }
            }

            let time_sync_delta = 0;

            function getTimeDelta() {
                return time_sync_delta;
            }

            let dbg_inteval = setInterval(function () {
                //console.log(kmplayer.volume);
            }, 300);

            // 读取音量
            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不生效,为什么呢?
            }



            // 覆盖已有设定,有一说一,光屏蔽F12有用吗,Chrome还可以 Ctrl+Shift+I 呢。
            window.onkeydown = window.onkeyup = window.onkeypress = window.oncontextmenu = undefined;

            let video_check_interval = setInterval(function () {
                if ($("video").length && $("#timesContorl").length) { // 直到video元素和#timesContorl元素生成,大概能表明播放器完全加载出来了
                    clearInterval(video_check_interval);

                    // 打开后默认不自动播放视频
                    let auto_pause_interval = setInterval(function () {
                        // console.log("time",getTime());
                        if (getTime() != 0) {
                            clearInterval(auto_pause_interval);
                            setPlay(false);
                        }
                    }, 50);

                    // 特殊判断只有一个视频流的点播视频
                    is_single_video = $(".cont-item-2").length == 0;

                    // 双击画面全屏
                    $("video").on('dblclick', function () {
                        kmplayer.scrren(); // 这都能拼错?
                        event.preventDefault(); // 阻断默认全屏行为
                        return false;
                    });

                    // 空格键或单击画面暂停/播放
                    $("video").on('click', function () {
                        togglePlay();
                    });

                    // 隐藏音量控制
                    $(".voice-icon").hide();

                    // 修正视频变形问题(但导致了空边)
                    $("video").css("object-fit", "contain");

                    // 将黑边修改为空白边
                    $(".kmd-app-container").css("background-color", "#0000");
                    $(".kmd-app").css("background-color", "#0000");
                    $(".kmd-container").css("background-color", "#0000");
                    $(".kmd-player").css("background-color", "#0000");
                    $("#rtcContent").css("background-color", "#0000");

                    $("#rtcMain").focus(); // 说不定有点用?

                    // 清空了视频下方的说明文字
                    $(".course-details").empty();

                    // 使右侧视频列表的内容更紧凑了,并增加了教室显示
                    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;
                        }
                    });

                    // 将右侧视频列表改为升序了
                    // $(".list-main").html($(".list-main>.list-item").get());

                    // 支持注册 MediaSession 从而通过系统控制视频的播放与暂停
                    if ('mediaSession' in navigator) {
                        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)
                        });
                        navigator.mediaSession.setActionHandler('pause', function () {
                            setPlay(false)
                        });
                        navigator.mediaSession.setActionHandler('stop', function () {
                            kmplayer.stop("stop")
                        });
                        navigator.mediaSession.playbackState = "paused";

                        // navigator.mediaSession.setActionHandler('seekbackward', function() { /* Code excerpted. */ });
                        // navigator.mediaSession.setActionHandler('seekforward', function() { /* Code excerpted. */ });
                        // navigator.mediaSession.setActionHandler('seekto', function() { /* Code excerpted. */ });
                        // navigator.mediaSession.setActionHandler('previoustrack', function() { /* Code excerpted. */ });
                        // navigator.mediaSession.setActionHandler('nexttrack', function() { /* Code excerpted. */ });
                        // navigator.mediaSession.setActionHandler('skipad', function() { /* Code excerpted. */ });
                    }

                    // 加快状态栏功能弹窗隐藏速度
                    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"));

                    // 修改默认音量设定为不静音,并移除静音说明
                    kmplayer.voice("voice!");
                    $(".mute-tip").hide();

                    setInterval(function () {
                        // 状态栏时间显示帧率优化(由1秒刷新加速为0.05秒刷新)
                        updateTimeText();
                        // 为小窗视频略微增加透明度
                        $(".cont-item-2 #kmd-video-player").css("filter", "brightness(1) contrast(1) saturate(1) hue-rotate(0deg) opacity(0.7)");
                        $(".cont-item-1 #kmd-video-player").css("filter", "brightness(1) contrast(1) saturate(1) hue-rotate(0deg)");
                        // 为两路声音设置均衡
                        rewriteVolume();
                        // 有两路视频时,开启画面同步控制功能
                        if (!is_single_video) {
                            syncTime();
                            if ($(".kmd-wrapper #kmd-video-player")[0].paused != $(".kmd-wrapper #kmd-video-player")[1].paused) {
                                setPlay(false); // 修复禁用启动后自动播放时的冲突导致的可能的暂停不彻底
                            }
                        }

                        // 支持注册 MediaSession 从而通过系统控制视频的播放与暂停
                        if ('mediaSession' in navigator) {
                            navigator.mediaSession.setPositionState({
                                duration: getDuration(),
                                playbackRate: parseFloat($("#timesContorl>span").html()) || 1,
                                position: getTime()
                            });
                        }
                    }, 50);

                    // 倍速列表选项优化(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");
                        }
                        return true;
                    });

                    // 增加了画中画模式,并允许从设置中直接选择所需画面
                    let split_choice_id = ["split_computer_only", "split_scene_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)");
                    }

                    // 有两路视频时,能够通过设定参考点的方式进行画面同步
                    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 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 ("0" + parseInt(time_min)).slice(-2) + ":" + ("0" + parseInt(time_sec)).slice(-2) + "." + ("00" + parseInt(time_ms)).slice(-3);
                        }

                        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));
                                        event.target.value = "撤销现场画面参考";
                                    } else {
                                        time_sync_delta = 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));
                                        event.target.value = "撤销电脑画面参考";
                                    } else {
                                        time_sync_delta = 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();
                                    time_sync_delta = 0;
                                    break;
                                }
                            }
                        });

                        new_hoverleave_callback($("#syncControl>div"), $("#syncControl"));
                    }

                    // 切换画中画设定
                    function enable_PiP(flag) {
                        if (flag) {
                            let video2 = $(".cont-item-2 #kmd-video-player"); // 右下角视频
                            video2.on('enterpictureinpicture', function () {
                                $(".cont-item-2").hide(); // 进入画中画,隐藏原小窗
                            });
                            video2.on('leavepictureinpicture', function () {
                                $(".cont-item-1").css("display", ""); // 退出画中画,恢复原小窗
                                $(".cont-item-2").css("display", ""); // 退出画中画,恢复原小窗
                            });
                            video2[0].requestPictureInPicture();
                        } else {
                            document.exitPictureInPicture();
                        }
                    }
                    $("#splitContorl").bind("click", function (event) {
                        if (event.target.tagName == "LI") {
                            $(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");
                                    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");
                                    break;
                                case "split_pip":
                                    if ($("#split_pip").html().includes("进入") && !changed_flag) {
                                        enable_PiP(true);
                                        $(event.target).html("退出画中画");
                                    }
                                    break;
                                default:
                                    break;
                            }
                        }
                    });

                    // 鼠标位于进度条上或拖动进度条时浮窗显示时刻
                    $("#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%)");

                    $("#rtcTool .tool-bar").css("background-color", "black");
                    $("#rtcContent").css("background-color", "black");

                    // 浮窗显示
                    $(".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));
                    });

                    $(".tool-progress").on("mouseleave", function (event) {
                        $("#custom-progress-hover").css("display", "none");
                    });

                    // 在画面左上角显示自动渐隐的文本,显示音量变化
                    $("#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", "#FFF8")
                        .css("margin", "5px 5px")
                        .css("z-index", "11")
                        .css("padding", "2px 2px;")
                        .css("height", "20px")
                        .css("top", 0)
                        .css("left", event.pageX - $("#rtcMain").offset().left);
                    $("#custom-status-text>span")
                        .css("font-size", "16px")
                        .css("color", "blue");
                } else {
                    // console.log("waiting for video: ",$("video").length ," bar: "+ $("#timesContorl").length);
                }
            }, 10);

            // 修复了暂停状态下改变进度条导致意外继续播放且进度条不再刷新的问题
            function repair_pause_playing() {
                if (!isPlaying()) {
                    setTimeout(function () {
                        if (!isPlaying()) {
                            setPlay(false);
                            updateTimeText();
                            // console.log("repairing...");
                        }
                    }, 100);
                }
            }
            $("body").on("mouseup", function (event) {
                // console.log("mouseup",event.originalEvent.button);
                if (event.originalEvent.button != 0) { // 仅响应鼠标左键
                    return;
                }

                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(/voice(\d)-/);
                    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);

                        //console.log("new vol: ",volume_ratio);
                    }
                }
                //return true;
            });
            $("body").on("keyup", function (event) {
                // console.log("keyup:",event.target);
                repair_pause_playing();
            });

            $("body").on("keydown", function (event) {
                // console.log("keydown:",event.target);

                // 左右方向键进行时移
                // 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;
                }
                switch (event.keyCode) {
                    case 32: // 空格
                    {
                        // 空格键或单击画面暂停/播放
                        togglePlay();
                        break;
                    }
                    case 39: // 方向键右
                    {
                        let target_time = getTime() + step;
                        kmplayer.timeUpdateFlag = false;
                        if (target_time > kmplayer.durationSec) {
                            target_time = kmplayer.durationSec;
                        }
                        setTime(target_time);
                        // console.log("add:", step, "to:", target_time);
                        break;
                    }
                    case 37: // 方向键左
                    {
                        let target_time = getTime() - step;
                        kmplayer.timeUpdateFlag = false;
                        if (target_time < 0) {
                            target_time = 0;
                        }
                        setTime(target_time);
                        // console.log("minus:", step, "to:", target_time);
                        break;
                    }
                    default:
                        return true;
                }
                event.preventDefault();
                return false;
            });

            $("body").on("wheel", function (event) {
                // console.log("wheel",event.originalEvent.deltaY);

                // 仅在视频上使用滚轮生效
                if (event.target.tagName != "VIDEO") {
                    return;
                }
                let volDelta = event.originalEvent.deltaY / 2500;
                setVolume(getVolume() - volDelta, 0);

                // 使用滚轮调节音量时,会在画面左上角显示自动渐隐的文本,显示音量变化
                putText("Volume: " + parseInt(getVolume() * 100));
            });

            // 将全局右键屏蔽改为仅应用至视频区域
            $("#rtcMain").bind('contextmenu', function (event) {
                // console.log("contextmenu");
                event.preventDefault();
                return false;
            });

            // 在右上角增加【在新标签页播放】按钮
            $(".lti-page-tab").append($('<a href="https://courses.sjtu.edu.cn/lti/app/lti/vodVideo/playPage" target="_blank"><input class="tab-help" type="button" value="在新标签页播放"></a>'));

            // 本插件顺利生效时,顶部标签卡颜色为金色
            $(".tab-item--active").css("color", "gold");
            $(".tab-item--active").css("border-bottom", "2px solid gold");

            // 将画面背景色由廉价的灰色改为圣洁白
            $(".lti-page").css("background-color", "white");

            // 通过增加边框使右侧视频列表更有质感
            $(".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("480px");
            $(".list-main").height("420px");
            $(".course-details").height(0);

            // 阻止向服务器回报观看日志
            updateVodPlayLog = updateLiveCount = addVodPlayLog = function () {};

            console.log("录播加强已启动!");
        });
    }
})();