YouTube Live In-page Fullscreen

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

// ==UserScript==
// @name         YouTube Live In-page Fullscreen
// @version      0.2.0
// @description  Automatically puts the player full-screen on the page when you go to the live page.
// @author       dragonish
// @namespace    https://github.com/dragonish
// @license      GNU General Public License v3.0 or later
// @require      https://cdn.jsdelivr.net/npm/[email protected]/minified/arrive.min.js
// @match        *://*.youtube.com/*
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function () {
  const FULLSCREEN = `
    body {
      overflow: hidden !important;
    }
    #player #player-container-outer,
    #player #player-container-inner,
    #player #player-container,
    #player #ytd-player,
    #player #container,
    #player #movie_player,
    #player .html5-video-container,
    #player .html5-main-video {
      position: fixed !important;
      margin: 0 !important;
      padding: 0 !important;
      top: 0 !important;
      left: 0 !important;
      border: none !important;
      width: 100% !important;
      height: 100% !important;
      contain: none !important;
      background-color: #000 !important;
      z-index: 10000;
    }
    #player .html5-main-video {
      object-fit: contain !important;
    }
    #player #container .html5-video-player > div {
      z-index: 10002 !important;
    }
    #player #container .ytp-chrome-bottom,
    #player .ytp-progress-bar .ytp-chapter-hover-container {
      width: calc(100% - 12px) !important;
    }
    #player .ytp-progress-bar-container .ytp-scrubber-container {
      transform: translateX(calc(100vw - 24px)) !important;
    }
  `;
  class Shortcuts {
    constructor() {
      this.keyVal = GM_getValue('shortcuts', '');
    }
    showPanel() {
      if (!this.panel) {
        this.panelGenerator();
      }
      this.panel.style.display = 'flex';
      const input = document.querySelector('#ylf-custom-shortcuts-input');
      if (input) {
        input.disabled = true;
        input.value = this.keyVal;
      }
    }
    hidePanel() {
      if (this.panel) {
        this.panel.style.display = 'none';
      }
    }
    keyHandler(evt) {
      if (!['Control', 'Shift', 'Alt', 'Escape', 'Backspace'].includes(evt.key)) {
        let keyVal = '';
        if (evt.ctrlKey) {
          keyVal = 'Ctrl';
        }
        if (evt.altKey) {
          keyVal = keyVal ? `${keyVal}+Alt` : 'Alt';
        }
        if (evt.shiftKey) {
          keyVal = keyVal ? `${keyVal}+Shift` : 'Shift';
        }
        const u = evt.key.toUpperCase();
        keyVal = keyVal ? `${keyVal}+${u}` : u;
        return keyVal;
      }
      return '';
    }
    panelGenerator() {
      this.panel = document.createElement('div');
      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;');
      this.panel.innerHTML = `
          <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;">
            <div style="margin: 20px 15px; text-align: center; font-size: 1.2em; font-weight: bold;">Settings</div>
            <div style="margin: 0px 10px;">
              <label>Shortcuts:</label>
              <input id="ylf-custom-shortcuts-input" value="" style="width: 150px" disabled/>
              <button id="ylf-custom-edit-shortcuts-btn">Edit</button>
            </div>
            <div style="display: inline-block; margin: 15px 15px; text-align: right;">
              <button id="ylf-custom-cancel-btn" style="cursor: pointer;">Cancel</button>
              <button id="ylf-custom-ok-btn" style="cursor: pointer; margin-left: 10px;">OK</button>
            </div>
          </div>
        `;
      this.panel.addEventListener('click', evt => {
        if (evt.target === this.panel) {
          this.hidePanel();
        }
      });
      document.body.appendChild(this.panel);
      const cancelBtn = document.getElementById('ylf-custom-cancel-btn');
      const okBtn = document.getElementById('ylf-custom-ok-btn');
      const input = document.querySelector('#ylf-custom-shortcuts-input');
      const editBtn = document.getElementById('ylf-custom-edit-shortcuts-btn');
      cancelBtn?.addEventListener('click', evt => {
        evt.stopPropagation();
        this.hidePanel();
      });
      okBtn?.addEventListener('click', evt => {
        evt.stopPropagation();
        if (input) {
          const value = input.value;
          GM_setValue('shortcuts', value);
          this.keyVal = value;
        }
        this.hidePanel();
      });
      editBtn?.addEventListener('click', evt => {
        evt.stopPropagation();
        if (input) {
          input.disabled = false;
          setTimeout(() => {
            input.focus();
          }, 0);
        }
      });
      if (input) {
        input.value = this.keyVal;
        input.addEventListener('keydown', evt => {
          evt.preventDefault();
          evt.stopPropagation();
          input.value = this.keyHandler(evt);
          return false;
        });
      }
    }
    isInputElement(element) {
      try {
        return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT';
      } catch {
        return false;
      }
    }
    keyMatcher(evt) {
      if (this.keyVal && !this.isInputElement(evt.target)) {
        return this.keyVal === this.keyHandler(evt);
      }
      return false;
    }
  }
  class FullScreen {
    constructor() {
      this.shortcuts = new Shortcuts();
      GM_registerMenuCommand('Define shortcuts', () => {
        this.shortcuts.showPanel();
      });
      document.addEventListener('keydown', evt => {
        if (evt.key === 'Escape') {
          this.styleElement && this.exitFullscreen();
        } else if (this.shortcuts.keyMatcher(evt)) {
          if (this.styleElement) {
            this.exitFullscreen();
          } else {
            this.fullscreenHandler();
          }
        }
      });
    }
    fullscreenHandler() {
      if (!this.styleElement) {
        this.styleElement = GM_addStyle(FULLSCREEN);
      }
      console.info('enter fullscreen');
      this.menuHandler();
    }
    exitFullscreen() {
      if (this.styleElement) {
        this.styleElement.remove();
        this.styleElement = undefined;
        this.menuHandler();
        console.log('exit fullscreen');
      }
    }
    menuHandler() {
      this.menuKey = GM_registerMenuCommand(this.styleElement ? 'Exit fullscreen' : 'Enter fullscreen', () => {
        if (this.styleElement) {
          this.exitFullscreen();
        } else {
          this.fullscreenHandler();
        }
      }, {
        id: this.menuKey
      });
    }
  }
  function main() {
    const isVideoPage = location.href.includes('watch?v=');
    if (!isVideoPage) return;
    const isLivePage = document.querySelector('.ytp-live') != null;
    console.debug('isLivePage: ', isLivePage);
    if (!isLivePage) return;
    document.body.arrive('#player-container-outer', {
      onceOnly: true,
      existing: true
    }, () => {
      console.debug('found the player element');
      const fs = new FullScreen();
      fs.fullscreenHandler();
    });
  }
  main();
})();