Adobe Learn Enhancer

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

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Advertisement:

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

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);
})();