Adobe Learn Enhancer

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

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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