YouTube - Force rounded corners + tweaks included

This script forces the rounded version of the layout (which includes some fewer tweaks applied which also improves bugs).

目前為 2024-08-14 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YouTube - Force rounded corners + tweaks included
// @version      2024.08.14
// @description  This script forces the rounded version of the layout (which includes some fewer tweaks applied which also improves bugs).
// @author       Joey_JTS (original author: Magma_Craft)
// @license MIT
// @match        *://www.youtube.com/*
// @namespace    https://gf.qytechs.cn/en/users/933798
// @icon         https://www.youtube.com/favicon.ico
// @run-at       document-start
// @grant        none
// ==/UserScript==

// Attributes to remove from <html>
const ATTRS = [
    "darker-dark-theme",
    "darker-dark-theme-deprecate"
];

// Regular config keys.
const CONFIGS = {
    BUTTON_REWORK: true
}

// Experiment flags.
const EXPFLAGS = {
    /* Force rounded corners */
    web_button_rework: true,
    web_button_rework_with_live: true,
    web_darker_dark_theme: true,
    web_filled_subscribed_button: true,
    web_guide_ui_refresh: true,
    web_modern_ads: true,
    web_modern_buttons: true,
    web_modern_chips: true,
    web_modern_dialogs: true,
    web_modern_playlists: true,
    web_modern_subscribe: true,
    web_rounded_containers: true,
    web_rounded_thumbnails: true,
    web_searchbar_style: "rounded_corner_borders_light_btn",
    web_segmented_like_dislike_button: true,
    web_sheets_ui_refresh: true,
    web_snackbar_ui_refresh: true,
    /* Force rounded watch layout and few tweaks to be included (such as disabling the useless 'watch grid' UI */
    kevlar_watch_metadata_refresh: true,
    kevlar_watch_metadata_refresh_no_old_secondary_data: false,
    kevlar_watch_metadata_refresh_attached_subscribe: true,
    kevlar_watch_metadata_refresh_clickable_description: true,
    kevlar_watch_metadata_refresh_compact_view_count: true,
    kevlar_watch_metadata_refresh_description_info_dedicated_line: true,
    kevlar_watch_metadata_refresh_description_inline_expander: true,
    kevlar_watch_metadata_refresh_description_primary_color: true,
    kevlar_watch_metadata_refresh_for_live_killswitch: true,
    kevlar_watch_metadata_refresh_full_width_description: true,
    kevlar_watch_metadata_refresh_narrower_item_wrap: true,
    kevlar_watch_metadata_refresh_relative_date: true,
    kevlar_watch_metadata_refresh_top_aligned_actions: true,
    kevlar_watch_modern_metapanel: true,
    kevlar_watch_modern_panels: true,
    kevlar_watch_panel_height_matches_player: true,
    kevlar_watch_grid: false,
    kevlar_watch_grid_hide_chips: false,
    small_avatars_for_comments: false,
    small_avatars_for_comments_ep: false,
    web_watch_compact_comments: false,
    web_watch_compact_comments_ep: false,
    web_watch_theater_chat: false,
    web_watch_theater_fixed_chat: false,
    live_chat_over_engagement_panels: false,
    live_chat_scaled_height: false,
    live_chat_smaller_min_height: false,
    wn_grid_max_item_width: 0,
    wn_grid_min_item_width: 0,
    kevlar_watch_flexy_metadata_height: "136",
    kevlar_watch_max_player_width: "1280",
    web_watch_rounded_player_large: false,
    kevlar_watch_cinematics: false,
    /* Additional tweaks (which includes disabling animations except for the like and subscribe button rainbow overlays) */
    kevlar_refresh_on_theme_change: false,
    smartimation_background: false,
    web_animated_actions: false,
    web_animated_like: false,
    web_animated_like_lazy_load: false
}

// Player flags
// !!! USE STRINGS FOR VALUES !!!
// For example: "true" instead of true
const PLYRFLAGS = {
    web_rounded_containers: "true",
    web_rounded_thumbnails: "true"
}

