Youtube Chat Flow

Chat Flow.

目前为 2024-06-12 提交的版本。查看 最新版本

// ==UserScript==
// @name         Youtube Chat Flow
// @version      0.2
// @description  Chat Flow.
// @license      MIT
// @homepageURL  https://github.com/willy67k/tampermonkey-userscripts
// @homepage     https://github.com/willy67k/tampermonkey-userscripts
// @source       https://github.com/willy67k/tampermonkey-userscripts/raw/master/src/youtube-chat-flow.js
// @namespace    https://github.com/willy67k/tampermonkey-userscripts/raw/master/src/youtube-chat-flow.js
// @author       Lilp
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        none
// ==/UserScript==

(function () {
  'use strict';
// prettier-ignore
window.killString = ["html5-video-player", "ytp-transparent", "ytp-exp-bottom-control-flexbox", "ytp-exp-ppp-update", "ytp-hide-info-bar", "ytp-large-width-mode", "ytp-fine-scrubbing-exp", "ytp-autonav-endscreen-cancelled-state", "ytp-fit-cover-video", "ytp-heat-map", "ytp-branding-shown", "ytp-progress-bar-decoration", "ytp-progress-bar-hover", "ytp-autohide", "ytp-autohide-active"];
window.topOffset = 30;
window.chatGap = 250;
window.chatDuration = 5;

window.startChatFlow = async function () {
  window.urlParams = new URLSearchParams(window.location.search).get("v");
  const liveIframe = document.querySelector(".ytd-live-chat-frame");
  const html5Player = document.querySelector(".html5-video-player");
  let html5Video = document.querySelector(".html5-video-player-chat-flow-box");
  if (!html5Video) {
    html5Player.insertAdjacentHTML("afterbegin", "<div class=\"html5-video-player-chat-flow-box\"></div>");
    html5Video = document.querySelector(".html5-video-player-chat-flow-box");
  }
  const listRenderer = liveIframe.contentWindow.document.querySelector("yt-live-chat-renderer");
  let chats = {};
  let renderChats = {};
  let couldObserve = false;

  function appendStyle() {
    const style = `<style>
  .html5-video-player-chat-flow-box {
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
  }

  .chat-flow-message {
    font-size: 24px;
    font-weight: bold;
    position: absolute;
    z-index: 99;
    user-select: none;
    pointer-events: none;
    white-space: nowrap;
    -webkit-text-stroke: 1px rgba(0, 0, 0, 0.3);
    animation-name: chat-flow-animate;
    animation-duration: 2s;
    animation-timing-function: linear;
    animation-iteration-count: 1;
    animation-direction: normal;
    animation-fill-mode: forwards;
  }

  .chat-flow-message.paused {
    animation-play-state: paused;
  }

  @keyframes chat-flow-animate {
    from {
      left: 100%;
      transform: translateX(0%);
    }
    to {
      left: 0%;
      transform: translateX(-120%);
    }
  }
  </style>`;
    document.body.insertAdjacentHTML("beforeend", style);
  }

  function observeChat(node) {
    const mutationObserver = new MutationObserver((m, o) => {
      if (couldObserve) return;
      m.forEach((el) => {
        el.addedNodes.forEach((node) => {
          if (node.tagName === "YT-LIVE-CHAT-TEXT-MESSAGE-RENDERER") {
            const message = node.querySelector("#message").textContent;
            chats[node.id] = message;
            generateFlowChat(message, node.id);
          }
        });
      });
    });
    couldObserve = false;

    mutationObserver.observe(node, { childList: true, subtree: true });
    return mutationObserver;
  }

  function observerChatFinalIn(chat, width = 250) {
    const option = {
      root: html5Video,
      rootMargin: `0px -${width}px 0px 0px`,
      threshold: 0,
    };

    const callback = (entries) => {
      if (entries[0].isIntersecting) {
        for (const key in renderChats) {
          if (renderChats[key] === chat.id) {
            delete renderChats[key];
          }
        }
        observer.unobserve(entries[0].target);
      }
    };
    const observer = new IntersectionObserver(callback, option);

    observer.observe(chat);
  }

  function observerChatOut(chat) {
    const option = {
      root: html5Video,
      rootMargin: "0px 0px 0px 0px",
      threshold: 0,
    };

    const callback = (entries) => {
      if (!entries[0].isIntersecting) {
        for (const key in renderChats) {
          if (renderChats[key] === chat.id) {
            delete renderChats[key];
          }
        }
        observer.unobserve(entries[0].target);
        entries[0].target.remove();
      }
    };
    const observer = new IntersectionObserver(callback, option);
    observer.observe(chat);
  }

  function generateFlowChat(str, id) {
    const p = document.createElement("p");
    p.className = "chat-flow-message";
    p.id = `flow-chat-${id}`;
    p.textContent = `${str}`;
    html5Video.append(p);
    p.style.animationDuration = window.chatDuration * ((html5Player.clientWidth + p.clientWidth) / html5Player.clientWidth) + "s";
    let top = 0;
    while (true) {
      if (!renderChats[top]) {
        renderChats[top] = p.id;
        break;
      } else {
        top += window.topOffset;
      }
    }
    p.style.top = top + "px";

    observerChatFinalIn(p, p.clientWidth + window.chatGap);
    observerChatOut(p);
  }

  appendStyle();
  const mutationObserver = new MutationObserver((m, o) => {
    const control = [...html5Player.classList].filter((el) => !window.killString.includes(el));
    switch (true) {
      case control.includes("seeking-mode") || control.includes("buffering-mode"):
        couldObserve = true;
        mutationObserverChat.disconnect();
        chats = {};
        renderChats = {};
        break;
      case control.includes("playing-mode"):
        if (couldObserve) {
          mutationObserverChat = observeChat(listRenderer);
        }
        document.querySelectorAll(".chat-flow-message").forEach((el) => {
          el.classList.remove("paused");
        });
        break;

      case control.includes("paused-mode"):
        if (couldObserve) {
          mutationObserverChat = observeChat(listRenderer);
        }
        document.querySelectorAll(".chat-flow-message").forEach((el) => {
          el.classList.add("paused");
        });
        break;

      default:
        break;
    }
  });
  mutationObserver.observe(html5Player, { childList: true, attributes: true, attributeOldValue: true });
  let mutationObserverChat = observeChat(listRenderer);
};

window.urlParams = "";
window.interval = setInterval(() => {
  if (window.urlParams === new URLSearchParams(window.location.search).get("v")) return;

  const liveIframe = document.querySelector(".ytd-live-chat-frame");
  if (!liveIframe) return;
  const listRenderer = liveIframe.contentWindow.document.querySelector("yt-live-chat-renderer");
  if (!listRenderer) return;
  window.startChatFlow();
}, 1000);



})();




QingJ © 2025

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