哔哩哔哩直播显示平均码率

A userscript to display the average bitrate of Bilibili live streams.

// ==UserScript==
// @name         哔哩哔哩直播显示平均码率
// @namespace    bili_live_average_bitrate_display
// @version      1.1.1
// @author       Raven-tu
// @description  A userscript to display the average bitrate of Bilibili live streams.
// @license      MIT
// @icon         https://live.bilibili.com/favicon.ico
// @match        *://live.bilibili.com/*
// @connect      live.bilibili.com
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  var __typeError = (msg) => {
    throw TypeError(msg);
  };
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
  var _currentVideoSrc, _averageBitrate, _bitrateRecord, _panel, _bitrateDisplayElement, _VideoMetricsMonitor_instances, setupUpdateVideoTemplateProxy_fn, processStreamInfo_fn, renderBitrateDisplay_fn, createBitrateDisplayElement_fn;
  const name = "bili_live_average_bitrate_display";
  const version = "1.1.1";
  const Package = {
    name,
    version
  };
  const PROJECT_NAME = Package.name;
  const PROJECT_VERSION = Package.version;
  var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
  const BITRATE_RECORD_MAX_LENGTH = 30;
  const BYTES_TO_KBPS_FACTOR = 8 / 1024;
  const VIDEO_INFO_CONTAINER_ID = "p-video-info-videoInfo";
  const VIDEO_BITRATE_DISPLAY_ID = "p-video-info-videobitrate";
  class VideoMetricsMonitor {
    /**
     * `VideoMetricsMonitor` 类的构造函数。
     * @param {VideoPanel} panel - 视频面板对象。
     * @throws {Error} 如果传入的 `panel` 对象无效。
     */
    constructor(panel) {
      __privateAdd(this, _VideoMetricsMonitor_instances);
      /** 当前正在播放的视频源 URL。 */
      __privateAdd(this, _currentVideoSrc, "");
      /** 平均码率,单位:千比特每秒 (Kbps)。 */
      __privateAdd(this, _averageBitrate, 0);
      /** 码率记录数组,存储最近的码率样本。 */
      __privateAdd(this, _bitrateRecord, []);
      /** 对 `VideoPanel` 实例的引用。 */
      __privateAdd(this, _panel);
      /** 缓存的码率显示 DOM 元素。 */
      __privateAdd(this, _bitrateDisplayElement, null);
      if (!panel || typeof panel.updateVideoTemplate !== "function") {
        throw new Error("VideoMetricsMonitor: 构造函数参数无效。传入的 panel 对象必须包含 updateVideoTemplate 方法。");
      }
      __privateSet(this, _panel, panel);
      __privateMethod(this, _VideoMetricsMonitor_instances, setupUpdateVideoTemplateProxy_fn).call(this);
      console.debug("VideoMetricsMonitor: 实例已创建并成功代理 updateVideoTemplate 方法。");
    }
    /**
     * 获取当前的平均码率。
     * @returns {number} 平均码率,单位:Kbps。
     */
    getAverageBitrate() {
      return __privateGet(this, _averageBitrate);
    }
    /**
     * 获取当前的码率记录数组的副本。
     * @returns {number[]} 码率记录数组。
     */
    getBitrateRecord() {
      return [...__privateGet(this, _bitrateRecord)];
    }
    /**
     * 获取当前监控的视频源 URL。
     * @returns {string} 视频源 URL。
     */
    getCurrentVideoSrc() {
      return __privateGet(this, _currentVideoSrc);
    }
  }
  _currentVideoSrc = new WeakMap();
  _averageBitrate = new WeakMap();
  _bitrateRecord = new WeakMap();
  _panel = new WeakMap();
  _bitrateDisplayElement = new WeakMap();
  _VideoMetricsMonitor_instances = new WeakSet();
  /** 设置 `panel.updateVideoTemplate` 的代理。 */
  setupUpdateVideoTemplateProxy_fn = function() {
    __privateGet(this, _panel).updateVideoTemplate = new Proxy(__privateGet(this, _panel).updateVideoTemplate, {
      /**
       * 拦截 `updateVideoTemplate` 方法的调用。
       * @param {Function} target - 原始的 `updateVideoTemplate` 方法。
       * @param {object} thisArg - 原始方法被调用时的 `this` 上下文。
       * @param {Array<any>} args - 传递给原始方法的参数数组。
       * @returns {any} 原始方法的返回值。
       */
      apply: (target, thisArg, args) => {
        const streamInfo = args[0];
        __privateMethod(this, _VideoMetricsMonitor_instances, processStreamInfo_fn).call(this, streamInfo);
        __privateMethod(this, _VideoMetricsMonitor_instances, renderBitrateDisplay_fn).call(this);
        return Reflect.apply(target, thisArg, args);
      }
    });
  };
  /**
   * 处理传入的视频流信息,计算当前码率并更新内部的码率记录和平均码率。
   * @param {StreamInfo} streamInfo - 包含实时视频数据的对象。
   */
  processStreamInfo_fn = function(streamInfo) {
    const { realtimeInfo, mediaInfo } = streamInfo;
    const currentBitrate = realtimeInfo.videoNetworkActivity * BYTES_TO_KBPS_FACTOR;
    const newVideoSrc = mediaInfo.videoSrc;
    if (__privateGet(this, _currentVideoSrc).length === 0 || __privateGet(this, _currentVideoSrc) !== newVideoSrc) {
      __privateSet(this, _currentVideoSrc, newVideoSrc);
      __privateSet(this, _bitrateRecord, []);
      console.debug(`VideoMetricsMonitor: 视频源已更改为: ${__privateGet(this, _currentVideoSrc)},码率记录已重置。`);
    }
    __privateGet(this, _bitrateRecord).unshift(currentBitrate);
    if (__privateGet(this, _bitrateRecord).length > BITRATE_RECORD_MAX_LENGTH) {
      __privateGet(this, _bitrateRecord).pop();
    }
    const sumBitrate = __privateGet(this, _bitrateRecord).reduce((sum, bitrate) => sum + bitrate, 0);
    __privateSet(this, _averageBitrate, Number.parseFloat((sumBitrate / __privateGet(this, _bitrateRecord).length).toFixed(2)));
  };
  /** 在页面上创建或更新码率显示元素。 */
  renderBitrateDisplay_fn = function() {
    if (!__privateGet(this, _bitrateDisplayElement)) {
      __privateSet(this, _bitrateDisplayElement, document.getElementById(VIDEO_BITRATE_DISPLAY_ID));
      if (!__privateGet(this, _bitrateDisplayElement)) {
        __privateMethod(this, _VideoMetricsMonitor_instances, createBitrateDisplayElement_fn).call(this);
      }
    }
    if (__privateGet(this, _bitrateDisplayElement)) {
      const dataElement = __privateGet(this, _bitrateDisplayElement).querySelector(".web-player-line-data");
      if (dataElement) {
        dataElement.textContent = ` [${__privateGet(this, _bitrateRecord).length}s] ${__privateGet(this, _averageBitrate)} Kbps.`;
      } else {
        console.warn(`VideoMetricsMonitor: 码率显示元素 (ID: ${VIDEO_BITRATE_DISPLAY_ID}) 缺少 '.web-player-line-data' 子元素。`);
      }
    } else {
      console.warn(`VideoMetricsMonitor: 无法找到或创建码率显示元素 (ID: ${VIDEO_BITRATE_DISPLAY_ID})。`);
    }
  };
  /** 创建码率显示 DOM 元素并将其插入到页面中指定位置。 */
  createBitrateDisplayElement_fn = function() {
    const targetElement = document.getElementById(VIDEO_INFO_CONTAINER_ID);
    if (targetElement) {
      const newDiv = document.createElement("div");
      newDiv.id = VIDEO_BITRATE_DISPLAY_ID;
      newDiv.style.minWidth = "290px";
      newDiv.style.lineHeight = "18px";
      newDiv.style.fontSize = "12px";
      const labelDiv = document.createElement("div");
      labelDiv.style.display = "inline-block";
      labelDiv.style.whiteSpace = "nowrap";
      labelDiv.style.width = "100px";
      labelDiv.style.textAlign = "right";
      labelDiv.style.fontWeight = "500";
      labelDiv.style.marginRight = "15px";
      labelDiv.textContent = "Video Bitrate:";
      newDiv.appendChild(labelDiv);
      const dataDiv = document.createElement("div");
      dataDiv.style.display = "inline-block";
      dataDiv.style.minWidth = "58px";
      dataDiv.classList.add("web-player-line-data");
      newDiv.appendChild(dataDiv);
      targetElement.insertAdjacentElement("afterend", newDiv);
      __privateSet(this, _bitrateDisplayElement, newDiv);
      console.debug(`VideoMetricsMonitor: 码率显示元素 (ID: ${VIDEO_BITRATE_DISPLAY_ID}) 已成功创建并插入。`);
    } else {
      console.warn(`VideoMetricsMonitor: 未找到 id 为 "${VIDEO_INFO_CONTAINER_ID}" 的目标元素,无法创建码率显示 div。`);
    }
  };
  function initializeScriptHook() {
    console.log(`${PROJECT_NAME} ${PROJECT_VERSION} - 脚本已加载,正在尝试捕获 VideoPanel 实例...`);
    const originalWeakMapSet = WeakMap.prototype.set;
    let isHookActive = true;
    const isVideoPanelCandidate = (obj) => {
      return obj && typeof obj === "object" && typeof obj.updateVideoTemplate === "function" && typeof obj.createTemplateProxy === "function";
    };
    WeakMap.prototype.set = new Proxy(originalWeakMapSet, {
      /**
       * 拦截 `WeakMap.prototype.set` 的调用。
       * @param {Function} target - 原始的 `WeakMap.prototype.set` 方法。
       * @param {WeakMap} thisArg - 调用 `set` 方法的 `WeakMap` 实例。
       * @param {Array<any>} args - 传递给 `set` 方法的参数 `[key, value]`。
       * @returns {any} 原始 `set` 方法的返回值。
       */
      apply(target, thisArg, args) {
        if (isHookActive) {
          const [key, value] = args;
          let panelObject = null;
          if (isVideoPanelCandidate(key)) {
            panelObject = key;
          } else if (isVideoPanelCandidate(value)) {
            panelObject = value;
          }
          if (panelObject) {
            isHookActive = false;
            console.debug("Hook: 成功捕获到 VideoPanel 实例。");
            try {
              _unsafeWindow.debugVideoMetrics = new VideoMetricsMonitor(panelObject);
              console.debug("Hook: VideoMetricsMonitor 初始化成功,可通过 unsafeWindow.debugVideoMetrics 访问调试信息。");
            } catch (e) {
              console.error("Hook: VideoMetricsMonitor 初始化失败:", e);
            }
          }
        }
        return Reflect.apply(target, thisArg, args);
      }
    });
  }
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initializeScriptHook);
  } else {
    initializeScriptHook();
  }

})();

QingJ © 2025

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