@xmov/avatar 2.4.0 → 2.5.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -6
- package/package-dist/agent/avatar.cjs +1 -0
- package/package-dist/agent/avatar.modern.js +1 -0
- package/package-dist/agent/avatar.module.js +1 -0
- package/package-dist/agent/avatar.umd.js +1 -0
- package/package-dist/agent/index.d.ts +42 -0
- package/{dist/agent/types.d.ts → package-dist/agent/public-types.d.ts} +115 -116
- package/package-dist/index.cjs +3 -0
- package/package-dist/index.d.ts +34 -0
- package/package-dist/index.modern.js +1 -0
- package/package-dist/index.module.js +1 -0
- package/package-dist/index.umd.js +14 -0
- package/package-dist/public-types.d.ts +205 -0
- package/package.json +41 -33
- package/dist/agent/avatar.cjs +0 -1
- package/dist/agent/avatar.modern.js +0 -1
- package/dist/agent/avatar.module.js +0 -1
- package/dist/agent/avatar.umd.js +0 -1
- package/dist/agent/index.d.ts +0 -137
- package/dist/baseRender/AudioRenderer.d.ts +0 -46
- package/dist/baseRender/AudioWorklet.d.ts +0 -78
- package/dist/baseRender/AvatarRenderer.d.ts +0 -225
- package/dist/baseRender/MSEAudioPlayer.d.ts +0 -51
- package/dist/baseRender/UIRenderer.d.ts +0 -39
- package/dist/baseRender/pcm-audio-processor.d.ts +0 -63
- package/dist/control/APIForwarder.d.ts +0 -23
- package/dist/control/DataCacheQueue.d.ts +0 -69
- package/dist/control/EventDispatcher.d.ts +0 -35
- package/dist/control/RenderScheduler.d.ts +0 -171
- package/dist/control/SaveAndDownload.d.ts +0 -14
- package/dist/control/StreamingClient.d.ts +0 -33
- package/dist/control/VideoPlayer.d.ts +0 -47
- package/dist/control/ttsa.d.ts +0 -125
- package/dist/encoding/media-recorder-encoder.d.ts +0 -33
- package/dist/encoding/pcm-webm-encoder.d.ts +0 -13
- package/dist/encoding/webcodec-opus-encoder.d.ts +0 -41
- package/dist/encoding/webm-muxer.d.ts +0 -90
- package/dist/index.cjs +0 -2
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -251
- package/dist/index.modern.js +0 -2
- package/dist/index.modern.js.map +0 -1
- package/dist/index.module.js +0 -2
- package/dist/index.module.js.map +0 -1
- package/dist/index.umd.js +0 -2
- package/dist/index.umd.js.map +0 -1
- package/dist/modules/Composition.d.ts +0 -28
- package/dist/modules/ResourceManager.d.ts +0 -299
- package/dist/modules/TrackRenderer/base-track.d.ts +0 -4
- package/dist/modules/TrackRenderer/index.d.ts +0 -12
- package/dist/modules/TrackRenderer/render-implements.d.ts +0 -19
- package/dist/modules/TrackRenderer/track-pic.d.ts +0 -8
- package/dist/modules/TrackRenderer/track-subtitle.d.ts +0 -8
- package/dist/modules/cache-manager.d.ts +0 -5
- package/dist/modules/decoder.d.ts +0 -84
- package/dist/modules/error-handle.d.ts +0 -17
- package/dist/modules/network.d.ts +0 -21
- package/dist/modules/sentry-reporter.d.ts +0 -58
- package/dist/proto/face_data_pb.d.ts +0 -0
- package/dist/types/capability.d.ts +0 -15
- package/dist/types/error.d.ts +0 -70
- package/dist/types/event.d.ts +0 -25
- package/dist/types/frame-data.d.ts +0 -107
- package/dist/types/index.d.ts +0 -196
- package/dist/types/render.d.ts +0 -18
- package/dist/utils/DataInterface.d.ts +0 -113
- package/dist/utils/GLDevice.d.ts +0 -46
- package/dist/utils/GLPipeline.d.ts +0 -84
- package/dist/utils/GLPipelineDebugTools.d.ts +0 -5
- package/dist/utils/Math.d.ts +0 -21
- package/dist/utils/__tests__/capability-checker.test.d.ts +0 -1
- package/dist/utils/audio-session.d.ts +0 -11
- package/dist/utils/audio.d.ts +0 -1
- package/dist/utils/capability-checker.d.ts +0 -28
- package/dist/utils/encodeToken.d.ts +0 -14
- package/dist/utils/face.d.ts +0 -1
- package/dist/utils/float32-decoder.d.ts +0 -26
- package/dist/utils/fpsTracker.d.ts +0 -27
- package/dist/utils/index.d.ts +0 -22
- package/dist/utils/logger.d.ts +0 -9
- package/dist/utils/media-recorder-timestamp.d.ts +0 -15
- package/dist/utils/pcmAnalyzer.d.ts +0 -40
- package/dist/utils/perfermance.d.ts +0 -15
- package/dist/utils/request.d.ts +0 -20
- package/dist/utils/requestAnimateFrames.d.ts +0 -31
- package/dist/utils/time.d.ts +0 -9
- package/dist/utils/video-probe-asset.d.ts +0 -28
- package/dist/view/DebugOverlay.d.ts +0 -54
- package/dist/worker/streaming-video.d.ts +0 -1
- package/src/baseRender/AudioRenderer.ts +0 -328
- package/src/baseRender/AudioWorklet.ts +0 -776
- package/src/baseRender/AvatarRenderer.ts +0 -1550
- package/src/baseRender/MSEAudioPlayer.ts +0 -254
- package/src/baseRender/UIRenderer.ts +0 -195
- package/src/baseRender/pcm-audio-processor.js +0 -267
- package/src/control/APIForwarder.ts +0 -67
- package/src/control/DataCacheQueue.ts +0 -372
- package/src/control/EventDispatcher.ts +0 -106
- package/src/control/RenderScheduler.ts +0 -1000
- package/src/control/SaveAndDownload.js +0 -77
- package/src/control/StreamingClient.ts +0 -138
- package/src/control/VideoPlayer.ts +0 -306
- package/src/control/ttsa.ts +0 -754
- package/src/encoding/media-recorder-encoder.ts +0 -175
- package/src/encoding/pcm-webm-encoder.ts +0 -72
- package/src/encoding/webcodec-opus-encoder.ts +0 -258
- package/src/encoding/webm-muxer.ts +0 -337
- package/src/global.d.ts +0 -112
- package/src/index.ts +0 -2053
- package/src/modules/Composition.ts +0 -79
- package/src/modules/ResourceManager.ts +0 -926
- package/src/modules/TrackRenderer/base-track.ts +0 -7
- package/src/modules/TrackRenderer/index.ts +0 -40
- package/src/modules/TrackRenderer/render-implements.ts +0 -269
- package/src/modules/TrackRenderer/track-pic.ts +0 -19
- package/src/modules/TrackRenderer/track-subtitle.ts +0 -16
- package/src/modules/cache-manager.ts +0 -10
- package/src/modules/decoder.ts +0 -548
- package/src/modules/error-handle.ts +0 -20
- package/src/modules/network.ts +0 -71
- package/src/modules/sentry-reporter.ts +0 -171
- package/src/proto/face_data.proto +0 -45
- package/src/proto/face_data_pb.js +0 -1297
- package/src/proto/protobuf.min.js +0 -8
- package/src/types/capability.ts +0 -24
- package/src/types/error.ts +0 -211
- package/src/types/event.ts +0 -38
- package/src/types/frame-data.ts +0 -121
- package/src/types/index.ts +0 -220
- package/src/types/render.ts +0 -18
- package/src/utils/DataInterface.ts +0 -746
- package/src/utils/GLDevice.ts +0 -286
- package/src/utils/GLPipeline.ts +0 -1293
- package/src/utils/GLPipelineDebugTools.ts +0 -174
- package/src/utils/Math.ts +0 -266
- package/src/utils/__tests__/capability-checker.test.ts +0 -159
- package/src/utils/audio-session.ts +0 -144
- package/src/utils/audio.ts +0 -44
- package/src/utils/blueimp-md5.d.ts +0 -3
- package/src/utils/capability-checker.ts +0 -762
- package/src/utils/encodeToken.ts +0 -115
- package/src/utils/face.ts +0 -29
- package/src/utils/float32-decoder.js +0 -130
- package/src/utils/fpsTracker.ts +0 -100
- package/src/utils/index.ts +0 -182
- package/src/utils/logger.js +0 -36
- package/src/utils/media-recorder-timestamp.ts +0 -79
- package/src/utils/pcmAnalyzer.ts +0 -154
- package/src/utils/perfermance.ts +0 -141
- package/src/utils/request.ts +0 -107
- package/src/utils/requestAnimateFrames.ts +0 -195
- package/src/utils/time.ts +0 -32
- package/src/utils/video-probe-asset.ts +0 -99
- package/src/view/DebugOverlay.ts +0 -409
- package/src/worker/streaming-video.ts +0 -4647
- /package/{dist → package-dist}/agent/index.cjs +0 -0
- /package/{dist → package-dist}/agent/index.umd.js +0 -0
|
@@ -1,762 +0,0 @@
|
|
|
1
|
-
import type { CapabilitySeverity, ICapabilityCheckItem, ICapabilityCheckReport } from '../types/capability';
|
|
2
|
-
import { VIDEO_PROBE_BASE64 } from './video-probe-asset';
|
|
3
|
-
|
|
4
|
-
// ==================== 平台判定 ====================
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 判定是否为 Android 系平台(含 OpenHarmony / ArkWeb)
|
|
8
|
-
*
|
|
9
|
-
* 与 streaming-video.ts 的解码策略保持一致:
|
|
10
|
-
* - Android:主路径为 prefer-software 软解码
|
|
11
|
-
* - 其余环境:默认配置(硬解码)或客户传入配置
|
|
12
|
-
*/
|
|
13
|
-
function isAndroidPlatform(): boolean {
|
|
14
|
-
const userAgent = (navigator.userAgent || '').toLowerCase();
|
|
15
|
-
return /android/.test(userAgent) || /openharmony|arkweb/.test(userAgent);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// ==================== 同步检查(constructor 使用) ====================
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 同步检查安全上下文(HTTPS / localhost)
|
|
22
|
-
*/
|
|
23
|
-
function checkSecureContextSync(): ICapabilityCheckItem {
|
|
24
|
-
const supported = window.isSecureContext;
|
|
25
|
-
return {
|
|
26
|
-
name: 'secureContext',
|
|
27
|
-
supported,
|
|
28
|
-
severity: 'fatal',
|
|
29
|
-
reason: supported ? undefined : '当前环境不是安全上下文,请使用 HTTPS 或 localhost',
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 同步检查 WebGL2 支持(创建上下文)
|
|
35
|
-
*/
|
|
36
|
-
function checkWebGL2Sync(): ICapabilityCheckItem {
|
|
37
|
-
try {
|
|
38
|
-
const canvas = document.createElement('canvas');
|
|
39
|
-
const gl = canvas.getContext('webgl2', { failIfMajorPerformanceCaveat: true });
|
|
40
|
-
if (!gl) {
|
|
41
|
-
return {
|
|
42
|
-
name: 'webgl2',
|
|
43
|
-
supported: false,
|
|
44
|
-
severity: 'fatal',
|
|
45
|
-
reason: '浏览器不支持 WebGL2',
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
// 获取基本信息
|
|
49
|
-
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
50
|
-
const detail = {
|
|
51
|
-
version: gl.getParameter(gl.VERSION),
|
|
52
|
-
vendor: gl.getParameter(gl.VENDOR),
|
|
53
|
-
renderer: gl.getParameter(gl.RENDERER),
|
|
54
|
-
gpuVendor: debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : undefined,
|
|
55
|
-
gpuRenderer: debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : undefined,
|
|
56
|
-
};
|
|
57
|
-
return {
|
|
58
|
-
name: 'webgl2',
|
|
59
|
-
supported: true,
|
|
60
|
-
severity: 'fatal',
|
|
61
|
-
detail,
|
|
62
|
-
};
|
|
63
|
-
} catch (e) {
|
|
64
|
-
return {
|
|
65
|
-
name: 'webgl2',
|
|
66
|
-
supported: false,
|
|
67
|
-
severity: 'fatal',
|
|
68
|
-
reason: 'WebGL2 初始化失败',
|
|
69
|
-
detail: e,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 同步检查音频系统(PCM 或 MSE 任一可用即可)
|
|
76
|
-
*
|
|
77
|
-
* SDK 有两种音频播放器:
|
|
78
|
-
* 1. PCM 播放器:AudioWorklet + AudioContext(raw_audio = true)
|
|
79
|
-
* 2. MSE 播放器:MediaSource + HTMLAudioElement(raw_audio = false)
|
|
80
|
-
*
|
|
81
|
-
* 两者二选一,任一可用则 SDK 音频能力可用
|
|
82
|
-
*/
|
|
83
|
-
function checkAudioSystemSync(): ICapabilityCheckItem {
|
|
84
|
-
// PCM 路径:需要 AudioContext + AudioWorkletNode
|
|
85
|
-
const hasAudioContext = typeof (window as any).AudioContext !== 'undefined'
|
|
86
|
-
|| typeof (window as any).webkitAudioContext !== 'undefined';
|
|
87
|
-
const hasAudioWorklet = typeof (window as any).AudioWorkletNode !== 'undefined';
|
|
88
|
-
const pcmSupported = hasAudioContext && hasAudioWorklet;
|
|
89
|
-
|
|
90
|
-
// MSE 路径:需要 MediaSource + SourceBuffer
|
|
91
|
-
const hasMediaSource = typeof (window as any).MediaSource !== 'undefined';
|
|
92
|
-
const mseSupported = hasMediaSource;
|
|
93
|
-
|
|
94
|
-
const supported = pcmSupported || mseSupported;
|
|
95
|
-
|
|
96
|
-
const detail = {
|
|
97
|
-
pcm: {
|
|
98
|
-
supported: pcmSupported,
|
|
99
|
-
hasAudioContext,
|
|
100
|
-
hasAudioWorklet,
|
|
101
|
-
},
|
|
102
|
-
mse: {
|
|
103
|
-
supported: mseSupported,
|
|
104
|
-
hasMediaSource,
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
if (!supported) {
|
|
109
|
-
return {
|
|
110
|
-
name: 'audio',
|
|
111
|
-
supported: false,
|
|
112
|
-
severity: 'fatal',
|
|
113
|
-
reason: '音频播放能力不可用:PCM(AudioWorklet)和 MSE(MediaSource)均不支持',
|
|
114
|
-
detail,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
name: 'audio',
|
|
120
|
-
supported: true,
|
|
121
|
-
severity: 'fatal',
|
|
122
|
-
detail,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* 同步检查 WebCodecs API 是否存在(不探测具体编解码器)
|
|
128
|
-
*
|
|
129
|
-
* 与 streaming-video.ts 解码策略一致标注平台解码路径:
|
|
130
|
-
* - Android(含 OpenHarmony/ArkWeb):prefer-software 软解码
|
|
131
|
-
* - 其余环境:默认配置(硬解码)
|
|
132
|
-
* 实际解码配置支持度由异步 checkWebCodecsDetailed 探测
|
|
133
|
-
*/
|
|
134
|
-
function checkWebCodecsSync(): ICapabilityCheckItem {
|
|
135
|
-
const hasVideoDecoder = typeof (window as any).VideoDecoder !== 'undefined';
|
|
136
|
-
const isAndroid = isAndroidPlatform();
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
name: 'webcodecs',
|
|
140
|
-
supported: hasVideoDecoder,
|
|
141
|
-
severity: 'fatal',
|
|
142
|
-
reason: hasVideoDecoder ? undefined : '浏览器不支持 WebCodecs VideoDecoder API',
|
|
143
|
-
detail: {
|
|
144
|
-
hasVideoDecoder,
|
|
145
|
-
isAndroid,
|
|
146
|
-
hardwareAcceleration: isAndroid ? 'prefer-software' : 'default',
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* 同步检查视频解码能力(仅判定 API 存在性,不探测具体编解码器)
|
|
153
|
-
*
|
|
154
|
-
* 真实解码探测由异步 checkVideoDecodeDetailed 完成(新增能力项 videoDecode)。
|
|
155
|
-
* 此处仅作为 constructor 阶段的快速占位:VideoDecoder API 缺失是确定性缺陷,
|
|
156
|
-
* 任何平台都判 fatal。
|
|
157
|
-
*/
|
|
158
|
-
function checkVideoDecodeSync(): ICapabilityCheckItem {
|
|
159
|
-
const hasVideoDecoder = typeof (window as any).VideoDecoder !== 'undefined';
|
|
160
|
-
return {
|
|
161
|
-
name: 'videoDecode',
|
|
162
|
-
supported: hasVideoDecoder,
|
|
163
|
-
severity: 'fatal',
|
|
164
|
-
reason: hasVideoDecoder ? undefined : '浏览器不支持 WebCodecs VideoDecoder API',
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 同步检查 Web Worker 构造函数是否存在
|
|
170
|
-
*/
|
|
171
|
-
function checkWorkerSync(): ICapabilityCheckItem {
|
|
172
|
-
const supported = typeof Worker !== 'undefined';
|
|
173
|
-
return {
|
|
174
|
-
name: 'worker',
|
|
175
|
-
supported,
|
|
176
|
-
severity: 'fatal',
|
|
177
|
-
reason: supported ? undefined : '浏览器不支持 Web Worker',
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* 同步检查所有浏览器能力(用于 constructor 快速校验)
|
|
183
|
-
*
|
|
184
|
-
* 检查项:
|
|
185
|
-
* - secureContext:安全上下文(HTTPS / localhost)
|
|
186
|
-
* - webgl2:WebGL2 渲染能力
|
|
187
|
-
* - audio:音频播放能力(PCM 或 MSE 任一可用即可)
|
|
188
|
-
* - webcodecs:WebCodecs API 存在性
|
|
189
|
-
* - videoDecode:VideoDecoder API 存在性(真实解码探测见异步 checkVideoDecodeDetailed)
|
|
190
|
-
* - worker:Web Worker 支持
|
|
191
|
-
*
|
|
192
|
-
* 音频检查逻辑:SDK 有两种音频播放器(PCM 和 MSE),只需其中一种可用即可
|
|
193
|
-
* - PCM 播放器依赖:AudioContext + AudioWorkletNode
|
|
194
|
-
* - MSE 播放器依赖:MediaSource
|
|
195
|
-
* - 两者都不可用时,音频能力检查失败
|
|
196
|
-
*/
|
|
197
|
-
export function checkCapabilitySync(audioOnlyMode = false): ICapabilityCheckReport {
|
|
198
|
-
const items: ICapabilityCheckItem[] = [
|
|
199
|
-
checkSecureContextSync(),
|
|
200
|
-
];
|
|
201
|
-
|
|
202
|
-
if (audioOnlyMode) {
|
|
203
|
-
// audioOnlyMode: only check secureContext, audio, and worker
|
|
204
|
-
items.push(checkAudioSystemSync());
|
|
205
|
-
items.push(checkWorkerSync());
|
|
206
|
-
} else {
|
|
207
|
-
// Full mode: check all capabilities
|
|
208
|
-
items.push(
|
|
209
|
-
checkWebGL2Sync(),
|
|
210
|
-
checkAudioSystemSync(),
|
|
211
|
-
checkWebCodecsSync(),
|
|
212
|
-
checkVideoDecodeSync(),
|
|
213
|
-
checkWorkerSync(),
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const fatalCount = items.filter(i => i.severity === 'fatal' && !i.supported).length;
|
|
218
|
-
const warnCount = items.filter(i => i.severity === 'warn' && !i.supported).length;
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
supported: fatalCount === 0,
|
|
222
|
-
fatalCount,
|
|
223
|
-
warnCount,
|
|
224
|
-
items,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ==================== 异步检查(init 详细探测使用) ====================
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* 异步检查 WebCodecs 编解码器声明支持度(仅作诊断信息)
|
|
232
|
-
*
|
|
233
|
-
* 与 streaming-video.ts 解码策略一致:
|
|
234
|
-
* - Android(含 OpenHarmony/ArkWeb):主路径为 prefer-software 软解码,默认配置兜底
|
|
235
|
-
* - 其余环境:主路径为默认配置(硬解码)
|
|
236
|
-
*
|
|
237
|
-
* 注意:本检查使用不带 description(avcC/SPS-PPS)的 config,与真实解码 config 并不等价,
|
|
238
|
-
* 其 isConfigSupported 结论存在假通过/假失败,因此统一降级为 warn,不再参与 SDK 可用性判定。
|
|
239
|
-
* SDK 可用性由 videoDecode(真实解码探测)负责。
|
|
240
|
-
*/
|
|
241
|
-
async function checkWebCodecsDetailed(): Promise<ICapabilityCheckItem> {
|
|
242
|
-
try {
|
|
243
|
-
const hasVideoDecoder = typeof (window as any).VideoDecoder !== 'undefined';
|
|
244
|
-
|
|
245
|
-
if (!hasVideoDecoder) {
|
|
246
|
-
return {
|
|
247
|
-
name: 'webcodecs',
|
|
248
|
-
supported: false,
|
|
249
|
-
severity: 'warn',
|
|
250
|
-
reason: '浏览器不支持 WebCodecs VideoDecoder API',
|
|
251
|
-
detail: { hasVideoDecoder },
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// 平台判定与 streaming-video.ts 保持一致
|
|
256
|
-
const isAndroid = isAndroidPlatform();
|
|
257
|
-
|
|
258
|
-
// 探测配置补全尺寸字段:与业务 isConfigSupported(config) 一致(纯 codec 在部分浏览器被拒)
|
|
259
|
-
const probeW = 2160;
|
|
260
|
-
const probeH = 1920;
|
|
261
|
-
|
|
262
|
-
// 探测具体编解码器支持
|
|
263
|
-
// 注意:WebCodecs 的 codec 字段是纯编解码器字符串(如 avc1.640032),
|
|
264
|
-
// 不能用 video/mp4; codecs="..." 的 MIME 形式(会导致 isConfigSupported 抛异常)
|
|
265
|
-
let codecSupport: Record<string, { default: boolean; software: boolean | null; error?: string }> = {};
|
|
266
|
-
const testCodecs = [
|
|
267
|
-
'avc1.42E01E', // H.264 Baseline
|
|
268
|
-
'avc1.4D001F', // H.264 Main
|
|
269
|
-
'avc1.64001F', // H.264 High
|
|
270
|
-
];
|
|
271
|
-
|
|
272
|
-
// Android:仅以 prefer-software 软解码为准(硬解/默认配置不算通过)
|
|
273
|
-
// 其余平台:主路径(默认配置)与兜底配置任一支持即判定可用
|
|
274
|
-
let primarySupported = false;
|
|
275
|
-
let fallbackSupported = false;
|
|
276
|
-
// 探测过程是否出现异常(区别于"确实不支持"):异常时降级 warn,避免误杀可用环境
|
|
277
|
-
let probeFailed = false;
|
|
278
|
-
for (const codec of testCodecs) {
|
|
279
|
-
try {
|
|
280
|
-
// 默认配置(浏览器默认/硬解),业务兜底路径
|
|
281
|
-
const defaultResult = await (window as any).VideoDecoder.isConfigSupported({
|
|
282
|
-
codec,
|
|
283
|
-
codedWidth: probeW,
|
|
284
|
-
codedHeight: probeH,
|
|
285
|
-
});
|
|
286
|
-
const defaultOk = !!defaultResult?.supported;
|
|
287
|
-
if (defaultOk) {
|
|
288
|
-
fallbackSupported = true;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Android 主路径:prefer-software 软解码(与 streaming-video.ts 一致)
|
|
292
|
-
let softwareOk: boolean | null = null;
|
|
293
|
-
if (isAndroid) {
|
|
294
|
-
try {
|
|
295
|
-
const softwareResult = await (window as any).VideoDecoder.isConfigSupported({
|
|
296
|
-
codec,
|
|
297
|
-
codedWidth: probeW,
|
|
298
|
-
codedHeight: probeH,
|
|
299
|
-
hardwareAcceleration: 'prefer-software',
|
|
300
|
-
});
|
|
301
|
-
softwareOk = !!softwareResult?.supported;
|
|
302
|
-
} catch {
|
|
303
|
-
softwareOk = false;
|
|
304
|
-
}
|
|
305
|
-
if (softwareOk) {
|
|
306
|
-
primarySupported = true;
|
|
307
|
-
}
|
|
308
|
-
} else if (defaultOk) {
|
|
309
|
-
primarySupported = true;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
codecSupport[codec] = { default: defaultOk, software: softwareOk };
|
|
313
|
-
} catch {
|
|
314
|
-
// isConfigSupported 抛异常:探测方式受限(如浏览器实现差异),非"确实不支持"
|
|
315
|
-
probeFailed = true;
|
|
316
|
-
codecSupport[codec] = { default: false, software: null, error: 'probe-failed' };
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const supported = isAndroid ? primarySupported : (primarySupported || fallbackSupported);
|
|
321
|
-
return {
|
|
322
|
-
name: 'webcodecs',
|
|
323
|
-
supported,
|
|
324
|
-
// 诊断项:结论仅供参考,不参与 SDK 可用性判定(真实判据见 videoDecode)
|
|
325
|
-
severity: 'warn',
|
|
326
|
-
reason: supported
|
|
327
|
-
? undefined
|
|
328
|
-
: probeFailed
|
|
329
|
-
? 'WebCodecs 声明支持度探测异常(运行时将以实际视频配置二次确认)'
|
|
330
|
-
: isAndroid
|
|
331
|
-
? 'Android 不支持软解码(prefer-software 声明不可用)'
|
|
332
|
-
: 'WebCodecs 声明不支持 H.264(默认/软解配置均不可用)',
|
|
333
|
-
detail: {
|
|
334
|
-
hasVideoDecoder,
|
|
335
|
-
isAndroid,
|
|
336
|
-
hardwareAcceleration: isAndroid ? 'prefer-software' : 'default',
|
|
337
|
-
codecSupport,
|
|
338
|
-
},
|
|
339
|
-
};
|
|
340
|
-
} catch (e) {
|
|
341
|
-
return {
|
|
342
|
-
name: 'webcodecs',
|
|
343
|
-
supported: false,
|
|
344
|
-
severity: 'warn',
|
|
345
|
-
reason: 'WebCodecs 声明支持度探测失败',
|
|
346
|
-
detail: e,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// ==================== 视频解码真实探测 ====================
|
|
352
|
-
|
|
353
|
-
/** 探测超时:需覆盖 Worker 冷启动 + 内联 MP4Box 脚本解析 + 解码 flush */
|
|
354
|
-
const VIDEO_PROBE_TIMEOUT_MS = 2000;
|
|
355
|
-
/** 通过所需的最少输出帧数:排除"只吐出首帧即挂掉"的假活 */
|
|
356
|
-
const VIDEO_PROBE_MIN_FRAMES = 2;
|
|
357
|
-
/** 探测片帧数(10 帧),解码区间为 [0, VIDEO_PROBE_LAST_FRAME_INDEX] */
|
|
358
|
-
const VIDEO_PROBE_LAST_FRAME_INDEX = 9;
|
|
359
|
-
|
|
360
|
-
interface IVideoDecodeProbeResult {
|
|
361
|
-
supported: boolean;
|
|
362
|
-
reason?: string;
|
|
363
|
-
/** 实际输出的帧数 */
|
|
364
|
-
frames: number;
|
|
365
|
-
/** 探测耗时(毫秒) */
|
|
366
|
-
duration: number;
|
|
367
|
-
/** Worker 回报的实际探测 config 中的 codec 字符串 */
|
|
368
|
-
codec?: string;
|
|
369
|
-
width?: number;
|
|
370
|
-
height?: number;
|
|
371
|
-
detail?: unknown;
|
|
372
|
-
/** Worker 回报的主配置声明支持情况(Android 主配置为 prefer-software 软解码) */
|
|
373
|
-
primaryConfig?: { hardwareAcceleration?: string; supported: boolean };
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/** base64 解码为 ArrayBuffer(探测片以内联 base64 交付,避免引入网络依赖) */
|
|
377
|
-
function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
378
|
-
const binary = atob(base64);
|
|
379
|
-
const bytes = new Uint8Array(binary.length);
|
|
380
|
-
for (let i = 0; i < binary.length; i += 1) {
|
|
381
|
-
bytes[i] = binary.charCodeAt(i);
|
|
382
|
-
}
|
|
383
|
-
return bytes.buffer;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* 复用 streaming-video 的 Worker 执行一次真实解码
|
|
388
|
-
*
|
|
389
|
-
* 判定以"输出帧数"为主、done 为辅:
|
|
390
|
-
* - worker 在区间内扫不到关键帧时会零帧直接 done,仅判 done 会假通过
|
|
391
|
-
* - worker 内 flush() 的 catch 为空实现,flush 失败表现为"既无 done 也无 error",靠超时兜底
|
|
392
|
-
*/
|
|
393
|
-
function probeVideoDecode(
|
|
394
|
-
workerURL: string,
|
|
395
|
-
hardwareAcceleration: string,
|
|
396
|
-
): Promise<IVideoDecodeProbeResult> {
|
|
397
|
-
return new Promise((resolve) => {
|
|
398
|
-
let worker: Worker | null = null;
|
|
399
|
-
let settled = false;
|
|
400
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
401
|
-
let frames = 0;
|
|
402
|
-
let codec: string | undefined;
|
|
403
|
-
let primaryConfig: { hardwareAcceleration?: string; supported: boolean } | undefined;
|
|
404
|
-
let width: number | undefined;
|
|
405
|
-
let height: number | undefined;
|
|
406
|
-
const startAt = Date.now();
|
|
407
|
-
|
|
408
|
-
const elapsed = () => Date.now() - startAt;
|
|
409
|
-
|
|
410
|
-
const finish = (result: IVideoDecodeProbeResult) => {
|
|
411
|
-
if (settled) return;
|
|
412
|
-
settled = true;
|
|
413
|
-
if (timeout !== null) clearTimeout(timeout);
|
|
414
|
-
worker?.terminate();
|
|
415
|
-
resolve({ ...result, primaryConfig });
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
try {
|
|
419
|
-
worker = new Worker(workerURL);
|
|
420
|
-
} catch (error) {
|
|
421
|
-
finish({
|
|
422
|
-
supported: false,
|
|
423
|
-
reason: '视频解码探测 Worker 创建失败',
|
|
424
|
-
frames,
|
|
425
|
-
duration: elapsed(),
|
|
426
|
-
detail: error,
|
|
427
|
-
});
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
worker.onerror = (event) => {
|
|
432
|
-
finish({
|
|
433
|
-
supported: false,
|
|
434
|
-
reason: '视频解码探测 Worker 运行时错误',
|
|
435
|
-
frames,
|
|
436
|
-
duration: elapsed(),
|
|
437
|
-
detail: event,
|
|
438
|
-
});
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
worker.onmessage = (event: MessageEvent) => {
|
|
442
|
-
const data = event.data ?? {};
|
|
443
|
-
|
|
444
|
-
if (data.type === 'frame') {
|
|
445
|
-
frames += 1;
|
|
446
|
-
if (width == null || height == null) {
|
|
447
|
-
width = data.frame?.codedWidth ?? data.frame?.displayWidth;
|
|
448
|
-
height = data.frame?.codedHeight ?? data.frame?.displayHeight;
|
|
449
|
-
}
|
|
450
|
-
// 探测只需判定"能否出帧",帧数据不参与渲染,必须显式释放
|
|
451
|
-
data.frame?.close?.();
|
|
452
|
-
if (frames >= VIDEO_PROBE_MIN_FRAMES) {
|
|
453
|
-
finish({ supported: true, frames, duration: elapsed(), codec, width, height });
|
|
454
|
-
}
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (data.type === 'configSupport') {
|
|
459
|
-
// 用于回报"实际探测用的 codec 字符串",便于与探测片期望值比对(保鲜排查)
|
|
460
|
-
codec = data.configSupport?.config?.codec ?? codec;
|
|
461
|
-
// 只取首条:Worker 先回报主配置(Android 为 prefer-software),其后才是 defaultConfig 兜底配置
|
|
462
|
-
if (primaryConfig === undefined) {
|
|
463
|
-
primaryConfig = {
|
|
464
|
-
hardwareAcceleration: data.configSupport?.config?.hardwareAcceleration,
|
|
465
|
-
supported: !!data.configSupport?.configSupport?.supported,
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (data.type === 'error') {
|
|
472
|
-
finish({
|
|
473
|
-
supported: false,
|
|
474
|
-
reason: data.error || '视频解码失败',
|
|
475
|
-
frames,
|
|
476
|
-
duration: elapsed(),
|
|
477
|
-
codec,
|
|
478
|
-
detail: data,
|
|
479
|
-
});
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (data.type === 'done' && frames < VIDEO_PROBE_MIN_FRAMES) {
|
|
484
|
-
// done 仅作辅助信号:正常情况下帧数达标时已提前返回
|
|
485
|
-
finish({
|
|
486
|
-
supported: false,
|
|
487
|
-
reason: `视频解码未输出足够帧(${frames}/${VIDEO_PROBE_MIN_FRAMES})`,
|
|
488
|
-
frames,
|
|
489
|
-
duration: elapsed(),
|
|
490
|
-
codec,
|
|
491
|
-
detail: { done: true },
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
let probeData: ArrayBuffer;
|
|
497
|
-
try {
|
|
498
|
-
probeData = base64ToArrayBuffer(VIDEO_PROBE_BASE64);
|
|
499
|
-
} catch (error) {
|
|
500
|
-
finish({
|
|
501
|
-
supported: false,
|
|
502
|
-
reason: '视频解码探测片解析失败',
|
|
503
|
-
frames,
|
|
504
|
-
duration: elapsed(),
|
|
505
|
-
detail: error,
|
|
506
|
-
});
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
worker.postMessage(
|
|
511
|
-
{
|
|
512
|
-
type: 'decode',
|
|
513
|
-
file: { data: probeData },
|
|
514
|
-
start: 0,
|
|
515
|
-
end: VIDEO_PROBE_LAST_FRAME_INDEX,
|
|
516
|
-
hardwareAcceleration,
|
|
517
|
-
},
|
|
518
|
-
[probeData],
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
timeout = setTimeout(() => {
|
|
522
|
-
finish({
|
|
523
|
-
supported: false,
|
|
524
|
-
reason: `视频解码探测超时(${VIDEO_PROBE_TIMEOUT_MS}ms)`,
|
|
525
|
-
frames,
|
|
526
|
-
duration: elapsed(),
|
|
527
|
-
codec,
|
|
528
|
-
});
|
|
529
|
-
}, VIDEO_PROBE_TIMEOUT_MS);
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* 异步检查视频解码真实能力(使用内置最小探测片执行一次完整解码)
|
|
535
|
-
*
|
|
536
|
-
* 与 streaming-video.ts 走同一条解码链路(同一 Worker、同一份 avcC description 提取逻辑、
|
|
537
|
-
* 同一套 Android prefer-software 规则),因此探测结论等价于生产结论。
|
|
538
|
-
*
|
|
539
|
-
* severity 策略:
|
|
540
|
-
* - VideoDecoder API 缺失:确定性缺陷,任何平台均 fatal
|
|
541
|
-
* - 解码失败/超时:Android 为 fatal(本次修复的目标平台,故障已确诊);
|
|
542
|
-
* 其余平台缺乏故障证据且 WebCodecs 实现差异较大,降级 warn,避免误杀可用设备
|
|
543
|
-
*
|
|
544
|
-
* 失败后不做任何重试或换配置兜底:生产在 configure() 成功后遇到 decode() 报错即直接失败,
|
|
545
|
-
* 探测若通过换配置而通过会制造假通过。
|
|
546
|
-
*
|
|
547
|
-
* Android 额外强制软解码:即使真实解码出帧,只要主配置(prefer-software)声明不被支持,
|
|
548
|
-
* 也判定为不支持(硬解/default 兜底不算通过),reason 返回"Android 不支持软解码"。
|
|
549
|
-
*/
|
|
550
|
-
async function checkVideoDecodeDetailed(hardwareAcceleration: string): Promise<ICapabilityCheckItem> {
|
|
551
|
-
const isAndroid = isAndroidPlatform();
|
|
552
|
-
const acceleration = hardwareAcceleration || 'default';
|
|
553
|
-
|
|
554
|
-
if (typeof (window as any).VideoDecoder === 'undefined') {
|
|
555
|
-
return {
|
|
556
|
-
name: 'videoDecode',
|
|
557
|
-
supported: false,
|
|
558
|
-
severity: 'fatal',
|
|
559
|
-
reason: '浏览器不支持 WebCodecs VideoDecoder API',
|
|
560
|
-
detail: { isAndroid, hardwareAcceleration: isAndroid ? 'prefer-software' : acceleration },
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const severity: CapabilitySeverity = isAndroid ? 'fatal' : 'warn';
|
|
565
|
-
|
|
566
|
-
let workerURL: string;
|
|
567
|
-
try {
|
|
568
|
-
// 动态引入:streaming-video 模块加载时会执行 URL.createObjectURL,
|
|
569
|
-
// 顶层静态引入会让 import 阶段就产生副作用(影响 node 环境单测)
|
|
570
|
-
workerURL = (await import('../worker/streaming-video')).workerURL;
|
|
571
|
-
} catch (error) {
|
|
572
|
-
return {
|
|
573
|
-
name: 'videoDecode',
|
|
574
|
-
supported: false,
|
|
575
|
-
severity,
|
|
576
|
-
reason: '视频解码探测模块加载失败',
|
|
577
|
-
detail: { isAndroid, hardwareAcceleration: acceleration, error },
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const result = await probeVideoDecode(workerURL, acceleration);
|
|
582
|
-
|
|
583
|
-
// Android 强制要求软解码:主配置(prefer-software)声明不支持即判定失败。
|
|
584
|
-
// 生产 Worker 在 Android 上仍会用 defaultConfig(硬解)兜底,但校验不接受该兜底结果。
|
|
585
|
-
const primaryConfig = result.primaryConfig;
|
|
586
|
-
const softwareRejected = isAndroid
|
|
587
|
-
&& primaryConfig !== undefined
|
|
588
|
-
&& (primaryConfig.hardwareAcceleration !== 'prefer-software' || !primaryConfig.supported);
|
|
589
|
-
const supported = result.supported && !softwareRejected;
|
|
590
|
-
|
|
591
|
-
return {
|
|
592
|
-
name: 'videoDecode',
|
|
593
|
-
supported,
|
|
594
|
-
severity,
|
|
595
|
-
reason: supported
|
|
596
|
-
? undefined
|
|
597
|
-
: softwareRejected
|
|
598
|
-
? 'Android 不支持软解码(prefer-software 声明不可用)'
|
|
599
|
-
: result.reason,
|
|
600
|
-
detail: {
|
|
601
|
-
isAndroid,
|
|
602
|
-
hardwareAcceleration: isAndroid ? 'prefer-software' : acceleration,
|
|
603
|
-
declaredSoftwareSupported: primaryConfig?.supported,
|
|
604
|
-
probeCodec: result.codec,
|
|
605
|
-
outputFrames: result.frames,
|
|
606
|
-
outputSize: result.width != null && result.height != null
|
|
607
|
-
? `${result.width}x${result.height}`
|
|
608
|
-
: undefined,
|
|
609
|
-
duration: result.duration,
|
|
610
|
-
cause: result.detail,
|
|
611
|
-
},
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* 异步检查 Worker 实际执行能力
|
|
617
|
-
*/
|
|
618
|
-
const WORKER_CHECK_TIMEOUT_MS = 3000;
|
|
619
|
-
const WORKER_CHECK_MAX_ATTEMPTS = 2;
|
|
620
|
-
|
|
621
|
-
type WorkerProbeResult =
|
|
622
|
-
| { status: 'supported' }
|
|
623
|
-
| { status: 'timeout' }
|
|
624
|
-
| { status: 'failed'; reason: string; detail?: unknown };
|
|
625
|
-
|
|
626
|
-
function probeWorkerExecution(): Promise<WorkerProbeResult> {
|
|
627
|
-
return new Promise((resolve) => {
|
|
628
|
-
let worker: Worker | null = null;
|
|
629
|
-
let url: string | null = null;
|
|
630
|
-
let settled = false;
|
|
631
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
632
|
-
|
|
633
|
-
const finish = (result: WorkerProbeResult) => {
|
|
634
|
-
if (settled) return;
|
|
635
|
-
settled = true;
|
|
636
|
-
if (timeout !== null) clearTimeout(timeout);
|
|
637
|
-
worker?.terminate();
|
|
638
|
-
if (url) URL.revokeObjectURL(url);
|
|
639
|
-
resolve(result);
|
|
640
|
-
};
|
|
641
|
-
|
|
642
|
-
try {
|
|
643
|
-
const blob = new Blob(['self.postMessage("ok");'], { type: 'application/javascript' });
|
|
644
|
-
url = URL.createObjectURL(blob);
|
|
645
|
-
worker = new Worker(url);
|
|
646
|
-
worker.onmessage = (event) => {
|
|
647
|
-
finish(event.data === 'ok'
|
|
648
|
-
? { status: 'supported' }
|
|
649
|
-
: { status: 'failed', reason: 'Web Worker 执行异常' });
|
|
650
|
-
};
|
|
651
|
-
worker.onerror = (event) => {
|
|
652
|
-
finish({ status: 'failed', reason: 'Web Worker 创建失败', detail: event });
|
|
653
|
-
};
|
|
654
|
-
timeout = setTimeout(() => {
|
|
655
|
-
finish({ status: 'timeout' });
|
|
656
|
-
}, WORKER_CHECK_TIMEOUT_MS);
|
|
657
|
-
} catch (error) {
|
|
658
|
-
finish({ status: 'failed', reason: 'Web Worker 异常', detail: error });
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
async function checkWorkerDetailed(): Promise<ICapabilityCheckItem> {
|
|
664
|
-
if (typeof Worker === 'undefined') {
|
|
665
|
-
return {
|
|
666
|
-
name: 'worker',
|
|
667
|
-
supported: false,
|
|
668
|
-
severity: 'fatal',
|
|
669
|
-
reason: '浏览器不支持 Web Worker',
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
for (let attempt = 1; attempt <= WORKER_CHECK_MAX_ATTEMPTS; attempt += 1) {
|
|
674
|
-
const result = await probeWorkerExecution();
|
|
675
|
-
if (result.status === 'supported') {
|
|
676
|
-
return {
|
|
677
|
-
name: 'worker',
|
|
678
|
-
supported: true,
|
|
679
|
-
severity: 'fatal',
|
|
680
|
-
detail: { attempts: attempt },
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
if (result.status === 'failed') {
|
|
684
|
-
return {
|
|
685
|
-
name: 'worker',
|
|
686
|
-
supported: false,
|
|
687
|
-
severity: 'fatal',
|
|
688
|
-
reason: result.reason,
|
|
689
|
-
detail: result.detail,
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// iOS/WebKit can suspend or delay a Blob Worker while the page is being
|
|
695
|
-
// inspected, backgrounded, or recovering from a reload. The Worker API is
|
|
696
|
-
// present and no explicit error was raised, so a timing-only probe must not
|
|
697
|
-
// reject the entire SDK initialization. Real worker creation still reports
|
|
698
|
-
// its own fatal error later if the capability is genuinely unavailable.
|
|
699
|
-
return {
|
|
700
|
-
name: 'worker',
|
|
701
|
-
supported: false,
|
|
702
|
-
severity: 'warn',
|
|
703
|
-
reason: `Web Worker 创建超时(已重试 ${WORKER_CHECK_MAX_ATTEMPTS} 次)`,
|
|
704
|
-
detail: { attempts: WORKER_CHECK_MAX_ATTEMPTS },
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* 异步检查所有浏览器能力(详细版,用于 init 阶段详细探测)
|
|
710
|
-
*
|
|
711
|
-
* 包含真实视频解码探测(videoDecode)和 Worker 执行测试。
|
|
712
|
-
* audioOnlyMode 下跳过画面相关检查(webgl2 / webcodecs / videoDecode)。
|
|
713
|
-
*
|
|
714
|
-
* @param audioOnlyMode 纯音频模式,仅检查安全上下文、音频与 Worker
|
|
715
|
-
* @param hardwareAcceleration 客户传入的解码加速配置,需与生产解码保持一致
|
|
716
|
-
*/
|
|
717
|
-
export async function checkCapability(
|
|
718
|
-
audioOnlyMode = false,
|
|
719
|
-
hardwareAcceleration = 'default',
|
|
720
|
-
): Promise<ICapabilityCheckReport> {
|
|
721
|
-
// 先执行同步检查
|
|
722
|
-
const syncReport = checkCapabilitySync(audioOnlyMode);
|
|
723
|
-
|
|
724
|
-
if (audioOnlyMode) {
|
|
725
|
-
// audioOnlyMode: only detailed check worker
|
|
726
|
-
const detailedItems = await Promise.all([
|
|
727
|
-
Promise.resolve(syncReport.items.find(i => i.name === 'secureContext')!),
|
|
728
|
-
Promise.resolve(syncReport.items.find(i => i.name === 'audio')!),
|
|
729
|
-
checkWorkerDetailed(),
|
|
730
|
-
]);
|
|
731
|
-
|
|
732
|
-
const fatalCount = detailedItems.filter(i => i.severity === 'fatal' && !i.supported).length;
|
|
733
|
-
const warnCount = detailedItems.filter(i => i.severity === 'warn' && !i.supported).length;
|
|
734
|
-
|
|
735
|
-
return {
|
|
736
|
-
supported: fatalCount === 0,
|
|
737
|
-
fatalCount,
|
|
738
|
-
warnCount,
|
|
739
|
-
items: detailedItems,
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// 替换 webcodecs / videoDecode / worker 为详细版本
|
|
744
|
-
const detailedItems = await Promise.all([
|
|
745
|
-
Promise.resolve(syncReport.items.find(i => i.name === 'secureContext')!),
|
|
746
|
-
Promise.resolve(syncReport.items.find(i => i.name === 'webgl2')!),
|
|
747
|
-
Promise.resolve(syncReport.items.find(i => i.name === 'audio')!),
|
|
748
|
-
checkWebCodecsDetailed(),
|
|
749
|
-
checkVideoDecodeDetailed(hardwareAcceleration),
|
|
750
|
-
checkWorkerDetailed(),
|
|
751
|
-
]);
|
|
752
|
-
|
|
753
|
-
const fatalCount = detailedItems.filter(i => i.severity === 'fatal' && !i.supported).length;
|
|
754
|
-
const warnCount = detailedItems.filter(i => i.severity === 'warn' && !i.supported).length;
|
|
755
|
-
|
|
756
|
-
return {
|
|
757
|
-
supported: fatalCount === 0,
|
|
758
|
-
fatalCount,
|
|
759
|
-
warnCount,
|
|
760
|
-
items: detailedItems,
|
|
761
|
-
};
|
|
762
|
-
}
|