Bilibili触屏优化

优化Bilibili触屏操作体验,支持长按、滑动、缩放等手势操作,支持倍速播放。

// ==UserScript==
// @name         Bilibili触屏优化
// @namespace    http://tampermonkey.net/
// @version      0.0.2
// @description  优化Bilibili触屏操作体验,支持长按、滑动、缩放等手势操作,支持倍速播放。
// @author       Blysh
// @match        *://*.bilibili.com/*
// @grant        none
// @license     MIT
// @icon        https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/images/logo-small.png
// ==/UserScript==

class VideoGestureHandler {

    //! ------------------Contructor------------------
    constructor(videoElement) {
        this.biliPlayer = videoElement;
        this.videoElement = videoElement.querySelector("video");
        this.ctrlWrap = videoElement.querySelector(".bpx-player-control-bottom");
        this.playerController = videoElement.querySelector(".bpx-player-container");
        this.playerJindutiao = videoElement.querySelector(".bpx-player-control-entity");
        this.longPressInterval = null;
        this.touchMoveInterval = null;
        this.intervals = [];
        this.longPressThreshold = 400; // 长按阈值,单位为毫秒
        this.gestureType = "none";
        this.playbackRate = "1x";
        this.touchStartX = 0;
        this.touchStartY = 0;
        this.touchX = 0;
        this.touchY = 0;
        this.lastDistance = 0;
        this.add_time = 0;
        this.touchStartTime = 0;
        this.touchEndTime = 0;
        this.currenttime = 0;
        this.textBox = null;
        this.photoShotStyle = null;
        this.progressElement = null;
        this.clickTimeout = null;
        this.tryGetTargetElement3Times = 0;
        this.swipeTouchEnd = this.swipeTouchEnd.bind(this);
        this.longPressTouchEnd = this.longPressTouchEnd.bind(this);
        this.normolTouchEnd = this.normolTouchEnd.bind(this);
        this.zoomTouchEnd = this.zoomTouchEnd.bind(this);
        this.swipeUDTouchEnd = this.swipeUDTouchEnd.bind(this);
        this.volume = videoElement.volume;
        this.videoElement.style.filter = 'brightness(1)';
        this.brightness = 1;
        this.ctrlTimeoutID = null;
        this.addHint();
        //* 创建代理对象
        this.proxyGestureType = new Proxy({ gestureType: this.gestureType }, this.handler());
        this.proxyPlaybackRate = new Proxy({ playbackRate: this.playbackRate }, this.playbackRateHandler());
        //* 添加事件监听器
        this.videoElement.addEventListener('touchstart', this.handleTouchStart.bind(this));
        this.videoElement.addEventListener('touchmove', this.handleTouchMove.bind(this));
        this.videoElement.addEventListener('touchend', this.normolTouchEnd);
        this.ctrlWrap.addEventListener('touchstart', event => {
            clearTimeout(this.ctrlTimeoutID);
            this.ctrlTimeoutID = null;
        })

    }

    //! ------------------Proxy------------------

