Douyin Auto Pause + Smart Unmute

进入抖音网页和切换视频时自动暂停并静音;用户点击播放时自动打开声音,下一个视频重新静音并自动暂停。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Douyin Auto Pause + Smart Unmute
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  进入抖音网页和切换视频时自动暂停并静音;用户点击播放时自动打开声音,下一个视频重新静音并自动暂停。
// @match        https://www.douyin.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  // 最近一次用户交互时间
  let lastUserInteraction = 0;

  // 记录用户交互(点击、键盘、触摸等)
  function setupUserInteractionTracker() {
    const updateInteractionTime = () => {
      lastUserInteraction = Date.now();
    };

    ['click', 'keydown', 'touchstart', 'pointerdown'].forEach(type => {
      window.addEventListener(type, updateInteractionTime, true);
    });
  }

  function pauseVideo(video) {
    try {
      video.muted = true;   // 确保静音
      video.pause();
    } catch (e) {
      console.error('Pause video failed:', e);
    }
  }

  function pauseAndMuteAllVideosExcept(exceptVideo) {
    const videos = document.querySelectorAll('video');
    videos.forEach(v => {
      if (v === exceptVideo) return;
      pauseVideo(v);
    });
  }

  // 尝试给当前播放器“点一下”音量按钮,让 UI 同步
  function clickPlayerVolumeButton() {
    try {
      // 你提供的那个 selector,我们尽量找到它的上层可点击元素
      const svg = document.querySelector(
        '#sliderVideo xg-icon.xgplayer-volume .xg-volume-mute svg'
      );
      if (svg) {
        const clickable = svg.closest('xg-icon, button, div');
        if (clickable) {
          clickable.dispatchEvent(
            new MouseEvent('click', { bubbles: true, cancelable: true, view: window })
          );
          return;
        }
      }

      // 如果上面那个找不到,退而求其次找通用的音量图标
      const genericIcon = document.querySelector('#sliderVideo xg-icon.xgplayer-volume');
      if (genericIcon) {
        genericIcon.dispatchEvent(
          new MouseEvent('click', { bubbles: true, cancelable: true, view: window })
        );
      }
    } catch (e) {
      console.error('Click volume button failed:', e);
    }
  }

  // 取消静音当前视频
  function unmuteVideo(video) {
    try {
      video.muted = false;
      if (video.volume === 0) {
        video.volume = 1.0;
      }
    } catch (e) {
      console.error('Unmute video failed:', e);
    }

    // 尝试“点一下”播放器的音量图标,让 UI 同步
    clickPlayerVolumeButton();
  }

  // 暂停页面上所有 video(用于初始 & 新增)
  function pauseAllVideos() {
    const videos = document.querySelectorAll('video');
    videos.forEach(video => {
      pauseVideo(video);
    });
  }

  // 钩住 play 事件:分辨自动播放 & 用户点击播放
  function hookPlayEvent() {
    document.addEventListener(
      'play',
      function (e) {
        const target = e.target;
        if (!target || target.tagName !== 'VIDEO') return;

        const now = Date.now();
        const delta = now - lastUserInteraction;

        // 没有用户操作,或者时间差太久 => 认为是自动播放,拦截
        if (delta > 500) {
          setTimeout(() => {
            pauseVideo(target);
          }, 0);
        } else {
          // 用户刚刚有交互 => 允许播放 & 打开声音 & 其它视频静音暂停
          unmuteVideo(target);
          pauseAndMuteAllVideosExcept(target);
        }
      },
      true // 捕获阶段,优先于页面自己的监听
    );
  }

  // 监听 DOM 变化,发现新 video 就暂停并静音
  function observeNewVideos() {
    const observer = new MutationObserver(mutations => {
      let needPause = false;

      for (const mutation of mutations) {
        mutation.addedNodes.forEach(node => {
          if (!(node instanceof HTMLElement)) return;

          if (node.tagName === 'VIDEO') {
            needPause = true;
          }

          const videosInside =
            node.querySelectorAll && node.querySelectorAll('video');
          if (videosInside && videosInside.length > 0) {
            needPause = true;
          }
        });
      }

      if (needPause) {
        pauseAllVideos();
      }
    });

    const target = document.documentElement || document.body;
    if (target) {
      observer.observe(target, {
        childList: true,
        subtree: true
      });
    }
  }

  function init() {
    setupUserInteractionTracker();
    hookPlayEvent();

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', () => {
        pauseAllVideos();
      });
    } else {
      pauseAllVideos();
    }

    observeNewVideos();
  }

  init();
})();