Stick YouTube Progress Bar

Stick YouTube video progress bar to the player bottom

当前为 2023-11-18 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          Stick YouTube Progress Bar
// @version       1.0.8
// @match         https://www.youtube.com/**
// @author        peng-devs
// @namespace     https://greasyfork.org/users/57176
// @description   Stick YouTube video progress bar to the player bottom
// @icon          https://www.youtube.com/s/desktop/c1d331ff/img/favicon_48x48.png
// @grant         none
// @allFrames     true
// @license       MIT
// ==/UserScript==

(function() {
  'use strict';

  const NAME = "Stick YouTube Progress Bar";
  const UPDATE_INTERVAL = 500;
  const SMOOTH_ANIMATION = false;

  let INTERVAL;
  function main() {
    if (!location.pathname.startsWith('/watch') &&
       !location.pathname.startsWith('/live'))
      return

    if (INTERVAL) {
      INTERVAL = undefined;
    }

    const observer = new MutationObserver((_) => {
      const progress = document.getElementById('stick_progress');

      // 如果是直播的話就不用了
      const live_badge = document.querySelector(".ytp-live-badge");
      if (live_badge && getComputedStyle(live_badge).display !== 'none') {
        // TODO: 或許不用砍掉,可以直接用 css 的方式把它藏起來就好了?
        progress?.remove();
        observer.disconnect();
        console.log(`[${NAME}] cancaled in livestream`);
        return;
      }

      // 已經建好的可以重複利用
      if (progress) return

      // 判斷 youtube player 初始化完了沒
      const intitailized = document.querySelector("video.video-stream");
      if (!intitailized) return;

      console.log(`[${NAME}] initializing...`);

      INTERVAL = stick_progress_bar();

      observer.disconnect();
      console.log(`[${NAME}] loaded`);
    });
    observer.observe(document.body, { childList: true, subtree: true });
  }

  function stick_progress_bar() {
    inject_custom_style(`

      #stick_progress {
        display: none;
        z-index: 32;
        position: absolute;
        bottom: 4px;
        width: 97.5%;
        height: 4px;
        margin: 0 1.25%;
        background-color: rgba(255, 255, 255, .2);
      }

      .stick_play_progress, .stick_load_progress {
        position: absolute;
        width: 100%;
        height: 100%;
      }

      .stick_play_progress, .stick_load_progress {
        transform-origin: left;
        ${SMOOTH_ANIMATION ? `transition: all ${UPDATE_INTERVAL - 50}ms linear;` : ''}
        transform: scaleX(0);
      }

      .stick_load_progress {
        z-index: 33;
        background-color: rgba(255, 255, 255, .4);
      }

      .stick_play_progress {
        z-index: 34;
        background-color: #f00;
      }

      .ytp-autohide #stick_progress {
        display: block;
      }
    `);

    const progress = document.createElement("div");
    progress.id = "stick_progress";

    const play_progress = document.createElement("div");
    play_progress.className = "stick_play_progress";
    progress.append(play_progress);

    const load_progress = document.createElement("div");
    load_progress.className = "stick_load_progress";
    progress.append(load_progress);

    const player = document.querySelector("#movie_player");
    player.append(progress);

    const video = document.querySelector("video.video-stream");
    const interval = setInterval(() => {
      // 只有在自動隱藏的時候才去更新進度條,不想浪費資源
      if (!player.classList.contains("ytp-autohide")) return;

      const progress = video.currentTime / video.duration;
      play_progress.style.transform = `scaleX(${progress})`;

      const loaded_progress = video.buffered.end(0) / video.duration;
      load_progress.style.transform = `scaleX(${loaded_progress})`;
    }, UPDATE_INTERVAL);

    return interval;
  }

  function inject_custom_style(css) {
    const style = document.createElement("style");
    document.head.append(style);
    style.dataset.source = NAME;
    style.innerHTML = css;
  }

  main();
  document.addEventListener('yt-navigate-finish', main, true);

})();