V LIVE Video Rotation

Adds buttons and keyboard shortcuts to rotate and flip the video.

  1. // ==UserScript==
  2. // @name V LIVE Video Rotation
  3. // @description Adds buttons and keyboard shortcuts to rotate and flip the video.
  4. // @version 1.10
  5. // @author aqmx
  6. // @namespace aqmx
  7. // @license MIT
  8. // @match https://www.vlive.tv/*
  9. // @grant GM_addStyle
  10. // ==/UserScript==
  11.  
  12.  
  13. GM_addStyle(`
  14. #video-rotation-controls {
  15. margin-left: 15px;
  16. }
  17. #video-rotation-controls button {
  18. font-size: 20px;
  19. background: #f5f5f5;
  20. border: 1px solid #d9d9d9;
  21. color: #414141;
  22. line-height: 22px;
  23. height: 24px;
  24. width: 24px;
  25. outline: 0;
  26. }
  27. #video-rotation-controls button:not(:last-child) {
  28. margin-right: 3px;
  29. }
  30. #playerBoxArea > .u_rmcplayer:fullscreen div[id*=".videoArea"],
  31. #playerBoxArea > .vwplayer_vlivelive:fullscreen .videoBox {
  32. height: calc(100% - 4px);
  33. width: calc(100% - 4px);
  34. }
  35. `);
  36.  
  37.  
  38. let directions = {
  39. left: 'r270',
  40. up: 'r0',
  41. right: 'r90',
  42. down: 'r180',
  43. flipH: 'flipH',
  44. flipV: 'flipV',
  45. }
  46. let rotations = {
  47. r90: 'rotate(90deg)',
  48. r180: 'rotate(180deg)',
  49. r270: 'rotate(270deg)',
  50. };
  51. let scaling = {
  52. flipH: 'scaleX(-1)',
  53. flipV: 'scaleY(-1)',
  54. portrait: 'scale(1.77777)',
  55. landscape: 'scale(0.5625)',
  56. };
  57.  
  58. /*let brightnessPercent = 100;
  59. let brightness = {
  60. up: 5,
  61. down: -5,
  62. reset: 0,
  63. }*/
  64.  
  65. let attempts = 0;
  66. let controlsID = 'video-rotation-controls';
  67. let shortcutsEnabled = true;
  68. let shortcuts = {
  69. 'Numpad8': directions.up,
  70. 'Numpad2': directions.down,
  71. 'Numpad4': directions.left,
  72. 'Numpad6': directions.right,
  73. 'Numpad9': directions.flipH,
  74. 'Numpad3': directions.flipV,
  75. }
  76. let stylesheet = null;
  77. let timer = null;
  78.  
  79.  
  80. (() => {
  81. stylesheet = document.createElement('style');
  82. document.head.appendChild(stylesheet);
  83.  
  84. let getAllSubsets = arr => arr.reduce((subsets, value) => subsets.concat(subsets.map(set => [...set, value])), [[]]).slice(1);
  85. let scalings = getAllSubsets(Object.keys(scaling));
  86.  
  87. for (let rule in rotations) {
  88. stylesheet.sheet.insertRule(`video.${rule} { transform: ${rotations[rule]}; }`, stylesheet.sheet.cssRules.length);
  89. for (let scalingSet of scalings) {
  90. stylesheet.sheet.insertRule(`video.${rule}.${scalingSet.reduce((s, v) => s+'.'+v)} { transform: ${rotations[rule] + scalingSet.reduce((s, v) => s+' '+scaling[v], '')}; }`, stylesheet.sheet.cssRules.length);
  91. }
  92. }
  93.  
  94. let flips = getAllSubsets([directions.flipH, directions.flipV]);
  95. for (let flipSet of flips) {
  96. stylesheet.sheet.insertRule(`video.${flipSet.reduce((s, v) => s+'.'+v)} { transform:${flipSet.reduce((s, v) => s+' '+scaling[v], '')}; }`, stylesheet.sheet.cssRules.length);
  97. }
  98.  
  99. addButtons();
  100.  
  101. // Re-add buttons if page changes
  102. window.history.pushState = function() {
  103. History.prototype.pushState.apply(history, arguments);
  104. attempts = 0;
  105. setTimeout(addButtons, 2000);
  106. }
  107. })();
  108.  
  109. function addButtons() {
  110. clearTimeout(timer);
  111. let btnArea = document.querySelector('[class^="video_content"] [class^="video_detail"] [class^="post_detail_reaction_info"]');
  112.  
  113. if (!btnArea) {
  114. if (++attempts < 10) {
  115. timer = setTimeout(addButtons, 500);
  116. }
  117. return;
  118. }
  119. else if (document.getElementById(controlsID)) {
  120. return;
  121. }
  122.  
  123. let btnDirections = [
  124. [directions.left, '🠘'],
  125. [directions.up, '🠙'],
  126. [directions.right, '🠚'],
  127. [directions.down, '🠛'],
  128. [directions.flipV, '🡙'],
  129. [directions.flipH, '🡘'],
  130. ];
  131. /*let btnBrightness = [
  132. ['up', '+'],
  133. ['down', '−'],
  134. ['reset', '⟳'],
  135. ];*/
  136.  
  137. let div = document.createElement('div');
  138. div.id = controlsID;
  139. for (let entry of btnDirections) {
  140. let btn = document.createElement('button');
  141. btn.dataset.direction = entry[0];
  142. btn.textContent = entry[1];
  143. div.appendChild(btn);
  144. }
  145. /*for (let entry of btnBrightness) {
  146. let btn = document.createElement('button');
  147. btn.dataset.brightness = entry[0];
  148. btn.textContent = entry[1];
  149. div.appendChild(btn);
  150. }*/
  151.  
  152. btnArea.style.justifyContent = 'initial';
  153. btnArea.appendChild(div);
  154. }
  155.  
  156. function rotateVideo(direction) {
  157. let video = document.querySelector('.u_rmcplayer_video video') || document.querySelector('.vwplayer_vlivelive .videoBox video');
  158. if (!video) return;
  159. let flip = [directions.flipH, directions.flipV].includes(direction);
  160.  
  161. if (flip) {
  162. video.classList.toggle(direction);
  163. }
  164. else {
  165. video.classList.remove(directions.left, directions.right, directions.down, 'portrait', 'landscape');
  166. if (direction != directions.up) {
  167. video.classList.add(direction);
  168. if ([directions.left, directions.right].includes(direction)) {
  169. video.classList.add(video.videoHeight > video.videoWidth ? 'portrait' : 'landscape');
  170. }
  171. }
  172. }
  173. }
  174.  
  175. /*function changeBrightness(value) {
  176. let video = document.querySelector('.u_rmcplayer_video video') || document.querySelector('.vwplayer_vlivelive .videoBox video');
  177. if (!video) return;
  178.  
  179. if (brightness[value]) {
  180. brightnessPercent += brightness[value];
  181. video.style.filter = 'brightness('+brightnessPercent+'%)';
  182. }
  183. else {
  184. brightnessPercent = 100;
  185. video.style.filter = null;
  186. }
  187. }*/
  188.  
  189. window.addEventListener('click', function(e) {
  190. if (e.target.parentNode.id != controlsID || e.target.tagName.toLowerCase() != 'button') {
  191. return;
  192. }
  193. else if (e.target.dataset.direction) {
  194. rotateVideo(e.target.dataset.direction);
  195. }
  196. /*else if (e.target.dataset.brightness) {
  197. changeBrightness(e.target.dataset.brightness);
  198. }*/
  199. }, true);
  200.  
  201.  
  202. // Shortcuts
  203. window.addEventListener('keydown', function(e) {
  204. let stopPropagation = false;
  205. if (shortcuts[e.code] && shortcutsEnabled) {
  206. rotateVideo(shortcuts[e.code]);
  207. stopPropagation = true;
  208. }
  209. else if (e.code == 'Pause') {
  210. shortcutsEnabled = !shortcutsEnabled;
  211. stopPropagation = true;
  212. }
  213.  
  214. if (stopPropagation) {
  215. e.preventDefault();
  216. e.stopPropagation();
  217. }
  218. }, true);