Video Control

Adjust HTML5 video playback speed and volume on webpages.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Video Control
// @namespace    https://example.local/video-control
// @version      1.2.0
// @description  Adjust HTML5 video playback speed and volume on webpages.
// @author       local
// @match        http://*/*
// @match        https://*/*
// @run-at       document-start
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function () {
  "use strict";

  const DEFAULT_SETTINGS = Object.freeze({
    speed: 1,
    volume: 100,
    collapsed: false,
    antiPause: true,
    siteMemory: false,
    siteProfiles: {}
  });

  const LIMITS = Object.freeze({
    minSpeed: 0.5,
    maxSpeed: 5,
    minVolume: 0,
    maxVolume: 100
  });

  const STORAGE_KEY = "video-control-settings";
  const BOOTSTRAP_INTERVAL_MS = 1000;
  const RESUME_WINDOW_MS = 8000;
  const controlledVideos = new WeakSet();
  const videoStates = new WeakMap();
  let settings = loadSettings();
  let host = null;
  let shadow = null;
  let ui = {};
  let pageIsBlurred = false;
  let lastUrl = window.location.href;
  let lastUserActivationAt = 0;
  let bootstrapTimer = null;
  let settingsSnapshot = "";

  function isTopFrame() {
    try {
      return window.top === window;
    } catch (error) {
      return true;
    }
  }

  function getSiteKey() {
    let hostname = "";

    if (!isTopFrame() && document.referrer) {
      try {
        hostname = new URL(document.referrer).hostname;
      } catch (error) {
        hostname = "";
      }
    }

    if (!hostname) {
      hostname = window.location.hostname;
    }

    return String(hostname || "local")
      .toLowerCase()
      .replace(/^www\./, "");
  }

  function isRecord(value) {
    return Boolean(value && typeof value === "object" && !Array.isArray(value));
  }

  function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }

  function normalizeSettings(nextSettings) {
    const speed = Number(nextSettings && nextSettings.speed);
    const volume = Number(nextSettings && nextSettings.volume);
    const siteProfiles = isRecord(nextSettings && nextSettings.siteProfiles)
      ? { ...nextSettings.siteProfiles }
      : {};

    return {
      speed: clamp(Number.isFinite(speed) ? speed : DEFAULT_SETTINGS.speed, LIMITS.minSpeed, LIMITS.maxSpeed),
      volume: clamp(Number.isFinite(volume) ? Math.round(volume) : DEFAULT_SETTINGS.volume, LIMITS.minVolume, LIMITS.maxVolume),
      collapsed: Boolean(nextSettings && nextSettings.collapsed),
      antiPause: nextSettings && Object.prototype.hasOwnProperty.call(nextSettings, "antiPause")
        ? Boolean(nextSettings.antiPause)
        : DEFAULT_SETTINGS.antiPause,
      siteMemory: Boolean(nextSettings && nextSettings.siteMemory),
      siteProfiles
    };
  }

  function normalizeProfile(profile, fallback) {
    const speed = Number(profile && profile.speed);
    const volume = Number(profile && profile.volume);

    return {
      speed: clamp(Number.isFinite(speed) ? speed : fallback.speed, LIMITS.minSpeed, LIMITS.maxSpeed),
      volume: clamp(Number.isFinite(volume) ? Math.round(volume) : fallback.volume, LIMITS.minVolume, LIMITS.maxVolume),
      antiPause: profile && Object.prototype.hasOwnProperty.call(profile, "antiPause")
        ? Boolean(profile.antiPause)
        : fallback.antiPause
    };
  }

  function applySiteProfile(baseSettings) {
    const normalized = normalizeSettings(baseSettings);

    if (!normalized.siteMemory) {
      return normalized;
    }

    const profile = normalized.siteProfiles[getSiteKey()];

    if (!profile) {
      return normalized;
    }

    return {
      ...normalized,
      ...normalizeProfile(profile, normalized)
    };
  }

  function readValue(key, fallback) {
    try {
      if (typeof GM_getValue === "function") {
        return GM_getValue(key, fallback);
      }
    } catch (error) {
      // Fall back to localStorage below.
    }

    try {
      const raw = window.localStorage.getItem(key);
      return raw ? JSON.parse(raw) : fallback;
    } catch (error) {
      return fallback;
    }
  }

  function writeValue(key, value) {
    try {
      if (typeof GM_setValue === "function") {
        GM_setValue(key, value);
        return;
      }
    } catch (error) {
      // Fall back to localStorage below.
    }

    try {
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      // Storage can be disabled on some pages.
    }
  }

  function loadSettings() {
    return applySiteProfile(readValue(STORAGE_KEY, DEFAULT_SETTINGS));
  }

  function saveSettings() {
    const storedSettings = normalizeSettings(readValue(STORAGE_KEY, DEFAULT_SETTINGS));
    const nextSettings = {
      ...storedSettings,
      speed: settings.speed,
      volume: settings.volume,
      collapsed: settings.collapsed,
      antiPause: settings.antiPause,
      siteMemory: settings.siteMemory,
      siteProfiles: { ...storedSettings.siteProfiles }
    };

    if (settings.siteMemory) {
      nextSettings.siteProfiles[getSiteKey()] = {
        speed: settings.speed,
        volume: settings.volume,
        antiPause: settings.antiPause
      };
    }

    writeValue(STORAGE_KEY, nextSettings);
    settingsSnapshot = JSON.stringify(applySiteProfile(nextSettings));
  }

  function refreshSettingsFromStorage() {
    const nextSettings = loadSettings();
    const nextSnapshot = JSON.stringify(nextSettings);

    if (nextSnapshot === settingsSnapshot) {
      return false;
    }

    settings = nextSettings;
    settingsSnapshot = nextSnapshot;
    render();
    return true;
  }

  function formatSpeed(speed) {
    return `${speed.toFixed(2).replace(/\.?0+$/, "")}x`;
  }

  function isVideoElement(node) {
    return Boolean(
      node
      && node.nodeType === Node.ELEMENT_NODE
      && String(node.tagName).toLowerCase() === "video"
      && typeof node.play === "function"
    );
  }

  function applySettingsToVideo(video) {
    try {
      video.playbackRate = settings.speed;
      video.defaultPlaybackRate = settings.speed;
    } catch (error) {
      // Some protected players reject playback-rate changes.
    }

    try {
      video.volume = settings.volume / 100;
      video.muted = settings.volume === 0;
    } catch (error) {
      // Keep applying settings to other videos if this one is restricted.
    }
  }

  function getVideoState(video) {
    let state = videoStates.get(video);

    if (!state) {
      state = {
        wasPlaying: false,
        lastPlayingAt: 0,
        resumeTimer: null,
        resumeAttempts: 0
      };
      videoStates.set(video, state);
    }

    return state;
  }

  function isPageInactive() {
    return pageIsBlurred || document.hidden || document.visibilityState === "hidden" || !document.hasFocus();
  }

  function rememberUserActivation() {
    lastUserActivationAt = Date.now();
  }

  function canAutoResume(video) {
    const state = getVideoState(video);
    const wasRecentlyPlaying = state.wasPlaying || Date.now() - state.lastPlayingAt < RESUME_WINDOW_MS;
    const userJustInteracted = Date.now() - lastUserActivationAt < 600;

    return settings.antiPause
      && wasRecentlyPlaying
      && !userJustInteracted
      && isPageInactive()
      && !video.ended
      && video.readyState > 0;
  }

  function tryResume(video) {
    if (!settings.antiPause || !video.paused || video.ended) {
      return;
    }

    applySettingsToVideo(video);

    try {
      const result = video.play();
      if (result && typeof result.catch === "function") {
        result.catch(() => {
          // Browsers can reject play() without a prior user gesture.
        });
      }
    } catch (error) {
      // Some players block programmatic playback.
    }
  }

  function scheduleResume(video) {
    if (!canAutoResume(video)) {
      return;
    }

    const state = getVideoState(video);
    window.clearTimeout(state.resumeTimer);
    state.resumeAttempts = Math.min(state.resumeAttempts + 1, 6);

    state.resumeTimer = window.setTimeout(() => {
      if (canAutoResume(video)) {
        tryResume(video);
      }
    }, state.resumeAttempts * 120);
  }

  function bindVideo(video) {
    if (!isVideoElement(video)) {
      return;
    }

    applySettingsToVideo(video);

    if (controlledVideos.has(video)) {
      return;
    }

    controlledVideos.add(video);
    video.addEventListener("loadedmetadata", () => applySettingsToVideo(video));
    video.addEventListener("playing", () => {
      const state = getVideoState(video);
      state.wasPlaying = true;
      state.lastPlayingAt = Date.now();
      state.resumeAttempts = 0;
      applySettingsToVideo(video);
    });
    video.addEventListener("play", () => {
      const state = getVideoState(video);
      state.wasPlaying = true;
      state.lastPlayingAt = Date.now();
      applySettingsToVideo(video);
    });
    video.addEventListener("pause", () => {
      scheduleResume(video);
    }, true);
    video.addEventListener("ratechange", () => {
      if (video.playbackRate !== settings.speed) {
        window.requestAnimationFrame(() => applySettingsToVideo(video));
      }
    });
    video.addEventListener("volumechange", () => {
      const expectedVolume = settings.volume / 100;
      if (Math.abs(video.volume - expectedVolume) > 0.001 || video.muted !== (settings.volume === 0)) {
        window.requestAnimationFrame(() => applySettingsToVideo(video));
      }
    });
  }

  function scanVideos(root) {
    if (!root) {
      return;
    }

    if (isVideoElement(root)) {
      bindVideo(root);
      return;
    }

    if (typeof root.querySelectorAll === "function") {
      root.querySelectorAll("video").forEach(bindVideo);
      root.querySelectorAll("*").forEach((element) => {
        if (element.shadowRoot) {
          scanVideos(element.shadowRoot);
        }
      });

      root.querySelectorAll("iframe").forEach((iframe) => {
        try {
          if (iframe.contentDocument) {
            scanVideos(iframe.contentDocument);
          }
        } catch (error) {
          // Cross-origin frames are handled by their own userscript instance.
        }
      });
    }
  }

  function applyToPage() {
    scanVideos(document);
  }

  function updateSettings(partial) {
    settings = normalizeSettings({ ...settings, ...partial });
    saveSettings();
    render();
    applyToPage();
  }

  function toggleAntiPause() {
    updateSettings({ antiPause: !settings.antiPause });
  }

  function toggleSiteMemory() {
    updateSettings({ siteMemory: !settings.siteMemory });
  }

  function setCollapsed(collapsed) {
    updateSettings({ collapsed });
  }

  function createUi() {
    if (!isTopFrame()) {
      return;
    }

    if (host) {
      if (!document.documentElement.contains(host)) {
        document.documentElement.appendChild(host);
      }
      return;
    }

    host = document.createElement("div");
    host.id = "video-control-userscript";
    host.style.position = "fixed";
    host.style.right = "max(12px, env(safe-area-inset-right))";
    host.style.bottom = "max(12px, env(safe-area-inset-bottom))";
    host.style.zIndex = "2147483647";

    shadow = host.attachShadow({ mode: "closed" });
    shadow.innerHTML = `
      <style>
        :host {
          color-scheme: light dark;
          font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
          -webkit-tap-highlight-color: transparent;
        }

        * {
          box-sizing: border-box;
        }

        button,
        input {
          font: inherit;
        }

        .panel {
          width: min(300px, calc(100vw - 24px));
          border: 1px solid rgba(15, 23, 42, 0.14);
          border-radius: 8px;
          background: rgba(255, 255, 255, 0.96);
          color: #172033;
          box-shadow: 0 14px 40px rgba(15, 23, 42, 0.2);
          backdrop-filter: blur(10px);
          overflow: hidden;
        }

        .panel.is-collapsed {
          display: none;
        }

        .mini {
          display: none;
          min-width: 52px;
          min-height: 44px;
          border: 1px solid rgba(15, 23, 42, 0.16);
          border-radius: 999px;
          padding: 0 12px;
          background: #0f766e;
          color: #ffffff;
          box-shadow: 0 10px 30px rgba(15, 23, 42, 0.24);
          font-size: 13px;
          font-weight: 750;
          cursor: pointer;
          touch-action: manipulation;
        }

        .mini.is-visible {
          display: inline-flex;
          align-items: center;
          justify-content: center;
        }

        .header {
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 8px;
          padding: 10px 12px;
          border-bottom: 1px solid rgba(15, 23, 42, 0.1);
        }

        .title {
          margin: 0;
          font-size: 14px;
          font-weight: 750;
        }

        .hide {
          min-width: 36px;
          min-height: 34px;
          border: 1px solid rgba(15, 23, 42, 0.16);
          border-radius: 6px;
          background: #ffffff;
          color: #172033;
          cursor: pointer;
          touch-action: manipulation;
        }

        .body {
          display: grid;
          gap: 12px;
          padding: 12px;
        }

        .control {
          display: grid;
          gap: 8px;
        }

        .switch-row {
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 12px;
          min-height: 34px;
          font-size: 13px;
          font-weight: 650;
        }

        .switch-row input {
          width: 20px;
          height: 20px;
          accent-color: #0f766e;
        }

        .switches {
          display: grid;
          gap: 6px;
        }

        .row {
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 12px;
          font-size: 13px;
          font-weight: 650;
        }

        output {
          color: #0f766e;
          font-variant-numeric: tabular-nums;
          font-weight: 800;
        }

        input[type="range"] {
          width: 100%;
          min-height: 28px;
          accent-color: #0f766e;
        }

        .presets {
          display: grid;
          grid-template-columns: repeat(4, minmax(0, 1fr));
          gap: 6px;
        }

        .presets button {
          min-height: 34px;
          border: 1px solid rgba(15, 23, 42, 0.16);
          border-radius: 6px;
          background: #f8fafc;
          color: #172033;
          font-size: 12px;
          cursor: pointer;
          touch-action: manipulation;
        }

        .presets button:active,
        .hide:active,
        .mini:active {
          transform: translateY(1px);
        }

        @media (max-width: 520px) {
          .panel {
            width: min(280px, calc(100vw - 20px));
          }

          .body {
            gap: 10px;
            padding: 10px;
          }

          .presets {
            grid-template-columns: repeat(3, minmax(0, 1fr));
          }
        }

        @media (prefers-color-scheme: dark) {
          .panel {
            border-color: rgba(226, 232, 240, 0.16);
            background: rgba(23, 27, 34, 0.96);
            color: #edf2f7;
            box-shadow: 0 14px 40px rgba(0, 0, 0, 0.34);
          }

          .header {
            border-bottom-color: rgba(226, 232, 240, 0.12);
          }

          .hide,
          .presets button {
            border-color: rgba(226, 232, 240, 0.16);
            background: #222936;
            color: #edf2f7;
          }

          output {
            color: #5eead4;
          }
        }
      </style>

      <button class="mini" type="button" aria-label="显示视频控制">VC</button>

      <section class="panel" aria-label="视频控制">
        <header class="header">
          <h2 class="title">视频控制</h2>
          <button class="hide" type="button" aria-label="隐藏视频控制">隐藏</button>
        </header>

        <div class="body">
          <div class="control">
            <div class="row">
              <label for="vc-speed">倍速</label>
              <output id="vc-speed-value" for="vc-speed">1x</output>
            </div>
            <input id="vc-speed" type="range" min="0.5" max="5" step="0.05" value="1">
            <div class="presets" aria-label="常用倍速">
              <button type="button" data-speed="0.5">0.5x</button>
              <button type="button" data-speed="1">1x</button>
              <button type="button" data-speed="1.5">1.5x</button>
              <button type="button" data-speed="2">2x</button>
              <button type="button" data-speed="3">3x</button>
              <button type="button" data-speed="5">5x</button>
            </div>
          </div>

          <div class="control">
            <div class="row">
              <label for="vc-volume">音量</label>
              <output id="vc-volume-value" for="vc-volume">100%</output>
            </div>
            <input id="vc-volume" type="range" min="0" max="100" step="1" value="100">
            <div class="presets" aria-label="常用音量">
              <button type="button" data-volume="0">0%</button>
              <button type="button" data-volume="5">5%</button>
              <button type="button" data-volume="8">8%</button>
              <button type="button" data-volume="10">10%</button>
              <button type="button" data-volume="25">25%</button>
              <button type="button" data-volume="50">50%</button>
              <button type="button" data-volume="100">100%</button>
            </div>
          </div>

          <div class="switches">
            <label class="switch-row" for="vc-site-memory">
              <span>本站记忆</span>
              <input id="vc-site-memory" type="checkbox">
            </label>

            <label class="switch-row" for="vc-anti-pause">
              <span>防失焦暂停</span>
              <input id="vc-anti-pause" type="checkbox">
            </label>
          </div>
        </div>
      </section>
    `;

    ui = {
      panel: shadow.querySelector(".panel"),
      mini: shadow.querySelector(".mini"),
      hide: shadow.querySelector(".hide"),
      speed: shadow.getElementById("vc-speed"),
      speedValue: shadow.getElementById("vc-speed-value"),
      volume: shadow.getElementById("vc-volume"),
      volumeValue: shadow.getElementById("vc-volume-value"),
      antiPause: shadow.getElementById("vc-anti-pause"),
      siteMemory: shadow.getElementById("vc-site-memory")
    };

    ui.speed.addEventListener("input", () => updateSettings({ speed: Number(ui.speed.value) }));
    ui.volume.addEventListener("input", () => updateSettings({ volume: Number(ui.volume.value) }));
    ui.antiPause.addEventListener("change", () => updateSettings({ antiPause: ui.antiPause.checked }));
    ui.siteMemory.addEventListener("change", () => updateSettings({ siteMemory: ui.siteMemory.checked }));
    ui.hide.addEventListener("click", () => setCollapsed(true));
    ui.mini.addEventListener("click", () => setCollapsed(false));

    shadow.querySelectorAll("[data-speed]").forEach((button) => {
      button.addEventListener("click", () => updateSettings({ speed: Number(button.dataset.speed) }));
    });

    shadow.querySelectorAll("[data-volume]").forEach((button) => {
      button.addEventListener("click", () => updateSettings({ volume: Number(button.dataset.volume) }));
    });

    document.documentElement.appendChild(host);
    render();
  }

  function render() {
    if (!ui.panel) {
      return;
    }

    ui.speed.value = String(settings.speed);
    ui.speedValue.value = formatSpeed(settings.speed);
    ui.volume.value = String(settings.volume);
    ui.volumeValue.value = `${settings.volume}%`;
    ui.antiPause.checked = settings.antiPause;
    ui.siteMemory.checked = settings.siteMemory;
    ui.panel.classList.toggle("is-collapsed", settings.collapsed);
    ui.mini.classList.toggle("is-visible", settings.collapsed);
  }

  function watchForVideos() {
    const observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        mutation.addedNodes.forEach(scanVideos);
      }
    });

    observer.observe(document.documentElement || document, {
      childList: true,
      subtree: true
    });
  }

  function installPageGuards() {
    const markBlurred = () => {
      pageIsBlurred = true;
      window.setTimeout(applyToPage, 0);
      window.setTimeout(resumePausedVideos, 160);
    };
    const markFocused = () => {
      pageIsBlurred = false;
      applyToPage();
    };

    ["pointerdown", "mousedown", "touchstart", "keydown"].forEach((eventName) => {
      window.addEventListener(eventName, rememberUserActivation, true);
      document.addEventListener(eventName, rememberUserActivation, true);
    });

    window.addEventListener("blur", markBlurred, true);
    window.addEventListener("focus", markFocused, true);
    document.addEventListener("visibilitychange", () => {
      if (document.hidden || document.visibilityState === "hidden") {
        markBlurred();
      } else {
        markFocused();
      }
    }, true);

    ["mouseleave", "mouseout", "pointerleave"].forEach((eventName) => {
      document.addEventListener(eventName, () => {
        if (!document.hasFocus()) {
          markBlurred();
        }
      }, true);
    });

    patchPauseMethod();
  }

  function patchPauseMethod() {
    const descriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, "pause");
    if (!descriptor || typeof descriptor.value !== "function" || descriptor.value.__videoControlPatched) {
      return;
    }

    const nativePause = descriptor.value;
    const patchedPause = function () {
      if (isVideoElement(this) && canAutoResume(this)) {
        scheduleResume(this);
        return undefined;
      }

      return nativePause.apply(this, arguments);
    };

    patchedPause.__videoControlPatched = true;

    try {
      Object.defineProperty(HTMLMediaElement.prototype, "pause", {
        ...descriptor,
        value: patchedPause
      });
    } catch (error) {
      // Some browsers/pages prevent prototype patching.
    }
  }

  function resumePausedVideos() {
    const videos = [];
    scanVideos(document);
    document.querySelectorAll("video").forEach((video) => videos.push(video));

    videos.forEach((video) => {
      if (canAutoResume(video)) {
        tryResume(video);
      }
    });
  }

  function installHistoryGuards() {
    const scheduleAfterNavigation = () => {
      window.setTimeout(bootstrap, 80);
      window.setTimeout(bootstrap, 500);
      window.setTimeout(bootstrap, 1500);
    };

    ["pushState", "replaceState"].forEach((methodName) => {
      const original = history[methodName];
      if (typeof original !== "function" || original.__videoControlPatched) {
        return;
      }

      history[methodName] = function () {
        const result = original.apply(this, arguments);
        scheduleAfterNavigation();
        return result;
      };
      history[methodName].__videoControlPatched = true;
    });

    window.addEventListener("popstate", scheduleAfterNavigation, true);
    window.addEventListener("hashchange", scheduleAfterNavigation, true);
  }

  function bootstrap() {
    if (!document.documentElement) {
      return;
    }

    const changed = refreshSettingsFromStorage();
    createUi();
    applyToPage();

    if (changed) {
      window.setTimeout(applyToPage, 80);
    }

    if (lastUrl !== window.location.href) {
      lastUrl = window.location.href;
      window.setTimeout(applyToPage, 300);
      window.setTimeout(applyToPage, 1200);
    }
  }

  function startBootstrapLoop() {
    window.clearInterval(bootstrapTimer);
    bootstrapTimer = window.setInterval(bootstrap, BOOTSTRAP_INTERVAL_MS);
    bootstrap();
  }

  function registerMenuCommands() {
    if (typeof GM_registerMenuCommand !== "function") {
      return;
    }

    GM_registerMenuCommand("显示/隐藏视频控制", () => {
      setCollapsed(!settings.collapsed);
    });

    GM_registerMenuCommand("开启/关闭防失焦暂停", () => {
      toggleAntiPause();
    });

    GM_registerMenuCommand("开启/关闭本站记忆", () => {
      toggleSiteMemory();
    });

    GM_registerMenuCommand("恢复默认视频设置", () => {
      updateSettings(DEFAULT_SETTINGS);
    });
  }

  installPageGuards();
  installHistoryGuards();
  startBootstrapLoop();
  watchForVideos();
  if (isTopFrame()) {
    registerMenuCommands();
  }
})();