videoPlayerSource.js
4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*
* @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,
};