class YTP {
    static observer = new MutationObserver(this.onNewScript);

    static _config = {};

    static isObject(item) {
        return (item && typeof item === "object" && !Array.isArray(item));
    }

    static mergeDeep(target, ...sources) {
        if (!sources.length) return target;
        const source = sources.shift();

        if (this.isObject(target) && this.isObject(source)) {
            for (const key in source) {
                if (this.isObject(source[key])) {
                    if (!target[key]) Object.assign(target, { [key]: {} });
                    this.mergeDeep(target[key], source[key]);
                } else {
                    Object.assign(target, { [key]: source[key] });
                }
            }
        }

        return this.mergeDeep(target, ...sources);
    }


    static onNewScript(mutations) {
        for (var mut of mutations) {
            for (var node of mut.addedNodes) {
                YTP.bruteforce();
            }
        }
    }

    static start() {
        this.observer.observe(document, {childList: true, subtree: true});
    }

    static stop() {
        this.observer.disconnect();
    }

    static bruteforce() {
        if (!window.yt) return;
        if (!window.yt.config_) return;

        this.mergeDeep(window.yt.config_, this._config);
    }

    static setCfg(name, value) {
        this._config[name] = value;
    }

    static setCfgMulti(configs) {
        this.mergeDeep(this._config, configs);
    }

    static setExp(name, value) {
        if (!("EXPERIMENT_FLAGS" in this._config)) this._config.EXPERIMENT_FLAGS = {};

        this._config.EXPERIMENT_FLAGS[name] = value;
    }

    static setExpMulti(exps) {
        if (!("EXPERIMENT_FLAGS" in this._config)) this._config.EXPERIMENT_FLAGS = {};

        this.mergeDeep(this._config.EXPERIMENT_FLAGS, exps);
    }

    static decodePlyrFlags(flags) {
        var obj = {},
            dflags = flags.split("&");

        for (var i = 0; i < dflags.length; i++) {
            var dflag = dflags[i].split("=");
            obj[dflag[0]] = dflag[1];
        }

        return obj;
    }

    static encodePlyrFlags(flags) {
        var keys = Object.keys(flags),
            response = "";

        for (var i = 0; i < keys.length; i++) {
            if (i > 0) {
                response += "&";
            }
            response += keys[i] + "=" + flags[keys[i]];
        }

        return response;
    }

    static setPlyrFlags(flags) {
        if (!window.yt) return;
        if (!window.yt.config_) return;
        if (!window.yt.config_.WEB_PLAYER_CONTEXT_CONFIGS) return;
        var conCfgs = window.yt.config_.WEB_PLAYER_CONTEXT_CONFIGS;
        if (!("WEB_PLAYER_CONTEXT_CONFIGS" in this._config)) this._config.WEB_PLAYER_CONTEXT_CONFIGS = {};

        for (var cfg in conCfgs) {
            var dflags = this.decodePlyrFlags(conCfgs[cfg].serializedExperimentFlags);
            this.mergeDeep(dflags, flags);
            this._config.WEB_PLAYER_CONTEXT_CONFIGS[cfg] = {
                serializedExperimentFlags: this.encodePlyrFlags(dflags)
            }
        }
    }
}

window.addEventListener("yt-page-data-updated", function tmp() {
    YTP.stop();
    for (i = 0; i < ATTRS.length; i++) {
        document.getElementsByTagName("html")[0].removeAttribute(ATTRS[i]);
    }
    window.removeEventListener("yt-page-date-updated", tmp);
});

YTP.start();

YTP.setCfgMulti(CONFIGS);
YTP.setExpMulti(EXPFLAGS);
YTP.setPlyrFlags(PLYRFLAGS);

function $(q) {
    return document.querySelector(q);
}