    handler() {
        return {
            set: (target, property, value) => {
                target[property] = value;
                this.videoElement.removeEventListener('touchend', this.normolTouchEnd);
                if (property === 'gestureType' && value === 'longpress') {
                    this.videoElement.addEventListener('touchend', this.longPressTouchEnd);
                }
                else if (property === 'gestureType' && value === 'swipe') {
                    // 创建并触发 mouseenter 事件
                    const mouseEnterEvent = new MouseEvent('mouseenter', {
                        view: window,
                        bubbles: true,
                        cancelable: true
                    });
                    this.progressElement = document.querySelector(".bpx-player-progress");
                    this.progressElement.dispatchEvent(mouseEnterEvent);
                    // 将photoshot固定显示在屏幕下方
                    this.photoShot = document.querySelector(".bpx-player-progress-popup");
                    this.photoShotStyle = document.createElement("style");
                    this.photoShotStyle.innerHTML = `
                        .bpx-player-progress-popup{
                            left: 50%!important;
                            bottom:250px!important;
                            overflow:visible!important;
                            transform: translate(-100%, 100%);
                    }`;
                    this.photoShot.appendChild(this.photoShotStyle);
                    // photoshot放大两倍
                    this.photoShotSize = document.querySelector(".bpx-player-progress-preview-image");
                    this.photoShotSize.style.height = "200%";
                    this.photoShotSize.style.width = "200%";
                    // 隐藏进度条上的指示器和预览时间
                    this.indicator = document.querySelector(".bpx-player-progress-move-indicator");
                    //this.indicator.style.display = "none";
                    this.previewTime = document.querySelector(".bpx-player-progress-preview-time");
                    this.previewTime.style.display = "none";
                    // 添加touchend事件监听器
                    this.videoElement.addEventListener('touchend', this.swipeTouchEnd);
                }
                else if(property === 'gestureType' && value === 'swipeUD'){
                    this.videoElement.addEventListener('touchend', this.swipeUDTouchEnd);
                }
                else if(property === 'gestureType' && value === 'zoom'){
                    this.createButton();
                    this.videoElement.addEventListener('touchend', this.zoomTouchEnd);
                }
                this.videoElement.addEventListener('touchend', this.normolTouchEnd);
                return true;
            }
        };
    }

    playbackRateHandler() {
        return {
            set: (target, property, value) => {
                target[property] = value;
                if (property === 'playbackRate' && value === '1x') {
                    this.playbackRateHint.style.display = "none";
                }
                if (property === 'playbackRate' && value === '2x') {
                    this.playbackRateHint.style.display = '';
                    this.playbackRateHint.innerHTML = this.playbackRateHint.innerHTML.replace(/\d?倍速播放中/, "2倍速播放中");
                }
                else if (property === 'playbackRate' && value === '3x') {
                    this.playbackRateHint.style.display = '';
                    this.playbackRateHint.innerHTML = this.playbackRateHint.innerHTML.replace(/\d?倍速播放中/, "3倍速播放中");
                }
                return true;
            }
        };
    }


    //! ------------------EventListener------------------

    handleCtrl(){
        this.isCtrlShow = this.playerController.getAttribute("data-ctrl-hidden")
        if(this.isCtrlShow == 'false' || this.ctrlTimeoutID){
            this.hideCtrl();
            this.hideCtrlMenus();
            clearTimeout(this.ctrlTimeoutID);
            this.ctrlTimeoutID = null;
        }
        else{
            this.showCtrl();
            this.ctrlTimeoutID = setTimeout(() => {
                this.hideCtrl();
                this.ctrlTimeoutID = null;
            },3000);
        }
    }

    showCtrl(){
        this.playerController.classList.remove("bpx-state-no-cursor")
        this.playerController.setAttribute("data-ctrl-hidden","false")
        this.playerJindutiao.setAttribute("data-shadow-show","false")
        if(this.button)this.button.style.bottom = '10%';
    }

    hideCtrl(){
        this.playerController.classList.add("bpx-state-no-cursor")
        this.playerController.setAttribute("data-ctrl-hidden","true")
        this.playerJindutiao.setAttribute("data-shadow-show","true")
        if(this.button)this.button.style.bottom = '-2%';
    }

    hideCtrlMenus(){
        this.menus = document.querySelector(".bpx-player-control-bottom-right").childNodes;
        this.menus.forEach( menu => {
            menu.classList.remove("bpx-state-show");
        });
    }

    //* 单击事件
    handleClick(event) {
        this.handleCtrl();
    }

    //* 双击事件
    handleDblClick(event) {
        // 切换视频的播放/暂停状态
        if (this.videoElement.paused) {
            this.videoElement.play();
        } else {
            this.videoElement.pause();
        }
    }

