// ==UserScript== // @name Comment Render Smoother // @description 公式HTML5プレイヤーのコメントの動きをなめらかにする(主にFirefox) // @match *://www.nicovideo.jp/watch/* // @grant none // @author guest // @license public domain // @version 0.0.3 // @namespace https://greasyfork.org/users/218 // @noframes // @downloadURL none // ==/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(); if (isPlaying && lastVideoTime === videoTime) { stallCount ++; } else { stallCount = 0; } // stallCount = 0; // debug... if ( !isPlaying || // 再生してない or lastVideoTime > videoTime || // 時間が戻った ≒ シークした or videoTime - lastVideoTime > playbackRate || // いきなり1秒以上も進んだ ≒ シークした or stallCount > 5 // 詰まってんじゃねーの? の時 ) { lastVideoTime = videoTime; worldTime = now; _playbackRate(); return toFrameTime(videoTime); } else { const timeDiff = (now - worldTime) / 1000 * playbackRate; return toFrameTime(lastVideoTime + timeDiff); } }; 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: なんかプレイヤー初期化のタイミングでやる setTimeout(init, 5000); }); if (document.querySelector('#js-app')) { const script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.setAttribute('charset', 'UTF-8'); script.appendChild(document.createTextNode(`(${monkey})();`)); document.body.appendChild(script); }