Greasy Fork

Greasy Fork is available in English.

bilibili关灯

bilibili关灯(把被新版B站藏起来的关灯按钮揪出来,在关闭弹幕按钮左边,还可以用快捷键,默认'A')、弹幕控制快捷操作、非全屏滚轮音量控制等

当前为 2021-07-26 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         bilibili关灯
// @namespace    hhh2000
// @version      0.8.5
// @description  bilibili关灯(把被新版B站藏起来的关灯按钮揪出来,在关闭弹幕按钮左边,还可以用快捷键,默认'A')、弹幕控制快捷操作、非全屏滚轮音量控制等
// @author       hhh2000
// @include      *://*.bilibili.com/video/*
// @include      *://*.bilibili.tv/video/*
// @include      *://*.bilibili.com/bangumi/*
// @include      *://*.bilibili.tv/bangumi/*
// @require      https://cdn.staticfile.org/jquery/1.12.4/jquery.min.js
// @run-at       document-end
// @grant        none
// ==/UserScript==

'use strict';
hhh_lightoff_main = {
    init() {
        var //
            fps,
            h5Player;
        const [BILI_2_X, BILI_3_X, ALL] = ['bili_2.x', 'bili_3.x', 'all'];
        var //切换番剧和一般视频class
            bb = {},
            bb_type = '',
            bb_config = {
                bb_class_data: {  //其实这样不方便调试
                    //'player':{BILI_2_X:'.player', [BILI_3_X]:'.bpx-player'}, //main player

                    'webFullScreen':{[BILI_2_X]:'.bilibili-player-video-web-fullscreen', [BILI_3_X]:'.squirtle-video-pagefullscreen.squirtle-video-item'}, //网页全屏
                    'wideScreen':{[BILI_2_X]:'.bilibili-player-video-btn-widescreen', [BILI_3_X]:'.squirtle-video-widescreen.squirtle-video-item'}, //宽屏

                    'danmukuTopClose':{[BILI_2_X]:'.bilibili-player-block-filter-type[data-name=ctlbar_danmuku_top_close]', [BILI_3_X]:'.bpx-player-block-filter-type.bpx-player-block-typeTop.bpx-player-active'}, //顶部弹幕
                    'danmukuTop'     :{[BILI_2_X]:'.bilibili-player-block-filter-type[ftype=top]', [BILI_3_X]:'.bpx-player-block-filter-type.bpx-player-block-typeTop'}, //顶部弹幕
                    'danmukuBottomClose':{[BILI_2_X]:'.bilibili-player-block-filter-type[data-name=ctlbar_danmuku_bottom_close]', [BILI_3_X]:'.bpx-player-block-filter-type.bpx-player-block-typeBottom.bpx-player-active'}, //底部弹幕
                    'danmukuBottom'     :{[BILI_2_X]:'.bilibili-player-block-filter-type[ftype=bottom]', [BILI_3_X]:'.bpx-player-block-filter-type.bpx-player-block-typeBottom'}, //底部弹幕
                    'progressVal'   :{[ALL]:'.bui-progress-val'}, //弹幕透明度读数
                    'progressWrap'  :{[ALL]:'.bui-progress-wrap'}, //弹幕透明度进度条
                    'settingOpacity':{[BILI_2_X]:'.bilibili-player-setting-opacity', [BILI_3_X]:'.bpx-player-dm-setting-ui-opacity'}, //弹幕透明度
                    'settingArea'   :{[BILI_2_X]:'.bilibili-player-setting-area'}, //显示区域
                    'volumeHint'    :{[BILI_2_X]:'.bilibili-player-volumeHint'}, //音量显示
                    'volumeHintText':{[BILI_2_X]:'.bilibili-player-volumeHint-text'}, //音量显示百分比读数
                    'volumeHintIcon':{[BILI_2_X]:'.bilibili-player-volumeHint-icon'}, //音量显示图标

                    'switchBody':{[ALL]:'.bui-switch-body'}, //系统关灯css设置
                    'switchDot':{[ALL]:'.bui-switch-dot'}, //系统弹幕设置按钮wrap进度条拖动点
                    'switchInput':{[ALL]:'.bui-switch-input'}, //弹幕设置switch按钮
                    'danmakuRoot':{[BILI_2_X]:'.bilibili-player-video-danmaku-root', [BILI_3_X]:'.bpx-player-dm-root'}, //系统弹幕设置条
                    'danmakuSwitch':{[BILI_2_X]:'.bilibili-player-video-danmaku-switch', [BILI_3_X]:'.bpx-player-dm-switch'}, //关闭弹幕按钮
                    'danmakuSetting':{[BILI_2_X]:'.bilibili-player-video-danmaku-setting', [BILI_3_X]:'bpx-player-dm-setting'}, //系统弹幕设置按钮
                    'danmakuSettingWrap':{[BILI_2_X]:'.bilibili-player-video-danmaku-setting-wrap', [BILI_3_X]:'.bpx-player-dm-setting-wrap'}, //系统弹幕设置按钮wrap
                    'videoWrap':{[BILI_2_X]:'.bilibili-player-video-wrap', [BILI_3_X]:'.bpx-player-video-area'}, //播放wrap
                    'videoContextMenu':{[BILI_2_X]:'.bilibili-player-video-wrap', [BILI_3_X]:'.bpx-player-video-perch'}, //播放contextmenu
                    'video':{[BILI_2_X]:'.bilibili-player-video', [BILI_3_X]:'.bpx-player-video-wrap'}, //播放
                    'videoTopMask':{[BILI_2_X]:'.bilibili-player-video-top-mask'}, //全屏时鼠标悬停时产生的顶端mask

                    'playArea':{[ALL]:'.bilibili-player-area'}, //哔哩哔哩播放器
                    'playVideo':{[BILI_2_X]:'.bilibili-player-video-btn'}, //系统设置
                    'playSetting':{[BILI_2_X]:'.bilibili-player-video-btn-setting'}, //系统播放设置
                    'playSettingWrap':{[BILI_2_X]:'.bilibili-player-video-btn-setting-wrap'}, //系统播放设置wrap
                    'playSettingAutoplay':{[BILI_2_X]:'.bilibili-player-video-btn-setting-left-autoplay'}, //自动播放
                    'playSettingRepeatInput':{[BILI_2_X]:'.bilibili-player-video-btn-setting-left-repeat input', [BILI_3_X]:'input.squirtle-setting-loop'}, //洗脑循环按钮
                    'playSettingLightoff':{[BILI_2_X]:'.bilibili-player-video-btn-setting-right-others-content-lightoff input', [BILI_3_X]:'.squirtle-single-setting-other-choice.squirtle-lightoff'}, //关灯按钮
                    'bpxStateLightOff':{[BILI_3_X]:'.bpx-state-light-off'}, //关灯bpx

                    //'playerContextMenu':{[BILI_2_X]:'.bilibili-player-context-menu-container', [BILI_3_X]:'.bpx-player-contextmenu.bpx-player-black.bpx-player-active'}, //右键菜单
                    'playerContextMenu':{[BILI_2_X]:'.bilibili-player-context-menu-container.black.bilibili-player-context-menu-origin', [BILI_3_X]:'.bpx-player-contextmenu.bpx-player-black.bpx-player-active'}, //右键菜单
                    'hotkeyPanel':{[BILI_2_X]:'.bilibili-player-hotkey-panel'}, //快捷键说明面板
                    'videoInfo':{[BILI_2_X]:'.bilibili-player-video-info', [BILI_3_X]:'.bpx-player-info'}, //视频统计信息
                    'videoInfoContainer':{[BILI_2_X]:'.bilibili-player-video-info-container', [BILI_3_X]:'.bpx-player-info-container'}, //视频统计信息
                    'videoInfoShow':{[BILI_2_X]:'.bilibili-player-video-info-container active', [BILI_3_X]:'.bpx-player-info-container'}, //视频统计信息面板显示
                    'DOMNodeInsertedVideoInfoShow':{[BILI_2_X]:'.bilibili-player-video-info-container active', [BILI_3_X]:'.info-line'}, //
                },
                set_bb(bb_type) {
                    for(var k in this.bb_class_data){
                        var class_str = this.bb_class_data[k][bb_type] || this.bb_class_data[k][[ALL]];
                        bb[k] = class_str;
                    }
                }
           };

        const [ON, OFF] = [true, false];
        var //config
            keycode = {
                'left': 37,
                'right': 39,
                'up': 38,
                'down': 40,
            }
            config = {
                //一些主要开关设置
                sets: {},
                getCheckboxSetting(key) {
                    return this.sets[key]['status'];
                },
                saveCheckboxSetting() {
                    for(var o in this){
                        if(o.indexOf('b_') === 0){
                            var op = this[o]['options'];
                            for(var k in op){
                                this.sets[k] = op[k];
                            }
                        }
                    }
                },
                b_playerCheckbox: {
                    options: {
                        lightOffWhenPlaying: { text: '播放时自动关灯', status: OFF },
                        lightOnWhenPause: { text: '暂停时自动开灯', status: OFF },
                        autoPlay: { text: '自动播放', status: OFF, fn: '', tips: '' },
                        lightOff: { text: '自动关灯', status: OFF, tips: '' },
                        repeat: { text: '洗脑循环', status: ON, tips: '' },
                        volumeControlWhenNonFullScreen: { text: '非全屏滚轮音量调节', status: ON, tips: '' },
                        volumeControlWhenPause: { text: '非全屏暂停时滚轮音量调节', status: ON, tips: '' },
                        danmuOpacityControl: { text: '滚轮弹幕透明度控制', status: ON, tips: '' }, //ctrl+滚轮
                        removeVideoTopMask: { text: '去掉顶部mask', status: ON, tips: '' },
                        //不显示有明显变化的提示,关灯、关弹幕等,因为对有些人来说这些操作变化明显可见,提示反而多余且遮挡屏幕
                        hotKeyHint: { text: '快捷键屏幕提示', status: OFF, tips: '' },
                    },
                    btn: '设置'
                },
                //快捷键
                QDs: {}, //未使用
                getQD(key) {
                    return this.QDs[key];
                }, //未使用
                saveQD() {
                    for (let [key, { value, text }] of Object.entries(this.hotKeyMenu)) {
                        this.QDs[key] = {value: value, keyCode: value.charCodeAt(), text: text};
                    }
                }, //未使用
                hotKeyMenu: {  //只是右键菜单的数据,如需改动快捷键改run函数
                    'lightOff': { value: 'A', text: '关灯/开灯', },
                    'webFullscreen': { value: 'W', text: '网页全屏', },
                    'widescreen': { value: 'Q', text: '宽屏模式', },
                    'danmu': { value: 'D', text: '弹幕/关闭弹幕', },
                    'danmuTopBottom': { value: 'T/B', text: '顶部/底部弹幕', },
                    'videoRepeat': { value: 'R', text: '洗脑循环', },
                    'addsubDanmuOpacity': { value: 'Z/C', text: '减增弹幕透明度10%', },
                    'quarterArea': { value: '1', text: '1/4屏', },
                    'halfArea': { value: '2', text: '半屏', },
                    'threeQuarterArea': { value: '3', text: '3/4屏', },
                    'nonOverArea': { value: '4', text: '不重叠', },
                    'fullArea': { value: '5', text: '不限', },
                    'wheelDanmuOpacity': { value: 'Ctrl + 滚轮', text: '增减弹幕透明度5%', },
                    'fastForwardBackward30s': { value: 'Ctrl + ←/→', text: '快进/快退30s', },
                    '1/FPS': { value: 'Shift + ←/→', text: '逐帧操作', },
                },
            };

        function log(e) {console.log(e)}
        function dir(e) {console.dir(e)}
        function waitForNode(nodeSelector, callback, times) {
            if(times < 0) return;
            var node = nodeSelector();
            if (node) {
                callback(node);
            } else {
                times-=1;
                setTimeout(function() { waitForNode(nodeSelector, callback, times); }, 100);
            }
        }
        function waitForTrue(ifTrue, callback) {
            if (ifTrue()) {
                callback();
            } else {
                setTimeout(function() { waitForTrue(ifTrue, callback); }, 100);
            }
        }
        function is_fullscreen() {
            if(bb_type === BILI_2_X){ return player.isFullScreen() }
            else if(bb_type === BILI_3_X){ return $('.bpx-player-container').attr('data-screen') === 'web' || $('.bpx-player-container').attr('data-screen') === 'full' }  //normal wide web full
        }
        function is_lightoff() {
            if (bb_type === BILI_2_X) { return !player.getPlayerState().lightOn }
            else if(bb_type === BILI_3_X) { return $(bb['playSettingLightoff']).hasClass('active') }
        }
        function is_danmaku_show(){
            return $(bb['danmakuSwitch']+' '+bb['switchInput']+':last')[0].checked;
        }
        function lightoff() {
            $(bb['playSettingLightoff']).click()
        }
        //关灯按钮样式
        function lightoff_btn_css() {
            var body_brgb = 'rgb(160, 130, 110)';
            var dot_crgb = 'rgb(230, 200, 180)';
            var dot_brgb = 'rgb(50, 50, 50)';
            var dark_rgb = 'rgb(77, 77, 77)';
            if ($('#hhh_lightoff '+bb['switchInput'])[0].checked === false) {  //关灯
                $('#hhh_lightoff '+bb['switchBody']+':first').css('background-color', dark_rgb);
                $('#hhh_lightoff '+bb['switchBody']+':first>'+bb['switchDot']).css('color', dark_rgb);
            }
            else {
                $('#hhh_lightoff '+bb['switchBody']+':first').css('background-color', body_brgb);
                $('#hhh_lightoff '+bb['switchBody']+':first>'+bb['switchDot']).css({'color': dot_crgb, 'background-color': dot_brgb});
            }
        }
        //关灯按钮
        function lightoff_btn() {
            lightoff();
            if(is_lightoff() === $('#hhh_lightoff '+bb['switchInput'])[0].checked) {  //checked==true开灯 false关灯
                $('#hhh_lightoff '+bb['switchInput'])[0].checked = !$('#hhh_lightoff '+bb['switchInput'])[0].checked;
            }
            lightoff_btn_css();
        }

        //模拟B站音量调节
        //2.X版本可以直接调用系统函数window.player.volume()
        //3.X版本去掉了,需要自己模拟,不精确
        function volume(v){
            function volume_bar(v){  //未使用
                if(v === undefined) return;
                v = v<0? 0: v>1? 1: v;
                $('.bilibili-player-video-volume-num').text(Math.round(v*100));
                $('.bilibili-player-video-volumebar-wrp .bui-bar.bui-bar-normal')[0].style.transform = `scaleY(${v})`;
                $('.bilibili-player-video-volumebar-wrp .bui-thumb')[0].style.transform = `translateY(${-48*v}px)`;
                v === 0? $('.bilibili-player-video-btn.bilibili-player-video-btn-volume').addClass('video-state-volume-min'): $('.bilibili-player-video-btn.bilibili-player-video-btn-volume').removeClass('video-state-volume-min')
            }
            function volume_hint_bar(v){
                if(v === undefined) return;
                v = v<0? 0: v>1? 1: v;
                var $volumeHintIcon = $(`#hhh_volumeHint ${bb['volumeHintIcon']}`);
                if(v <= 0) $volumeHintIcon.attr('class', `${bb['volumeHintIcon'].substr(1)} video-state-volume-min`);
                else if(v >= 1) $volumeHintIcon.attr('class', `${bb['volumeHintIcon'].substr(1)} video-state-volume-max`);
                else $volumeHintIcon.attr('class', bb['volumeHintIcon'].substr(1));

                if(v <= 0) showHint(this, '#hhh_volumeHint', '静音');
                else showHint(this, '#hhh_volumeHint', Math.round(v*100)+'%');
            }
            if(v === undefined) return h5Player.volume;
            v = v<0? 0: v>1? 1: v;
            bb_type === BILI_2_X? player.volume(v): set_progress_volume('.bilibili-player-video-volumebar', v*100);  //set_progress_volume不精确
            volume_hint_bar(h5Player.volume);  //set_progress_volume不精确,重新读取
            return v;
        }

        //显示提示
        function showHint(parent, selector_str, text){
            $(bb['volumeHint']).css('display', 'none');  //隐藏所有提示,避免提示重叠
            $(`${selector_str}>${bb['volumeHintText']}`).text(text);  //百分比显示
            var Hint = $(selector_str);  //显示及渐隐效果(抄bilibili^^)
            clearTimeout(parent.showHintTimer),
                Hint.stop().css("opacity", 1).show(),
                parent.showHintTimer = window.setTimeout((function() {
                Hint.animate({
                    opacity: 0
                }, 300, (function() {
                    $(this).hide()
                }))
            }
            ), 1e3)
        }

        //非全屏滚轮音量调节 0~1 (b站默认滚轮操作某些情况会失效,一并处理全屏情况)
        //两个参数指定屏幕范围(按百分比),第三个参数表示滚动一下增加的音量百分比,参数四表示暂停时是否调解
        function wheel_volumeHint(screenLeft=0.3, screenRight=0.7, delta=3, isPauseVolume){
            //add wheelevent
            $(bb['videoWrap']).off('mousewheel.hhh_volumeHint');
            $(bb['videoWrap']).on('mousewheel.hhh_volumeHint', function(e){
                if(e.ctrlKey || e.altKey || e.shiftKey) return;
                //缺省屏幕百分比参数,默认0.3~0.7
                screenLeft = (screenLeft<0 || screenLeft>1)? 0.3: screenLeft;
                screenRight = (screenRight<0 || screenRight>1)? 0.7: screenRight;
                //缺省音量百分比,默认3
                delta = (delta<1 || delta>100)? 3: delta;

                //非暂停(可选) && 鼠标在屏幕指定位置时处理
                var pauseState = isPauseVolume || player.getState() !== 'PAUSED';
                var Rect = $(bb['videoWrap'])[0].getBoundingClientRect();
                var offsetX = e.originalEvent.x - Rect.x;
                var inLimit = offsetX > Rect.width*screenLeft && offsetX < Rect.width*screenRight;

                if(is_fullscreen() || (pauseState && inLimit)) {
                    //阻止页面滚动 && 阻止冒泡
                    e.preventDefault();
                    e.stopPropagation();
                    var wheelDelta = e.originalEvent.wheelDelta;
                    var v = volume();
                    if(wheelDelta >= 120) {  //向上滚动,减少音量
                        volume(v+(delta/100));
                    } else if(wheelDelta <= -120) {  //向下滚动,增大音量
                        volume(v-(delta/100));
                    }
                }
            });
        }

        /*
         * 控制进度条
         * .bilibili-player-setting-opacity 透明度
         * .bilibili-player-setting-area 显示区域
         * .bilibili-player-setting-speedplus 弹幕速度 等
         * 利用系统mousedown事件
         * 0 ~ 100
         */
        function set_progress(selector_str, percent){
            var selector = document.querySelector(selector_str);
            var e1 = new MouseEvent('mousedown'); var e2 = new MouseEvent('mouseup');

            function calc_bar_len(percent, bar_width){
                var p = percent - 10;
                p = p<0? 0: p>90? 90: p;
                return p*bar_width/90;  //进度条对应百分比的系统算法
            }

            $(bb['danmakuSettingWrap']).css({"display":"block"});

            var selector_rect = selector.getClientRects();
            var clientX = selector_rect[0].left + calc_bar_len(percent, $(`${selector_str} ${bb['progressWrap']}`).innerWidth());
            e1.initMouseEvent('mousedown',1,1,window,1,0,0,clientX,0,0,0,0,0,0,null);
            e2.initMouseEvent('mouseup'  ,1,1,window,1,0,0,clientX,0,0,0,0,0,0,null);
            selector.dispatchEvent(e1); selector.dispatchEvent(e2);

            $(bb['danmakuSettingWrap']).css({"display":"none"});

            $(bb['danmakuSetting']).mouseleave();  //激活设置,记忆进度条位置
            return $(`${selector_str} ${bb['progressVal']}`).text();
        }
        /*音量进度条控制
          系统鼠标调解存在无法精确调节问题,个位数只能在0 2 3 5 7 8之间循环,1 4 6 9体现不出来,比如1显示不出来,只能显示0和2
          应该是进度条高度只有60,所以另40个数表示不出来*/
        function set_progress_volume(selector_str, percent){
            var selector = document.querySelector(selector_str);
            var e1 = new MouseEvent('mousedown'); var e2 = new MouseEvent('mouseup');

            $('.bilibili-player-video-volumebar-wrp').css({"display":"block"});

            var selector_rect = selector.getClientRects();
            var clientY = Math.ceil(selector_rect[0].bottom - percent/100*selector_rect[0].height);
            e1.initMouseEvent('mousedown',1,1,window,1,0,0,0,clientY,0,0,0,0,0,null);
            e2.initMouseEvent('mouseup'  ,1,1,window,1,0,0,0,clientY,0,0,0,0,0,null);
            selector.dispatchEvent(e1); selector.dispatchEvent(e2);

            $('.bilibili-player-video-volumebar-wrp').css({"display":"none"});

            return $(`${selector_str}`).prev(':first').text();
        }

        /*
         * 调节透明度
         * 利用系统mousedown事件
         * '正数': right,  '负数': left,  -100 ~ +100
         */
        function adjust_progress(selector_str, inc_percent){
            var curr_percent = Number($(`${selector_str} ${bb['progressVal']}`).text().slice(0,-1));
            return set_progress(selector_str, curr_percent + inc_percent);
        }

        //滚轮调节弹幕透明度(ctrl),参数表示滚动一下增加的透明度百分比
        function wheel_opacity(delta=5){
            //add wheelevent
            $(bb['videoWrap']).off('mousewheel.hhh_opacity');
            $(bb['videoWrap']).on('mousewheel.hhh_opacity', function(e){
                if(e.ctrlKey === true) {
                    //阻止页面滚动 && 阻止冒泡
                    e.preventDefault();
                    e.stopPropagation();
                    //缺省透明度百分比,默认5
                    delta = (delta<1 || delta>100)? 5: delta;
                    var wheelDelta = e.originalEvent.wheelDelta;
                    var opacity;
                    if(wheelDelta >= 120) {  //向上滚动,增大透明度
                        opacity = adjust_progress(bb['settingOpacity'], delta);
                    } else if(wheelDelta <= -120) {  //向下滚动,减少透明度
                        opacity = adjust_progress(bb['settingOpacity'], -delta);
                    }
                    if(opacity !== undefined) showHint(document, '#hhh_opacityHint', '透 '+opacity);
                }
            });
        }

        //添加右键菜单自定义快捷键说明
        function add_custom_hotkey_menu(custom_hotkey) {
            //主class名,去掉"."
            var hotkey_panel_class = bb['hotkeyPanel'].replace(/\./g, ' ').substr(1);

            //生成自定义快捷键说明页面
            (function add_custom_hotkey(){
                $(bb['videoWrap'])[0].addEventListener('DOMNodeInserted', function(e) {
                    if(typeof e.target.className === 'string' && e.target.className.indexOf(`${hotkey_panel_class}-container active`) !== -1) {
                        this.removeEventListener('DOMNodeInserted', arguments.callee);
                        var $hotkey = $(e.target).attr('class', `${hotkey_panel_class}-container`);
                        $hotkey.clone().insertBefore($hotkey).attr({'class': '','id': 'sys_hotkey'}).css('display', 'none');
                        $hotkey.clone().insertBefore($hotkey).attr({'class': '','id': 'hhh_hotkey'}).css('display', 'none');

                        (function set_custom_hotkey($hotkey, hotkey) {
                            var $hotkey_panel = $hotkey.find(`.${hotkey_panel_class}:first`);
                            var $hotkey_item  = $hotkey.find(`.${hotkey_panel_class}-item:first`);
                            $hotkey_panel.empty();
                            for (let [key, { value, text }] of Object.entries(hotkey)) {
                                $hotkey_item.clone().appendTo($hotkey_panel);
                                var $hotkey_key = $hotkey_panel.find(`.${hotkey_panel_class}-key:last`);
                                $hotkey_key.text(value);
                                $hotkey_key.next().text(text);
                            }
                        })($('#hhh_hotkey'), custom_hotkey);
                    }
                });
            })();
            //右键菜单弹出时添加项
            (function add_menu(){
                $('#bilibiliPlayer')[0].addEventListener('DOMSubtreeModified', function(e) {
                    let target = e.target;
                    if(typeof target.className === 'string' && target.className.indexOf(bb['playerContextMenu'].replace(/\./g, ' ').substr(1)) !== -1 &&  target.id === '') {  //还有一个id = __sizzle__
                        this.removeEventListener('DOMSubtreeModified', arguments.callee);

                        var $sys_hotkey = $(target).find('a:contains("快捷键说明"):first').parent();  //XXX: 此时contains("快捷键说明")多于一个
                        var $hhh_hotkey = $sys_hotkey.clone(false, false).insertAfter($sys_hotkey).css('display', '').find('a').text('快捷键说明(bilibili关灯)');

                        $sys_hotkey.find('a').click(function(){
                            $('#sys_hotkey').clone(true,true).replaceAll($(`.${hotkey_panel_class}-container:last`)).attr({'id': '', 'class': `${hotkey_panel_class}-container`, 'style': ''});
                        });
                        $hhh_hotkey.click(function(){
                            $('#hhh_hotkey').clone(true,true).replaceAll($(`.${hotkey_panel_class}-container:last`)).attr({'id': '', 'class': `${hotkey_panel_class}-container`, 'style': ''});
                        });

                        add_menu();
                    }
                });
            })();

            //模拟右键菜单消息,激活菜单DOM
            let evt = new MouseEvent('contextmenu', { clientX:-9999, clientY:-9999 });
            $(bb['videoWrap'])[0].dispatchEvent(evt);

            //模拟点击菜单,激活热键菜单DOM
            if(bb_type === BILI_2_X){
                let evt = new MouseEvent('click',{ bubbles:true });
                $(`${bb['playerContextMenu']} a:contains("快捷键说明")`)[0].dispatchEvent(evt);
                //$(`${bb['playerContextMenu']} a:contains("快捷键说明")`).click();
            }
            else if(bb_type === BILI_3_X) {
                let evt = new MouseEvent('mousedown',{ bubbles:true });
                $(`${bb['playerContextMenu']} li:contains("快捷键说明")`)[0].dispatchEvent(evt);
            }
        }

        //取得视频FPS(Frames Per Second)
        function get_video_fps() {
            $(bb['videoWrap'])[0].addEventListener('DOMNodeInserted', function(e) {
                //bpx...
                //if(typeof e.target.className === 'string' && e.target.className.indexOf(`bpx-player-loading-panel-text-row`) !== -1) {
                //}
                //插入info面板时截取fps值
                if(typeof e.target.className === 'string' && e.target.className.indexOf(`${bb['DOMNodeInsertedVideoInfoShow'].substr(1)}`) !== -1) {
                    this.removeEventListener('DOMNodeInserted', arguments.callee);
                    if(bb_type === BILI_2_X){
                        $('.bilibili-player-video-info-close').click();  //模拟关闭统计信息面板
                    }
                    else if(bb_type === BILI_3_X){
                        //var event = new MouseEvent('mousedown',{ bubbles:true });
                        //$('.bpx-player-info-close')[0].dispatchEvent(event);
                        $('.bpx-player-info-close').click();  //模拟关闭统计信息面板
                        $('.bpx-player-info-close').click();  //XXX:未知原因,执行一次关不掉面板,可以改用dispatchEvent测试
                    }

                    //text中取得fps值
                    var get_title_text = function(title) { return $(bb['videoInfoContainer']).find(`.info-title:contains("${title}")`).next().text(); }
                    fps = Number(get_title_text('Resolution').match(/\d+\.\d+/)) || Number(get_title_text('FPS')) || 30;
                }
            })

            //模拟右键菜单消息,激活菜单DOM
            let evt = new MouseEvent('contextmenu', { clientX:-9999, clientY:-9999 });
            $(bb['videoContextMenu'])[0].dispatchEvent(evt);

            //模拟点击菜单,激活热键菜单DOM
            if(bb_type === BILI_2_X){
                let evt = new MouseEvent('click',{ bubbles:true });
                $(`${bb['playerContextMenu']} a:contains("视频统计信息")`)[0].dispatchEvent(evt);
            }
            else if(bb_type === BILI_3_X) {
                let evt = new MouseEvent('mousedown',{ bubbles:true });
                $(`${bb['playerContextMenu']} li:contains("视频统计信息")`)[0].dispatchEvent(evt);
            }
        }

        //笨办法,激活系统音量设置,复制volumeHint DOM
        function pick_volume_hint(){
            var original_volume = h5Player.volume;
            //监视提取提示DOM
            $(bb['videoWrap'])[0].addEventListener('DOMSubtreeModified', function(e) {
                if(typeof e.target.className === 'string' && e.target.className.indexOf(`${bb['volumeHint'].substr(1)}`) !== -1) {
                    this.removeEventListener('DOMSubtreeModified', arguments.callee);

                    volume(original_volume);  //模拟鼠标无法按1%精确控制音量,系统自身限制或者说bug

                    //隐藏提示
                    $(bb['volumeHint']).css('display', 'none');

                    //添加 volumeHint wordsHint opacityHint DOM
                    $(bb['volumeHint']).clone().appendTo(bb['videoWrap']).attr('id','hhh_volumeHint');
                    $('#hhh_volumeHint').clone().appendTo(bb['videoWrap']).attr('id','hhh_wordsHint')
                        .css({'opacity':0,'display':'none','width':'auto','margin-left':'0px','padding-left':'8px','padding-right':'15px','transform':'translate(-50%)'})
                        .find('.bilibili-player-volumeHint-icon').remove().end()
                        .find('.bilibili-player-volumeHint-text').css({'width': 'auto', 'padding-left': '10px'});
                    $('#hhh_volumeHint').clone().appendTo(bb['videoWrap']).attr('id','hhh_opacityHint')
                        .css({'opacity':0,'display':'none'})
                        .find('.bilibili-player-volumeHint-icon').remove().end()
                        .find('.bilibili-player-volumeHint-text').css({'padding-right': '6px'});
                }

            });
            //激活系统音量设置,以复制volumeHint DOM
            let evt = new KeyboardEvent('keydown', { keyCode:keycode['up'] });
            window.dispatchEvent(evt);
        }

        //快进时显示醒目进度条
        function dynamicProgress(dynamicHeight, staticHeight){
            if($('.bilibili-player-area').hasClass('progress-shadow-show') === true){
                $('.bui-track.bui-track-video-progress').css('cssText', `height:${dynamicHeight}px !important`);
                clearTimeout(document.showVideoProgress);
                document.showVideoProgress = window.setTimeout((function() {
                    $('.bui-track.bui-track-video-progress').css('cssText', `height:${staticHeight}px !important`);
                }), 1300)
            }
        }

        //主程序
        function run(){

            //版本class
            bb_type = $('.bilibili-player-area').length===1? BILI_2_X:$('.bpx-player-primary-area').length===1? BILI_3_X: 'unknown_version';
            if(bb_type === 'unknown_version') return;
            bb_config.set_bb(bb_type);

            //防止重复加载
            if ($('#hhh_lightoff').length === 1) return;

            //bpx test
            function bpx_test(){
                log('-------------------');
                log('fps: '+fps);
                log('------h5Player-----');
                dir(h5Player);
                log('currentTime  : '+h5Player.currentTime);
                log('duration     : '+h5Player.duration);
                log('playbackRate : '+h5Player.playbackRate);
                log('volume       : '+h5Player.volume);
                log('paused       : '+h5Player.paused);
                log('pause()      : '+'h5Player.pause()');
                log('videoHeight  : '+h5Player.videoHeight);
                log('videoWidth   : '+h5Player.videoWidth);
                log('-------------------');
            }

            /*-----------------------------------
             *初始化等
             *----------------------------------*/
            //保存设置信息 && 快捷键信息
            config.saveCheckboxSetting();

            //取得h5 video
            h5Player = $(`#bilibili-player ${bb['video']}>video`)[0];

            //插入关灯按钮
            $(`${bb['danmakuSwitch']}:first`).clone().prependTo(`${bb['danmakuRoot']}:first`)[0].id = 'hhh_lightoff';
            $(`#hhh_lightoff ${bb['switchDot']}`)[0].innerHTML = '灯';

            //初始化关灯按钮
            lightoff_btn_css();

            //激活系统弹幕设置,以此调用系统网页全屏等
            $(bb['danmakuSetting']).mouseenter().mouseleave();

            //激活系统关灯设置,以此调用系统关灯等
            //去掉mouseout(),否则如果太快执行mouseout()无法激活关灯class,应该是mouseenter()未执行完就被mouseout打断了
            $(`${bb['playVideo']}${bb['playSetting']}`).mouseenter();

            //避免显示设置页面
            waitForNode(() => document.querySelector(bb['playSettingWrap']), (node) => {
                $(node).css({"visibility":"hidden"});  //visible
            })

            //解决因为激活关灯class,导致全屏时滚轮操作(系统自带)无法调节音量的问题
            waitForTrue(()=> $(bb['playSettingWrap']).css('display') === 'block', () => {
                $(bb['playSettingWrap']).css('display', 'none').css('visibility', 'visible');
            });

            //取得视频fps
            get_video_fps();

            //TEST
            bpx_test();
            if(bb_type === BILI_3_X) return;

            //激活系统提示添加音量等自定义提示DOM
            pick_volume_hint();

            //添加自定义快捷键说明到右键菜单
            add_custom_hotkey_menu(config.hotKeyMenu);

            /*-----------------------------------
             *事件等
             *----------------------------------*/
            //点击关灯
            $(`#hhh_lightoff ${bb['switchInput']}:first`).click(function(){ lightoff_btn() });

            //键盘关灯等
            var opacity;
            var parent = document;
            var is_show_hint = config.getCheckboxSetting('hotKeyHint');
            $(document).off('keydown.hhh_lightoff');
            $(document).on('keydown.hhh_lightoff',function(e){
                if(e.shiftKey && (e.keyCode === keycode['left'] || e.keyCode === keycode['right'])) {  //逐帧 shift+ left/right
                    h5Player.pause();
                    h5Player.currentTime = e.keyCode === keycode['left']? h5Player.currentTime - 1/fps: h5Player.currentTime + 1/fps;
                } else if(e.ctrlKey && (e.keyCode === keycode['left'] || e.keyCode === keycode['right'])) {  //+ -30s ctrl+ left/right
                    h5Player.currentTime = e.keyCode === keycode['left']? h5Player.currentTime - 30: h5Player.currentTime + 30;
                    dynamicProgress(16, 2);
                }

                //以下不使用功能键
                if(e.ctrlKey || e.altKey || e.shiftKey) return;

                if(e.keyCode === 'A'.charCodeAt()){  //开关灯
                    is_show_hint && (is_lightoff() ? showHint(parent, '#hhh_wordsHint', '开灯') : showHint(parent, '#hhh_wordsHint', '关灯'));
                    lightoff_btn();
                } else if(e.keyCode === 'W'.charCodeAt()) {  //网页全屏
                    $(bb['webFullScreen']).click();
                } else if(e.keyCode === 'Q'.charCodeAt()) {  //宽屏模式
                    is_fullscreen() ? $(bb['webFullScreen']).click() : $(bb['wideScreen']).click();
                } else if(e.keyCode === 'D'.charCodeAt()) {  //开关弹幕
                    $(bb['danmakuSwitch']+' '+bb['switchInput']+':last').click();
                    is_show_hint && (is_danmaku_show() === true ? showHint(parent, '#hhh_wordsHint', '开弹幕') : showHint(parent, '#hhh_wordsHint', '关弹幕'));
                } else if(e.keyCode === 'T'.charCodeAt()) {  //开关顶部弹幕
                    $(bb['danmukuTopClose']).length === 0 ? showHint(parent, '#hhh_wordsHint', '关闭顶部弹幕') : showHint(parent, '#hhh_wordsHint', '打开顶部弹幕');
                    $(bb['danmukuTop']).click();
                } else if(e.keyCode === 'B'.charCodeAt()) {  //开关底部弹幕
                    $(bb['danmukuBottomClose']).length === 0 ? showHint(parent, '#hhh_wordsHint', '关闭底部弹幕') : showHint(parent, '#hhh_wordsHint', '打开底部弹幕');
                    $(bb['danmukuBottom']).click();
                } else if(e.keyCode === 'R'.charCodeAt()) {  //开关洗脑循环
                    $(bb['playSettingRepeatInput']).click();
                } else if(e.keyCode === 'Z'.charCodeAt()) {  //-弹幕透明度
                    window.setTimeout((function() {  //长按时保持DOM更新
                        //bpx-player-dm-setting-left-opacity-content
                        opacity = adjust_progress(bb['settingOpacity'], -10);
                        showHint(parent, '#hhh_opacityHint', '透 '+opacity);
                    }),10);
                } else if(e.keyCode === 'C'.charCodeAt()) {  //+弹幕透明度
                    window.setTimeout((function() {
                        opacity = adjust_progress(bb['settingOpacity'], 10);
                        showHint(parent, '#hhh_opacityHint', '透 '+opacity);
                    }),0);
                } else if(e.keyCode >= '1'.charCodeAt() && e.keyCode <= '5'.charCodeAt()) {  //弹幕显示区域
                    var area_text = {0:'1/4屏',25:'半屏',50:'3/4屏',75:'不重叠',100:'不限'};
                    var percent = (e.keyCode - '1'.charCodeAt()) * 25;  //((0~4)*25)%
                    set_progress(bb['settingArea'], percent);
                    showHint(parent, '#hhh_wordsHint', area_text[percent]);
                } else if(e.keyCode === keycode['left'] || e.keyCode === keycode['right']){  //快进时显示醒目进度条
                    dynamicProgress(16, 2);
                } else if(e.keyCode === keycode['up'] || e.keyCode === keycode['down']) { //隐藏所有提示,避免提示重叠
                    $(bb['volumeHint']).css('display', 'none');
                } else if(e.keyCode === 'X'.charCodeAt()) {  //显示、隐藏video-control
                    if($(bb['playArea']).hasClass('video-control-show') === true) $(bb['videoWrap']).mouseout();
                    else $(bb['videoWrap']).mousemove();
                } else if(e.keyCode === 'V'.charCodeAt()) {  //TEST
                    //$('.bilibili-player-video-danmaku-setting-wrap').css('display','block');
                }else{
                    //console.dir(e);
                }
            });

            //非全屏滚轮音量调节
            //两个参数指定屏幕范围(按百分比),第三个参数表示滚动一下增加的音量百分比,参数四表示暂停时是否调节
            if(config.getCheckboxSetting('volumeControlWhenNonFullScreen') === ON) wheel_volumeHint(0.30, 0.70, 3, config.getCheckboxSetting('volumeControlWhenPause'));

            //滚轮调节弹幕透明度(ctrl),参数表示滚动一下增加的透明度百分比,默认5
            if(config.getCheckboxSetting('danmuOpacityControl') === ON) wheel_opacity(5);

            //因为遮挡弹幕,去掉全屏时鼠标悬停时产生的顶端mask
            if(config.getCheckboxSetting('removeVideoTopMask') === ON) $(bb['videoTopMask']).removeClass();

            //播放关灯,暂停开灯
            player.addEventListener('video_media_playing', () => config.getCheckboxSetting('lightOffWhenPlaying') === ON && !is_lightoff() && lightoff_btn());
            player.addEventListener('video_media_pause', () => config.getCheckboxSetting('lightOnWhenPause') === ON && is_lightoff() && lightoff_btn());

            //自动运行
            if(config.getCheckboxSetting('autoPlay') === ON && $(`${bb['playSettingAutoplay']}>${bb['switchInput']}`)[0].checked === false)  //开启自动播放
                $(`${bb['playSettingAutoplay']}>${bb['switchInput']}`).click();
            if(config.getCheckboxSetting('repeat') === ON) $(bb['playSettingRepeatInput']).click();  //开启洗脑循环
            if(config.getCheckboxSetting('lightOff') === ON) lightoff_btn();  //自动关灯

        }

        //初始化
        function init() {
            let stageFlag = undefined;
            new MutationObserver((mutations, observer) => {
                mutations.forEach(mutation => {
                    const target = $(mutation.target);
                    const stage = mutation.previousSibling && target.attr('stage');
                    if(stage === '1'){
                        run();
                    }
                });
            }).observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        init();
    }
}

hhh_lightoff_main.init();