YouTube Live Theme

Removes the progress bar from YouTube live streams.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Live Theme
// @namespace    ModLabs
// @version      1.0.0-GitHUb
// @description  Removes the progress bar from YouTube live streams.
// @license      Apache License 2.0
// @author       ModLabs
// @match        https://www.youtube.com/*
// @match        https://m.youtube.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';

  const CHECK_INTERVAL_MS = 1200;
  const NAVIGATION_EVENTS = ['yt-navigate-finish', 'yt-page-data-updated'];
  const SHADOW_STYLE_ID = 'yt-live-progress-remover-shadow-style';
  const PLAYER_HIDE_CLASS = 'yt-live-progress-hidden';
  const PLAYER_PROGRESS_ACTIVE_CLASS = 'yt-live-progress-active';
  const HOVER_ZONE_PX = 90;
  const HIDE_DELAY_MS = 400;
  const PROGRESS_BAR_Y_OFFSET_PX = 54;
  const DEBUG = false;
  const DEBUG_OVERLAY_ID = 'yt-live-progress-debug';
  let lastLiveElement = null;

  const getLiveIndicatorElement = () => document.querySelector('.ytp-live');
  const getLiveBadgeElement = () => document.querySelector('.ytp-live');

  const removeDebugOverlay = () => {
    const o = document.getElementById(DEBUG_OVERLAY_ID);
    if (o) o.remove();
    if (lastLiveElement) {
      lastLiveElement.style.outline = '';
      lastLiveElement.style.outlineOffset = '';
      lastLiveElement = null;
    }
  };

  const showDebugOverlay = (el, badge) => {
    if (!DEBUG) return;
    let overlay = document.getElementById(DEBUG_OVERLAY_ID);
    const target = badge || el;
    const info = `${badge ? 'badge ' : ''}${target.tagName.toLowerCase()}${target.id ? '#' + target.id : ''}${target.classList.length ? '.' + [...target.classList].join('.') : ''}`;
    if (!overlay) {
      overlay = document.createElement('div');
      overlay.id = DEBUG_OVERLAY_ID;
      overlay.style.cssText = 'position:fixed;z-index:999999;top:8px;left:8px;padding:6px 10px;font:12px/1.3 system-ui,Segoe UI,Roboto,sans-serif;background:rgba(0,0,0,0.65);color:#ffbfd1;border:1px solid rgba(255,255,255,0.18);border-radius:8px;backdrop-filter:blur(8px) saturate(180%);-webkit-backdrop-filter:blur(8px) saturate(180%);pointer-events:none;box-shadow:0 4px 14px -4px rgba(0,0,0,0.6)';
      document.documentElement.appendChild(overlay);
    }
    overlay.textContent = 'Live detected: ' + info;
  };
  const CONTROL_SHADOW_CSS = `
    .html5-video-player.${PLAYER_HIDE_CLASS} .ytp-gradient-bottom {
      background: linear-gradient(
        0deg,
        rgba(0, 0, 0, 0.68) 0%,
        rgba(0, 0, 0, 0.38) 55%,
        rgba(0, 0, 0, 0) 100%
      ) !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS} .ytp-progress-bar-container {
      opacity: 0 !important;
      transform: translateY(${PROGRESS_BAR_Y_OFFSET_PX}px);
      transition: opacity 240ms ease;
      background: transparent !important;
      border: 1px solid transparent !important;
      box-shadow: none !important;
      position: relative;
      overflow: hidden;
      pointer-events: auto !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS} .ytp-progress-bar-container,
    .html5-video-player.${PLAYER_HIDE_CLASS} .ytp-progress-bar-container .ytp-progress-bar,
    .html5-video-player.${PLAYER_HIDE_CLASS} .ytp-progress-bar-container .ytp-progress-bar * {
      border-radius: 16px !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS} .ytp-progress-bar-container .ytp-progress-bar {
      opacity: 0.4 !important;
  height: 8px !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-progress-bar-container {
      opacity: 1 !important;
      transform: translateY(${PROGRESS_BAR_Y_OFFSET_PX}px);
      transition: opacity 160ms ease;
      background:
        linear-gradient(182deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 55%, rgba(0,0,0,0.35) 100%),
        rgba(0,0,0,0.42) !important;
      border: 1px solid rgba(255,255,255,0.18) !important;
      box-shadow:
        inset 0 0 0 1px rgba(255,255,255,0.05),
        inset 0 1px 0 rgba(255,255,255,0.28),
        0 4px 14px -4px rgba(0,0,0,0.55),
        0 18px 40px -10px rgba(0,0,0,0.55);
      backdrop-filter: blur(30px) saturate(260%) brightness(0.92);
      -webkit-backdrop-filter: blur(30px) saturate(260%) brightness(0.92);
      overflow: hidden;
      pointer-events: auto !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-progress-bar-container::before {
      content: '';
      position: absolute;
      inset: 2px 3px 3px 3px;
      border-radius: inherit;
      background:
        radial-gradient(120% 140% at 15% 0%, rgba(255,255,255,0.38) 0%, rgba(255,255,255,0) 55%),
        linear-gradient(90deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0) 22%, rgba(255,255,255,0) 78%, rgba(255,255,255,0.22) 100%);
      mix-blend-mode: screen;
      opacity: 0.22;
      pointer-events: none;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-progress-bar-container::after {
      content: '';
      position: absolute;
      inset: 0;
      background:
        linear-gradient(180deg, rgba(255,255,255,0.20) 0%, rgba(255,255,255,0.05) 42%, rgba(0,0,0,0.55) 100%),
        radial-gradient(85% 120% at 50% -30%, rgba(255,255,255,0.30) 0%, rgba(255,255,255,0) 70%);
      opacity: 0.18;
      pointer-events: none;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-progress-bar {
      height: 8px !important;
      border-radius: 999px !important;
      position: relative;
      z-index: 1;
      overflow: visible !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-progress-bar-padding {
      border-radius: 999px !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-progress-bar-background {
      background:
        linear-gradient(120deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.02) 100%),
        rgba(0,0,0,0.58) !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-load-progress {
      background:
        linear-gradient(125deg, rgba(255,255,255,0.42) 0%, rgba(255,255,255,0.12) 100%) !important;
      opacity: 0.42;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS} .ytp-progress-bar-container .ytp-play-progress,
    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-progress-bar-container .ytp-play-progress {
      position: relative;
      border-radius: 999px !important;
      opacity: 0.66 !important;
      background:
        linear-gradient(118deg, rgba(255,90,120,0.85) 0%, rgba(255,60,110,0.90) 40%, rgba(255,70,130,0.80) 72%, rgba(255,120,160,0.70) 100%),
        rgba(255,72,110,0.55) !important;
      box-shadow:
        inset 0 0 0 1px rgba(255,255,255,0.55),
        0 0 26px rgba(255,80,120,0.55),
        0 4px 14px rgba(255,80,120,0.25),
        0 0 2px 1px rgba(255,120,150,0.45);
      backdrop-filter: blur(12px) saturate(240%);
      -webkit-backdrop-filter: blur(12px) saturate(240%);
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-progress-bar-container .ytp-play-progress::after {
      content: '';
      position: absolute;
      inset: 0;
      border-radius: inherit;
      background:
        linear-gradient(100deg, rgba(255,255,255,0.75) 0%, rgba(255,255,255,0.35) 28%, rgba(255,255,255,0) 72%),
        linear-gradient(0deg, rgba(255,255,255,0.35), rgba(255,255,255,0));
      opacity: 0.55;
      mix-blend-mode: screen;
      pointer-events: none;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-scrubber-container {
      width: 18px !important;
      height: 18px !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-scrubber-button {
      width: 18px !important;
      height: 18px !important;
      margin-top: -5px !important;
      margin-left: -9px !important;
      border-radius: 50% !important;
      background:
        linear-gradient(140deg, rgba(255, 255, 255, 0.95) 0%, rgba(224, 228, 235, 0.85) 45%, rgba(176, 182, 196, 0.9) 100%) !important;
      box-shadow:
        0 6px 18px rgba(18, 20, 32, 0.38),
        0 1px 0 rgba(255, 255, 255, 0.65),
        inset 0 0 0 1px rgba(255, 255, 255, 0.95);
      border: none !important;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-scrubber-button::after {
      content: '';
      position: absolute;
      inset: 2px;
      border-radius: 50%;
      background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.45) 45%, rgba(255, 255, 255, 0) 100%);
      opacity: 0.9;
    }

    .html5-video-player.${PLAYER_HIDE_CLASS}.${PLAYER_PROGRESS_ACTIVE_CLASS} .ytp-chapter-hover-container {
      border-radius: 14px !important;
      backdrop-filter: blur(20px) saturate(180%);
      -webkit-backdrop-filter: blur(20px) saturate(180%);
      background: rgba(16, 20, 32, 0.75);
      border: 1px solid rgba(255, 255, 255, 0.2);
      box-shadow: 0 16px 40px rgba(0, 0, 0, 0.45);
    }
  `;

  let monitorId = null;
  const hoverBindingMap = new WeakMap();

  const bindProgressHoverHandlers = (player) => {
    const container = player.querySelector('.ytp-progress-bar-container');
    if (!container) {
      unbindProgressHoverHandlers(player);
      return;
    }

    const scrubber = player.querySelector('.ytp-scrubber-container');
    const existingBinding = hoverBindingMap.get(player);
    if (existingBinding?.container === container && existingBinding?.scrubber === scrubber) {
      return;
    }

    if (existingBinding) {
      unbindProgressHoverHandlers(player);
    }

    const state = {
      hideTimer: null,
      hoverZone: false,
      pointerInInteractive: false,
      focused: false,
      dragging: false,
    };

    const clearHideTimer = () => {
      if (state.hideTimer !== null) {
        clearTimeout(state.hideTimer);
        state.hideTimer = null;
      }
    };

    const activate = () => {
      clearHideTimer();
      if (!player.classList.contains(PLAYER_PROGRESS_ACTIVE_CLASS)) {
        player.classList.add(PLAYER_PROGRESS_ACTIVE_CLASS);
      }
    };

    const shouldHide = () => {
      return !state.hoverZone && !state.pointerInInteractive && !state.focused && !state.dragging;
    };

    const scheduleHide = () => {
      if (!shouldHide()) {
        return;
      }
      clearHideTimer();
      state.hideTimer = window.setTimeout(() => {
        state.hideTimer = null;
        if (shouldHide()) {
          player.classList.remove(PLAYER_PROGRESS_ACTIVE_CLASS);
        }
      }, HIDE_DELAY_MS);
    };

    const pointerMoveHandler = (event) => {
      const rect = player.getBoundingClientRect();
      const distanceFromBottom = rect.bottom - event.clientY;
      const insideZone = distanceFromBottom >= 0 && distanceFromBottom <= HOVER_ZONE_PX;
      if (insideZone) {
        if (!state.hoverZone) {
          state.hoverZone = true;
          activate();
        }
      } else if (state.hoverZone) {
        state.hoverZone = false;
        scheduleHide();
      }
    };

    const pointerLeaveHandler = () => {
      state.hoverZone = false;
      scheduleHide();
    };

    const pointerEnterInteractive = () => {
      state.pointerInInteractive = true;
      activate();
    };

    const pointerLeaveInteractive = () => {
      state.pointerInInteractive = false;
      scheduleHide();
    };

    const focusInHandler = () => {
      state.focused = true;
      activate();
    };

    const focusOutHandler = () => {
      state.focused = false;
      scheduleHide();
    };

    const pointerDownHandler = () => {
      state.dragging = true;
      activate();
    };

    const pointerUpHandler = () => {
      state.dragging = false;
      scheduleHide();
    };

  player.addEventListener('pointermove', pointerMoveHandler, { passive: true });
  player.addEventListener('pointerleave', pointerLeaveHandler, { passive: true });

  container.addEventListener('pointerenter', pointerEnterInteractive, { passive: true });
  container.addEventListener('pointerleave', pointerLeaveInteractive, { passive: true });
  container.addEventListener('pointerdown', pointerDownHandler, { passive: true });
    container.addEventListener('focusin', focusInHandler);
    container.addEventListener('focusout', focusOutHandler);

  scrubber?.addEventListener('pointerenter', pointerEnterInteractive, { passive: true });
  scrubber?.addEventListener('pointerleave', pointerLeaveInteractive, { passive: true });
  scrubber?.addEventListener('pointerdown', pointerDownHandler, { passive: true });

    window.addEventListener('pointerup', pointerUpHandler, true);

    hoverBindingMap.set(player, {
      container,
      scrubber,
      pointerMoveHandler,
      pointerLeaveHandler,
      pointerEnterInteractive,
      pointerLeaveInteractive,
      pointerDownHandler,
      pointerUpHandler,
      focusInHandler,
      focusOutHandler,
      state,
    });
  };

  const unbindProgressHoverHandlers = (player) => {
    const binding = hoverBindingMap.get(player);
    if (!binding) {
      player.classList.remove(PLAYER_PROGRESS_ACTIVE_CLASS);
      return;
    }

    const {
      container,
      scrubber,
      pointerMoveHandler,
      pointerLeaveHandler,
      pointerEnterInteractive,
      pointerLeaveInteractive,
      pointerDownHandler,
      pointerUpHandler,
      focusInHandler,
      focusOutHandler,
      state,
    } = binding;

    player.removeEventListener('pointermove', pointerMoveHandler);
    player.removeEventListener('pointerleave', pointerLeaveHandler);

    container?.removeEventListener('pointerenter', pointerEnterInteractive);
    container?.removeEventListener('pointerleave', pointerLeaveInteractive);
    container?.removeEventListener('pointerdown', pointerDownHandler);
    container?.removeEventListener('focusin', focusInHandler);
    container?.removeEventListener('focusout', focusOutHandler);

    scrubber?.removeEventListener('pointerenter', pointerEnterInteractive);
    scrubber?.removeEventListener('pointerleave', pointerLeaveInteractive);
    scrubber?.removeEventListener('pointerdown', pointerDownHandler);

    window.removeEventListener('pointerup', pointerUpHandler, true);

    state.dragging = false;
    if (state.hideTimer !== null) {
      clearTimeout(state.hideTimer);
      state.hideTimer = null;
    }

    player.classList.remove(PLAYER_PROGRESS_ACTIVE_CLASS);
    hoverBindingMap.delete(player);
  };

  const logPrefix = '[YT Live Progress Remover]';
  const log = (...args) => console.debug(logPrefix, ...args);

  const isLive = () => {
    const indicator = getLiveIndicatorElement();
    if (indicator) {
      const badge = getLiveBadgeElement();
      const highlightTarget = badge || indicator;
      if (DEBUG) {
        if (lastLiveElement !== highlightTarget) {
          if (lastLiveElement) {
            lastLiveElement.style.outline = '';
            lastLiveElement.style.outlineOffset = '';
          }
          lastLiveElement = highlightTarget;
        }
        showDebugOverlay(indicator, badge);
        highlightTarget.style.outline = '2px solid #ff2d55';
        highlightTarget.style.outlineOffset = '2px';
        console.debug('[YT Live Theme] Live indicator:', indicator, badge ? ' (badge preferred)' : '');
      }
      return true;
    }
    removeDebugOverlay();
    return false;
  };

  const setProgressBarHidden = (hidden) => {
    const players = document.querySelectorAll('.html5-video-player');
    if (!players.length) {
      return false;
    }

    let changed = false;
    players.forEach((player) => {
      if (hidden) {
        injectControlShadowTweaks();
        bindProgressHoverHandlers(player);
        if (!player.classList.contains(PLAYER_HIDE_CLASS)) {
          player.classList.add(PLAYER_HIDE_CLASS);
          changed = true;
        }
      } else if (player.classList.contains(PLAYER_HIDE_CLASS)) {
        player.classList.remove(PLAYER_HIDE_CLASS);
        changed = true;
      }

      if (!hidden) {
        player.classList.remove(PLAYER_PROGRESS_ACTIVE_CLASS);
        unbindProgressHoverHandlers(player);
      }
    });

    if (changed) {
      log(hidden ? 'Hid progress bar for livestream.' : 'Restored progress bar.');
    }

    if (!hidden) {
      const anyLivePlayers = Array.from(players).some(p => p.classList.contains(PLAYER_HIDE_CLASS));
      if (!anyLivePlayers) {
        const style = document.getElementById(SHADOW_STYLE_ID);
        style?.remove();
      }
    }

    return changed;
  };

  const injectControlShadowTweaks = () => {
    if (document.getElementById(SHADOW_STYLE_ID)) {
      return;
    }

    const style = document.createElement('style');
    style.id = SHADOW_STYLE_ID;
    style.textContent = CONTROL_SHADOW_CSS;

    (document.head || document.documentElement).appendChild(style);
    log('Injected control shadow tweaks.');
  };

  const teardownMonitor = () => {
    if (monitorId !== null) {
      clearInterval(monitorId);
      monitorId = null;
      log('Stopped live monitor.');
    }

    setProgressBarHidden(false);
    removeDebugOverlay();
  };

  const ensureMonitor = () => {
    if (monitorId !== null) {
      return;
    }

    monitorId = window.setInterval(() => {
      if (!isLive()) {
        teardownMonitor();
        return;
      }

      setProgressBarHidden(true);
    }, CHECK_INTERVAL_MS);

    log('Started live monitor.');
  };

  const handleStateChange = () => {
    if (isLive()) {
      setProgressBarHidden(true);
      ensureMonitor();
    } else {
      teardownMonitor();
    }
  };

  const waitForPlayerAndHandle = () => {
    const player = document.querySelector('.html5-video-player');
    if (player) {
      handleStateChange();
      return;
    }

    const observer = new MutationObserver((_, obs) => {
      if (document.querySelector('.html5-video-player')) {
        obs.disconnect();
        handleStateChange();
      }
    });

    observer.observe(document.documentElement, {
      childList: true,
      subtree: true,
    });
  };

  const attachNavigationListeners = () => {
    NAVIGATION_EVENTS.forEach((eventName) => {
      window.addEventListener(eventName, () => {
        setTimeout(waitForPlayerAndHandle, 150);
      });
    });

    window.addEventListener('popstate', () => {
      setTimeout(waitForPlayerAndHandle, 150);
    });
  };

  const init = () => {
    waitForPlayerAndHandle();
    attachNavigationListeners();
  };

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init, { once: true });
  } else {
    init();
  }
})();