Disney+ Speed Controller

Floating playback speed controls for Disney+

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

Advertisement:

// ==UserScript==
// @name         Disney+ Speed Controller
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Floating playback speed controls for Disney+
// @author       crichalchemist 
// @match        https://www.disneyplus.com/play/*
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 2];
  let currentSpeed = 1;
  let videoEl = null;
  let attachInterval = null;

  function setActiveButton(speed) {
    const panel = document.getElementById('dplus-speed-ui');
    if (!panel) return;
    panel.querySelectorAll('button').forEach(function (btn) {
      const isActive = parseFloat(btn.dataset.speed) === speed;
      btn.style.background = isActive ? 'white' : 'transparent';
      btn.style.color = isActive ? '#000' : 'white';
    });
  }

  function injectUI() {
    if (document.getElementById('dplus-speed-ui')) return;

    const panel = document.createElement('div');
    panel.id = 'dplus-speed-ui';
    panel.style.cssText = [
      'position: fixed',
      'bottom: 20px',
      'right: 20px',
      'z-index: 99999',
      'background: rgba(0,0,0,0.75)',
      'border-radius: 8px',
      'padding: 6px 10px',
      'display: flex',
      'gap: 4px',
      'align-items: center',
      'font-family: sans-serif',
      'font-size: 13px',
      'opacity: 0',
      'pointer-events: none',
      'transition: opacity 0.5s ease',
    ].join(';');

    SPEEDS.forEach(function (speed) {
      const btn = document.createElement('button');
      btn.dataset.speed = speed;
      btn.textContent = speed + '×';
      btn.style.cssText = [
        'background: transparent',
        'border: none',
        'color: white',
        'cursor: pointer',
        'padding: 2px 6px',
        'border-radius: 4px',
        'font-size: 13px',
        'font-family: sans-serif',
      ].join(';');
      panel.appendChild(btn);
    });

    panel.addEventListener('click', function (e) {
      const btn = e.target.closest('button[data-speed]');
      if (!btn) return;
      currentSpeed = parseFloat(btn.dataset.speed);
      if (videoEl) videoEl.playbackRate = currentSpeed;
      setActiveButton(currentSpeed);
    });

    document.body.appendChild(panel);
  }

  function attachVideo() {
    clearInterval(attachInterval);
    attachInterval = setInterval(function () {
      const el = [...document.querySelectorAll('video')].find(v => v.readyState > 0);
      if (el) {
        videoEl = el;
        videoEl.playbackRate = currentSpeed;
        clearInterval(attachInterval);
      }
    }, 500);
  }

  let fadeTimer = null;

  function showPanel() {
    const panel = document.getElementById('dplus-speed-ui');
    if (!panel) return;
    panel.style.opacity = '1';
    panel.style.pointerEvents = 'auto';
    clearTimeout(fadeTimer);
    fadeTimer = setTimeout(function () {
      panel.style.opacity = '0';
      panel.style.pointerEvents = 'none';
    }, 3000);
  }

  document.addEventListener('mousemove', showPanel);

  document.addEventListener('keydown', function (e) {
    const tag = document.activeElement?.tagName;
    if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
    if (e.key === '[') {
      currentSpeed = Math.max(0.5, currentSpeed - 0.25);
    } else if (e.key === ']') {
      currentSpeed = Math.min(2, currentSpeed + 0.25);
    } else {
      return;
    }
    if (videoEl) videoEl.playbackRate = currentSpeed;
    setActiveButton(currentSpeed);
    showPanel();
  });

  injectUI();
  setActiveButton(currentSpeed);
  attachVideo();

  let lastUrl = location.href;
  setInterval(function () {
    if (location.href !== lastUrl) {
      lastUrl = location.href;
      videoEl = null;
      attachVideo();
    }
  }, 1000);
})();