Studyforge Playback Speed

Adds a video speed slider to lesson pages

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name        Studyforge Playback Speed
// @namespace   studyforge-playback-speed
// @match       https://tool.studyforge.net/lesson/*
// @grant       none
// @version     1.3
// @author      InterstellarOne
// @license     MIT
// @description Adds a video speed slider to lesson pages
// ==/UserScript==

(function() {
    'use strict';

    // Slider
    const sizeMultiplier = 1.30;

    const sliderContainer = document.createElement('div');
    sliderContainer.id = 'video-speed-slider-container';
    sliderContainer.style.position = 'fixed';
    sliderContainer.style.bottom = '-4px';
    sliderContainer.style.right = '0px';
    sliderContainer.style.zIndex = '9999';
    sliderContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
    sliderContainer.style.padding = `${4 * sizeMultiplier}px`; // Apply multiplier to padding
    sliderContainer.style.color = 'white';
    sliderContainer.style.fontFamily = 'sans-serif';
    sliderContainer.style.fontSize = `${12 * sizeMultiplier}px`; // Apply multiplier to font size
    sliderContainer.style.borderRadius = `${4 * sizeMultiplier}px`; // Apply multiplier to border radius

    const sliderLabel = document.createElement('label');
    sliderLabel.style.padding = `${4 * sizeMultiplier}px`; // Apply multiplier to padding
    sliderLabel.textContent = 'Speed: ';
    sliderLabel.htmlFor = 'video-speed-slider';

    const sliderValueSpan = document.createElement('span');
    sliderValueSpan.id = 'video-speed-value';
    sliderValueSpan.textContent = '1.0'; // Initial display value

    const slider = document.createElement('input');
    slider.type = 'range';
    slider.id = 'video-speed-slider';
    slider.min = '0.5';
    slider.max = '2.5';
    slider.step = '0.05';
    slider.value = '1.0'; // Default value

    sliderContainer.appendChild(sliderLabel);
    sliderContainer.appendChild(sliderValueSpan);
    sliderContainer.appendChild(document.createElement('br')); // Add a line break
    sliderContainer.appendChild(slider);

    document.body.appendChild(sliderContainer);

    // Function to update playback rate
    function updatePlaybackRate(speed) {
        document.querySelectorAll("video").forEach(video => {
            // Check if the video is ready to have its playbackRate set
            if (video.readyState > 0) {
                video.playbackRate = speed;
            } else {
                // If not ready, wait for the 'loadedmetadata' or 'canplay' event
                const setSpeed = () => {
                    video.playbackRate = speed;
                };
                video.addEventListener('loadedmetadata', setSpeed, {
                    once: true
                }); // Used to remove the listener after it fires
                video.addEventListener('canplay', setSpeed, {
                    once: true
                });
            }
        });
    }

    // Listener for slider values
    slider.addEventListener('input', function() {
        const speed = parseFloat(this.value);
        sliderValueSpan.textContent = speed.toFixed(2); // Update displayed value with 2 decimal places
        updatePlaybackRate(speed);
    });

    // Initial setup: apply default speed to existing videos
    updatePlaybackRate(parseFloat(slider.value));

    // Function to check the display state of the question-fullscreen element
    function checkSliderVisibility() {
        const questionFullscreen = document.querySelector('.question-fullscreen');

        if (!questionFullscreen || window.getComputedStyle(questionFullscreen).display === 'none') {
            sliderContainer.style.display = 'block'; // Show the slider
        } else {
            sliderContainer.style.display = 'none'; // Hide the slider
        }
    }

    // Initial check on page load
    checkSliderVisibility();

    // Use a MutationObserver to watch for changes to the DOM,
    // specifically the style attribute of the question-fullscreen element
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
                checkSliderVisibility();
            }
            // Also check for added nodes, in case the question-fullscreen element
            // is added to the DOM dynamically
            if (mutation.type === 'childList') {
                 mutation.addedNodes.forEach(node => {
                     if (node.nodeType === Node.ELEMENT_NODE && node.classList && node.classList.contains('question-fullscreen')) {
                         checkSliderVisibility();
                     }
                 });
            }
        });
    });

    // Start observing the body for attribute and childList changes.
    observer.observe(document.body, {
        attributes: true,
        subtree: true, // Also observe children
        attributeFilter: ['style'], // Only observe changes to the 'style' attribute
        childList: true // Observe addition/removal of nodes
    });

})();