Greasy Fork is available in English.

Auto hide next up card for Amazon Prime Video

Hide next up card and other obtrusive elements of Amazon Prime Video.

// ==UserScript==
// @name          Auto hide next up card for Amazon Prime Video
// @namespace     http://tampermonkey.net/
// @version       2.3.1
// @description   Hide next up card and other obtrusive elements of Amazon Prime Video.
// @author        ryo-fujinone
// @match         https://*.amazon.co.jp/*
// @match         https://*.amazon.com/*
// @match         https://*.amazon.ae/*
// @match         https://*.amazon.co.uk/*
// @match         https://*.amazon.it/*
// @match         https://*.amazon.in/*
// @match         https://*.amazon.eg/*
// @match         https://*.amazon.com.au/*
// @match         https://*.amazon.nl/*
// @match         https://*.amazon.ca/*
// @match         https://*.amazon.sa/*
// @match         https://*.amazon.sg/*
// @match         https://*.amazon.se/*
// @match         https://*.amazon.es/*
// @match         https://*.amazon.de/*
// @match         https://*.amazon.com.tr/*
// @match         https://*.amazon.com.br/*
// @match         https://*.amazon.fr/*
// @match         https://*.amazon.com.be/*
// @match         https://*.amazon.pl/*
// @match         https://*.amazon.com.mx/*
// @match         https://*.amazon.cn/*
// @match         https://*.primevideo.com/*
// @license       MIT; https://github.com/ryo-fujinone/auto-hide-next-up-card-for-amazon-prime-video/blob/main/LICENSE
// ==/UserScript==