    //* 触摸开始
    handleTouchStart(event) {
        event.preventDefault();
        event.stopPropagation();
        this.currenttime = this.videoElement.currentTime;
        const touchCount = event.touches.length;
        const touch = event.touches[0];
        this.touchStartX = touch.clientX;
        this.touchStartY = touch.clientY;
        this.touchX = touch.clientX;
        this.touchY = touch.clientY;
        try{
        const touch2  = event.touches[1];
        this.touchStartX2 = touch2.clientX;
        this.touchStartY2 = touch2.clientY;
        this.touchX2 = touch2.clientX;
        this.touchY2 = touch2.clientY;}
        catch(err){}
        
        this.lastDistance = 0;
        this.lastAngle = 0;

        if(touchCount === 1){
            this.touchStartTime = Date.now();
        }
        this.clearAllInterval();
        if (touchCount === 1) {
            this.longPressInterval = setInterval(this.LongPressAction.bind(this), 200);
            this.intervals.push(this.longPressInterval);
            this.touchMoveInterval = setInterval(this.LRSwipeAction.bind(this), 50);
            this.intervals.push(this.touchMoveInterval);
        }
        if (touchCount === 2) {
            this.add_time = 0;
            this.currenttime = this.videoElement.currentTime;
            const secondTouchTime = Date.now();
            const timeDifference = secondTouchTime - this.touchStartTime;
            if (timeDifference < 100){
                this.zoomInterval = setInterval(this.zoomAction.bind(this),0);
                this.intervals.push(this.zoomInterval);
            }
        }
    }
    //* 触摸移动
    handleTouchMove(event) {
        event.preventDefault();
        const touch = event.touches[0];
        this.touchX = touch.clientX;
        this.touchY = touch.clientY;
        try{
        const touch2 = event.touches[1];
        this.touchX2 = touch2.clientX;
        this.touchY2 = touch2.clientY;}
        catch(err){}
    }

