Greasy Fork is available in English.

Khan Academy YouTube Playback Rate Enforcer

Remembers the playback rate you set on Khan Academy's YouTube player and enforces it across lessons

// ==UserScript==
// @name         Khan Academy YouTube Playback Rate Enforcer
// @namespace    https://jeffschofield.com/
// @version      0.4
// @description  Remembers the playback rate you set on Khan Academy's YouTube player and enforces it across lessons
// @author       Jeff Schofield
// @match        https://www.khanacademy.org/*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    const ENFORCED = Symbol('enforced');
    const GM_KEY = 'jeffschofield.com-playback_rate';
    const YOUTUBE_ORIGIN = /^https?:\/\/[-\.\w]*\.youtube(-nocookie)?\.com.*$/;

    var PLAYBACK_RATE = GM_getValue(GM_KEY, 1);
    function updatePlaybackRate({ data }) {
        if (data === PLAYBACK_RATE) return; // No change
        GM_setValue(GM_KEY, PLAYBACK_RATE = data);

        // console.log(`Debug: Updated playback rate to '${ PLAYBACK_RATE }'`);
    }

    window.addEventListener('message', ({ data, origin }) => { // The YouTube iFrame API uses the `message` event to communicate, so we watch here for any activity in the page
        if (!origin.match(YOUTUBE_ORIGIN)) return; // Ensure this message is from YouTube

        data = JSON.parse(data); // Message data is passed as JSON. Parse it
        let { event, info, id, channel } = data; // Destructure the parsed message data

        if (event === 'onReady') { // Watch for the onReady event coming from any iframe player

            // Note: I briefly explored using the ID and Channel to communicate directly with the player instead of re-scanning every `onReady` event,
            // ultimately the scanning method was faster to implement for the time I have. Revisions are welcome!

            document.querySelectorAll('iframe[id]').forEach($iframe => { // Scan through all iframes with an ID attribute defined on the page
                let player = YT.get($iframe.id); // Try to get a reference to the YT.Player instance attached to this iframe, if any

                if (!player) return; // No player instance on this iframe
                if (player[ENFORCED]) return; // Already enforced playback on this player

                player.setPlaybackRate(PLAYBACK_RATE); // Enforce playback rate!
                player.addEventListener('onPlaybackRateChange', updatePlaybackRate); // Listen for playback rate changes on this player to update the remembered value
                player[ENFORCED] = true; // Brand this player instance with our symbol to indicate it has been enforced

                // console.log(`Debug: Enforcing playback rate on '${ $iframe.id }'`);
            });
        }
    });
})();