Miigon's Youtube script

useful enhancements for my own youtube enjoyments

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Miigon's Youtube script
// @namespace    https://blog.miigon.net/
// @version      0.6
// @description  useful enhancements for my own youtube enjoyments
// @author       Miigon
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        none
// @license      MIT
// ==/UserScript==

/* Long press right arrow key to speed up video playback */
const RIGHT_ARROW_SPEEDUP_PRESS_TIME_MS = 500; // key hold time before speeding up
const SPEED_UP_PLAYBACK_RATE = 3;
/* Seeking in youtube shorts */
const SHORTS_SEEK_SECS = 5;

(function() {
    'use strict';

    let mlog = (...args)=>{console.log("miigon's ytb script:", ...args)}

    let right_pressed = false;
    let is_speeding_up = false;
    let speeding_timeout = -1; // timeout waiting before starting speeding up
    let last_playback_rate = 1;

    let is_shorts = () => window.location.href.includes("/shorts/");
    let is_editing = () => document.activeElement.contentEditable == "true";

    // youtube uses javascript to navigate around.
    // so cached_player is only valid if the page is still the same.
    let cached_player_href = null;
    let cached_player = null;
    let getPlayerElement = () => {
        if(window.location.href == cached_player_href && cached_player) {
            return cached_player;
        }

        mlog("invalidate player element cache")
        cached_player = null;
        let ytd_player = [...document.getElementsByTagName("ytd-player")];
        if(is_shorts()) {
            cached_player = ytd_player.find(v=>v.className.indexOf("ytd-shorts")!=-1);
        } else { // normal full-length video
            cached_player = ytd_player.find(v=>
                v.className.indexOf("preview") == -1
                && v.className.indexOf("ytd-shorts") == -1
            );
        }
        if(cached_player != null) {
            cached_player_href = window.location.href;
        }
        return cached_player;
    }
    let getPlayer = () => getPlayerElement().getPlayer();
    let getPlayerVideoTag = () => getPlayerElement().getElementsByTagName("video")[0];



    document.addEventListener("keydown", (e)=>{
        let used = false;

        // skip hotkeys when editing comment etc.
        if(is_editing()) return;

        if(e.key === "ArrowLeft") {
            // enables left seeking in shorts
            // ArrowRight is handled in keyup instead of keydown.
            if(is_shorts()){
                getPlayer().seekBy(-SHORTS_SEEK_SECS);
            }
        }

        if(e.miigon_ignore) {
            return;
        }

        if(e.key === "ArrowRight") {
            if(right_pressed == false){
                right_pressed = true;
                mlog("right pressed");
                speeding_timeout = setTimeout(()=>{
                    speeding_timeout = -1;
                    is_speeding_up = true;
                    let v = getPlayerVideoTag();
                    last_playback_rate = v.playbackRate;
                    v.playbackRate = SPEED_UP_PLAYBACK_RATE;
                    mlog("speeding up");

                    used = true;
                }, RIGHT_ARROW_SPEEDUP_PRESS_TIME_MS);
            }
            used = true;
        }

        if(used){
            e.stopPropagation();
            e.preventDefault();
        }
    }, /*useCapture*/true);

    document.addEventListener("keyup", (e)=>{
        let used = false;

        // skip hotkeys when editing comment etc.
        if(is_editing()) return;

        if(e.key === "ArrowRight") {
            right_pressed = false;
            if(speeding_timeout != -1) { // keyup before speeding up begins
                clearTimeout(speeding_timeout);
                speeding_timeout = -1;

                if(is_shorts()){
                    // shorts, so simulating a right arrow keypress wont work
                    // tell the player to seek directly.
                    // this enables right seeking in shorts
                    getPlayer().seekBy(SHORTS_SEEK_SECS);
                } else { // normal video
                    // dispatch a normal right arrow keypress event.
                    let event = new KeyboardEvent("keydown", {keyCode: 39})
                    event.miigon_ignore = true; // the script should not process this event
                    document.dispatchEvent(event);
                }
            }
            if(is_speeding_up) { // keyup after speeding up begins
                is_speeding_up = false;
                mlog("stop speeding up");
                getPlayerVideoTag().playbackRate = last_playback_rate;
                used = true;
            }
        }

        if(used){
            e.stopPropagation();
            e.preventDefault();
        }
    }, /*useCapture*/true);
})();