    //* 放大缩小画面
    zoomAction() {
        let deltaX = this.touchX - this.touchX2;
        let deltaY = this.touchY - this.touchY2;
        let distance = Math.abs(Math.sqrt(deltaX * deltaX + deltaY * deltaY));
        let angle = this.calculateAngle(this.touchX, this.touchY, this.touchX2, this.touchY2);
        if(this.lastAngle == 0){this.lastAngle = angle;return;}
        if (this.lastDistance == 0) {
            this.lastDistance = distance;
            return;
        }
        let multiple = distance - this.lastDistance;
        let rotate = angle - this.lastAngle;
        if (Math.abs(multiple)>0 && this.proxyGestureType.gestureType === 'none' && this.proxyGestureType.gestureType !== 'zoom'){
            this.proxyGestureType.gestureType = 'zoom';
            this.clearOtherInterval(this.zoomInterval);
        }


        if(this.videoZoomMul == null){this.videoZoomMul=1}
        if(this.videoRotateDeg == null){this.videoRotateDeg=0}

        if(Math.abs(rotate)>0.5){
            this.videoRotateDeg += rotate;
        }


        if(Math.abs(multiple)>0){
            if(multiple>10){multiple=1}
            this.videoZoomMul += multiple*0.01;
            if(this.videoZoomMul<0.8){
                this.videoZoomMul = 0.8
            }
        }


        this.videoElement.style.transform = `scale(${this.videoZoomMul}) rotate(${this.videoRotateDeg}deg)`;
        this.lastDistance = distance;
        this.lastAngle = angle;
    }
    //* 单指左右移动调整时间
    LRSwipeAction() {
        let deltaX = this.touchX - this.touchStartX;

        let distance = Math.sign(deltaX) * ( Math.abs(deltaX) - 5 );
        if (this.lastDistance == 0) {
            this.lastDistance = distance;
            return;
        }
        const move = Math.abs(distance - this.lastDistance); //确保手指有移动
        if (Math.abs(deltaX) > 5 && this.proxyGestureType.gestureType === 'none' && this.proxyGestureType.gestureType !== 'swipe') {
            this.proxyGestureType.gestureType = 'swipe';
            this.clearOtherInterval(this.touchMoveInterval);
        }
        if(this.proxyGestureType.gestureType === 'swipe'){
            if (move > 0.5) {
                this.add_time = distance * 0.1;
                let totalTime = this.currenttime + this.add_time;
                if (totalTime < 0) { totalTime = 0; }
                if (totalTime > this.videoElement.duration) { totalTime = this.videoElement.duration; }
                let positionRatio = totalTime / this.videoElement.duration;
                let start_X = this.progressElement.clientWidth * positionRatio;
                let videoRect = this.videoElement.getBoundingClientRect();

                // 模拟鼠标在进度条移动事件
                const mouseEvent = new MouseEvent('mousemove', {
                    view: window,
                    bubbles: true,
                    cancelable: true,
                    clientX: videoRect.left + start_X + 14
                });
                this.videoElement.dispatchEvent(mouseEvent);
                let add_txt;
                if(totalTime <= 0){
                    add_txt = "00:00" + " / " + this.sec2Time(this.videoElement.duration);
                }
                else if(totalTime >= this.videoElement.duration){
                    add_txt = `${this.sec2Time(this.videoElement.duration)} / ${this.sec2Time(this.videoElement.duration)}`;
                }
                else{
                    add_txt = `${this.sec2Time(this.add_time + this.currenttime)} / ${this.sec2Time(this.videoElement.duration)}`;
                }

                let display_sec = this.add_time >= 0 ? `+${Math.floor(this.add_time)}s` : `${Math.floor(this.add_time)}s`
                add_txt = add_txt + '\n' + display_sec
                this.textBox = this.createTextBox(this.textBox, add_txt);
            }
        }
        this.lastDistance = distance;
    }
    //* 单指上下移动调整音量和亮度
    UDSwipeAction() {
        let deltaY = this.touchY - this.touchStartY;
        if (this.lastDistance == 0) {
            this.lastDistance = this.touchY;
            return;
        }
        const move = this.touchY - this.lastDistance; //确保手指有移动
        if (Math.abs(deltaY) > 5 && this.proxyGestureType.gestureType === 'none' && this.proxyGestureType.gestureType !== 'swipeUD') {
            this.proxyGestureType.gestureType = 'swipeUD';
            this.clearOtherInterval(this.touchMoveUDInterval);
        }
        
        if(this.proxyGestureType.gestureType === 'swipeUD'){
            let videoRect = this.videoElement.getBoundingClientRect();
            let centerX = videoRect.left + videoRect.width / 2;
            if(this.touchX > centerX){
                if (Math.abs(move) > 5) {
                    this.add_volume = -Math.sign(move)*0.05;
                    this.volume = this.volume + this.add_volume;
                    if (this.volume > 1) {this.volume = 1;}
                    if (this.volume < 0) {this.volume = 0;}
                    this.videoElement.volume = this.volume;
                    this.add_txt = "🔈:" + Math.round(this.volume * 100) + "%";
                    this.textBox = this.createTextBox(this.textBox, this.add_txt);
                }}
            else if(this.touchX < centerX){
                if (Math.abs(move) > 5) {
                    this.add_brightness = -Math.sign(move)*0.05;
                    this.brightness = this.brightness + this.add_brightness;
                    if (this.brightness > 1) {this.brightness = 1;}
                    if (this.brightness < 0) {this.brightness = 0;}
                    this.videoElement.style.filter = `brightness(${this.brightness})`;
                    this.add_txt = "☀:" + Math.round((this.brightness) * 100) + "%";
                    this.textBox = this.createTextBox(this.textBox, this.add_txt);
                }
            }
        }
        this.lastDistance = this.touchY;
    }
    //* 单指长按倍速播放
    LongPressAction() {
        let touchDuration = Date.now() - this.touchStartTime;
        if (touchDuration >= this.longPressThreshold && this.proxyGestureType.gestureType === 'none' && this.proxyGestureType.gestureType !== 'longpress') {
            // 添加震动反馈
            navigator.vibrate(50); // 短震动表示长按开始
            
            this.proxyGestureType.gestureType = 'longpress';
            this.proxyPlaybackRate.playbackRate = '3x';
            this.videoElement.playbackRate = 3;
            this.clearOtherInterval(this.longPressInterval);
        }
        let deltaX = this.touchX - this.touchStartX;
        let deltaY = this.touchY - this.touchStartY;
        let direction = deltaX;
        if (deltaY > 5) { direction = "down"; }
        else if (deltaY < -5) { direction = "up"; }
        let distance = Math.abs(deltaX);
        if (this.lastDistance === 0) {
            this.lastDistance = distance;
        }
        if (Math.abs(distance) > 20 && direction === 'down') {
            if (this.proxyPlaybackRate.playbackRate !== '2x') {
                this.videoElement.playbackRate = 2;
                this.proxyPlaybackRate.playbackRate = '2x';
            }
        }
        if (Math.abs(distance) > 20 && direction === 'up') {
            if (this.proxyPlaybackRate.playbackRate !== '3x') {
                this.videoElement.playbackRate = 3;
                this.proxyPlaybackRate.playbackRate = '3x';
            }
        }
        this.lastDistance = distance;
    }

