videoPlayerSource.js 4.13 KB
/*
 * @Date: 2026-01-20 16:53:31
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2026-01-20 17:06:44
 * @FilePath: /mlaj/src/composables/videoPlayerSource.js
 * @Description: 文件描述
 */
/**
 * @description 从 url(或文件名)中提取扩展名(小写,不含点)。支持 base_url 作为 URL 解析基准。
 * @param {string} url 可能是完整 URL/相对路径/文件名
 * @param {string=} base_url URL 解析基准(默认取 window.location.href)
 * @returns {string}
 */
const getUrlPathExtension = (url, base_url) => {
    const url_text = (url || "").trim();
    if (!url_text) return "";
    try {
        const base = base_url || (typeof window !== "undefined" ? window.location?.href : undefined);
        const u = base ? new URL(url_text, base) : new URL(url_text);
        const pathname = u ? u.pathname : url_text;
        const last_dot = pathname.lastIndexOf(".");
        if (last_dot < 0) return "";
        return pathname.slice(last_dot + 1).toLowerCase();
    } catch (e) {
        // URL 构造失败时,用字符串兜底(移除 query/hash 再取扩展名)
        const without_query = url_text.split("?")[0].split("#")[0];
        const last_dot = without_query.lastIndexOf(".");
        if (last_dot < 0) return "";
        return without_query.slice(last_dot + 1).toLowerCase();
    }
};

/**
 * @description 根据 url / video_id 推断 video MIME。用于 blob 地址等无法从 url 取扩展名的场景。
 * @param {{url: string, video_id?: string, base_url?: string}} params
 * @returns {string}
 */
const getVideoMimeType = ({ url, video_id, base_url }) => {
    const url_text = (url || "").toLowerCase();
    if (url_text.includes(".m3u8")) return "application/x-mpegURL";
    // 1) 优先 url 扩展名;2) blob:xxx 这种取不到扩展名时,用 video_id 兜底(通常带 .mp4/.mov 等)
    const ext = getUrlPathExtension(url_text, base_url) || getUrlPathExtension(video_id, base_url);
    if (ext === "m3u8") return "application/x-mpegURL";
    if (ext === "mp4" || ext === "m4v") return "video/mp4";
    if (ext === "mov") return "video/quicktime";
    if (ext === "webm") return "video/webm";
    if (ext === "ogv" || ext === "ogg") return "video/ogg";
    return "";
};

/**
 * @description 从资源探测得到的 content-type 推断 video MIME。
 * @param {string} content_type
 * @returns {string}
 */
const inferVideoMimeTypeFromContentType = (content_type) => {
    const probe_type = (content_type || "").toLowerCase();
    return (probe_type.includes("application/vnd.apple.mpegurl") || probe_type.includes("application/x-mpegurl") ? "application/x-mpegURL" : "")
        || (probe_type.includes("video/mp4") ? "video/mp4" : "")
        || (probe_type.includes("video/quicktime") ? "video/quicktime" : "")
        || (probe_type.includes("video/webm") ? "video/webm" : "")
        || (probe_type.includes("video/ogg") ? "video/ogg" : "");
};

/**
 * @description 构造 video.js sources。尽可能给出 type,避免旧设备/部分内核出现“无法识别资源”的报错。
 * @param {{url: string, video_id?: string, probe_content_type?: string, base_url?: string}} params
 * @returns {Array<{src: string, type?: string}>}
 */
const buildVideoSources = ({ url, video_id, probe_content_type, base_url }) => {
    const inferred_type = getVideoMimeType({ url, video_id, base_url });
    const type = inferred_type || inferVideoMimeTypeFromContentType(probe_content_type);
    if (type) return [{ src: url, type }];
    return [{ src: url }];
};

/**
 * @description 判断当前浏览器是否原生支持 HLS(m3u8)。
 * @returns {boolean}
 */
const canPlayHlsNatively = () => {
    if (typeof document === "undefined") return false;
    const el = document.createElement("video");
    if (!el || typeof el.canPlayType !== "function") return false;
    const r1 = el.canPlayType("application/vnd.apple.mpegurl");
    const r2 = el.canPlayType("application/x-mpegURL");
    return r1 === "probably" || r1 === "maybe" || r2 === "probably" || r2 === "maybe";
};

export {
    getUrlPathExtension,
    getVideoMimeType,
    inferVideoMimeTypeFromContentType,
    buildVideoSources,
    canPlayHlsNatively,
};