Enhanced Audio Speed Controller with Time Info, Speed Highlight, and Toggle

Adds time information (duration, currentTime, etc.), adjusts for playback speed, highlights the active speed button, and has a toggle for hiding/showing the control panel.

当前为 2024-09-26 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Enhanced Audio Speed Controller with Time Info, Speed Highlight, and Toggle
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Adds time information (duration, currentTime, etc.), adjusts for playback speed, highlights the active speed button, and has a toggle for hiding/showing the control panel.
// @author       Josh Gough
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    let startTime = Date.now(); // Track the real-time start
    let isPanelVisible = true;  // Track panel visibility

    const odIcon = '🕰️';
    const adIcon = '⏰';
    const ctIcon = '⌚';
    const pcIcon = '➗';
    const trIcon = '⏳';
    const wcIcon = '🕛';

    // Helper function to convert fractional minutes into hh:mm:ss format
    function convertToTimeFormat(minutes) {
        const totalSeconds = Math.floor(minutes * 60); // Convert minutes to seconds
        const hours = Math.floor(totalSeconds / 3600); // Calculate full hours
        const remainingSeconds = totalSeconds % 3600; // Remaining seconds after hours
        const mins = Math.floor(remainingSeconds / 60); // Full minutes
        const secs = remainingSeconds % 60; // Remaining seconds

        // Format the time string to always show two digits
        const formattedTime =
            (hours > 9 ? hours : '0' + hours) + ':' +
            (mins > 9 ? mins : '0' + mins) + ':' +
            (secs > 9 ? secs : '0' + secs);

        return formattedTime;
    }

    // Function to calculate and update time stats
    function updateTimeStats() {
        const audioElement = document.querySelector('audio');
        if (audioElement && audioElement.duration && !isNaN(audioElement.duration)) {
            const duration = audioElement.duration;
            const adjustedDuration = audioElement.duration / audioElement.playbackRate / 60;
            const currentTime = audioElement.currentTime;
            const playbackRate = audioElement.playbackRate;
            const currentTimeDisp = currentTime / 60 / playbackRate;
            const percentComplete = (currentTime / duration) * 100; // Correct percentage format

            // Time remaining at current speed
            const timeRemaining = (adjustedDuration - currentTimeDisp);
            // Total elapsed wall clock time (accounting for pauses)
            const elapsedWallClockTime = (Date.now() - startTime) / 1000 / 60;

            // Update the DOM elements with the values
            document.getElementById('original-duration').innerHTML = `${odIcon}<br>${convertToTimeFormat(duration / 60)}`;
            document.getElementById('adjusted-duration').innerHTML = `${adIcon}<br>${convertToTimeFormat(adjustedDuration)}`;
            document.getElementById('current-time').innerHTML = `${ctIcon}<br>${convertToTimeFormat(currentTimeDisp)}`;
            document.getElementById('percent-complete').innerHTML = `<span class="rotate">${pcIcon}</span><br>${percentComplete.toFixed(2)}%`;
            document.getElementById('time-remaining').innerHTML = `${trIcon}<br>${convertToTimeFormat(timeRemaining)}`;
            document.getElementById('elapsed-wall-clock').innerHTML = `${wcIcon}<br>${convertToTimeFormat(elapsedWallClockTime)}`;
        }
    }

    // Function to create the control panel with buttons and time information
    function createControlPanel() {
        // Check if control panel already exists
        if (document.getElementById('audio-speed-control')) return;

        const controlDiv = document.createElement('div');
        controlDiv.id = 'audio-speed-control';
        controlDiv.style.position = 'fixed';
        controlDiv.style.top = '20%'; // Position it at 20% from the top (adjust as needed)
        controlDiv.style.right = '0'; // Align to the far right edge
        controlDiv.style.background = 'rgba(0, 0, 0, 0.15)'; // 85% transparent background
        controlDiv.style.padding = '5px';
        controlDiv.style.borderRadius = '5px';
        controlDiv.style.zIndex = '999999'; // High z-index
        controlDiv.style.display = 'flex';
        controlDiv.style.flexDirection = 'column'; // Stack buttons vertically
        controlDiv.style.fontSize = '7pt'; // Make font smaller as requested
        controlDiv.style.transition = 'transform 0.5s ease'; // Smooth horizontal sliding transition

        // Section for time stats
        const timeStats = document.createElement('div');
        timeStats.style.marginBottom = '4px'; // Spacing above the buttons
        timeStats.style.fontSize = '6pt';
        timeStats.style.color = 'white';
        timeStats.style.fontWeight = "bold";
        timeStats.style.textAlign = 'center';

        // Define an array of objects, where each object contains the id and innerHTML
        const timeStatsData = [
            { id: 'original-duration', label: odIcon },
            { id: 'adjusted-duration', label: adIcon },
            { id: 'current-time', label: ctIcon },
            { id: 'percent-complete', label: pcIcon },
            { id: 'time-remaining', label: trIcon },
            { id: 'elapsed-wall-clock', label: wcIcon }
        ];

        // Loop over the timeStatsData array to create and append each stat element
        timeStatsData.forEach(stat => {
            const statDiv = document.createElement('div');
            statDiv.id = stat.id;
            statDiv.innerHTML = stat.label + "<br>--:--:--"; // Placeholder until data is available
            timeStats.appendChild(statDiv);
        });

        // Append all the time stats
        controlDiv.appendChild(timeStats);

        // Now we create the speed control buttons as before
        const speeds = [1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5];
        let activeButton = null; // Track the currently active button

        speeds.forEach(speed => {
            const button = document.createElement('button');
            button.innerText = speed.toFixed(2); // Label each button with the speed
            button.style.padding = '3px 5px'; // Small button size
            button.style.marginBottom = '2px'; // Small margin between buttons
            button.style.backgroundColor = '#bada55';
            button.style.border = 'none';
            button.style.borderRadius = '3px';
            button.style.cursor = 'pointer';
            button.style.fontSize = '8px';
            button.style.fontWeight = 'bold'; // Small font size
            button.style.color = '#222';
            button.style.width = '36px'; // Small width for the buttons

            // Function to set button as active
            function setActiveButton() {
                if (activeButton) {
                    // Reset previously active button's style
                    activeButton.style.backgroundColor = '#bada55';
                    activeButton.style.color = '#222';
                    activeButton.style.fontWeight = 'normal';
                }
                // Set new active button's style
                button.style.backgroundColor = '#006400'; // Dark green background
                button.style.color = '#fff'; // White text
                button.style.fontWeight = 'bold'; // Bold text
                activeButton = button; // Set this as the active button
            }

            // Set audio speed when button is clicked
            button.addEventListener('click', function () {
                const audioElement = document.querySelector('audio');
                if (audioElement) {
                    audioElement.playbackRate = speed;
                    setActiveButton(); // Highlight the active button
                }
            });

            controlDiv.appendChild(button);
        });

        // Create toggle button attached to the top-left of the control panel
        const toggleButton = document.createElement('button');
        toggleButton.innerText = '◀'; // Icon for sliding out/in
        toggleButton.style.position = 'absolute';
        toggleButton.style.top = '5px';
        toggleButton.style.left = '-15px'; // Positioned just outside the left of the control panel
        toggleButton.style.backgroundColor = '#bada55';
        toggleButton.style.border = 'none';
        toggleButton.style.borderRadius = '20%';
        toggleButton.style.cursor = 'pointer';
        toggleButton.style.padding = '2px';
        toggleButton.style.zIndex = '1000'; // Ensure it stays on top

        // Event listener for the toggle button
        toggleButton.addEventListener('click', () => {
            if (isPanelVisible) {
                controlDiv.style.transform = 'translateX(100%)'; // Slide horizontally to the right
                toggleButton.innerText = '▶'; // Change icon to indicate sliding back
            } else {
                controlDiv.style.transform = 'translateX(0)'; // Slide back to the original position
                toggleButton.innerText = '◀'; // Change icon back
            }
            isPanelVisible = !isPanelVisible; // Toggle visibility flag
        });

        // Append toggle button to the control panel
        controlDiv.appendChild(toggleButton);

        // Simplify the control panel sliding transition
        controlDiv.style.transition = 'transform 0.5s ease'; // Smooth horizontal sliding transition

        // Append the control panel to the body
        document.body.appendChild(controlDiv);


    }

    // CSS to rotate the emoji
    const style = document.createElement('style');
    style.innerHTML = `
        .rotate {
            display: inline-block;
            transform: rotate(45deg); /* Rotates emoji */
        }
    `;
    document.head.appendChild(style);

    // Update the time stats periodically
    setInterval(updateTimeStats, 1000); // Update every second

    // Wait for the document to fully load and ensure audio element exists
    const observer = new MutationObserver((mutations, observer) => {
        const audioElement = document.querySelector('audio');
        if (audioElement) {
            audioElement.addEventListener('loadedmetadata', () => {
                audioElement.playbackRate = 2.0; // Set default playback speed to 2.0
                createControlPanel();
                updateTimeStats();
            });
            observer.disconnect(); // Stop observing once the audio is found
        }
    });

    // Start observing the document for changes
    observer.observe(document, {
        childList: true,
        subtree: true
    });

})();