(function() {
let css = `
    /* Add rounded corners under the player (except for seek previews) */
    div#ytp-id-17.ytp-popup.ytp-settings-menu,
    div#ytp-id-18.ytp-popup.ytp-settings-menu {
    border-radius: 12px !important
    }

    div.branding-context-container-inner.ytp-rounded-branding-context {
    border-radius: 8px !important
    }

    .iv-card {
    border-radius: 8px !important
    }

    .ytp-ad-overlay-container.ytp-overlay-ad .ytp-ad-overlay-image img, .ytp-ad-overlay-container.ytp-overlay-ad .ytp-ad-text-overlay, .ytp-ad-overlay-container.ytp-overlay-ad .ytp-ad-enhanced-overlay {
    border-radius: 8px !important
    }

    .ytp-tooltip.ytp-text-detail.ytp-preview .ytp-tooltip-bg {
    border-top-left-radius: 12px !important;
    border-bottom-left-radius: 12px !important
    }

    .ytp-tooltip.ytp-text-detail.ytp-preview {
    border-radius: 12px !important
    }

    .ytp-ce-video.ytp-ce-medium, .ytp-ce-playlist.ytp-ce-medium, .ytp-ce-medium .ytp-ce-expanding-overlay-background {
    border-radius: 8px !important
    }

    .ytp-autonav-endscreen-upnext-thumbnail {
    border-radius: 8px !important
    }

    .ytp-autonav-endscreen-upnext-button {
    border-radius: 18px !important
    }

    .ytp-videowall-still-image {
    border-radius: 8px !important
    }

    .ytp-sb-subscribe, .ytp-sb-unsubscribe {
    border-radius: 18px !important
    }

    .ytp-tooltip.ytp-rounded-tooltip.ytp-preview:not(.ytp-text-detail),
    .ytp-tooltip.ytp-rounded-tooltip.ytp-preview:not(.ytp-text-detail) .ytp-tooltip-bg {
      border-radius: 2px !important
    }

    /* Watch page tweaks (including the 'Revert video list' CSS) */
    ytd-watch-flexy[rounded-player-large]:not([fullscreen]):not([theater]) #ytd-player.ytd-watch-flexy {
      border-radius: 0px !important
    }

    #actions.ytd-watch-metadata {
      min-width: auto !important;
    }

    ytd-watch-flexy[default-layout][reduced-top-margin] #primary.ytd-watch-flexy, ytd-watch-flexy[default-layout][reduced-top-margin] #secondary.ytd-watch-flexy {
      padding-top: var(--ytd-margin-6x) !important
    }

    ytd-watch-metadata[title-headline-xs] h1.ytd-watch-metadata, ytd-watch-metadata[title-headline-m] h1.ytd-watch-metadata {
      font-family: "YouTube Sans","Roboto",sans-serif !important;
      font-weight: 600 !important;
      font-size: 2rem !important;
      line-height: 2.8rem !important
    }

    ytd-watch-flexy #comment-teaser.ytd-watch-metadata {
      display: none;
    }

    ytd-watch-flexy #dismissible.ytd-rich-grid-media {
      flex-direction: row;
    }

    ytd-watch-flexy #attached-survey.ytd-rich-grid-media,
    ytd-watch-flexy #avatar-link.ytd-rich-grid-media {
      display: none;
    }

    ytd-watch-flexy ytd-thumbnail.ytd-rich-grid-media,
    ytd-watch-flexy ytd-playlist-thumbnail.ytd-rich-grid-media {
      margin-right: 8px;
      height: 94px;
      width: 168px;
    }

    ytd-watch-flexy ytd-thumbnail[size=large] a.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large]:before,
    ytd-watch-flexy ytd-thumbnail[size=large][large-margin] a.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large][large-margin]:before {
      border-radius: 8px;
    }

    ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-button-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-toggle-button-renderer.ytd-thumbnail,
    ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-button-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-toggle-button-renderer.ytd-thumbnail {
      margin: 4px;
    }

    ytd-watch-flexy ytd-rich-item-renderer,
    ytd-watch-flexy ytd-rich-grid-row #contents.ytd-rich-grid-row {
      margin: 0;
    }

    ytd-watch-flexy ytd-rich-item-renderer[reduced-bottom-margin] {
      margin-top: 8px;
      margin-bottom: 0;
    }

    ytd-watch-flexy ytd-rich-grid-renderer[reduced-top-margin] #contents.ytd-rich-grid-renderer {
      padding-top: 0px;
    }

    ytd-watch-flexy ytd-rich-grid-media {
      margin-bottom: 8px;
    }

    ytd-watch-flexy #details.ytd-rich-grid-media {
      width: 100%;
      min-width: 0;
    }

    ytd-watch-flexy ytd-video-meta-block[rich-meta] #metadata-line.ytd-video-meta-block,
    ytd-watch-flexy #channel-name.ytd-video-meta-block {
      font-family: "Roboto", "Arial", sans-serif;
      font-size: 1.2rem;
      line-height: 1.8rem;
      font-weight: 400;
    }

    ytd-watch-flexy #video-title.ytd-rich-grid-media {
      margin: 0 0 4px 0;
      display: block;
      font-family: "Roboto", "Arial", sans-serif;
      font-size: 1.4rem;
      line-height: 2rem;
      font-weight: 500;
      overflow: hidden;
      display: block;
      max-height: 4rem;
      -webkit-line-clamp: 2;
      display: box;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      text-overflow: ellipsis;
      white-space: normal;
    }

    ytd-watch-flexy h3.ytd-rich-grid-media {
      margin: 0;
    }

    ytd-watch-flexy .title-badge.ytd-rich-grid-media, ytd-watch-flexy .video-badge.ytd-rich-grid-media {
      margin-top: 0;
    }

    ytd-watch-flexy ytd-rich-section-renderer.style-scope.ytd-rich-grid-renderer {
      display: none;
    }

    ytd-watch-flexy ytd-rich-grid-renderer[hide-chips-bar] ytd-feed-filter-chip-bar-renderer.ytd-rich-grid-renderer, ytd-watch-flexy ytd-rich-grid-renderer[hide-chips-bar-on-watch] ytd-feed-filter-chip-bar-renderer.ytd-rich-grid-renderer, ytd-watch-flexy ytd-rich-grid-renderer[hide-chips-bar-on-home] #header.ytd-rich-grid-renderer ytd-feed-filter-chip-bar-renderer.ytd-rich-grid-renderer {
      display: flex;
      height: 51px;
      margin-bottom: 8px;
    }

    ytd-watch-flexy #chips-wrapper.ytd-feed-filter-chip-bar-renderer {
      position: relative;
      top: 0;
    }

    ytd-watch-flexy ytd-feed-filter-chip-bar-renderer[fluid-width] #chips-content.ytd-feed-filter-chip-bar-renderer {
      padding: 0;
    }

    ytd-watch-flexy yt-chip-cloud-chip-renderer.ytd-feed-filter-chip-bar-renderer, ytd-watch-flexy yt-chip-cloud-chip-renderer.ytd-feed-filter-chip-bar-renderer:first-of-type {
      margin: 8px;
      margin-left: 0;
    }

    ytd-watch-flexy ytd-button-renderer.ytd-feed-filter-chip-bar-renderer {
      margin: 0;
      padding: 0 8px;
    }

    /* More tweaks to be applied */
    #title.ytd-playlist-sidebar-primary-info-renderer,
    ytd-inline-form-renderer[component-style=INLINE_FORM_STYLE_TITLE] #text-displayed.ytd-inline-form-renderer {
      font-family: YouTube Sans !important;
      font-weight: 700 !important
    }

    ytd-comments-header-renderer[use-space-between] #title.ytd-comments-header-renderer {
    justify-content: start !important
    }

    #panel-button.ytd-comments-header-renderer {
    margin-left: 32px;
    margin-right: 8px
    }

    #panel-button .yt-spec-button-shape-next__icon {
    margin-right: 0
    }

    #panel-button .yt-spec-button-shape-next--size-m {
    padding-left: 12px;
    padding-right: 6px
    }

    #panel-button .yt-spec-button-shape-next__button-text-content {
    display: none !important
    }

    #panel-button .yt-spec-button-shape-next__icon path {
    d: path("M10 3H17V7H10V3ZM20 0H0V14H20V0ZM1 1H19V13H1V1Z");
    transform: scale(1.20)
    }

    [d*="M18 4v15.06l-5.42-3.87-.58-.42-.58.42L6 19.06V4h12m1-1H5v18l7-5 7 5V3z"] {
    d: path("M22 13h-4v4h-2v-4h-4v-2h4V7h2v4h4v2zm-8-6H2v1h12V7zM2 12h8v-1H2v1zm0 4h8v-1H2v1z")
    }

    button.yt-spec-button-shape-next.yt-spec-button-shape-next--tonal.yt-spec-button-shape-next--mono.yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--icon-leading[aria-label="Save to playlist"] path,
    button.yt-spec-button-shape-next.yt-spec-button-shape-next--tonal.yt-spec-button-shape-next--overlay.yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--icon-button[aria-label="Save playlist"] path {
    d: path("M22 13h-4v4h-2v-4h-4v-2h4V7h2v4h4v2zm-8-6H2v1h12V7zM2 12h8v-1H2v1zm0 4h8v-1H2v1z")
    }`;
if (typeof GM_addStyle !== "undefined") {
  GM_addStyle(css);
} else {
  let styleNode = document.createElement("style");
  styleNode.appendChild(document.createTextNode(css));
  (document.querySelector("head") || document.documentElement).appendChild(styleNode);
}
})();

