Greasy Fork

Greasy Fork is available in English.

Youtube剧场模式,双语字幕,记忆播放速度

自动进入Youtube剧场模式,中文字幕位于播放器下方,解说词位于播放器右下侧。有字幕时,自动记忆设置的播放速度,重新进入Youtube不丢失;无字幕时,不自动调整播放速度。

当前为 2020-05-29 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Youtube剧场模式,双语字幕,记忆播放速度
// @namespace    http://greasyfork.icu
// @version      2.2.0
// @description  自动进入Youtube剧场模式,中文字幕位于播放器下方,解说词位于播放器右下侧。有字幕时,自动记忆设置的播放速度,重新进入Youtube不丢失;无字幕时,不自动调整播放速度。
// @author      [email protected]
// @source      https://github.com/szdailei/GM-scripts
// @match       https://www.youtube.com/*
// @run-at       document-start
// ==/UserScript==

'use strict';
/**
require:  Trigger the event yt-navigate-finish. That's a special event in www.youtube.com, happens when open link in an exsit tab.
ensure: 
    1. Open theater mode.
    2. If there is subtitle, save and restore play speed.
    3. If there is Chinese subtitle, turn on it. If no, but with auto-translation, translate to Simp Chinese.
    4. If there is transcript, trun on transcript.
*/
function onYtNavigateFinish() {
  const MAX_VIDEO_LOAD_COUNT = 10;
  const MAX_SUBTITLE_MENU_LOAD_COUNT = 10;
  const MAX_VIDEO_PRIMARY_INFO_LOAD_COUNT = 10;
  const MIN_MORE_ACTIONS_MENU_LOAD_COUNT = 2;
  const MAX_MORE_ACTIONS_MENU_LOAD_COUNT = 10;
  const MAX_OPEN_TRANSCRIPT_COUNT = 5;
  const PLAY_SPEED_LOCAL_STORAGE_KEY = 'greasyfork-org-youtube-subtitle-play-speed';

  let ytdPlayer,
    subtitlesEnableButton,
    settingsButton,
    playSpeedButton,
    subtitlesSelectButton,
    ytdVideoPrimaryInfo,
    openTranscriptButton;
  let playerSizeChanged = false;
  let videoLoadCount, subtitleMenuLoadCount, videoPrimaryInfoLoadCount, moreActionsMenuLoadCount, openTranscriptCount;
  videoLoadCount = subtitleMenuLoadCount = videoPrimaryInfoLoadCount = moreActionsMenuLoadCount = openTranscriptCount = 0;

  if (window.location.pathname.indexOf('/watch') === -1) {
    return;
  }

  // config on https://www.youtube.com/watch?*
  youtubeConfig();

  function youtubeConfig() {
    if (videoLoadCount >= MAX_VIDEO_LOAD_COUNT) {
      return;
    }
    ytdPlayer = document.getElementById('ytd-player');
    if (ytdPlayer !== null) {
      let videos = ytdPlayer.getElementsByTagName('video');
      if (videos !== null && videos.length > 0 && videos.item(0) !== null) {
        onVideoPlayed();
        return;
      }
    }
    videoLoadCount++;
    setTimeout(youtubeConfig, 1000);
  }

  function onVideoPlayed() {
    if (subtitleMenuLoadCount >= MAX_SUBTITLE_MENU_LOAD_COUNT) {
      return;
    }

    if (playerSizeChanged === false) {
      setPlayerFullSize();
      playerSizeChanged = true;
    }

    subtitlesEnableButton = getElementByClassNameAndAttribute(
      ytdPlayer,
      'ytp-subtitles-button',
      'aria-label',
      '字幕 (c)'
    );
    if (subtitlesEnableButton !== null) {
      if (subtitlesEnableButton.style.display === 'none') {
        return;
      }
      if (subtitlesEnableButton.getAttribute('aria-pressed') === 'false') {
        subtitlesEnableButton.click();
      }
      onSubtitlesMenuLoaded();
      return;
    }
    subtitleMenuLoadCount++;
    setTimeout(onVideoPlayed, 1000);
  }

  function setPlayerFullSize() {
    let sizeButtons = ytdPlayer.getElementsByClassName('ytp-size-button');
    if (sizeButtons !== null && sizeButtons.length === 1) {
      let sizeButton = sizeButtons[0];
      if (sizeButton.title.indexOf('剧场模式') !== -1) {
        sizeButton.click();
      }
    }
  }

  function onSubtitlesMenuLoaded() {
    console.log('gggd');
    settingsButton = getElementByClassNameAndAttribute(ytdPlayer, 'ytp-settings-button', 'aria-label', '设置');
    settingsButton.click();

    playSpeedButton = getElementByClassNameAndInnerText(ytdPlayer, 'ytp-menuitem-label', '播放速度');
    subtitlesSelectButton = getElementByClassNameAndPartInnerText(ytdPlayer, 'ytp-menuitem-label', '字幕');

    listenPlaySpeedButtonClickAndRestoretPlaySpeed();
    turnOnSubtitle();
  }

  function listenPlaySpeedButtonClickAndRestoretPlaySpeed() {
    settingsButton.click();
    playSpeedButton.click();
    listenPlaySpeedButtonClick();

    let playSpeedInLocalStorage = localStorage.getItem(PLAY_SPEED_LOCAL_STORAGE_KEY);
    if (playSpeedInLocalStorage === null) {
      settingsButton.click();
      return;
    }

    let radio = getElementByClassNameAndInnerText(ytdPlayer, 'ytp-menuitem', playSpeedInLocalStorage);
    if (radio !== null) {
      radio.click();
      return;
    }
    settingsButton.click();
  }

  function listenPlaySpeedButtonClick() {
    let menuItemRadios = ytdPlayer.querySelectorAll('[role="menuitemradio"]');
    let length = menuItemRadios.length;
    for (let i = 0; i < length; i++) {
      menuItemRadios[i].addEventListener('click', savePlaySpeed);
    }
  }

  function savePlaySpeed() {
    let playSpeed = this.innerText;
    localStorage.setItem(PLAY_SPEED_LOCAL_STORAGE_KEY, playSpeed);
  }

  function turnOnSubtitle() {
    settingsButton.click();
    subtitlesSelectButton.click();
    let radio = getElementByClassNameAndPartInnerText(ytdPlayer, 'ytp-menuitem', '中文');
    if (radio !== null) {
      radio.click();
      settingsButton.click();
      turnOnTranscript();
      return;
    }

    radio = getElementByClassNameAndInnerText(ytdPlayer, 'ytp-menuitem', '自动翻译');
    if (radio !== null) {
      radio.click();
      getElementByClassNameAndInnerText(ytdPlayer, 'ytp-menuitem', '中文(简体)').click();
      turnOnTranscript();
      return;
    }
    settingsButton.click();
  }

  function turnOnTranscript() {
    if (videoPrimaryInfoLoadCount >= MAX_VIDEO_PRIMARY_INFO_LOAD_COUNT) {
      return;
    }
    let ytdVideoPrimaryInfos = document.getElementsByTagName('ytd-video-primary-info-renderer');
    if (ytdVideoPrimaryInfos !== null && ytdVideoPrimaryInfos.length > 0) {
      ytdVideoPrimaryInfo = ytdVideoPrimaryInfos[0];
      let time = MIN_MORE_ACTIONS_MENU_LOAD_COUNT - videoPrimaryInfoLoadCount;
      if (time > 0) {
        setTimeout(enterMoreActionsMenu, 1000 * time);
      } else {
        enterMoreActionsMenu;
      }
      return;
    }
    videoPrimaryInfoLoadCount++;
    setTimeout(turnOnTranscript, 1000);
  }

  function enterMoreActionsMenu() {
    if (moreActionsMenuLoadCount >= MAX_MORE_ACTIONS_MENU_LOAD_COUNT) {
      return;
    }

    let iconButtons = ytdVideoPrimaryInfo.getElementsByClassName('yt-icon-button');
    if (iconButtons !== null) {
      let length = iconButtons.length;
      for (let i = 0; i < length; i++) {
        if (iconButtons[i].getAttribute('aria-label') === '其他操作') {
          iconButtons[i].click();
          openTranscript();
          return;
        }
      }
    }
    moreActionsMenuLoadCount++;
    setTimeout(enterMoreActionsMenu, 1000);
  }

  function openTranscript() {
    if (openTranscriptCount >= MAX_OPEN_TRANSCRIPT_COUNT) {
      return;
    }

    let menuPopupRenderers = document.getElementsByTagName('ytd-menu-popup-renderer');
    if (menuPopupRenderers !== null) {
      let length = menuPopupRenderers.length;
      for (let i = 0; i < length; i++) {
        openTranscriptButton = getElementByTagNameAndInnerText(
          menuPopupRenderers[i],
          'yt-formatted-string',
          '打开解说词'
        );
        if (openTranscriptButton !== null) {
          openTranscriptButton.click();
          setTimeout(closeMoreActionsMenu, 300);
          return;
        }
      }
    }

    openTranscriptCount++;
    setTimeout(openTranscript, 1000);
  }

  function closeMoreActionsMenu() {
    //click openTranscriptButton again to close the more actions menu
    openTranscriptButton.click();
    return;
  }

  function getElementByClassNameAndAttribute(element, className, attributeName, attributeValue) {
    let results = element.getElementsByClassName(className);
    if (results !== null) {
      let length = results.length;
      for (let i = 0; i < length; i++) {
        if (results[i].getAttribute(attributeName) === attributeValue) {
          return results[i];
        }
      }
    }
    return null;
  }

  function getElementByClassNameAndInnerText(element, className, innerText) {
    let results = element.getElementsByClassName(className);
    if (results !== null) {
      let length = results.length;
      for (let i = 0; i < length; i++) {
        if (results[i].innerText === innerText) {
          return results[i];
        }
      }
    }
    return null;
  }

  function getElementByClassNameAndPartInnerText(element, className, innerText) {
    let results = element.getElementsByClassName(className);
    if (results !== null) {
      let length = results.length;
      for (let i = 0; i < length; i++) {
        if (results[i].innerText.indexOf(innerText) !== -1) {
          return results[i];
        }
      }
    }
    return null;
  }

  function getElementByTagNameAndInnerText(element, tagName, innerText) {
    let results = element.getElementsByTagName(tagName);
    if (results !== null) {
      let length = results.length;
      for (let i = 0; i < length; i++) {
        if (results[i].innerText === innerText) {
          return results[i];
        }
      }
    }
    return null;
  }
}

/**
require:  The document is still loading
ensure:  addEventListener on yt-navigate-finish event.
*/
(function () {
  window.addEventListener('yt-navigate-finish', onYtNavigateFinish);
})();