Adobe Learn Enhancer

Disable captions automatically (after the user hits play) and make the video control bar transparent on Adobe Learn videos.

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 !)

Advertisement:

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!)

Advertisement:

// ==UserScript==
// @name         Adobe Learn Enhancer
// @namespace    karim.adobe.learn
// @version      3.0.0
// @description  Disable captions automatically (after the user hits play) and make the video control bar transparent on Adobe Learn videos.
// @author       Karim Elgazar
// @match        *://*.adobe.com/*
// @match        *://video.tv.adobe.com/*
// @match        *://*.tv.adobe.com/*
// @run-at       document-start
// @icon         https://www.google.com/s2/favicons?sz=64&domain_url=adobe.com
// @grant        none
// @license      Apache License, Version 2.0
// ==/UserScript==

(function () {
    'use strict';

    const TAG = '[Adobe Learn Enhancer]';
    const log = (...a) => console.debug(TAG, ...a);

    // ---------- 1. Transparent control bar ----------
    // The player markup (inside the video iframe):
    //   <div class="controls pointer-events-auto" data-v-aa047c22 style="--controls-scale: 1;">
    // .controls has background-color: var(--bg-color) which is what's covering the video.
    const css = `
        .controls,
        .controls.pointer-events-auto,
        div.controls[class*="pointer-events"] {
            background-color: transparent !important;
            background-image: linear-gradient(
                to top,
                rgba(0, 0, 0, 0.55) 0%,
                rgba(0, 0, 0, 0.25) 60%,
                rgba(0, 0, 0, 0) 100%
            ) !important;
        }
        .controls .v-button,
        .controls .controls__timestamp {
            filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.9));
            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
            opacity: 1 !important;
        }

        /* Hide the top-left chapter-marker bubble. */
        .chapter-marker {
            display: none !important;
        }
    `;

    function injectStyle() {
        if (document.getElementById('adobe-learn-enhancer-style')) return;
        const style = document.createElement('style');
        style.id = 'adobe-learn-enhancer-style';
        style.textContent = css;
        (document.head || document.documentElement).appendChild(style);
    }
    injectStyle();
    document.addEventListener('DOMContentLoaded', injectStyle, { once: true });

    // ---------- 2. Disable captions when they appear ----------
    // Goal: whenever the closed-captions button is reachable AND reports it's ON
    // (aria-pressed="true"), click it once to turn captions OFF. We don't know
    // exactly when the player will mount its controls — it might be on hydrate,
    // it might be only after the user presses play — so we use three triggers:
    //   (a) a MutationObserver on the whole document
    //   (b) a listener on every <video>'s "play" event
    //   (c) a slow polling interval as a final safety net
    const CC_SELECTOR =
        'button[aria-label="closed captions" i], button[aria-label*="caption" i]';
    const handled = new WeakSet();

    function disableCaptionsOnce(btn) {
        if (handled.has(btn)) return false;
        const pressed = btn.getAttribute('aria-pressed');
        if (pressed === 'true') {
            try {
                btn.click();
                handled.add(btn);
                log('Clicked CC button to turn captions OFF.');
                return true;
            } catch (e) {
                console.warn(TAG, 'CC click failed:', e);
            }
        } else if (pressed === 'false') {
            // Already off — remember it so we don't fight the user if they
            // turn captions back on manually.
            handled.add(btn);
        }
        return false;
    }

    function forceTextTracksOff() {
        document.querySelectorAll('video').forEach((v) => {
            if (!v.textTracks || !v.textTracks.length) return;
            for (const t of v.textTracks) {
                if (t.mode === 'showing') {
                    t.mode = 'disabled';
                    log('Forced textTrack OFF:', t.label || t.language || '(no label)');
                }
            }
        });
    }

    function scanAndDisable() {
        let acted = false;
        document.querySelectorAll(CC_SELECTOR).forEach((btn) => {
            if (disableCaptionsOnce(btn)) acted = true;
        });
        forceTextTracksOff();
        return acted;
    }

    // (a) Watch the DOM for the CC button being added or its aria-pressed flipping to "true".
    const mo = new MutationObserver((mutations) => {
        let shouldScan = false;
        for (const m of mutations) {
            if (m.type === 'attributes' && m.attributeName === 'aria-pressed') {
                shouldScan = true;
                break;
            }
            if (m.type === 'childList' && m.addedNodes.length) {
                shouldScan = true;
                break;
            }
        }
        if (shouldScan) scanAndDisable();
    });
    function startObserver() {
        mo.observe(document.documentElement, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['aria-pressed'],
        });
    }
    startObserver();

    // (b) Hook every <video> we ever see, so we can react the moment the user hits play.
    const wiredVideos = new WeakSet();
    function wireVideos() {
        document.querySelectorAll('video').forEach((v) => {
            if (wiredVideos.has(v)) return;
            wiredVideos.add(v);
            const onPlay = () => {
                log('Video play event — scanning for CC button.');
                // Run a few times because the controls bar may animate in.
                scanAndDisable();
                setTimeout(scanAndDisable, 150);
                setTimeout(scanAndDisable, 600);
                setTimeout(scanAndDisable, 1500);
            };
            v.addEventListener('play', onPlay);
            v.addEventListener('playing', onPlay);
            log('Wired play listener on a <video>.');
        });
    }
    // Re-run wiring whenever the DOM changes.
    new MutationObserver(wireVideos).observe(document.documentElement, {
        childList: true,
        subtree: true,
    });
    document.addEventListener('DOMContentLoaded', wireVideos, { once: true });

    // (c) Safety-net poll (cheap, every 1s for up to 60s).
    let tries = 0;
    const poll = setInterval(() => {
        tries++;
        wireVideos();
        scanAndDisable();
        if (tries >= 60) clearInterval(poll);
    }, 1000);

    console.info(TAG, 'v3.0.0 active in', location.host, location.pathname);
})();