Comment Render Smoother for embed player

公式HTML5埋め込みプレイヤーのコメントの動きをなめらかにする(主にFirefox)

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name           Comment Render Smoother for embed player
// @description    公式HTML5埋め込みプレイヤーのコメントの動きをなめらかにする(主にFirefox)
// @match          *://embed.nicovideo.jp/watch/*
// @grant          none
// @author         rinsuki (original author: guest https://greasyfork.org/ja/scripts/377400/code?version=668741 )
// @license        public domain
// @version        0.0.4.4
// @namespace      https://rinsuki.net/
// ==/UserScript==

const monkey = (() => {
  let fps = 60;
  let playbackRate = 1.0;

  const toFrameTime = (time) => {
    return time; // こっちは体感しづらいかも
    // const timeMs = time * 1000;
    // const MSEC_PER_FRAME = 1000 / fps * playbackRate;
    // const nextFrame = Math.ceil(timeMs / MSEC_PER_FRAME);
    // return nextFrame * MSEC_PER_FRAME / 1000;
  };

  const init = () => {
    if (!window.__videoplayer) { return; }
    const player = window.__videoplayer;
    const _currentTime = player.currentTime.bind(player);
    const _playbackRate = player.playbackRate.bind(player);
    _playbackRate();

    console.log('%cinitialize Comment Render Smoother playbackRate:%s', 'background: cyan;', playbackRate);

    let worldTime = performance.now();
    let lastVideoTime = _currentTime();

    let stallCount = 0;

    player.currentTime = (time) => {
      const now = performance.now();
      if (typeof time === 'number') {
        lastVideoTime = time;
        worldTime = now;
        return _currentTime(time);
      }


      const isPlaying = !player.paused();
      const videoTime = _currentTime();
      const timeDiff = (now - worldTime) / 1000 * playbackRate;
      const predictionTime = lastVideoTime + timeDiff;

      if (isPlaying && lastVideoTime === videoTime) {
        stallCount ++;
      } else {
        stallCount = 0;
      }

      // stallCount = 0; // debug...
      if (
        !isPlaying ||                     // 再生してない or
        lastVideoTime > videoTime ||      // 時間が戻った ≒ シークした or
        //videoTime - lastVideoTime > playbackRate ||  // いきなり1秒以上も進んだ ≒ シークした or
        Math.abs(predictionTime - videoTime) > 1 || // 予測とn秒以上の誤差ができた
        stallCount > 5                    // 詰まってんじゃねーの?  の時
      ) {
        lastVideoTime = videoTime;
        worldTime = now;
        _playbackRate();

        return toFrameTime(videoTime);
      } else {
        return toFrameTime(predictionTime);
      }
    };

    player.playbackRate = rate => {
      if (typeof rate !== 'number') {
        const currentRate = _playbackRate();
        if (playbackRate !== currentRate) {
          console.log('%cupdate playbackRate %s -> %s', 'background: yellow;', playbackRate, currentRate);
          playbackRate = currentRate;
          worldTime = performance.now();
          lastVideoTime = _currentTime();
        }
        return currentRate;
      }
      if (rate === playbackRate || rate <= 0) {
        return;
      }
      console.log('%cset playbackRate %s -> %s', 'background: orange;', playbackRate, rate);

      playbackRate = rate;
      worldTime = performance.now();
      lastVideoTime = _currentTime();
      return _playbackRate(rate);
    };
    // player.play();
  };

  // TODO: なんかプレイヤー初期化のタイミングでやる
  init();

});

if (document.querySelector('#ext-player')) {
  const script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.setAttribute('charset', 'UTF-8');
  script.appendChild(document.createTextNode(`(${monkey})();`));
  document.body.appendChild(script);
}