(function () {
  'use strict';

  const observeConfig = Object.freeze({ childList: true, subtree: true });

  const getDefaultOptions = () => {
    return {
      hideSkipIntroBtn: true,
      showSkipIntroBtnOnOverlay: false,
      hideNextup: true,
      temporarilyDisableOverlay: true,
      showNextupOnOverlay: false,
      hideRating: true,
      preventsDarkening: false,
      moveCenterButtonsToBottom: false,
      shortcutKey: {
        ctrl: false,
        alt: true,
        shift: false,
        charCode: "KeyP",
      },
      shortcutKeyIsEnabled: true,
      scriptVersion: "2.3.1",
    };
  };

  const getScriptInfo = () => {
    // user script
    /**
     * When using optional chaining with window.GM_info in tampermonkey,
     * it sometimes became undefined for some reason, so I implemented it using try-catch.
     */
    try {
      const gmVer = window.GM_info.script.version;
      if (typeof gmVer === "string") {
        return {
          scriptType: "user-script",
          scriptVersion: gmVer,
        };
      }
    } catch (e) {
      // console.log(e);
    }

    // chrome extension
    try {
      const chromeExtVer = chrome?.runtime?.getManifest()?.version;
      if (typeof chromeExtVer === "string") {
        return {
          scriptType: "chrome-extension",
          scriptVersion: chromeExtVer,
        };
      }
    } catch (e) {
      // console.log(e);
    }

    // unknown
    return {
      scriptType: "unknown",
      scriptVersion: getDefaultOptions().scriptVersion,
    };
  };

  // array of alphabets used to set shortcut keys.
  const charObj = {
    _chars: [],
    _codeStrs: [],
    _startCode: "A".charCodeAt(0),
    getChars() {
      if (this._chars.length) {
        return this._chars;
      }
      [...Array(26)].forEach((_, i) => {
        const char = String.fromCharCode(this._startCode + i);
        this._chars.push(char);
      });
      return this._chars;
    },
    getCodeStrs() {
      if (this._codeStrs.length) {
        return this._codeStrs;
      }
      this.getChars().forEach((c) => {
        this._codeStrs.push("Key" + c);
      });
      return this._codeStrs;
    },
  };

  const addStyle = (css, id) => {
    const style = document.createElement("style");
    if (id) {
      style.setAttribute("id", id);
    }
    style.textContent = css;
    document.head.appendChild(style);
  };

  const saveDefaultOptions = () => {
    const jsonStr = JSON.stringify(getDefaultOptions());
    localStorage.setItem("nextup-ext", jsonStr);
  };

  const getOptions = () => {
    const jsonStr = localStorage.getItem("nextup-ext");
    if (!jsonStr) {
      saveDefaultOptions();
      return getDefaultOptions();
    }
    return JSON.parse(jsonStr);
  };

  const saveOptions = (_newOptions = {}) => {
    const options = getOptions();
    const newOptions = {
      ...options,
      ..._newOptions,
    };
    const jsonStr = JSON.stringify(newOptions);
    localStorage.setItem("nextup-ext", jsonStr);
  };

  const updateOptionVersion = (scriptInfo) => {
    const options = getOptions();
    if (options.scriptVersion === scriptInfo.scriptVersion) {
      return;
    }

    const defaultOptions = getDefaultOptions();
    const mergedOptions = {
      ...defaultOptions,
      ...options,
      scriptVersion: scriptInfo.scriptVersion,
    };
    const mergedOptionsKeys = Object.keys(mergedOptions);
    const newOptions = mergedOptionsKeys.reduce((obj, key) => {
      if (Object.hasOwn(defaultOptions, key)) {
        obj[key] = mergedOptions[key];
      }
      return obj;
    }, {});
    const jsonStr = JSON.stringify(newOptions);
    localStorage.setItem("nextup-ext", jsonStr);
  };

  const getOptionDialog = () => {
    return document.querySelector(".nextup-ext-opt-dialog");
  };

  const getShortcutKeyInput = () => {
    return document.querySelector("#shortcutkey-for-dialog");
  };

  const getVisibleVideo = () => {
    return document.querySelector(
      ".dv-player-fullscreen .webPlayerContainer video"
    );
  };

  const playVideo = () => {
    const video = getVisibleVideo();
    if (!video) {
      return;
    }
    if (video.paused) {
      video.play();
    }
  };

  const pauseVideo = () => {
    const video = getVisibleVideo();
    if (!video) {
      return;
    }
    if (!video.paused) {
      video.pause();
    }
  };

  const worksWithDialog = {
    clickedOutSide: null,
    _clickedOutSide: function (e) {
      if (e.target.classList.contains("nextup-ext-opt-dialog")) {
        e.target.close();
        this.whenClosed();
      }
    },
    setShortcutKeyVal: function () {
      const options = getOptions();
      let shortcutKeyStrs = [];
      if (options.shortcutKey.ctrl) {
        shortcutKeyStrs.push("Ctrl");
      }
      if (options.shortcutKey.alt) {
        shortcutKeyStrs.push("Alt");
      }
      if (options.shortcutKey.shift) {
        shortcutKeyStrs.push("Shift");
      }
      const codeStrs = charObj.getCodeStrs();
      const chars = charObj.getChars();
      const char = chars[codeStrs.indexOf(options.shortcutKey.charCode)];
      if (char) {
        shortcutKeyStrs.push(char);
      } else {
        shortcutKeyStrs = ["Alt", "P"];
        saveOptions({ shortcutKey: getDefaultOptions().shortcutKey });
      }

      if (!this.changeShortcutKeyVal) {
        this.changeShortcutKeyVal = this._changeShortcutKeyVal.bind(this);
      }
      const shortcutKeyStr = shortcutKeyStrs.join(" + ");
      const shortcutKeyInput = getShortcutKeyInput();
      if (shortcutKeyInput) {
        shortcutKeyInput.value = shortcutKeyStr;
        shortcutKeyInput.addEventListener("keydown", this.changeShortcutKeyVal);
      }
    },
    changeShortcutKeyVal: null,
    _changeShortcutKeyVal: function (e) {
      if (e.code === "Tab" || e.code === "Escape" || e.code === "F5") {
        return;
      }
      const codeStrs = charObj.getCodeStrs();
      if (codeStrs.indexOf(e.code) === -1 || (!e.ctrlKey && !e.altKey)) {
        e.preventDefault();
        return;
      }

      const newShortcutKeyOptions = getDefaultOptions().shortcutKey;
      let shortcutKeyStrs = [];
      if (e.ctrlKey) {
        shortcutKeyStrs.push("Ctrl");
      }
      newShortcutKeyOptions.ctrl = e.ctrlKey;
      if (e.altKey) {
        shortcutKeyStrs.push("Alt");
      }
      newShortcutKeyOptions.alt = e.altKey;
      if (e.shiftKey) {
        shortcutKeyStrs.push("Shift");
      }
      newShortcutKeyOptions.shift = e.shiftKey;
      const chars = charObj.getChars();
      const char = chars[codeStrs.indexOf(e.code)];
      shortcutKeyStrs.push(char);
      newShortcutKeyOptions.charCode = e.code;

      const shortcutKeyStr = shortcutKeyStrs.join(" + ");
      const shortcutKeyInput = getShortcutKeyInput();
      shortcutKeyInput.value = shortcutKeyStr;

      saveOptions({ shortcutKey: newShortcutKeyOptions });
    },
    whenOpening: function () {
      pauseVideo();
      this.setShortcutKeyVal();
      if (!this.clickedOutSide) {
        this.clickedOutSide = this._clickedOutSide.bind(this);
      }
      document.addEventListener("click", this.clickedOutSide);
    },
    whenClosed: function () {
      const shortcutKeyInput = getShortcutKeyInput();
      if (shortcutKeyInput) {
        shortcutKeyInput.removeEventListener(
          "keydown",
          this.changeShortcutKeyVal
        );
      }
      document.removeEventListener("click", this.clickedOutSide);
      playVideo();
    },
  };

  const createOptionMessages = () => {
    const jaMessages = {
      promptReloadPage: "オプションを変更した場合はページをリロードしてください",
      hideSkipIntroBtn: "イントロスキップボタンを非表示にする",
      showSkipIntroBtnOnOverlay:
        "オーバーレイ表示が有効な時はイントロスキップボタンを表示する",
      hideNextup: "Next upを非表示にする",
      temporarilyDisableOverlay:
        "非表示ボタンの自動クリック時に5秒間オーバーレイ表示を無効にする",
      showNextupOnOverlay:
        "オーバーレイ表示が有効な時はNext upを表示する(非表示ボタンが無い場合のみ)",
      hideRating: "レーティング(推薦年齢対象)を非表示にする",
      preventsDarkening: "オーバーレイ表示が有効な時に暗くならないようにする",
      moveCenterButtonsToBottom:
        "実験的: 中央のボタン(再生/停止、戻る、進む)を下部に移動する",
      enableShortcutKey:
        "ショートカットキーでオプションダイアログを開けるようにする",
      shortcutKeyForDialog: "オプションダイアログを開くショートカットキー",
      shortcutKeyForDialog_Tooltip: "Ctrl/Altとアルファベットは必須",
      close: "閉じる",
    };
    const enMessages = {
      promptReloadPage: "If you change the options, please reload the page",
      hideSkipIntroBtn: "Hide skip intro button",
      showSkipIntroBtnOnOverlay:
        "Show skip intro button when overlay display is enabled",
      hideNextup: "Hide next up card",
      temporarilyDisableOverlay:
        "Disable overlay for 5 seconds when auto-clicking hide button",
      showNextupOnOverlay:
        "Show next up card when overlay display is enabled (only if there is no hide button)",
      hideRating: "Hide rating",
      preventsDarkening: "Prevents darkening when overlay display is enabled",
      moveCenterButtonsToBottom:
        "Experimental: Move the center buttons(Play/Pause, Back and Forward) to the bottom",
      enableShortcutKey: "Enable shortcut key to open the options dialog",
      shortcutKeyForDialog: "Shortcut key to open the options dialog",
      shortcutKeyForDialog_Tooltip: "Ctrl/Alt and alphabets are required",
      close: "Close",
    };
    return /ja|ja-JP/.test(window.navigator.language) ? jaMessages : enMessages;
  };

  const createOptionDialog = (scriptVersion) => {
    if (getOptionDialog()) {
      return;
    }

    const messages = createOptionMessages();
    const options = getOptions();

    const dialogHtmlStr = `
    <dialog class="nextup-ext-opt-dialog">
        <div class="dialog-inner">
            <div class="nextup-ext-opt-dialog-note">
              <p>${messages.promptReloadPage}</p>            
            </div>
            <label>
                <input type="checkbox" id="hide-skip-intro-btn" name="hide-skip-intro-btn" ${
                  options.hideSkipIntroBtn ? "checked" : ""
                } />
                <p>${messages.hideSkipIntroBtn}</p>
            </label>
            <label class="indent1">
                <input type="checkbox" id="show-skip-intro-btn" name="show-skip-intro-btn" ${
                  options.showSkipIntroBtnOnOverlay ? "checked" : ""
                } />
                <p>${messages.showSkipIntroBtnOnOverlay}</p>
            </label>
            <label>
                <input type="checkbox" id="hide-nextup" name="hide-nextup" ${
                  options.hideNextup ? "checked" : ""
                } />
                <p>${messages.hideNextup}</p>
            </label>
            <label class="indent1">
                <input type="checkbox" id="temporarily-disable-overlay" name="temporarily-disable-overlay" ${
                  options.temporarilyDisableOverlay ? "checked" : ""
                } />
                <p>${messages.temporarilyDisableOverlay}</p>
            </label>
            <label class="indent1">
                <input type="checkbox" id="show-nextup" name="show-nextup" ${
                  options.showNextupOnOverlay ? "checked" : ""
                } />
                <p>${messages.showNextupOnOverlay}</p>
            </label>
            <label>
                <input type="checkbox" id="hide-rationg" name="hide-rationg" ${
                  options.hideRating ? "checked" : ""
                } />
                <p>${messages.hideRating}</p>
            </label>
            <label>
                <input type="checkbox" id="prevents-darkening" name="prevents-darkening" ${
                  options.preventsDarkening ? "checked" : ""
                } />
                <p>${messages.preventsDarkening}</p>
            </label>
            <label>
                <input type="checkbox" id="move-center-buttons-to-bottom" name="move-center-buttons-to-bottom" ${
                  options.moveCenterButtonsToBottom ? "checked" : ""
                } />
                <p>${messages.moveCenterButtonsToBottom}</p>
            </label>
            <label>
                <input type="checkbox" id="enable-shortcutkey" name="enable-shortcutkey" ${
                  options.shortcutKeyIsEnabled ? "checked" : ""
                } />
                <p>${messages.enableShortcutKey}</p>
            </label>
            <ul>
                <li>
                    <label title="${messages.shortcutKeyForDialog_Tooltip}">
                        <span style="margin-right: 4px;">${
                          messages.shortcutKeyForDialog
                        }</span>
                        <input type="text" id="shortcutkey-for-dialog" name="shortcutkey-for-dialog" />
                    </label>
                </li>
            </ul>
            <div class="nextup-ext-opt-dialog-btn-wrapper">
                <button id="nextup-ext-opt-dialog-close">${
                  messages.close
                }</button>
                <div class="nextup-ext-opt-dialog-version"><span>v${scriptVersion}</span></div>
            </div>
        </div>
    </dialog>
    `;
    document.body.insertAdjacentHTML("beforeend", dialogHtmlStr);

    const css = [
      ".nextup-ext-opt-dialog {padding: 0; word-break: break-all;}",
      ".dialog-inner {padding: 14px;}",
      ".nextup-ext-opt-dialog-note {text-align: center; color: green; margin-bottom: 10px; font-weight: 700;}",
      ".nextup-ext-opt-dialog label {display: block;}",
      ".nextup-ext-opt-dialog label.indent1 {margin-left: 14px;}",
      ".nextup-ext-opt-dialog label input[type='checkbox'] {float: left;}",
      ".nextup-ext-opt-dialog label p {float: left; margin-bottom: 5px; width: calc(100% - 24px);}",
      ".nextup-ext-opt-dialog ul li {margin-left: 18px;}",
      ".nextup-ext-opt-dialog label input[type='text'] {height: 20px;}",
      ".nextup-ext-opt-dialog .nextup-ext-opt-dialog-btn-wrapper {margin-top: 12px; position: relative;}",
      ".nextup-ext-opt-dialog div:has(#nextup-ext-opt-dialog-close):not(.dialog-inner) {text-align: center;}",
      "#nextup-ext-opt-dialog-close {border-color: black; border: solid 1px; background-color: #EEE}",
      "#nextup-ext-opt-dialog-close {width: 120px; letter-spacing: 4px;}",
      "#nextup-ext-opt-dialog-close:hover {background-color: #DDD}",
      ".nextup-ext-opt-dialog-version {position: absolute; top: 0px; right: 0px;}",
    ];
    addStyle(css.join(""));

    const optDialog = getOptionDialog();

    //  Adjust width of options dialog.
    optDialog.style.setProperty("visibility", "hidden", "important");
    optDialog.toggleAttribute("open");
    let maxWidth = 650;
    if (optDialog.offsetWidth > 500) {
      maxWidth = optDialog.offsetWidth + 14;
    }
    optDialog.style.maxWidth = maxWidth + "px";
    optDialog.style.width = "100%";
    optDialog.toggleAttribute("open");
    optDialog.style.setProperty("visibility", "");

    optDialog.addEventListener(
      "click",
      (e) => {
        const idName = e.target.id;
        if (idName === "") {
          return;
        }

        switch (idName) {
          case "hide-skip-intro-btn":
            saveOptions({ hideSkipIntroBtn: e.target.checked });
            break;
          case "show-skip-intro-btn":
            saveOptions({ showSkipIntroBtnOnOverlay: e.target.checked });
            break;
          case "hide-nextup":
            saveOptions({ hideNextup: e.target.checked });
            break;
          case "temporarily-disable-overlay":
            saveOptions({ temporarilyDisableOverlay: e.target.checked });
          case "show-nextup":
            saveOptions({ showNextupOnOverlay: e.target.checked });
            break;
          case "hide-rationg":
            saveOptions({ hideRating: e.target.checked });
            break;
          case "prevents-darkening":
            saveOptions({ preventsDarkening: e.target.checked });
            break;
          case "move-center-buttons-to-bottom":
            saveOptions({ moveCenterButtonsToBottom: e.target.checked });
            break;
          case "enable-shortcutkey":
            saveOptions({ shortcutKeyIsEnabled: e.target.checked });
            break;
          case "nextup-ext-opt-dialog-close":
            optDialog.close();
            worksWithDialog.whenClosed();
            break;
        }
      },
      true
    );
  };

  const addEventListenerForShortcutKey = (options = getDefaultOptions()) => {
    if (!options.shortcutKeyIsEnabled) {
      return;
    }

    document.body.addEventListener("keydown", (e) => {
      const video = getVisibleVideo();
      if (!video || !video.checkVisibility()) {
        return;
      }

      const shortcutKeyInput = getShortcutKeyInput();
      if (shortcutKeyInput === document.activeElement) {
        return;
      }

      const options = getOptions();
      if (
        e.code === options.shortcutKey.charCode &&
        e.ctrlKey === options.shortcutKey.ctrl &&
        e.altKey === options.shortcutKey.alt &&
        e.shiftKey === options.shortcutKey.shift
      ) {
        const optDialog = getOptionDialog();
        if (optDialog.hasAttribute("open")) {
          optDialog.close();
          worksWithDialog.whenClosed();
        } else {
          worksWithDialog.whenOpening();
          optDialog.showModal();
        }
      }
    });
  };

  class ElementHider {
    constructor(player) {
      this.player = player;
    }

    createOptionBtn() {
      new MutationObserver((_, observer) => {
        if (this.player.querySelector(".nextup-ext-opt-btn-container")) {
          return;
        }

        const btnsContainer = this.player.querySelector(
          ".atvwebplayersdk-hideabletopbuttons-container"
        );
        if (!btnsContainer) {
          return;
        }

        observer.disconnect();

        const optContainer = btnsContainer.querySelector(
          ".atvwebplayersdk-options-wrapper span div:has(.atvwebplayersdk-optionsmenu-button)"
        );
        const clone = optContainer.cloneNode(true);
        clone.classList.add("nextup-ext-opt-btn-container");
        btnsContainer
          .querySelector("div:has(.atvwebplayersdk-options-wrapper)")
          .appendChild(clone);

        const cloneOptBtn = clone.querySelector(
          ".atvwebplayersdk-optionsmenu-button"
        );
        cloneOptBtn.classList.remove("atvwebplayersdk-optionsmenu-button");
        cloneOptBtn.classList.add("nextup-ext-opt-btn");

        const cloneOptBtnImg = cloneOptBtn.querySelector("img");
        cloneOptBtnImg.style.filter =
          "sepia(100%) saturate(2000%) hue-rotate(120deg)";

        const cloneTooltip = clone.querySelector("button + div div");
        cloneTooltip.textContent = "Option - Auto hide next up card";

        cloneOptBtn.addEventListener("click", (_) => {
          const optDialog = getOptionDialog();
          worksWithDialog.whenOpening();
          optDialog.showModal();
        });
      }).observe(this.player, observeConfig);
    }

    hideSkipIntroBtn(options = getDefaultOptions()) {
      if (!options.hideSkipIntroBtn) {
        return;
      }

      if (!document.querySelector("#hideSkipIntroBtn")) {
        const css = [
          ".atvwebplayersdk-skipelement-button {display: none !important;}",
        ];
        addStyle(css.join(""), "hideSkipIntroBtn");
      }

      if (!options.showSkipIntroBtnOnOverlay) {
        return;
      }
      new MutationObserver((_, outerObserver) => {
        const btnsContainer = this.player.querySelector(
          ".atvwebplayersdk-hideabletopbuttons-container"
        );
        if (!btnsContainer) {
          return;
        }
        outerObserver.disconnect();
        new MutationObserver((_) => {
          const skipIntroBtn = this.player.querySelector(
            ".atvwebplayersdk-skipelement-button"
          );
          if (!skipIntroBtn) {
            return;
          }
          if (btnsContainer.classList.contains("hide")) {
            skipIntroBtn.style.setProperty("display", "none", "important");
          } else {
            skipIntroBtn.style.setProperty("display", "block", "important");
          }
        }).observe(btnsContainer, {
          attributes: true,
        });
      }).observe(this.player, observeConfig);
    }

    temporarilyDisableOverlay(options = getDefaultOptions(), delay = 5000) {
      if (!options.temporarilyDisableOverlay) {
        return;
      }
      const overlaysWrapper = this.player.querySelector(
        ".atvwebplayersdk-overlays-wrapper"
      );
      if (!overlaysWrapper) {
        return;
      }
      overlaysWrapper.style.display = "none";
      setTimeout(() => {
        overlaysWrapper.style.display = "";
      }, delay);
    }

    hideNextupCard(options = getDefaultOptions()) {
      if (!options.hideNextup) {
        return;
      }

      if (!document.querySelector("#hideNextupCard")) {
        const css = [
          ".atvwebplayersdk-nextupcard-wrapper {display: none !important;}",
        ];
        addStyle(css.join(""), "hideNextupCard");
      }

      new MutationObserver((_, outerObserver) => {
        const wrapper = this.player.querySelector(
          ".atvwebplayersdk-nextupcard-wrapper"
        );
        if (!wrapper) {
          return;
        }
        outerObserver.disconnect();
        new MutationObserver((_) => {
          const hideButton = wrapper.querySelector(
            ".atvwebplayersdk-nextupcardhide-button"
          );
          if (hideButton) {
            // Temporarily disable the overlay because it will be displayed by executing click().
            this.temporarilyDisableOverlay(options, 5000);
            hideButton.click();
          }
        }).observe(wrapper, observeConfig);

        if (options.showNextupOnOverlay) {
          new MutationObserver((_, outerObserver2) => {
            const btnsContainer = this.player.querySelector(
              ".atvwebplayersdk-hideabletopbuttons-container"
            );
            if (!btnsContainer) {
              return;
            }
            outerObserver2.disconnect();
            new MutationObserver((_) => {
              const img = wrapper.querySelector("img");
              if (!img || !img.getAttribute("src")) {
                wrapper.style.setProperty("display", "none", "important");
                return;
              }
              if (btnsContainer.classList.contains("hide")) {
                wrapper.style.setProperty("display", "none", "important");
              } else {
                wrapper.style.setProperty("display", "block", "important");
              }
            }).observe(btnsContainer, {
              attributes: true,
            });
          }).observe(this.player, observeConfig);
        }
      }).observe(this.player, observeConfig);
    }

    hideRatingText(options = getDefaultOptions()) {
      if (!options.hideRating) {
        return;
      }
      if (!document.querySelector("#hideRatingText")) {
        const css = [
          ".atvwebplayersdk-regulatory-overlay {display: none !important;}",
        ];
        addStyle(css.join(""), "hideRatingText");
      }
    }

    preventsDarkening(options = getDefaultOptions()) {
      if (!options.preventsDarkening) {
        return;
      }

      if (!document.querySelector("#preventsDarkening")) {
        const css = [
          ".atvwebplayersdk-overlays-container > div.fkpovp9 {display: none !important;}",
        ];
        addStyle(css.join(""), "preventsDarkening");
      }
    }

    // This feature is experimental.
    // Move the center buttons(Play/Pause, Back and Forward) to the bottom.
    moveCenterButtonsToBottom(options = getDefaultOptions()) {
      if (!options.moveCenterButtonsToBottom) {
        return;
      }

      new MutationObserver((_, observer) => {
        const playPauseButton = this.player.querySelector(
          ".atvwebplayersdk-playpause-button"
        );
        if (!playPauseButton) {
          return;
        }
        observer.disconnect();

        const container = playPauseButton.parentNode.parentNode.parentNode;
        const computedStyle = window.getComputedStyle(container);
        if (!parseFloat(computedStyle.marginTop) < 0) {
          return;
        }

        container.style.position = "absolute";
        container.style.bottom = 0;
        container.style.zIndex = 999;

        const adjustElementSize = (element) => {
          if (element) {
            const elementComputedStyle = window.getComputedStyle(element);
            const width = parseFloat(elementComputedStyle.width);
            const height = parseFloat(elementComputedStyle.height);
            const newWidth = width * 0.65;
            const newHeight = height * 0.65;
            // console.log(element);
            // console.log(`width: ${width} -> ${newWidth}`);
            // console.log(`height: ${height} -> ${newHeight}`);
            element.style.width = newWidth + "px";
            element.style.height = newHeight + "px";
          }
        };

        const buttons = container.querySelectorAll("button");
        for (const button of buttons) {
          adjustElementSize(button);
        }

        window.addEventListener("resize", () => {
          const buttons = container.querySelectorAll("button");
          for (const button of buttons) {
            button.style.width = "";
            button.style.height = "";
            adjustElementSize(button);
          }
        });
      }).observe(this.player, observeConfig);
    }
  }

  const main = () => {
    if (!localStorage.getItem("nextup-ext")) {
      saveDefaultOptions();
    }

    const scriptInfo = getScriptInfo();
    updateOptionVersion(scriptInfo);

    const options = getOptions();
    let isFirstPlayer = true;

    new MutationObserver((_) => {
      const players = document.querySelectorAll(
        "[id*='dv-web-player']:not([data-detected-from-ext='true'])"
      );
      players.forEach((player) => {
        player.dataset.detectedFromExt = "true";
        new MutationObserver((_, observer) => {
          const video = player.querySelector("video");
          if (!video || !video.checkVisibility()) {
            return;
          }

          observer.disconnect();

          if (isFirstPlayer) {
            isFirstPlayer = false;
            createOptionDialog(scriptInfo.scriptVersion);
            addEventListenerForShortcutKey(options);
          }

          const hider = new ElementHider(player);

          try {
            hider.createOptionBtn();
          } catch (e) {
            console.log(e);
          }

          try {
            hider.hideSkipIntroBtn(options);
          } catch (e) {
            console.log(e);
          }

          try {
            hider.hideNextupCard(options);
          } catch (e) {
            console.log(e);
          }

          try {
            hider.hideRatingText(options);
          } catch (e) {
            console.log(e);
          }

          try {
            hider.preventsDarkening(options);
          } catch (e) {
            console.log(e);
          }

          try {
            hider.moveCenterButtonsToBottom(options);
          } catch (e) {
            console.log(e);
          }
        }).observe(player, observeConfig);
      });
    }).observe(document, observeConfig);
  };

  main();

})();