YouTube Live In-page Fullscreen

Automatically puts the player full-screen on the page when you go to the live page.

  1. // ==UserScript==
  2. // @name YouTube Live In-page Fullscreen
  3. // @version 0.2.0
  4. // @description Automatically puts the player full-screen on the page when you go to the live page.
  5. // @author dragonish
  6. // @namespace https://github.com/dragonish
  7. // @license GNU General Public License v3.0 or later
  8. // @require https://cdn.jsdelivr.net/npm/arrive@2.4.1/minified/arrive.min.js
  9. // @match *://*.youtube.com/*
  10. // @grant GM_addStyle
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. const FULLSCREEN = `
  18. body {
  19. overflow: hidden !important;
  20. }
  21. #player #player-container-outer,
  22. #player #player-container-inner,
  23. #player #player-container,
  24. #player #ytd-player,
  25. #player #container,
  26. #player #movie_player,
  27. #player .html5-video-container,
  28. #player .html5-main-video {
  29. position: fixed !important;
  30. margin: 0 !important;
  31. padding: 0 !important;
  32. top: 0 !important;
  33. left: 0 !important;
  34. border: none !important;
  35. width: 100% !important;
  36. height: 100% !important;
  37. contain: none !important;
  38. background-color: #000 !important;
  39. z-index: 10000;
  40. }
  41. #player .html5-main-video {
  42. object-fit: contain !important;
  43. }
  44. #player #container .html5-video-player > div {
  45. z-index: 10002 !important;
  46. }
  47. #player #container .ytp-chrome-bottom,
  48. #player .ytp-progress-bar .ytp-chapter-hover-container {
  49. width: calc(100% - 12px) !important;
  50. }
  51. #player .ytp-progress-bar-container .ytp-scrubber-container {
  52. transform: translateX(calc(100vw - 24px)) !important;
  53. }
  54. `;
  55. class Shortcuts {
  56. constructor() {
  57. this.keyVal = GM_getValue('shortcuts', '');
  58. }
  59. showPanel() {
  60. if (!this.panel) {
  61. this.panelGenerator();
  62. }
  63. this.panel.style.display = 'flex';
  64. const input = document.querySelector('#ylf-custom-shortcuts-input');
  65. if (input) {
  66. input.disabled = true;
  67. input.value = this.keyVal;
  68. }
  69. }
  70. hidePanel() {
  71. if (this.panel) {
  72. this.panel.style.display = 'none';
  73. }
  74. }
  75. keyHandler(evt) {
  76. if (!['Control', 'Shift', 'Alt', 'Escape', 'Backspace'].includes(evt.key)) {
  77. let keyVal = '';
  78. if (evt.ctrlKey) {
  79. keyVal = 'Ctrl';
  80. }
  81. if (evt.altKey) {
  82. keyVal = keyVal ? `${keyVal}+Alt` : 'Alt';
  83. }
  84. if (evt.shiftKey) {
  85. keyVal = keyVal ? `${keyVal}+Shift` : 'Shift';
  86. }
  87. const u = evt.key.toUpperCase();
  88. keyVal = keyVal ? `${keyVal}+${u}` : u;
  89. return keyVal;
  90. }
  91. return '';
  92. }
  93. panelGenerator() {
  94. this.panel = document.createElement('div');
  95. this.panel.setAttribute('style', 'position: fixed; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.5); align-items: center; justify-content: center; z-index: 10099;');
  96. this.panel.innerHTML = `
  97. <div style="position: relative; border-radius: 12px; display: flex; flex-direction: column; box-shadow: 0 1px 10px rgba(0, 0, 0, 0.8); background-color: rgb(21, 32, 43); border: 1px solid #000; color: #fff; width: 300px;">
  98. <div style="margin: 20px 15px; text-align: center; font-size: 1.2em; font-weight: bold;">Settings</div>
  99. <div style="margin: 0px 10px;">
  100. <label>Shortcuts:</label>
  101. <input id="ylf-custom-shortcuts-input" value="" style="width: 150px" disabled/>
  102. <button id="ylf-custom-edit-shortcuts-btn">Edit</button>
  103. </div>
  104. <div style="display: inline-block; margin: 15px 15px; text-align: right;">
  105. <button id="ylf-custom-cancel-btn" style="cursor: pointer;">Cancel</button>
  106. <button id="ylf-custom-ok-btn" style="cursor: pointer; margin-left: 10px;">OK</button>
  107. </div>
  108. </div>
  109. `;
  110. this.panel.addEventListener('click', evt => {
  111. if (evt.target === this.panel) {
  112. this.hidePanel();
  113. }
  114. });
  115. document.body.appendChild(this.panel);
  116. const cancelBtn = document.getElementById('ylf-custom-cancel-btn');
  117. const okBtn = document.getElementById('ylf-custom-ok-btn');
  118. const input = document.querySelector('#ylf-custom-shortcuts-input');
  119. const editBtn = document.getElementById('ylf-custom-edit-shortcuts-btn');
  120. cancelBtn?.addEventListener('click', evt => {
  121. evt.stopPropagation();
  122. this.hidePanel();
  123. });
  124. okBtn?.addEventListener('click', evt => {
  125. evt.stopPropagation();
  126. if (input) {
  127. const value = input.value;
  128. GM_setValue('shortcuts', value);
  129. this.keyVal = value;
  130. }
  131. this.hidePanel();
  132. });
  133. editBtn?.addEventListener('click', evt => {
  134. evt.stopPropagation();
  135. if (input) {
  136. input.disabled = false;
  137. setTimeout(() => {
  138. input.focus();
  139. }, 0);
  140. }
  141. });
  142. if (input) {
  143. input.value = this.keyVal;
  144. input.addEventListener('keydown', evt => {
  145. evt.preventDefault();
  146. evt.stopPropagation();
  147. input.value = this.keyHandler(evt);
  148. return false;
  149. });
  150. }
  151. }
  152. isInputElement(element) {
  153. try {
  154. return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT';
  155. } catch {
  156. return false;
  157. }
  158. }
  159. keyMatcher(evt) {
  160. if (this.keyVal && !this.isInputElement(evt.target)) {
  161. return this.keyVal === this.keyHandler(evt);
  162. }
  163. return false;
  164. }
  165. }
  166. class FullScreen {
  167. constructor() {
  168. this.shortcuts = new Shortcuts();
  169. GM_registerMenuCommand('Define shortcuts', () => {
  170. this.shortcuts.showPanel();
  171. });
  172. document.addEventListener('keydown', evt => {
  173. if (evt.key === 'Escape') {
  174. this.styleElement && this.exitFullscreen();
  175. } else if (this.shortcuts.keyMatcher(evt)) {
  176. if (this.styleElement) {
  177. this.exitFullscreen();
  178. } else {
  179. this.fullscreenHandler();
  180. }
  181. }
  182. });
  183. }
  184. fullscreenHandler() {
  185. if (!this.styleElement) {
  186. this.styleElement = GM_addStyle(FULLSCREEN);
  187. }
  188. console.info('enter fullscreen');
  189. this.menuHandler();
  190. }
  191. exitFullscreen() {
  192. if (this.styleElement) {
  193. this.styleElement.remove();
  194. this.styleElement = undefined;
  195. this.menuHandler();
  196. console.log('exit fullscreen');
  197. }
  198. }
  199. menuHandler() {
  200. this.menuKey = GM_registerMenuCommand(this.styleElement ? 'Exit fullscreen' : 'Enter fullscreen', () => {
  201. if (this.styleElement) {
  202. this.exitFullscreen();
  203. } else {
  204. this.fullscreenHandler();
  205. }
  206. }, {
  207. id: this.menuKey
  208. });
  209. }
  210. }
  211. function main() {
  212. const isVideoPage = location.href.includes('watch?v=');
  213. if (!isVideoPage) return;
  214. const isLivePage = document.querySelector('.ytp-live') != null;
  215. console.debug('isLivePage: ', isLivePage);
  216. if (!isLivePage) return;
  217. document.body.arrive('#player-container-outer', {
  218. onceOnly: true,
  219. existing: true
  220. }, () => {
  221. console.debug('found the player element');
  222. const fs = new FullScreen();
  223. fs.fullscreenHandler();
  224. });
  225. }
  226. main();
  227. })();