// Integrate 'YouTube Video Resize Fix' script (special thanks to CY Fung)
/* jshint esversion:8 */

((__CONTEXT01__) => {
  'use strict';


  const win = this instanceof Window ? this : window;

  // Create a unique key for the script and check if it is already running
  const hkey_script = 'ahceihvpbosz';
  if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
  win[hkey_script] = true;

  const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  const indr = o => insp(o).$ || o.$ || 0;

  /** @type {globalThis.PromiseConstructor} */
  const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  const cleanContext = async (win) => {
    const waitFn = requestAnimationFrame; // shall have been binded to window
    try {
      let mx = 16; // MAX TRIAL
      const frameId = 'vanillajs-iframe-v1'
      let frame = document.getElementById(frameId);
      let removeIframeFn = null;
      if (!frame) {
        frame = document.createElement('iframe');
        frame.id = frameId;
        const blobURL = typeof webkitCancelAnimationFrame === 'function' && typeof kagi === 'undefined' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null; // avoid Brave Crash
        frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
        let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
        n.appendChild(frame);
        while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
        const root = document.documentElement;
        root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
        if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL));

        removeIframeFn = (setTimeout) => {
          const removeIframeOnDocumentReady = (e) => {
            e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
            e = n;
            n = win = removeIframeFn = 0;
            setTimeout ? setTimeout(() => e.remove(), 200) : e.remove();
          }
          if (!setTimeout || document.readyState !== 'loading') {
            removeIframeOnDocumentReady();
          } else {
            win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
          }
        }
      }
      while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
      const fc = frame.contentWindow;
      if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
      try {
        const { requestAnimationFrame, setTimeout, clearTimeout } = fc;
        const res = { requestAnimationFrame, setTimeout, clearTimeout };
        for (let k in res) res[k] = res[k].bind(win); // necessary
        if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
        return res;
      } catch (e) {
        if (removeIframeFn) removeIframeFn();
        return null;
      }
    } catch (e) {
      console.warn(e);
      return null;
    }
  };

  const isWatchPageURL = (url) => {
    url = url || location;
    return location.pathname === '/watch' || location.pathname.startsWith('/live/')
  };

  cleanContext(win).then(__CONTEXT02__ => {
    if (!__CONTEXT02__) return null;

    const { ResizeObserver } = __CONTEXT01__;
    const { requestAnimationFrame, setTimeout, clearTimeout } = __CONTEXT02__;
    const elements = {};
    let rid1 = 0;
    let rid2 = 0;
    /** @type {MutationObserver | null} */
    let attrObserver = null;
    /** @type {ResizeObserver | null} */
    let resizeObserver = null;
    let isHTMLAttrApplied = false;
    const core = {
      begin() {
        document.addEventListener('yt-player-updated', core.hanlder, true);
        document.addEventListener('ytd-navigate-finish', core.hanlder, true);
      },
      hanlder: () => {
        rid1++;
        if (rid1 > 1e9) rid1 = 9;
        const tid = rid1;
        requestAnimationFrame(() => {
          if (tid !== rid1) return;
          core.runner();
        })
      },
      async runner() {
        if (!location.href.startsWith('https://www.youtube.com/')) return;
        if (!isWatchPageURL()) return;

        elements.ytdFlexy = document.querySelector('ytd-watch-flexy');
        elements.video = document.querySelector('ytd-watch-flexy #movie_player video, ytd-watch-flexy #movie_player audio.video-stream.html5-main-video');
        if (elements.ytdFlexy && elements.video) { } else return;
        elements.moviePlayer = elements.video.closest('#movie_player');
        if (!elements.moviePlayer) return;

        // resize Video
        let { ytdFlexy } = elements;
        if (!ytdFlexy.ElYTL) {
          ytdFlexy.ElYTL = 1;
          const ytdFlexyCnt = insp(ytdFlexy);
          if (typeof ytdFlexyCnt.calculateNormalPlayerSize_ === 'function') {
            ytdFlexyCnt.calculateNormalPlayerSize_ = core.resizeFunc(ytdFlexyCnt.calculateNormalPlayerSize_, 1);
          } else {
            console.warn('ytdFlexyCnt.calculateNormalPlayerSize_ is not a function.')
          }
          if (typeof ytdFlexyCnt.calculateCurrentPlayerSize_ === 'function') {
            ytdFlexyCnt.calculateCurrentPlayerSize_ = core.resizeFunc(ytdFlexyCnt.calculateCurrentPlayerSize_, 0);
          } else {
            console.warn('ytdFlexyCnt.calculateCurrentPlayerSize_ is not a function.')
          }
        }
        ytdFlexy = null;

        // when video is fetched
        elements.video.removeEventListener('canplay', core.triggerResizeDelayed, false);
        elements.video.addEventListener('canplay', core.triggerResizeDelayed, false);

        // when video is resized
        if (resizeObserver) {
          resizeObserver.disconnect();
          resizeObserver = null;
        }
        if (typeof ResizeObserver === 'function') {
          resizeObserver = new ResizeObserver(core.triggerResizeDelayed);
          resizeObserver.observe(elements.moviePlayer);
        }

        // MutationObserver:[collapsed] @ ytd-live-chat-frame#chat
        if (attrObserver) {
          attrObserver.takeRecords();
          attrObserver.disconnect();
          attrObserver = null;
        }
        let chat = document.querySelector('ytd-watch-flexy ytd-live-chat-frame#chat');
        if (chat) {
          // resize due to DOM update
          attrObserver = new MutationObserver(core.triggerResizeDelayed);
          attrObserver.observe(chat, { attributes: true, attributeFilter: ["collapsed"] });
          chat = null;
        }

        // resize on idle
        Promise.resolve().then(core.triggerResizeDelayed);
      },
      resizeFunc(originalFunc, kb) {
        return function () {
          rid2++;
          if (!isHTMLAttrApplied) {
            isHTMLAttrApplied = true;
            Promise.resolve(0).then(() => {
              document.documentElement.classList.add('youtube-video-resize-fix');
            }).catch(console.warn);
          }
          if (document.fullscreenElement === null) {

            // calculateCurrentPlayerSize_ shall be always return NaN to make correct positioning of toolbars
            if (!kb) return { width: NaN, height: NaN };

            let ret = core.calculateSize();
            if (ret.height > 0 && ret.width > 0) {
              return ret;
            }
          }
          return originalFunc.apply(this, arguments);
        }
      },
      calculateSize_() {
        const { moviePlayer, video } = elements;
        const rect1 = { width: video.videoWidth, height: video.videoHeight }; // native values independent of css rules
        if (rect1.width > 0 && rect1.height > 0) {
          const rect2 = moviePlayer.getBoundingClientRect();
          const aspectRatio = rect1.width / rect1.height;
          let h2 = rect2.width / aspectRatio;
          let w2 = rect2.height * aspectRatio;
          return { rect2, h2, w2 };
        }
        return null;
      },
      calculateSize() {
        let rs = core.calculateSize_();
        if (!rs) return { width: NaN, height: NaN };
        const { rect2, h2, w2 } = rs;
        if (h2 > rect2.height) {
          return { width: w2, height: rect2.height };
        } else {
          return { width: rect2.width, height: h2 };
        }
      },
      triggerResizeDelayed: () => {
        rid2++;
        if (rid2 > 1e9) rid2 = 9;
        const tid = rid2;
        requestAnimationFrame(() => {
          if (tid !== rid2) return;
          const { ytdFlexy } = elements;
          let r = false;
          const ytdFlexyCnt = insp(ytdFlexy);
          const windowSize_ = ytdFlexyCnt.windowSize_;
          if (windowSize_ && typeof ytdFlexyCnt.onWindowResized_ === 'function') {
            try {
              ytdFlexyCnt.onWindowResized_(windowSize_);
              r = true;
            } catch (e) { }
          }
          if (!r) window.dispatchEvent(new Event('resize'));
        })
      }
    };
    core.begin();







    // YouTube Watch Page Reflect (WPR)



    // This script enhances the functionality of YouTube pages by reflecting changes in the page state.

    (async function youTubeWPR() {

      let checkPageVisibilityChanged = false;

      // A WeakSet to keep track of elements being monitored for mutations.
      const monitorWeakSet = new WeakSet();

      /** @type {globalThis.PromiseConstructor} */
      const Promise = (async () => { })().constructor;

      // Function to reflect the current state of the YouTube page.
      async function _reflect() {
        await Promise.resolve();

        const youtubeWpr = document.documentElement.getAttribute("youtube-wpr");
        let s = '';

        // Check if the current page is the video watch page.
        if (isWatchPageURL()) {
          let watch = document.querySelector("ytd-watch-flexy");
          let chat = document.querySelector("ytd-live-chat-frame#chat");

          if (watch) {
            // Determine the state of the chat and video player on the watch page and generate a state string.
            s += !chat ? 'h0' : (chat.hasAttribute('collapsed') || !document.querySelector('iframe#chatframe')) ? 'h1' : 'h2';
            s += watch.hasAttribute('is-two-columns_') ? 's' : 'S';
            s += watch.hasAttribute('fullscreen') ? 'F' : 'f';
            s += watch.hasAttribute('theater') ? 'T' : 't';
          }
        }

        // Update the reflected state if it has changed.
        if (s !== youtubeWpr) {
          document.documentElement.setAttribute("youtube-wpr", s);
        }

      }

      // Function to reflect changes in specific attributes of monitored elements.
      async function reflect(nodeName, attrNames, forced) {
        await Promise.resolve();

        if (!forced) {
          let skip = true;
          for (const attrName of attrNames) {
            if (nodeName === 'ytd-live-chat-frame') {
              if (attrName === 'collapsed') skip = false;
            } else if (nodeName === 'ytd-watch-flexy') {
              if (attrName === 'is-two-columns_') skip = false;
              else if (attrName === 'fullscreen') skip = false;
              else if (attrName === 'theater') skip = false;
            }
          }
          if (skip) return;
        }

        // Log the mutated element and its attributes.
        // console.log(nodeName, attrNames);

        // Call _reflect() to update the reflected state.
        _reflect();
      }

      // Callback function for the MutationObserver that tracks mutations in monitored elements.
      function callback(mutationsList) {
        const attrNames = new Set();
        let nodeName = null;
        for (const mutation of mutationsList) {
          if (nodeName === null && mutation.target) nodeName = mutation.target.nodeName.toLowerCase();
          attrNames.add(mutation.attributeName);
        }
        reflect(nodeName, attrNames, false);
      }

      function getParent(element) {
        return element.__shady_native_parentNode || element.__shady_parentNode || element.parentNode;
      }

      let lastPageTypeChanged = 0;
      function chatContainerMutationHandler() {
        if (Date.now() - lastPageTypeChanged < 800) _reflect();
      }

      // Function to start monitoring an element for mutations.
      function monitor(element) {
        if (!element) return;
        if (monitorWeakSet.has(element)) {
          return;
        }

        monitorWeakSet.add(element);

        const observer = new MutationObserver(callback);
        observer.observe(element, { attributes: true });

        if (element.id === 'chat') {
          const parentNode = getParent(element);
          if (parentNode instanceof Element && parentNode.id === 'chat-container' && !monitorWeakSet.has(parentNode)) {
            monitorWeakSet.add(parentNode);
            const observer = new MutationObserver(chatContainerMutationHandler);
            observer.observe(parentNode, { childList: true, subtree: false });
          }
        }

        return 1;
      }

      let timeout = 0;

      // Function to monitor relevant elements and update the reflected state.
      let g = async (forced) => {
        await Promise.resolve();
        let b = 0;
        b = b | monitor(document.querySelector("ytd-watch-flexy"));
        b = b | monitor(document.querySelector("ytd-live-chat-frame#chat"));
        if (b || forced) {
          _reflect();
        }
      }
      // let renderId = 0;
      // Event handler function that triggers when the page finishes navigation or page data updates.
      let eventHandlerFunc = async (evt) => {
        checkPageVisibilityChanged = true;
        timeout = Date.now() + 800;
        g(1);
        if (evt.type === 'yt-navigate-finish') {
          // delay required when page type is changed for #chat (home -> watch).
          setTimeout(() => {
            g(1);
          }, 80);
        } else if (evt.type === 'yt-page-type-changed') {
          lastPageTypeChanged = Date.now();
          // setTimeout(() => {
          //   if (renderId > 1e9) renderId = 9;
          //   const t = ++renderId;
          //   requestAnimationFrame(() => {
          //     if (t !== renderId) return;
          //     g(1);
          //   });
          // }, 180);
          if (typeof requestIdleCallback === 'function') {
            requestIdleCallback(() => {
              g(1);
            });
          }
        }
      }

      let loadState = 0;

      // Function to initialize the script and start monitoring the page.
      async function actor() {
        if (loadState === 0) {
          if (!document.documentElement.hasAttribute("youtube-wpr")) {
            loadState = 1;
            document.documentElement.setAttribute("youtube-wpr", "");
            document.addEventListener("yt-navigate-finish", eventHandlerFunc, false);
            document.addEventListener("yt-page-data-updated", eventHandlerFunc, false);
            document.addEventListener("yt-page-type-changed", eventHandlerFunc, false);
          } else {
            loadState = -1;
            document.removeEventListener("yt-page-data-fetched", actor, false);
            return;
          }
        }
        if (loadState === 1) {
          timeout = Date.now() + 800;
          // Function to continuously monitor elements and update the reflected state.
          let pf = () => {
            g(0);
            if (Date.now() < timeout) requestAnimationFrame(pf);
          };
          pf();
        }
      }

      // Event listener that triggers when page data is fetched.
      document.addEventListener("yt-page-data-fetched", actor, false);

      // Update after visibility changed (looks like there are bugs due to inactive tab)
      document.addEventListener('visibilitychange', () => {
        if (document.visibilityState !== 'visible') return;
        if (checkPageVisibilityChanged) {
          checkPageVisibilityChanged = false;
          setTimeout(() => {
            g(1);
          }, 100);
          requestAnimationFrame(() => {
            g(1);
          });
        }
      }, false);


    })();

  });

})({ ResizeObserver });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址