ShogiPremium Screen Comment Scroller

将棋プレミアムのコメントをニコニコ風にスクロールさせます。

  1. // ==UserScript==
  2. // @name ShogiPremium Screen Comment Scroller
  3. // @namespace knoa.jp
  4. // @description 将棋プレミアムのコメントをニコニコ風にスクロールさせます。
  5. // @include http://www.igoshogi.net/shogipremium/live/*
  6. // @version 0.9
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. (function(){
  11. /* カスタマイズ */
  12. var SCRIPTNAME = 'ScreenCommentScroller';
  13. var COLOR = '#ffffff';/*コメント色*/
  14. var OCOLOR = '#000000';/*コメント縁取り色*/
  15. var OWIDTH = 1/20;/*コメント縁取りの太さ(比率)*/
  16. var OPACITY = '0.5';/*コメントの不透明度*/
  17. var MAXLINES = 10;/*コメント最大行数*/
  18. var LINEHEIGHT = 1.2;/*コメント行高さ*/
  19. var DURATION = 5;/*スクロール秒数*/
  20. var FPS = 60;/*秒間コマ数*/
  21. /* サイト定義 */
  22. var site = {
  23. getScreen: function(){return document.querySelector('div.video_container')},
  24. getBoard: function(){return document.querySelector('#auto_scroll')},
  25. getComments: function(node){return node.querySelectorAll('span.text')},
  26. getPlay: function(){return true},
  27. isPlaying: function(play){return true},
  28. };
  29. /* 処理本体 */
  30. var screen, board, play, video, canvas, context, lines = [], fontsize;
  31. var core = {
  32. /* 初期化 */
  33. initialize: function(){
  34. console.log(SCRIPTNAME, 'initialize...');
  35. /* 主要要素が取得できるまで読み込み待ち */
  36. screen = site.getScreen();
  37. board = site.getBoard();
  38. play = site.getPlay();
  39. if(!screen || !board || !play){
  40. window.setTimeout(function(){
  41. core.initialize();
  42. }, 1000);
  43. return;
  44. }
  45. /* コメントをスクロールさせるCanvasの設置 */
  46. /* (描画処理の軽さは HTML5 Canvas, CSS Position Left, CSS Transition の順) */
  47. canvas = document.createElement('canvas');
  48. canvas.id = SCRIPTNAME;
  49. screen.appendChild(canvas);
  50. context = canvas.getContext('2d');
  51. /* メイン処理 */
  52. core.listenComments();
  53. core.scrollComments();
  54. },
  55. /* *スクリーンサイズに変化があればcanvasも変化させる* */
  56. modify: function(){
  57. if(canvas.width == screen.offsetWidth) return;
  58. //console.log(SCRIPTNAME, 'modify...');
  59. canvas.width = screen.offsetWidth;
  60. canvas.height = screen.offsetHeight;
  61. fontsize = (canvas.height / MAXLINES) / LINEHEIGHT;
  62. context.font = 'bold ' + (fontsize) + 'px sans-serif';
  63. context.fillStyle = COLOR;
  64. context.strokeStyle = OCOLOR;
  65. context.lineWidth = fontsize * OWIDTH;
  66. },
  67. /* コメントの新規追加を見守る */
  68. listenComments: function(){
  69. //console.log(SCRIPTNAME, 'listenComments...');
  70. board.addEventListener('DOMNodeInserted', function(e){
  71. let comments = site.getComments(e.target);
  72. if(!comments || !comments.length) return;
  73. core.modify();
  74. for(let i=0; comments[i]; i++){
  75. core.attachComment(comments[i]);
  76. }
  77. });
  78. },
  79. /* コメントが追加されるたびにスクロールキューに追加 */
  80. attachComment: function(comment){
  81. //console.log(SCRIPTNAME, 'attachComment...');
  82. let record = {};
  83. record.text = comment.textContent;/*流れる文字列*/
  84. record.width = context.measureText(record.text).width;/*文字列の幅*/
  85. record.life = DURATION * FPS;/*文字列が消えるまでのコマ数*/
  86. record.left = canvas.width;/*左端からの距離*/
  87. record.delta = (canvas.width + record.width) / (record.life);/*コマあたり移動距離*/
  88. record.reveal = record.width / record.delta;/*文字列が右端から抜けてあらわになるまでのコマ数*/
  89. record.touch = canvas.width / record.delta;/*文字列が左端に触れるまでのコマ数*/
  90. /* 追加されたコメントをどの行に流すかを決定する */
  91. for(let i=0; i<MAXLINES; i++){
  92. let length = lines[i] ? lines[i].length : 0;/*同じ行に詰め込まれているコメント数*/
  93. switch(true){
  94. /* 行が空いていれば追加 */
  95. case(lines[i] == undefined || !length):
  96. lines[i] = [];
  97. /* 以前のコメントより長い(速い)文字列なら、左端に到達する時間で判断する */
  98. case(lines[i][length - 1].reveal < 0 && lines[i][length - 1].delta > record.delta):
  99. /* 以前のコメントより短い(遅い)文字列なら、右端から姿を見せる時間で判断する */
  100. case(lines[i][length - 1].life < record.touch && lines[i][length - 1].delta < record.delta):
  101. /*条件に当てはまればすべてswitch文のあとの処理で行に追加*/
  102. break;
  103. default:
  104. /*条件に当てはまらなければ次の行に入れられるかの判定へ*/
  105. continue;
  106. }
  107. record.top = ((canvas.height / MAXLINES) * i) + fontsize;
  108. lines[i].push(record);
  109. break;
  110. }
  111. },
  112. /* FPSタイマー駆動 */
  113. scrollComments: function(){
  114. //console.log(SCRIPTNAME, 'scrollComment...');
  115. var interval = window.setInterval(function(){
  116. /* 再生中じゃなければ処理しない */
  117. if(!site.isPlaying(play)) return;
  118. /* Canvas描画 */
  119. context.clearRect(0, 0, canvas.width, canvas.height);
  120. for(let i=0; lines[i]; i++){
  121. for(let j=0; lines[i][j]; j++){
  122. /*視認性を向上させるスクロール文字の縁取りは、幸いにもパフォーマンスにほぼ影響しない*/
  123. context.strokeText(lines[i][j].text, lines[i][j].left, lines[i][j].top);
  124. context.fillText(lines[i][j].text, lines[i][j].left, lines[i][j].top);
  125. lines[i][j].life--;
  126. lines[i][j].reveal--;
  127. lines[i][j].touch--;
  128. lines[i][j].left -= lines[i][j].delta;
  129. }
  130. if(lines[i][0] && lines[i][0].life == 0){
  131. lines[i].shift();
  132. }
  133. }
  134. }, 1000/FPS);
  135. },
  136. };
  137. (function(css){
  138. let style = document.createElement('style');
  139. style.type = 'text/css';
  140. style.textContent = css;
  141. document.head.appendChild(style);
  142. })(`
  143. canvas#${SCRIPTNAME}{
  144. pointer-events: none;
  145. position: fixed;
  146. top: 0;
  147. left: 0;
  148. width: 100%;
  149. height: 100%;
  150. opacity: ${OPACITY};
  151. z-index: 99999;
  152. }
  153. .video_container,
  154. #StrobeMediaPlayback{
  155. position: fixed;
  156. top: 0;
  157. left: 0;
  158. width: 100%;
  159. height: 100%;
  160. z-index: 9999;
  161. }
  162. div.smp_head{
  163. display: none;
  164. }
  165. `);
  166. core.initialize();
  167. })();