    //! ------------------TouchEND------------------

    longPressTouchEnd(event) {
        this.videoElement.playbackRate = 1;
        this.clearAllInterval();
        this.proxyGestureType.gestureType = 'none';
        this.proxyPlaybackRate.playbackRate = '1x';
        this.videoElement.removeEventListener('touchend', this.longPressTouchEnd);
    }

    swipeTouchEnd(event) {
        const mouseEvent3 = new MouseEvent('mouseleave', {
            view: window,
            bubbles: true,
            cancelable: true,
        });
        this.progressElement.dispatchEvent(mouseEvent3);
        this.clearAllInterval();
        this.fadeoutTextBox(this.textBox);
        this.videoElement.currentTime = this.currenttime + this.add_time;
        this.add_time = 0;
        this.lastDistance = 0;
        this.proxyGestureType.gestureType = 'none';
        this.photoShotStyle.remove();
        this.photoShotSize.style.height = "100%";
        this.videoElement.removeEventListener('touchend', this.swipeTouchEnd);
    }

    swipeUDTouchEnd(event) {
        this.clearAllInterval();
        this.fadeoutTextBox(this.textBox);
        this.lastDistance = 0;
        this.proxyGestureType.gestureType = 'none';
        this.videoElement.removeEventListener('touchend', this.swipeUDTouchEnd);
    }

    zoomTouchEnd(event){
        this.clearAllInterval();
        this.lastDistance = 0;
        this.proxyGestureType.gestureType = 'none';
        this.videoElement.removeEventListener('touchend', this.zoomTouchEnd);
    }

    normolTouchEnd(event) {
        this.clearAllInterval();
        this.videoElement.playbackRate = 1;
        this.proxyGestureType.gestureType = 'none';
        this.touchEndTime = Date.now();
        const touchDuration = this.touchEndTime - this.touchStartTime;

        if (touchDuration < 100) { // 判断是否为点击
            if (this.clickTimeout) {
                clearTimeout(this.clickTimeout);
                this.clickTimeout = null;
                this.handleDblClick(event); // 处理双击
            } else {
                this.clickTimeout = setTimeout(() => {
                    this.clickTimeout = null;
                    this.handleClick(event); // 处理单击
                }, 200);
            }
        }

    }

    //! ------------------工具函数------------------

    clearAllInterval() {
        for (let i = 0; i < this.intervals.length; i++) {
            clearInterval(this.intervals[i]);
        }
    }

    clearOtherInterval(except) {
        for (let i = 0; i < this.intervals.length; i++) {
            if (this.intervals[i] !== except) {
                clearInterval(this.intervals[i]);
            }
        }
    }
    
    createTextBox(oldtextBox = null, txt) {
        if (oldtextBox) {
            oldtextBox.remove();
        }
        if(this.button) this.button.style.opacity = 0;
        const textBox = document.createElement('div');
        textBox.innerText = txt; // 设置文本内容
        // 设置文本框的样式
        textBox.style.position = 'absolute';
        textBox.style.bottom = '7%'; 
        
        textBox.style.left = '50%'; 
        textBox.style.padding = '5px';
        textBox.style.backgroundColor = 'rgba(255, 255, 255, 0.7)'; // 半透明白色背景
        textBox.style.border = '1px solid #ccc';
        textBox.style.borderRadius = '5px';
        textBox.style.zIndex = '1000'; // 确保文本框在视频上方
        textBox.style.pointerEvents = 'none';
        textBox.style.transform = 'translate(-50%, -50%)'; // 
        textBox.style.fontSize = '20px';
        textBox.style.textAlign = 'center'
        // 将文本框添加到视频元素的父元素中
        this.videoElement.parentElement.style.position = 'relative'; // 确保父元素是相对定位
        this.videoElement.parentElement.appendChild(textBox);
        return textBox;
    }

