YouTube Defaulter

Set speed, quality, subtitles and volume as default globally or specialize for each channel

当前为 2024-08-24 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Defaulter
// @namespace    https://greasyfork.org/ru/users/901750-gooseob
// @version      1.10.2
// @description  Set speed, quality, subtitles and volume as default globally or specialize for each channel
// @author       GooseOb
// @license      MIT
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// ==/UserScript==

(function(){// index.ts
function debounce(callback, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = window.setTimeout(() => {
      callback.apply(this, args);
    }, delay);
  };
}
var translations = {
  "be-BY": {
    OPEN_SETTINGS: "Адкрыць дадатковыя налады",
    SUBTITLES: "Субтытры",
    SPEED: "Хуткасьць",
    CUSTOM_SPEED: "Свая хуткасьць",
    CUSTOM_SPEED_HINT: 'Калі вызначана, будзе выкарыстоўвацца замест "хуткасьць"',
    QUALITY: "Якасьць",
    VOLUME: "Гучнасьць, %",
    GLOBAL: "глябальна",
    LOCAL: "гэты канал",
    SHORTS: "Адкрываць shorts як звычайныя",
    NEW_TAB: "Адкрываць відэа ў новай картцы",
    COPY_SUBS: "Капіяваць субтытры ў поўнаэкранным, Ctrl+C",
    STANDARD_MUSIC_SPEED: "Звычайная хуткасьць на каналах музыкаў",
    ENHANCED_BITRATE: "Палепшаны бітрэйт (для карыстальнікаў Premium)",
    SAVE: "Захаваць",
    EXPORT: "Экспарт",
    IMPORT: "Імпарт"
  }
};
var text = {
  OPEN_SETTINGS: "Open additional settings",
  SUBTITLES: "Subtitles",
  SPEED: "Speed",
  CUSTOM_SPEED: "Custom speed",
  CUSTOM_SPEED_HINT: 'If defined, will be used instead of "speed"',
  QUALITY: "Quality",
  VOLUME: "Volume, %",
  GLOBAL: "global",
  LOCAL: "this channel",
  SHORTS: "Open shorts as a usual video",
  NEW_TAB: "Open videos in a new tab",
  COPY_SUBS: "Copy subtitles by Ctrl+C in fullscreen mode",
  STANDARD_MUSIC_SPEED: "Normal speed on artist channels",
  ENHANCED_BITRATE: "Quality: Enhanced bitrate (for Premium users)",
  SAVE: "Save",
  DEFAULT: "-",
  EXPORT: "Export",
  IMPORT: "Import",
  ...translations[document.documentElement.lang]
};
var cfgLocalStorage = localStorage["YTDefaulter"];
var cfg = cfgLocalStorage ? JSON.parse(cfgLocalStorage) : {
  _v: 4,
  global: {},
  channels: {},
  flags: {
    shortsToUsual: false,
    newTab: false,
    copySubs: false,
    standardMusicSpeed: false,
    enhancedBitrate: false
  }
};
var isDescendantOrTheSame = (child, parents) => {
  while (child !== null) {
    if (parents.includes(child))
      return true;
    child = child.parentNode;
  }
  return false;
};
var saveCfg = () => {
  const cfgCopy = { ...cfg };
  const channelsCfgCopy = { ...cfg.channels };
  outer:
    for (const key in channelsCfgCopy) {
      const channelCfg = channelsCfgCopy[key];
      if (channelCfg.subtitles)
        continue;
      for (const cfgKey in channelCfg)
        if (cfgKey !== "subtitles")
          continue outer;
      delete channelsCfgCopy[key];
    }
  cfgCopy.channels = channelsCfgCopy;
  localStorage["YTDefaulter"] = JSON.stringify(cfgCopy);
};
var updateValuesIn = (controls, cfgPart) => {
  controls["speed"].value = cfgPart["speed"] || text.DEFAULT;
  controls["customSpeed"].value = cfgPart["customSpeed"] || "";
  controls["quality"].value = cfgPart["quality"] || text.DEFAULT;
  controls["volume"].value = cfgPart["volume"] || "";
  controls["subtitles"].checked = cfgPart["subtitles"] || false;
};
var menuControls = {
  global: {
    ["speed"]: null,
    ["customSpeed"]: null,
    ["quality"]: null,
    ["volume"]: null,
    ["subtitles"]: null
  },
  thisChannel: {
    ["speed"]: null,
    ["customSpeed"]: null,
    ["quality"]: null,
    ["volume"]: null,
    ["subtitles"]: null
  },
  flags: {
    shortsToUsual: null,
    newTab: null,
    copySubs: null,
    standardMusicSpeed: null,
    enhancedBitrate: null
  },
  updateThisChannel() {
    updateValuesIn(this.thisChannel, channelConfig.current);
  },
  updateValues() {
    console.log(cfg.global, channelConfig.current, cfg.flags);
    console.log(this);
    updateValuesIn(this.global, cfg.global);
    this.updateThisChannel();
    for (const key in cfg.flags) {
      this.flags[key].checked = cfg.flags[key];
    }
  }
};
var updateCfg = () => {
  const doUpdate = cfg._v !== 4;
  if (doUpdate) {
    switch (cfg._v) {
      case 2:
        cfg.flags.standardMusicSpeed = false;
        cfg._v = 3;
      case 3:
        cfg.global.quality = cfg.global.qualityMax;
        delete cfg.global.qualityMax;
        for (const key in cfg.channels) {
          const currCfg = cfg.channels[key];
          currCfg.quality = currCfg.qualityMax;
          delete currCfg.qualityMax;
        }
        cfg._v = 4;
    }
    saveCfg();
  }
  return doUpdate;
};
updateCfg();
var restoreFocusAfter = (cb) => {
  const el = document.activeElement;
  cb();
  el.focus();
};
var until = (getItem, check, msToWait = 1e4, msReqTimeout = 20) => new Promise((res, rej) => {
  const reqLimit = msToWait / msReqTimeout;
  let i = 0;
  const interval = setInterval(() => {
    if (i++ > reqLimit)
      exit(rej);
    const item = getItem();
    if (!check(item))
      return;
    exit(() => res(item));
  }, msReqTimeout);
  const exit = (cb) => {
    clearInterval(interval);
    cb();
  };
});
var untilAppear = (getItem, msToWait) => until(getItem, Boolean, msToWait);
var ytSettingItems = {};
var channelConfig = { current: null };
var video;
var subtitlesBtn;
var muteBtn;
var SPEED_NORMAL;
var isSpeedChanged = false;
var menu = {
  element: null,
  btn: null,
  isOpen: false,
  width: 0,
  _closeListener: {
    onClick(e) {
      const el = e.target;
      if (isDescendantOrTheSame(el, [menu.element, menu.btn]))
        return;
      menu.toggle();
    },
    onKeyUp(e) {
      if (e.code !== "Escape")
        return;
      menu._setOpen(false);
      menu.btn.focus();
    },
    add() {
      document.addEventListener("click", this.onClick);
      document.addEventListener("keyup", this.onKeyUp);
    },
    remove() {
      document.removeEventListener("click", this.onClick);
      document.removeEventListener("keyup", this.onKeyUp);
    }
  },
  firstElement: null,
  _setOpen(bool) {
    if (bool) {
      this.fixPosition();
      this.element.style.visibility = "visible";
      this._closeListener.add();
      this.firstElement.focus();
    } else {
      this.element.style.visibility = "hidden";
      this._closeListener.remove();
    }
    this.isOpen = bool;
  },
  toggle: debounce(function() {
    this._setOpen(!this.isOpen);
  }, 100),
  fixPosition() {
    const { y, height, width, left } = this.btn.getBoundingClientRect();
    this.element.style.top = y + height + 8 + "px";
    this.element.style.left = left + width - this.width + "px";
  }
};
var $ = (id) => document.getElementById(id);
var getChannelUsername = (aboveTheFold) => /(?<=@|\/c\/).+?$/.exec(aboveTheFold.querySelector(".ytd-channel-name > a").href)?.[0];
var getPlr = () => $("movie_player");
var getAboveTheFold = () => $("above-the-fold");
var getActionsBar = () => $("actions")?.querySelector("ytd-menu-renderer");
var iconD = {
  ["quality"]: "M15,17h6v1h-6V17z M11,17H3v1h8v2h1v-2v-1v-2h-1V17z M14,8h1V6V5V3h-1v2H3v1h11V8z            M18,5v1h3V5H18z M6,14h1v-2v-1V9H6v2H3v1 h3V14z M10,12h11v-1H10V12z",
  ["speed"]: "M10,8v8l6-4L10,8L10,8z M6.3,5L5.7,4.2C7.2,3,9,2.2,11,2l0.1,1C9.3,3.2,7.7,3.9,6.3,5z            M5,6.3L4.2,5.7C3,7.2,2.2,9,2,11 l1,.1C3.2,9.3,3.9,7.7,5,6.3z            M5,17.7c-1.1-1.4-1.8-3.1-2-4.8L2,13c0.2,2,1,3.8,2.2,5.4L5,17.7z            M11.1,21c-1.8-0.2-3.4-0.9-4.8-2 l-0.6,.8C7.2,21,9,21.8,11,22L11.1,21z            M22,12c0-5.2-3.9-9.4-9-10l-0.1,1c4.6,.5,8.1,4.3,8.1,9s-3.5,8.5-8.1,9l0.1,1 C18.2,21.5,22,17.2,22,12z"
};
var getYtElementFinder = (elems) => (name) => findInNodeList(elems, (el) => !!el.querySelector(`path[d="${iconD[name]}"]`));
var untilChannelUsernameAppear = (aboveTheFold) => untilAppear(() => getChannelUsername(aboveTheFold)).catch(() => "");
var isMusicChannel = (aboveTheFold) => !!aboveTheFold.querySelector(".badge-style-type-verified-artist");
var findInNodeList = (list, callback) => {
  for (const item of list)
    if (callback(item))
      return item;
};
var ytMenu = {
  async updatePlayer(plr) {
    this.element = plr.querySelector(".ytp-settings-menu");
    this._btn = plr.querySelector(".ytp-settings-button");
    const clickBtn = this._btn.click.bind(this._btn);
    restoreFocusAfter(clickBtn);
    await delay(50);
    restoreFocusAfter(clickBtn);
  },
  element: null,
  _btn: null,
  isOpen() {
    return this.element.style.display !== "none";
  },
  setOpen(bool) {
    if (bool !== this.isOpen())
      this._btn.click();
  },
  openItem(item) {
    this.setOpen(true);
    item.click();
    return this.element.querySelectorAll(".ytp-panel-animate-forward .ytp-menuitem-label");
  },
  findInItem(item, callback) {
    return findInNodeList(this.openItem(item), callback);
  }
};
var validateVolume = (value) => {
  const num = +value;
  return num < 0 || num > 100 ? "out of range" : isNaN(num) ? "not a number" : false;
};
var getElCreator = (tag) => (props) => Object.assign(document.createElement(tag), props);
var comparators = {
  ["quality"]: (target, current) => +target >= parseInt(current) && (cfg.flags.enhancedBitrate || !current.toLowerCase().includes("premium")),
  ["speed"]: (target, current) => target === current
};
var logger = {
  prefix: "[YT-Defaulter]",
  err(...msgs) {
    console.error(this.prefix, ...msgs);
  },
  outOfRange(what) {
    this.err(what, "value is out of range");
  }
};
var valueSetters = {
  _ytSettingItem(value, settingName) {
    const isOpen = ytMenu.isOpen();
    const compare = comparators[settingName];
    ytMenu.findInItem(ytSettingItems[settingName], (btn) => compare(value, btn.textContent))?.click();
    ytMenu.setOpen(isOpen);
  },
  speed(value) {
    this._ytSettingItem(isSpeedChanged ? SPEED_NORMAL : value, "speed");
    isSpeedChanged = !isSpeedChanged;
  },
  customSpeed(value) {
    try {
      video.playbackRate = isSpeedChanged ? 1 : +value;
    } catch {
      logger.outOfRange("Custom speed");
      return;
    }
    isSpeedChanged = !isSpeedChanged;
  },
  subtitles(value) {
    if (subtitlesBtn.ariaPressed !== value.toString())
      subtitlesBtn.click();
  },
  volume(value) {
    const num = +value;
    muteBtn ||= document.querySelector(".ytp-mute-button");
    const isMuted = muteBtn.dataset.titleNoTooltip !== "Mute";
    if (num === 0) {
      if (!isMuted)
        muteBtn.click();
      return;
    }
    if (isMuted)
      muteBtn.click();
    try {
      video.volume = num / 100;
    } catch {
      logger.outOfRange("Volume");
    }
  },
  quality(value) {
    this._ytSettingItem(value, "quality");
  }
};
var div = getElCreator("div");
var input = getElCreator("input");
var checkbox = (props) => input({ type: "checkbox", ...props });
var option = getElCreator("option");
var _label = getElCreator("label");
var labelEl = (forId, props) => {
  const elem = _label(props);
  elem.setAttribute("for", forId);
  return elem;
};
var selectEl = getElCreator("select");
var btnClass = "yt-spec-button-shape-next";
var btnClassFocused = btnClass + "--focused";
var _button = getElCreator("button");
var button = (text2, props) => _button({
  textContent: text2,
  className: `${btnClass} ${btnClass}--tonal ${btnClass}--mono ${btnClass}--size-m`,
  onfocus() {
    this.classList.add(btnClassFocused);
  },
  onblur() {
    this.classList.remove(btnClassFocused);
  },
  ...props
});

class Hint {
  constructor(prefix, props) {
    this.element = div(props);
    this.element.className ||= "YTDef-setting-hint";
    this.prefix = prefix;
    this.hide();
  }
  hide() {
    this.element.style.display = "none";
  }
  show(msg) {
    this.element.style.display = "block";
    if (msg)
      this.element.textContent = this.prefix + msg;
  }
  prefix;
  element;
}
var delay = (ms) => new Promise((res) => setTimeout(res, ms));
var onPageChange = async () => {
  if (location.pathname !== "/watch")
    return;
  const aboveTheFold = await untilAppear(getAboveTheFold);
  const channelUsername = await untilChannelUsernameAppear(aboveTheFold);
  channelConfig.current = cfg.channels[channelUsername] ||= {};
  const plr = await untilAppear(getPlr);
  await delay(1000);
  const getAd = () => plr.querySelector(".ytp-ad-player-overlay");
  if (getAd())
    await until(getAd, (ad) => !ad, 200000);
  await ytMenu.updatePlayer(plr);
  const getMenuItems = () => ytMenu.element.querySelectorAll('.ytp-menuitem[role="menuitem"]');
  const getYtElement = getYtElementFinder(await until(getMenuItems, (arr) => !!arr.length));
  Object.assign(ytSettingItems, {
    quality: getYtElement("quality"),
    speed: getYtElement("speed")
  });
  if (!SPEED_NORMAL)
    restoreFocusAfter(() => {
      const btn = ytMenu.findInItem(ytSettingItems.speed, (btn2) => !+btn2.textContent);
      if (btn)
        SPEED_NORMAL = btn.textContent;
    });
  const doNotChangeSpeed = cfg.flags.standardMusicSpeed && isMusicChannel(aboveTheFold);
  const settings = {
    ...cfg.global,
    ...channelConfig.current
  };
  const isChannelSpeed = "speed" in channelConfig.current;
  const isChannelCustomSpeed = "customSpeed" in channelConfig.current;
  if (doNotChangeSpeed && !isChannelCustomSpeed || isChannelSpeed)
    delete settings.customSpeed;
  if (doNotChangeSpeed && !isChannelSpeed)
    settings.speed = SPEED_NORMAL;
  if (doNotChangeSpeed) {
    settings.speed = SPEED_NORMAL;
    delete settings.customSpeed;
  }
  const { customSpeed } = settings;
  delete settings.customSpeed;
  isSpeedChanged = false;
  video ||= plr.querySelector(".html5-main-video");
  subtitlesBtn ||= plr.querySelector(".ytp-subtitles-button");
  restoreFocusAfter(() => {
    for (const setting in settings)
      valueSetters[setting](settings[setting]);
    if (!isNaN(+customSpeed)) {
      isSpeedChanged = false;
      valueSetters.customSpeed(customSpeed);
    }
    ytMenu.setOpen(false);
  });
  if (menu.element) {
    menuControls.updateThisChannel();
    return;
  }
  menu.element = div({
    id: "YTDef-menu"
  });
  menu.btn = button("", {
    id: "YTDef-btn",
    ariaLabel: text.OPEN_SETTINGS,
    tabIndex: 0,
    onclick() {
      menu.toggle();
    }
  });
  const toOptions = (values, getText) => [
    option({
      value: text.DEFAULT,
      textContent: text.DEFAULT
    })
  ].concat(values.map((value) => option({
    value,
    textContent: getText(value)
  })));
  const speedValues = [
    "2",
    "1.75",
    "1.5",
    "1.25",
    SPEED_NORMAL,
    "0.75",
    "0.5",
    "0.25"
  ];
  const qualityValues = [
    "144",
    "240",
    "360",
    "480",
    "720",
    "1080",
    "1440",
    "2160",
    "4320"
  ];
  const createSection = (sectionId, title, sectionCfg) => {
    const section = div({ role: "group" });
    section.setAttribute("aria-labelledby", sectionId);
    const getLocalId = (name) => "YTDef-" + name + "-" + sectionId;
    const addItem = (name, innerHTML, elem) => {
      const item = div();
      const id = getLocalId(name);
      const label = labelEl(id, { innerHTML });
      const valueProp = elem.type === "checkbox" ? "checked" : "value";
      Object.assign(elem, {
        id,
        name,
        onchange() {
          const value = this[valueProp];
          if (value === "" || value === text.DEFAULT)
            delete sectionCfg[name];
          else
            sectionCfg[name] = value;
        }
      });
      const cfgValue = sectionCfg[name];
      if (cfgValue)
        setTimeout(() => {
          elem[valueProp] = cfgValue;
        });
      item.append(label, elem);
      section.append(item);
      menuControls[sectionId][name] = elem;
      if (elem.hint)
        section.append(elem.hint.element);
      return { elem };
    };
    const addSelectItem = (name, label, options, getText) => {
      const { elem } = addItem(name, label, selectEl({ value: text.DEFAULT }));
      elem.append(...toOptions(options, getText));
      return elem;
    };
    section.append(getElCreator("span")({ textContent: title, id: sectionId }));
    const firstElement = addSelectItem("speed", text.SPEED, speedValues, (val) => val);
    if (sectionId === "global")
      menu.firstElement = firstElement;
    addItem("customSpeed", text.CUSTOM_SPEED, input({
      type: "number",
      onfocus() {
        this.hint.show();
      },
      onblur() {
        this.hint.hide();
      },
      hint: new Hint("", { textContent: text.CUSTOM_SPEED_HINT })
    }));
    addSelectItem("quality", text.QUALITY, qualityValues, (val) => val + "p");
    addItem("volume", text.VOLUME, input({
      type: "number",
      min: "0",
      max: "100",
      oninput() {
        settings.volume = this.value;
        const warning = validateVolume(this.value);
        if (warning) {
          this.hint.show(warning);
        } else {
          this.hint.hide();
        }
      },
      hint: new Hint("Warning: ")
    }));
    addItem("subtitles", text.SUBTITLES, checkbox());
    return section;
  };
  const sections = div({ className: "YTDef-" + "sections" });
  sections.append(createSection("global", text.GLOBAL, cfg.global), createSection("thisChannel", text.LOCAL, channelConfig.current));
  const checkboxDiv = (id, prop, text2) => {
    const cont = div({ className: "check-cont" });
    id = "YTDef-" + id;
    const elem = checkbox({
      id,
      checked: cfg.flags[prop],
      onclick() {
        cfg.flags[prop] = this.checked;
      }
    });
    menuControls.flags[prop] = elem;
    cont.append(labelEl(id, { textContent: text2 }), elem);
    return cont;
  };
  const controlStatus = div();
  const updateControlStatus = (content) => {
    controlStatus.textContent = `[${new Date().toLocaleTimeString()}] ${content}`;
  };
  const controlDiv = div({ className: "control-cont" });
  controlDiv.append(button(text.SAVE, {
    onclick() {
      saveCfg();
      updateControlStatus(text.SAVE);
    }
  }), button(text.EXPORT, {
    onclick: () => {
      navigator.clipboard.writeText(localStorage["YTDefaulter"]).then(() => {
        updateControlStatus(text.EXPORT);
      });
    }
  }), button(text.IMPORT, {
    onclick: async () => {
      try {
        const raw = await navigator.clipboard.readText();
        const newCfg = JSON.parse(raw);
        if (typeof newCfg !== "object" || !newCfg._v) {
          throw new Error("Import: Invalid data");
        }
        if (!updateCfg()) {
          localStorage["YTDefaulter"] = raw;
          cfg = newCfg;
        }
        channelConfig.current = cfg.channels[channelUsername] ||= {};
      } catch (e) {
        updateControlStatus(e.message);
        return;
      }
      updateControlStatus(text.IMPORT);
      menuControls.updateValues();
    }
  }));
  menu.element.append(sections, checkboxDiv("shorts", "shortsToUsual", text.SHORTS), checkboxDiv("new-tab", "newTab", text.NEW_TAB), checkboxDiv("copy-subs", "copySubs", text.COPY_SUBS), checkboxDiv("standard-music-speed", "standardMusicSpeed", text.STANDARD_MUSIC_SPEED), checkboxDiv("enhanced-bitrate", "enhancedBitrate", text.ENHANCED_BITRATE), controlDiv, controlStatus);
  menu.element.addEventListener("keyup", (e) => {
    const el = e.target;
    if (e.code === "Enter" && el.type === "checkbox")
      el.checked = !el.checked;
  });
  const settingsIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  const iconStyle = {
    viewBox: "0 0 24 24",
    width: "24",
    height: "24",
    fill: "var(--yt-spec-text-primary)"
  };
  for (const key in iconStyle)
    settingsIcon.setAttribute(key, iconStyle[key]);
  settingsIcon.append($("settings"));
  menu.btn.setAttribute("aria-controls", "YTDef-menu");
  menu.btn.classList.add(btnClass + "--icon-button");
  menu.btn.append(settingsIcon);
  const actionsBar = await untilAppear(getActionsBar);
  actionsBar.insertBefore(menu.btn, actionsBar.lastChild);
  document.querySelector("ytd-popup-container").append(menu.element);
  menu.width = menu.element.getBoundingClientRect().width;
  sections.style.maxWidth = sections.offsetWidth + "px";
};
var lastHref;
setInterval(() => {
  if (lastHref === location.href)
    return;
  lastHref = location.href;
  setTimeout(onPageChange, 1000);
}, 1000);
var onClick = (e) => {
  const { shortsToUsual, newTab } = cfg.flags;
  if (!shortsToUsual && !newTab)
    return;
  let el = e.target;
  if (el.tagName !== "A") {
    el = el.closest("a");
    if (!el)
      return;
  }
  if (!/shorts\/|watch\?v=/.test(el.href))
    return;
  if (shortsToUsual)
    el.href = el.href.replace("shorts/", "watch?v=");
  if (newTab) {
    el.target = "_blank";
    e.stopPropagation();
  }
};
document.addEventListener("click", onClick, { capture: true });
document.addEventListener("keyup", (e) => {
  if (e.code === "Enter")
    return onClick(e);
  if (!e.ctrlKey || e.shiftKey)
    return;
  if (cfg.flags.copySubs && e.code === "KeyC") {
    const plr = document.querySelector(".html5-video-player");
    if (!plr?.classList.contains("ytp-fullscreen"))
      return;
    const text2 = Array.from(plr.querySelectorAll(".captions-text > span"), (line) => line.textContent).join(" ");
    navigator.clipboard.writeText(text2);
    return;
  }
  if (e.code !== "Space")
    return;
  e.stopPropagation();
  e.preventDefault();
  const customSpeedValue = channelConfig.current ? channelConfig.current.customSpeed || !channelConfig.current.speed && cfg.global.customSpeed : cfg.global.customSpeed;
  if (customSpeedValue)
    return valueSetters.customSpeed(customSpeedValue);
  restoreFocusAfter(() => {
    valueSetters["speed"]((channelConfig.current || cfg.global)["speed"]);
  });
}, { capture: true });
var listener = () => {
  if (menu.isOpen)
    menu.fixPosition();
};
window.addEventListener("scroll", listener);
window.addEventListener("resize", listener);
var m = "#" + "YTDef-menu";
var d = " div";
var i = " input";
var s = " select";
var bg = "var(--yt-spec-menu-background)";
var underline = "border-bottom: 2px solid var(--yt-spec-text-primary);";
document.head.append(getElCreator("style")({
  textContent: `
#${"YTDef-btn"} {position: relative; margin-left: 8px}
${m} {
display: flex;
visibility: hidden;
color: var(--yt-spec-text-primary);
font-size: 14px;
flex-direction: column;
position: fixed;
background: ${bg};
border-radius: 2rem;
padding: 1rem;
text-align: center;
box-shadow: 0px 4px 32px 0px var(--yt-spec-static-overlay-background-light);
z-index: 2202
}
.control-cont > button {margin: .2rem}
${m + d} {display: flex; margin-bottom: 1rem}
${m + d + d} {
flex-direction: column;
margin: 0 2rem
}
${m + d + d + d} {
flex-direction: row;
margin: 1rem 0
}
${m + s}, ${m + i} {
text-align: center;
background: ${bg};
border: none;
${underline}
color: inherit;
width: 5rem;
padding: 0;
margin-left: auto
}
${m} .${"YTDef-setting-hint"} {margin: 0; text-align: end}
${m + i} {outline: none}
${m + d + d + d}:focus-within > label, ${m} .check-cont:focus-within > label {${underline}}
${m} .check-cont {padding: 0 1rem}
${m + s} {appearance: none; outline: none}
${m} label {margin-right: 1.5rem; white-space: nowrap}
${m + i}::-webkit-outer-spin-button,
${m + i}::-webkit-inner-spin-button {-webkit-appearance: none; margin: 0}
${m + i}[type=number] {-moz-appearance: textfield}
${m + s}::-ms-expand {display: none}`
}));
})()