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.1
// @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,
    #ytd-player #player-container-outer,
    #ytd-player #player-container-inner,
    #ytd-player #player-container,
    #ytd-player #ytd-player,
    #ytd-player #container,
    #ytd-player #movie_player,
    #ytd-player .html5-video-container,
    #ytd-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,
    #ytd-player .html5-main-video {
      object-fit: contain !important;
    }
    #player #container .html5-video-player > div,
    #ytd-player #container .html5-video-player > div {
      z-index: 10002 !important;
    }
    #player #container .html5-video-player > div.ytp-tooltip.ytp-bottom,
    #ytd-player #container .html5-video-player > div.ytp-tooltip.ytp-bottom {
      top: calc(100% - 90px) !important;
    }
    #player #container .ytp-chrome-bottom,
    #player .ytp-progress-bar .ytp-chapter-hover-container,
    #ytd-player #container .ytp-chrome-bottom,
    #ytd-player .ytp-progress-bar .ytp-chapter-hover-container {
      width: calc(100% - 12px) !important;
    }
    #player .ytp-progress-bar-container .ytp-scrubber-container,
    #ytd-player .ytp-progress-bar-container .ytp-scrubber-container {
      transform: translateX(calc(100vw - 24px)) !important;
    }
    #ylf-panel {
      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;
    }
    #ylf-panel-div {
      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;
    }
    #ylf-settings-header {
      margin: 20px 15px;
      text-align: center;
      font-size: 1.2em;
      font-weight: bold;
    }
    #ylf-settings-content {
      margin: 0px 10px;
    }
    #ylf-custom-shortcuts-input {
      width: 150px
    }
    #ylf-settings-footer {
      display: inline-block;
      margin: 15px 15px;
      text-align: right;
    }
    #ylf-custom-cancel-btn,
    #ylf-custom-ok-btn {
      cursor: pointer;
    }
    #ylf-custom-ok-btn {
      margin-left: 10px;
    }
  `;
  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.id = 'ylf-panel';
      const div = document.createElement('div');
      div.id = 'ylf-panel-div';
      this.panel.appendChild(div);
      const settingsHeader = document.createElement('div');
      settingsHeader.id = 'ylf-settings-header';
      settingsHeader.textContent = 'Settings';
      div.append(settingsHeader);
      const settingsContent = document.createElement('div');
      settingsContent.id = 'ylf-settings-content';
      div.append(settingsContent);
      const label = document.createElement('label');
      label.textContent = 'Shortcuts:';
      settingsContent.appendChild(label);
      const input = document.createElement('input');
      input.id = 'ylf-custom-shortcuts-input';
      input.value = this.keyVal;
      input.addEventListener('keydown', evt => {
        evt.preventDefault();
        evt.stopPropagation();
        input.value = this.keyHandler(evt);
        return false;
      });
      settingsContent.appendChild(input);
      const editBtn = document.createElement('button');
      editBtn.id = 'ylf-custom-edit-shortcuts-btn';
      editBtn.textContent = 'Edit';
      editBtn.addEventListener('click', evt => {
        evt.stopPropagation();
        if (input) {
          input.disabled = false;
          setTimeout(() => {
            input.focus();
          }, 0);
        }
      });
      settingsContent.appendChild(editBtn);
      const settingsFooter = document.createElement('div');
      settingsFooter.id = 'ylf-settings-footer';
      div.appendChild(settingsFooter);
      const cancelBtn = document.createElement('button');
      cancelBtn.id = 'ylf-custom-cancel-btn';
      cancelBtn.textContent = 'Cancel';
      cancelBtn.addEventListener('click', evt => {
        evt.stopPropagation();
        this.hidePanel();
      });
      settingsFooter.appendChild(cancelBtn);
      const okBtn = document.createElement('button');
      okBtn.id = 'ylf-custom-ok-btn';
      okBtn.textContent = 'OK';
      okBtn.addEventListener('click', evt => {
        evt.stopPropagation();
        if (input) {
          const value = input.value;
          GM_setValue('shortcuts', value);
          this.keyVal = value;
        }
        this.hidePanel();
      });
      settingsFooter.appendChild(okBtn);
      this.panel.addEventListener('click', evt => {
        if (evt.target === this.panel) {
          this.hidePanel();
        }
      });
      document.body.appendChild(this.panel);
    }
    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();
})();