Comment Render Smoother for embed player

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

目前為 2020-11-01 提交的版本,檢視 最新版本

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