    createButton(){
        if (this.button) {
            this.button.remove();
        }
        this.button = document.createElement('button');
        this.button.classList.add('bpx-retore-screen-button');
        this.button.innerText = '还原屏幕';
        this.button.style.position = 'absolute';
        this.button.style.bottom = '-2%';
        this.button.style.left = '50%';
        this.button.style.padding = '5px';
        this.button.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
        this.button.style.border = '1px solid #ccc';
        this.button.style.borderRadius = '5px';
        this.button.style.zIndex = '1000';
        this.button.style.pointerEvents = 'auto';
        this.button.style.transform = 'translate(-50%, -50%)';
        this.button.style.fontSize = '25px';
        this.button.addEventListener('click', (event) => {
            event.preventDefault();
            event.stopPropagation();
            this.videoZoomMul = 1;
            this.videoRotateDeg = 0;
            this.videoElement.style.transform = `scale(1) rotate(0deg)`;
            this.button.remove();
        });
        this.videoElement.parentElement.style.position = 'relative';
        this.videoElement.parentElement.appendChild(this.button);
    }

    fadeoutTextBox(textBox, timeout = 200) {
        if(this.button) this.button.style.opacity = 1;
        // 2秒后将文本框从 DOM 中移除
        setTimeout(function () {
            textBox.remove();
        }, timeout);
    }

    sec2Time(sec) {
        sec = Math.abs(sec);
        sec += 1;
        let h = Math.floor(sec / 3600);
        let m = Math.floor((sec % 3600) / 60);
        let s = Math.floor(sec % 60);
        m = m.toString().length === 1 ? m = "0" + m : m;
        s = s.toString().length === 1 ? s = "0" + s : s;
        h = h.toString().length === 1 ? h = "0" + h : h;
        if (m == 0) {
            return `00:${s}`;
        }
        if (h == 0) {
            return `${m}:${s}`;
        } else {
            return `${h}:${m}:${s}`;
        }
    }

    isFullScreen() {
        return document.fullscreenElement != null;
    }

    calculateAngle(x1, y1, x2, y2) {
        const deltaX = x2 - x1;
        const deltaY = y2 - y1;
        return Math.atan2(deltaY, deltaX) * (180 / Math.PI);
    }

