useVideoPlaybackOverlays.js 9.49 KB
import { ref } from "vue";

/**
 * 视频播放器叠层逻辑管理
 * @module useVideoPlaybackOverlays
 * @description
 * 管理视频播放器上的各种叠层信息,包括:
 * 1. 弱网提示:当网速过慢时显示提示。
 * 2. HLS 下载速率:显示当前 HLS 流的下载速度(调试模式)。
 * 3. 调试信息:显示播放器内核、带宽等技术参数。
 *
 * @param {Object} params - 初始化参数
 * @param {any} params.props - 组件 props
 * @param {import("vue").Ref<any>} params.player - Video.js 实例引用
 * @param {import("vue").Ref<boolean>} params.is_m3u8 - 是否为 m3u8 格式
 * @param {import("vue").Ref<boolean>} params.use_native_player - 是否使用原生播放器
 * @param {import("vue").Ref<boolean>} params.show_error_overlay - 是否显示错误叠层
 * @param {import("vue").Ref<boolean>} params.has_started_playback - 是否已开始播放
 * @returns {Object} 叠层状态和控制方法
 */
export const useVideoPlaybackOverlays = ({
    props,
    player,
    is_m3u8,
    use_native_player,
    show_error_overlay,
    has_started_playback,
}) => {
    // ==================== 状态定义 ====================

    /** @type {import('vue').Ref<boolean>} 是否显示网速叠层 */
    const show_network_speed_overlay = ref(false);
    /** @type {import('vue').Ref<string>} 网速文本 */
    const network_speed_text = ref("");
    let network_speed_timer = null;
    let last_weixin_network_type_at = 0;

    /** @type {import('vue').Ref<string>} HLS下载速度文本 */
    const hls_download_speed_text = ref("");
    let hls_speed_timer = null;
    /** @type {import('vue').Ref<string>} HLS调试信息文本 */
    const hls_speed_debug_text = ref("");

    // ==================== 方法定义 ====================

    /**
     * 设置 HLS 调试信息
     * @param {string} event_name - 事件名称
     * @param {string} [extra] - 额外信息
     */
    const set_hls_debug = (event_name, extra) => {
        if (!props || props.debug !== true) return;

        // tech(true) 在部分版本可能抛错,这里必须兜底,避免影响正常播放流程
        const p = player.value;
        const has_player = !!p && !p.isDisposed?.();
        let tech = null;
        try {
            tech = has_player && typeof p.tech === "function" ? p.tech(true) : null;
        } catch (e) {
            tech = null;
        }
        const stable_tech = tech || (has_player ? p.tech_ : null);
        const tech_name = has_player
            ? (p.techName_ || (stable_tech && stable_tech.name_) || (stable_tech && stable_tech.constructor && stable_tech.constructor.name) || "unknown")
            : "none";
        const mode = use_native_player.value ? "native" : "videojs";
        const vhs = stable_tech && stable_tech.vhs ? stable_tech.vhs : null;
        const hls = stable_tech && stable_tech.hls ? stable_tech.hls : null;
        const bw_kbps = vhs && typeof vhs.bandwidth === "number" ? Math.round(vhs.bandwidth / 1000) : 0;
        const hls_bw_kbps = hls && typeof hls.bandwidth === "number" ? Math.round(hls.bandwidth / 1000) : 0;

        const extra_text = extra ? ` ${extra}` : "";
        hls_speed_debug_text.value = `${event_name}${extra_text}\nmode:${mode} m3u8:${is_m3u8.value ? "1" : "0"} native:${use_native_player.value ? "1" : "0"}\ntech:${tech_name} vhs:${vhs ? "1" : "0"} bw:${bw_kbps}kbps hls_bw:${hls_bw_kbps}kbps`;
    };

    /**
     * 更新网速信息
     * @returns {boolean} 是否获取成功
     */
    const update_network_speed = () => {
        if (typeof navigator === "undefined") {
            network_speed_text.value = "未知";
            return false;
        }

        // 优先使用 Network Information API(部分浏览器不支持)
        const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        const downlink = connection && typeof connection.downlink === "number" ? connection.downlink : null;
        if (downlink && downlink > 0) {
            network_speed_text.value = `${downlink.toFixed(1)} Mbps`;
            return true;
        }

        const effective_type = connection && typeof connection.effectiveType === "string" ? connection.effectiveType : "";
        network_speed_text.value = effective_type ? `${effective_type}` : "未知";
        return effective_type ? true : false;
    };

    /**
     * 更新微信环境下的网络类型
     */
    const update_weixin_network_type = () => {
        if (typeof window === "undefined") return;
        if (!window.WeixinJSBridge || typeof window.WeixinJSBridge.invoke !== "function") return;

        const now = Date.now();
        // 微信接口调用频率过高会卡顿/被限流,这里做节流
        if (now - last_weixin_network_type_at < 3000) return;
        last_weixin_network_type_at = now;

        window.WeixinJSBridge.invoke("getNetworkType", {}, (res) => {
            const type = (res && (res.networkType || res.network_type)) ? String(res.networkType || res.network_type) : "";
            if (type) network_speed_text.value = type;
        });
    };

    /**
     * 显示网速叠层
     * @description 启动定时器定期获取网速
     */
    const show_network_speed = () => {
        // 没有进入过播放阶段时不显示,避免一加载就出现“弱网提示”造成误导
        if (!has_started_playback.value) return;
        if (show_error_overlay.value) return;
        if (show_network_speed_overlay.value) return;

        show_network_speed_overlay.value = true;
        const ok = update_network_speed();
        if (!ok) update_weixin_network_type();

        if (network_speed_timer) clearInterval(network_speed_timer);
        network_speed_timer = setInterval(() => {
            const ok2 = update_network_speed();
            if (!ok2) update_weixin_network_type();
        }, 800);
    };

    /**
     * 隐藏网速叠层
     */
    const hide_network_speed = () => {
        show_network_speed_overlay.value = false;
        if (network_speed_timer) {
            clearInterval(network_speed_timer);
            network_speed_timer = null;
        }
    };

    /**
     * 格式化速度显示
     * @param {number} bytes_per_second
     * @returns {string}
     */
    const format_speed = (bytes_per_second) => {
        const size = Number(bytes_per_second) || 0;
        if (!size) return "";

        const kb = 1024;
        const mb = kb * 1024;
        if (size >= mb) return `${(size / mb).toFixed(1)}MB/s`;
        if (size >= kb) return `${Math.round(size / kb)}kB/s`;
        return `${Math.round(size)}B/s`;
    };

    /**
     * 更新 HLS 下载速度
     */
    const update_hls_download_speed = () => {
        if (!player.value || player.value.isDisposed()) {
            hls_download_speed_text.value = "";
            set_hls_debug("update", "player:empty");
            return;
        }
        if (!is_m3u8.value || use_native_player.value) {
            // 非 m3u8 或原生播放器走系统内核,这里拿不到 VHS 带宽,直接隐藏
            hls_download_speed_text.value = "";
            set_hls_debug("update", "skip");
            return;
        }

        let tech = null;
        try {
            tech = typeof player.value.tech === "function" ? player.value.tech(true) : null;
        } catch (e) {
            tech = null;
        }
        const stable_tech = tech || player.value.tech_ || null;
        const vhs = stable_tech && stable_tech.vhs ? stable_tech.vhs : null;
        const hls = stable_tech && stable_tech.hls ? stable_tech.hls : null;
        const bandwidth_bits_per_second = (vhs && typeof vhs.bandwidth === "number" ? vhs.bandwidth : null)
            || (hls && typeof hls.bandwidth === "number" ? hls.bandwidth : null);
        if (!bandwidth_bits_per_second || bandwidth_bits_per_second <= 0) {
            hls_download_speed_text.value = "";
            set_hls_debug("update", "bw:0");
            return;
        }

        // VHS bandwidth 单位是 bits/s,这里换算成 bytes/s 再格式化展示
        hls_download_speed_text.value = format_speed(bandwidth_bits_per_second / 8);
        set_hls_debug("update", `speed:${hls_download_speed_text.value}`);
    };

    /**
     * 开始监控 HLS 下载速度
     */
    const start_hls_download_speed = () => {
        if (hls_speed_timer) return;
        if (!is_m3u8.value || use_native_player.value) return;

        set_hls_debug("start");
        update_hls_download_speed();
        hls_speed_timer = setInterval(() => {
            update_hls_download_speed();
        }, 1000);
    };

    /**
     * 停止监控 HLS 下载速度
     * @param {string} [reason] - 停止原因
     */
    const stop_hls_download_speed = (reason) => {
        if (hls_speed_timer) {
            clearInterval(hls_speed_timer);
            hls_speed_timer = null;
        }
        hls_download_speed_text.value = "";
        set_hls_debug("stop", reason || "");
    };

    /**
     * 销毁所有叠层
     */
    const dispose_overlays = () => {
        hide_network_speed();
        stop_hls_download_speed("dispose");
    };

    return {
        showNetworkSpeedOverlay: show_network_speed_overlay,
        networkSpeedText: network_speed_text,
        hlsDownloadSpeedText: hls_download_speed_text,
        hlsSpeedDebugText: hls_speed_debug_text,
        setHlsDebug: set_hls_debug,
        showNetworkSpeed: show_network_speed,
        hideNetworkSpeed: hide_network_speed,
        startHlsDownloadSpeed: start_hls_download_speed,
        stopHlsDownloadSpeed: stop_hls_download_speed,
        disposeOverlays: dispose_overlays,
    };
};