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 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

Advertisement:

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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