    addHint() {
        this.videoArea = document.querySelector(".bpx-player-video-area");
        this.playbackRateHint = document.createElement("div");
        this.playbackRateHint.style.display = "none";
        this.playbackRateHint.className = "bpx-player-three-playrate-hint";
        this.playbackRateHint.innerHTML = `<span class="bpx-player-three-playrate-hint-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 111 66" width="111" height="66" preserveAspectRatio="xMidYMid meet" style="width: 100%; height: 100%; transform: translate3d(0px, 0px, 0px);"><defs><clipPath id="__lottie_element_234"><rect width="111" height="66" x="0" y="0"></rect></clipPath></defs>
        
        <g clip-path="url(#__lottie_element_234)">
            <g  transform="matrix(1,0,0,1,94.5,32.5)" opacity="1" style="display: block;"><g opacity="1" transform="matrix(0,3,-3,0,0,0)"><path class="triangle" id="triangle3" fill="rgb(255,255,255)" fill-opacity="1" d=" M6.138000011444092,3.5460000038146973 C6.4679999351501465,4.105999946594238 6.2779998779296875,4.826000213623047 5.7179999351501465,5.156000137329102 C5.538000106811523,5.265999794006348 5.3379998207092285,5.326000213623047 5.118000030517578,5.326000213623047 C5.118000030517578,5.326000213623047 -5.122000217437744,5.326000213623047 -5.122000217437744,5.326000213623047 C-5.771999835968018,5.326000213623047 -6.302000045776367,4.796000003814697 -6.302000045776367,4.145999908447266 C-6.302000045776367,3.936000108718872 -6.242000102996826,3.7260000705718994 -6.142000198364258,3.5460000038146973 C-6.142000198364258,3.5460000038146973 -1.3519999980926514,-4.553999900817871 -1.3519999980926514,-4.553999900817871 C-0.9120000004768372,-5.294000148773193 0.04800000041723251,-5.544000148773193 0.7979999780654907,-5.104000091552734 C1.027999997138977,-4.973999977111816 1.218000054359436,-4.783999919891357 1.3480000495910645,-4.553999900817871 C1.3480000495910645,-4.553999900817871 6.138000011444092,3.5460000038146973 6.138000011444092,3.5460000038146973z"></path></g></g>
            
            <g  transform="matrix(1,0,0,1,55.5,32.5)" opacity="1" style="display: block;"><g opacity="1" transform="matrix(0,3,-3,0,0,0)"><path class="triangle" id="triangle2" fill="rgb(255,255,255)" fill-opacity="1" d=" M6.138000011444092,3.5460000038146973 C6.4679999351501465,4.105999946594238 6.2779998779296875,4.826000213623047 5.7179999351501465,5.156000137329102 C5.538000106811523,5.265999794006348 5.3379998207092285,5.326000213623047 5.118000030517578,5.326000213623047 C5.118000030517578,5.326000213623047 -5.122000217437744,5.326000213623047 -5.122000217437744,5.326000213623047 C-5.771999835968018,5.326000213623047 -6.302000045776367,4.796000003814697 -6.302000045776367,4.145999908447266 C-6.302000045776367,3.936000108718872 -6.242000102996826,3.7260000705718994 -6.142000198364258,3.5460000038146973 C-6.142000198364258,3.5460000038146973 -1.3519999980926514,-4.553999900817871 -1.3519999980926514,-4.553999900817871 C-0.9120000004768372,-5.294000148773193 0.04800000041723251,-5.544000148773193 0.7979999780654907,-5.104000091552734 C1.027999997138977,-4.973999977111816 1.218000054359436,-4.783999919891357 1.3480000495910645,-4.553999900817871 C1.3480000495910645,-4.553999900817871 6.138000011444092,3.5460000038146973 6.138000011444092,3.5460000038146973z"></path></g></g>
            
            <g transform="matrix(1,0,0,1,16.5,32.5)" opacity="1" style="display: block;"><g opacity="1" transform="matrix(0,3,-3,0,0,0)"><path  class="triangle" id="triangle1" fill="rgb(255,255,255)" fill-opacity="1" d=" M6.138000011444092,3.5460000038146973 C6.4679999351501465,4.105999946594238 6.2779998779296875,4.826000213623047 5.7179999351501465,5.156000137329102 C5.538000106811523,5.265999794006348 5.3379998207092285,5.326000213623047 5.118000030517578,5.326000213623047 C5.118000030517578,5.326000213623047 -5.122000217437744,5.326000213623047 -5.122000217437744,5.326000213623047 C-5.771999835968018,5.326000213623047 -6.302000045776367,4.796000003814697 -6.302000045776367,4.145999908447266 C-6.302000045776367,3.936000108718872 -6.242000102996826,3.7260000705718994 -6.142000198364258,3.5460000038146973 C-6.142000198364258,3.5460000038146973 -1.3519999980926514,-4.553999900817871 -1.3519999980926514,-4.553999900817871 C-0.9120000004768372,-5.294000148773193 0.04800000041723251,-5.544000148773193 0.7979999780654907,-5.104000091552734 C1.027999997138977,-4.973999977111816 1.218000054359436,-4.783999919891357 1.3480000495910645,-4.553999900817871 C1.3480000495910645,-4.553999900817871 6.138000011444092,3.5460000038146973 6.138000011444092,3.5460000038146973z"></path></g></g>
            
            </g></svg></span>倍速播放中`

        this.videoArea.appendChild(this.playbackRateHint);

        let hintStyle = document.createElement("style");
        hintStyle.innerHTML = `
            .triangle {
                animation: fadeToWhite 1.2s infinite; 
            }

            #triangle1 {
                animation-delay: 0s; 
            }

            #triangle2 {
                animation-delay: 0.18s;
            }

            #triangle3 {
                animation-delay: 0.35s;
            }

            @keyframes fadeToWhite {
                0% {
                opacity: 1; 
                filter: brightness(0.3);
                }
                25% {
                opacity: 1; 
                filter: brightness(0.6); 
                }
                50% {
                opacity: 1; 
                filter: brightness(1); 
                }
                75% {
                opacity: 1; 
                filter: brightness(0.6); 
                }
                100% {
                opacity: 1; 
                filter: brightness(0.3); 
                }
            }
        `
        this.playbackRateHint.appendChild(hintStyle);
    }
}
class AddRightEntryEventListener {
    constructor(rightEntry) {
        this.rightEntry = rightEntry;
        this.rightEntry.addEventListener('click', this.handleRightEntryClick.bind(this));
    }
    handleRightEntryClick(event) {
        let target = event.target;
        let href = target.closest('a').getAttribute('href');
        if (href && href.includes('//space')) {
            event.preventDefault();
            return;
        }
        if (href && href.includes('/history')) {
            event.preventDefault();
            return;
        }
    }
}

