useVideoPlaybackOverlays.js
9.49 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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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,
};
};