YouTube Speedrun Timer

Adds quality of life features for YouTube frame timing

// ==UserScript==
// @name         YouTube Speedrun Timer
// @namespace    http://tampermonkey.net/
// @version      0.2.4
// @description  Adds quality of life features for YouTube frame timing
// @author       Michael "Msbmteam" Y.
// @match        https://*.youtube.com/*
// @match        http://*.youtube.com/*
// @grant        none
// ==/UserScript==

// This script is based on the "YouTube Detailed Timecode" script by Charlie Laabs (https://gf.qytechs.cn/en/scripts/40740-youtube-detailed-timecode)

(function() {
    'use strict';

    // console.log('Running YouTube Speedrun Timer');
    var ytPlayer;
    var ytPlayerUnwrapped;
    var ytpCurrentTime;
    var timeHour = document.createElement('span');
    var timeMinute = document.createElement('span');
    var timeSecond = document.createElement('span');
    var frameCount = document.createElement('span');
    var lastDisplayedTime = 0;
    var playerInitialized = false;
    var fps;
    var frame;

    function getElements() {
        // get the YouTube player element from the page
        ytPlayer = document.getElementById("movie_player") || document.getElementsByClassName("html5-video-player")[0];
        ytPlayerUnwrapped = ytPlayer.wrappedJSObject;
        if (ytPlayer)
        {
            frameCount.innerHTML = "";
            ytPlayer.addEventListener("onStateChange", playerChanged, true );
            document.addEventListener("keyup", keyUp, true );
            document.addEventListener("keydown", keyDown, true);

            setFps();
        }
    }

    function playerStarted()
    {
        var timeDisp = ytPlayerUnwrapped.getElementsByClassName("ytp-time-display")[0];

        timeHour.className = 'ytsrt-hr';
        timeHour.style.paddingLeft = "0px";
        timeMinute.className = 'ytsrt-min';
        timeMinute.style.paddingLeft = "0px";
        timeSecond.className = 'ytsrt-sec';
        timeSecond.style.paddingLeft = "0px";


        frameCount.className = 'ytsrt-frame';
        frameCount.style.paddingLeft = '20px';
        timeDisp.appendChild(frameCount);

        playerInitialized = true;

    }

    function playerChanged(state)
    {
        //console.log("stateChanged: ", state);
        if (state != -1 && !playerInitialized ) {
            playerStarted();
        }
        if (state == 2) {
            displayTime();
        }

    }

    function keyUp(e)
    {
        displayTime();
    }

    function keyDown(e)
    {
        // "D" -- advance 1 second forward
        if (e.keyCode == 68) {
            ytPlayerUnwrapped.seekTo(ytPlayerUnwrapped.getCurrentTime() + 1);
        }
        // "A" -- advance 1 second backward
        if (e.keyCode == 65) {
            ytPlayerUnwrapped.seekTo(ytPlayerUnwrapped.getCurrentTime() - 1);
        }
        // "S" -- Toggle between regular speed, slow-mo, or super slow-mo (1.0, 0.5, or 0.1)
        if (e.keyCode == 83) {
            var playback_rate = ytPlayerUnwrapped.getPlaybackRate();
            if (playback_rate == 1.0)
            {
                ytPlayerUnwrapped.setPlaybackRate(0.5);
            }
            else if (ytPlayerUnwrapped.getPlaybackRate() == 0.5)
            {
                playback_rate.setPlaybackRate(0.1);
            }
            else
            {
                playback_rate.setPlaybackRate(1.0);
            }
        }
    }

    function setFps()
    {
        // Find fps from Stats for Nerds (can get manually by right clicking on YT player)
        var resolution = ytPlayerUnwrapped.getStatsForNerds().resolution;
        fps = resolution.substr(resolution.search('@')+1, 2);
        fps = Number.parseInt(fps);
        frame = 1/fps;
    }

    function frameAdvance(keycode, dir)
    {
        var num = Number(String.fromCharCode(keycode));
        if (num === 0) num = 10;
        if (dir == -1) num *= -1;
        ytPlayerUnwrapped.seekTo(ytPlayerUnwrapped.getCurrentTime() + num * frame, true);
    }

    function displayTime()
    {
        ytpCurrentTime = document.getElementsByClassName("ytp-time-current")[0];
        var currentTime = ytPlayerUnwrapped.getCurrentTime();
        ytpCurrentTime.innerHTML = "";
        var hour = Math.floor(currentTime / 3600);
        var minute = Math.floor(currentTime / 60) - (hour * 60);
        var second = (currentTime - (hour * 3600) - (minute * 60));
        if (hour > 0)
        {
            timeHour.innerHTML = hour.toString() + ":";
            ytpCurrentTime.appendChild(timeHour);
        }
        timeMinute.innerHTML = minute.toString() + ":";
        ytpCurrentTime.appendChild(timeMinute);
        if (second < 10)
        {
            timeSecond.innerHTML = "0" + (second.toFixed(3)).toString();
        }
        else
        {
            timeSecond.innerHTML = second.toFixed(3);
        }
        ytpCurrentTime.appendChild(timeSecond);

        setFps();

        var frameNum = Math.floor(currentTime * fps);
        frameCount.innerHTML = 'Current frame: ' + frameNum + ' (' + fps + ' FPS)';
    }

    getElements(); //for embedded videos
    document.addEventListener("yt-navigate-finish", getElements, true ); //for YouTube


})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址