function waitForVideoELement(timeout = 5000) {
    return new Promise((resolve, reject) => {
        const intervalID = setInterval(() => {
            let videoElement = document.querySelector("#bilibili-player");

            if (videoElement) {
                clearInterval(intervalID);
                clearTimeout(timeoutID);
                resolve(videoElement);
            }
        }, 100);

        const timeoutID = setTimeout(() => {
            clearInterval(intervalID);
            reject(new Error("Timeout: Video element not found"));
        }, timeout);
    });
}
function waitForRightEntry(timeout = 10000) {
    return new Promise((resolve, reject) => {
        const intervalID = setInterval(() => {
            if (document.querySelector("#nav-searchform") != null) {
                let header_position;
                header_position = document.querySelector("#biliMainHeader > div.bili-header.fixed-header > div > ul.right-entry") || document.querySelector(".right-entry");

                if (header_position) {
                    clearInterval(intervalID);
                    clearTimeout(timeoutID);
                    resolve(header_position);
                }
            }
        }, 100);

        const timeoutID = setTimeout(() => {
            clearInterval(intervalID);
            reject(new Error("Timeout: Right entry element not found"));
        }, timeout);
    });
}


let currentUrl = window.location.href; // 打开的网页
const exceptUrl = [
"https://www.bilibili.com/correspond",
"https://message.bilibili.com/pages/nav/header_sync"
];
let shouldTerminate = false;

for (let url of exceptUrl){
    if (currentUrl.includes(url)) {
        shouldTerminate = true;
        break;
    }
}
if (shouldTerminate) {
    shouldTerminate = false;
    return;
}


Promise.race([waitForVideoELement(), waitForRightEntry()])
    .then((result) => {
        if (result.getAttribute("id") == "bilibili-player") {
            console.log("Video element found!");
            new VideoGestureHandler(result);
            handleAnotherPromise(waitForRightEntry, AddRightEntryEventListener, "right");
        } else{
            console.log("Right entry element found!");
            new AddRightEntryEventListener(result);
            handleAnotherPromise(waitForVideoELement, VideoGestureHandler, "video");
        }
    })
    .catch((error) => {
        console.error('Error:', error);
    });

function handleAnotherPromise(promiseFunc, handler, type) {
    promiseFunc()
        .then((result) => {
            new handler(result);
            if (type == "video") {
                console.log("Video element found!");
            } else if (type == "right") {
                console.log("Right entry element found!");
            }
            return
        })
        .catch((error) => {
            console.error('Error:', error);
            return
        });
}