Comment Render Smoother for embed player

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

  1. // ==UserScript==
  2. // @name Comment Render Smoother for embed player
  3. // @description 公式HTML5埋め込みプレイヤーのコメントの動きをなめらかにする(主にFirefox)
  4. // @match *://embed.nicovideo.jp/watch/*
  5. // @grant none
  6. // @author rinsuki (original author: guest https://greasyfork.org/ja/scripts/377400/code?version=668741 )
  7. // @license public domain
  8. // @version 0.0.4.4
  9. // @namespace https://rinsuki.net/
  10. // ==/UserScript==
  11.  
  12. const monkey = (() => {
  13. let fps = 60;
  14. let playbackRate = 1.0;
  15.  
  16. const toFrameTime = (time) => {
  17. return time; // こっちは体感しづらいかも
  18. // const timeMs = time * 1000;
  19. // const MSEC_PER_FRAME = 1000 / fps * playbackRate;
  20. // const nextFrame = Math.ceil(timeMs / MSEC_PER_FRAME);
  21. // return nextFrame * MSEC_PER_FRAME / 1000;
  22. };
  23.  
  24. const init = () => {
  25. if (!window.__videoplayer) { return; }
  26. const player = window.__videoplayer;
  27. const _currentTime = player.currentTime.bind(player);
  28. const _playbackRate = player.playbackRate.bind(player);
  29. _playbackRate();
  30.  
  31. console.log('%cinitialize Comment Render Smoother playbackRate:%s', 'background: cyan;', playbackRate);
  32.  
  33. let worldTime = performance.now();
  34. let lastVideoTime = _currentTime();
  35.  
  36. let stallCount = 0;
  37.  
  38. player.currentTime = (time) => {
  39. const now = performance.now();
  40. if (typeof time === 'number') {
  41. lastVideoTime = time;
  42. worldTime = now;
  43. return _currentTime(time);
  44. }
  45.  
  46.  
  47. const isPlaying = !player.paused();
  48. const videoTime = _currentTime();
  49. const timeDiff = (now - worldTime) / 1000 * playbackRate;
  50. const predictionTime = lastVideoTime + timeDiff;
  51.  
  52. if (isPlaying && lastVideoTime === videoTime) {
  53. stallCount ++;
  54. } else {
  55. stallCount = 0;
  56. }
  57.  
  58. // stallCount = 0; // debug...
  59. if (
  60. !isPlaying || // 再生してない or
  61. lastVideoTime > videoTime || // 時間が戻った ≒ シークした or
  62. //videoTime - lastVideoTime > playbackRate || // いきなり1秒以上も進んだ ≒ シークした or
  63. Math.abs(predictionTime - videoTime) > 1 || // 予測とn秒以上の誤差ができた
  64. stallCount > 5 // 詰まってんじゃねーの? の時
  65. ) {
  66. lastVideoTime = videoTime;
  67. worldTime = now;
  68. _playbackRate();
  69.  
  70. return toFrameTime(videoTime);
  71. } else {
  72. return toFrameTime(predictionTime);
  73. }
  74. };
  75.  
  76. player.playbackRate = rate => {
  77. if (typeof rate !== 'number') {
  78. const currentRate = _playbackRate();
  79. if (playbackRate !== currentRate) {
  80. console.log('%cupdate playbackRate %s -> %s', 'background: yellow;', playbackRate, currentRate);
  81. playbackRate = currentRate;
  82. worldTime = performance.now();
  83. lastVideoTime = _currentTime();
  84. }
  85. return currentRate;
  86. }
  87. if (rate === playbackRate || rate <= 0) {
  88. return;
  89. }
  90. console.log('%cset playbackRate %s -> %s', 'background: orange;', playbackRate, rate);
  91.  
  92. playbackRate = rate;
  93. worldTime = performance.now();
  94. lastVideoTime = _currentTime();
  95. return _playbackRate(rate);
  96. };
  97. // player.play();
  98. };
  99.  
  100. // TODO: なんかプレイヤー初期化のタイミングでやる
  101. init();
  102.  
  103. });
  104.  
  105. if (document.querySelector('#ext-player')) {
  106. const script = document.createElement('script');
  107. script.setAttribute('type', 'text/javascript');
  108. script.setAttribute('charset', 'UTF-8');
  109. script.appendChild(document.createTextNode(`(${monkey})();`));
  110. document.body.appendChild(script);
  111. }