yjz-web-sdk 1.0.10 → 1.0.11-beta.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/lib/components/RemotePlayer/index.vue.d.ts +1 -73
- package/lib/composables/useCursorStyle.d.ts +1 -1
- package/lib/composables/useKeyboardControl.d.ts +1 -1
- package/lib/composables/useMouseTouchControl.d.ts +4 -4
- package/lib/composables/useRemoteVideo.d.ts +8 -25
- package/lib/composables/useResizeObserver.d.ts +1 -1
- package/lib/core/data/WebRtcError.d.ts +1 -2
- package/lib/core/data/WebrtcDataType.d.ts +1 -11
- package/lib/core/groupctrl/SdkController.d.ts +2 -2
- package/lib/core/rtc/WebRTCClient.d.ts +2 -5
- package/lib/core/rtc/WebRTCConfig.d.ts +1 -1
- package/lib/core/rtc/WebRtcNegotiate.d.ts +2 -2
- package/lib/core/signal/SignalingClient.d.ts +1 -1
- package/lib/core/util/TurnTestUtil.d.ts +2 -2
- package/lib/yjz-web-sdk.js +496 -1236
- package/package.json +5 -16
- package/lib/components/RemotePlayer/type.d.ts +0 -9
- package/lib/core/util/MapCache.d.ts +0 -20
- package/lib/render/Canvas2DRenderer.d.ts +0 -10
- package/lib/render/WebGLRenderer.d.ts +0 -16
- package/lib/render/WebGPURenderer.d.ts +0 -18
- package/lib/types/index.d.ts +0 -13
- package/lib/util/WasmUtil.d.ts +0 -17
- package/lib/worker/worker.d.ts +0 -1
- package/src/assets/icon/circle.svg +0 -1
- package/src/assets/icon/triangle.svg +0 -1
- package/src/assets/wasm/h264-atomic.wasm +0 -0
- package/src/assets/wasm/h264-simd.wasm +0 -0
- package/src/components/RemotePlayer/index.vue +0 -170
- package/src/components/RemotePlayer/type.ts +0 -11
- package/src/composables/useCursorStyle.ts +0 -15
- package/src/composables/useKeyboardControl.ts +0 -32
- package/src/composables/useMouseTouchControl.ts +0 -158
- package/src/composables/useRemoteVideo.ts +0 -248
- package/src/composables/useResizeObserver.ts +0 -27
- package/src/core/WebRTCSdk.ts +0 -561
- package/src/core/data/MessageType.ts +0 -70
- package/src/core/data/TurnType.ts +0 -25
- package/src/core/data/WebRtcError.ts +0 -93
- package/src/core/data/WebrtcDataType.ts +0 -354
- package/src/core/groupctrl/GroupCtrlSocketManager.ts +0 -94
- package/src/core/groupctrl/SdkController.ts +0 -96
- package/src/core/rtc/WebRTCClient.ts +0 -862
- package/src/core/rtc/WebRTCConfig.ts +0 -86
- package/src/core/rtc/WebRtcNegotiate.ts +0 -164
- package/src/core/signal/SignalingClient.ts +0 -221
- package/src/core/util/FileTypeUtils.ts +0 -75
- package/src/core/util/KeyCodeUtil.ts +0 -162
- package/src/core/util/Logger.ts +0 -83
- package/src/core/util/MapCache.ts +0 -135
- package/src/core/util/ScreenControlUtil.ts +0 -174
- package/src/core/util/TurnTestUtil.ts +0 -123
- package/src/env.d.ts +0 -30
- package/src/index.ts +0 -61
- package/src/render/Canvas2DRenderer.ts +0 -38
- package/src/render/WebGLRenderer.ts +0 -150
- package/src/render/WebGPURenderer.ts +0 -194
- package/src/types/index.ts +0 -15
- package/src/types/webgpu.d.ts +0 -1158
- package/src/util/WasmUtil.ts +0 -291
- package/src/worker/worker.ts +0 -292
package/src/util/WasmUtil.ts
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { AVCodecID } from "@libmedia/avutil";
|
|
2
|
-
import { base64 } from '@libmedia/common'
|
|
3
|
-
import wasmUrl from '../assets/wasm/h264-simd.wasm?url'
|
|
4
|
-
import atoWasmUrl from '../assets/wasm/h264-atomic.wasm?url'
|
|
5
|
-
import {DecoderSupportResult, RendererType, RenderingCapabilities} from "../types";
|
|
6
|
-
|
|
7
|
-
const supportAtomic = WebAssembly.validate(base64.base64ToUint8Array('AGFzbQEAAAABBgFgAX8BfwISAQNlbnYGbWVtb3J5AgMBgIACAwIBAAcJAQVsb2FkOAAACgoBCAAgAP4SAAAL'))
|
|
8
|
-
const supportSimd = WebAssembly.validate(base64.base64ToUint8Array('AGFzbQEAAAABBQFgAAF7AhIBA2VudgZtZW1vcnkCAwGAgAIDAgEACgoBCABBAP0ABAAL'))
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export function getWasm(codecId?: AVCodecID): string {
|
|
12
|
-
switch (codecId) {
|
|
13
|
-
// H264
|
|
14
|
-
case AVCodecID.AV_CODEC_ID_H264:
|
|
15
|
-
if(supportSimd){
|
|
16
|
-
return wasmUrl
|
|
17
|
-
} else if(supportAtomic){
|
|
18
|
-
return atoWasmUrl
|
|
19
|
-
} else {
|
|
20
|
-
return ''
|
|
21
|
-
}
|
|
22
|
-
default:
|
|
23
|
-
return ''
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 定义视频编码类型枚举
|
|
28
|
-
export enum VideoCodecType {
|
|
29
|
-
H264 = 'h264',
|
|
30
|
-
H265 = 'h265',
|
|
31
|
-
VP8 = 'vp8',
|
|
32
|
-
VP9 = 'vp9',
|
|
33
|
-
AV1 = 'av1',
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 对应 WebCodecs codec 字符串
|
|
37
|
-
const CodecStringMap: Record<VideoCodecType, string> = {
|
|
38
|
-
[VideoCodecType.H264]: 'avc1.42E01E', // baseline profile
|
|
39
|
-
[VideoCodecType.H265]: 'hvc1.1.6.L93.B0', // HEVC main profile
|
|
40
|
-
[VideoCodecType.VP8]: 'vp8',
|
|
41
|
-
[VideoCodecType.VP9]: 'vp09.00.10.08',
|
|
42
|
-
[VideoCodecType.AV1]: 'av01.0.04M.08',
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
export async function checkDecoderSupport(codec: VideoCodecType): Promise<DecoderSupportResult> {
|
|
47
|
-
if (typeof VideoDecoder === 'undefined' || !VideoDecoder.isConfigSupported) {
|
|
48
|
-
return { supported: false, hardware: false, software: false };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const baseConfig = {
|
|
52
|
-
codec: CodecStringMap[codec],
|
|
53
|
-
codedWidth: 720,
|
|
54
|
-
codedHeight: 1280
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
let supported = false;
|
|
58
|
-
let hardware = false;
|
|
59
|
-
let software = false;
|
|
60
|
-
|
|
61
|
-
// 基础能力检测
|
|
62
|
-
try {
|
|
63
|
-
const basic = await VideoDecoder.isConfigSupported(baseConfig as any);
|
|
64
|
-
supported = basic.supported === true;
|
|
65
|
-
} catch {}
|
|
66
|
-
|
|
67
|
-
if (!supported) {
|
|
68
|
-
return { supported: false, hardware: false, software: false };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 测试硬件解码
|
|
72
|
-
try {
|
|
73
|
-
const hard = await VideoDecoder.isConfigSupported({
|
|
74
|
-
...baseConfig,
|
|
75
|
-
hardwareAcceleration: "prefer-hardware",
|
|
76
|
-
} as any);
|
|
77
|
-
|
|
78
|
-
if (hard.supported) {
|
|
79
|
-
hardware = true;
|
|
80
|
-
}
|
|
81
|
-
} catch {}
|
|
82
|
-
|
|
83
|
-
// 测试软件解码
|
|
84
|
-
try {
|
|
85
|
-
const soft = await VideoDecoder.isConfigSupported({
|
|
86
|
-
...baseConfig,
|
|
87
|
-
hardwareAcceleration: "prefer-software",
|
|
88
|
-
} as any);
|
|
89
|
-
|
|
90
|
-
if (soft.supported) {
|
|
91
|
-
software = true;
|
|
92
|
-
}
|
|
93
|
-
} catch {}
|
|
94
|
-
|
|
95
|
-
// Safari fallback(支持 HEVC 但不返回 hardware/software)
|
|
96
|
-
if (!hardware && !software) {
|
|
97
|
-
const isSafari =
|
|
98
|
-
/^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
99
|
-
if (isSafari) {
|
|
100
|
-
software = true;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return { supported, hardware, software };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// 检查 VideoEncoder 支持
|
|
110
|
-
export async function isEncoderSupported(codec: VideoCodecType): Promise<boolean> {
|
|
111
|
-
if (typeof VideoEncoder === 'undefined' || !VideoEncoder.isConfigSupported) return false;
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const support = await (VideoEncoder as any).isConfigSupported({
|
|
115
|
-
codec: CodecStringMap[codec],
|
|
116
|
-
width: 640,
|
|
117
|
-
height: 480,
|
|
118
|
-
});
|
|
119
|
-
return support.supported;
|
|
120
|
-
} catch {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* 是否为移动端(基本所有移动端 WebGPU 都不稳定,直接禁用)
|
|
127
|
-
*/
|
|
128
|
-
const isMobileDevice = (): boolean => {
|
|
129
|
-
if (typeof navigator === 'undefined') return true
|
|
130
|
-
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS|OPiOS/i.test(
|
|
131
|
-
navigator.userAgent
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/* ==================== 1. WebGPU ==================== */
|
|
136
|
-
let _webgpuChecked = false
|
|
137
|
-
let _webgpuSupported = false
|
|
138
|
-
|
|
139
|
-
export const isWebGPUSupported = async (): Promise<boolean> => {
|
|
140
|
-
// 已经检测过直接返回缓存
|
|
141
|
-
if (_webgpuChecked) return _webgpuSupported
|
|
142
|
-
|
|
143
|
-
// 移动端直接否
|
|
144
|
-
if (isMobileDevice()) {
|
|
145
|
-
_webgpuSupported = false
|
|
146
|
-
_webgpuChecked = true
|
|
147
|
-
return false
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 基础 API 检查
|
|
151
|
-
if (!('gpu' in navigator) || typeof navigator.gpu?.requestAdapter !== 'function') {
|
|
152
|
-
_webgpuSupported = false
|
|
153
|
-
_webgpuChecked = true
|
|
154
|
-
return false
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
// 真正尝试获取 adapter —— 这是唯一 100% 可靠的方式
|
|
159
|
-
const adapter = await navigator.gpu.requestAdapter()
|
|
160
|
-
_webgpuSupported = !!adapter
|
|
161
|
-
} catch (e) {
|
|
162
|
-
// 任何异常(驱动黑名单、权限、Secure Context 等)都视为不支持
|
|
163
|
-
console.warn('[WebGPU] detection failed:', e)
|
|
164
|
-
_webgpuSupported = false
|
|
165
|
-
} finally {
|
|
166
|
-
_webgpuChecked = true
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return _webgpuSupported
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/* ==================== 2. WebGL2 ==================== */
|
|
173
|
-
let _webgl2Checked = false
|
|
174
|
-
let _webgl2Supported = false
|
|
175
|
-
|
|
176
|
-
export const isWebGL2Supported = (canvas?: HTMLCanvasElement): boolean => {
|
|
177
|
-
if (_webgl2Checked) return _webgl2Supported
|
|
178
|
-
|
|
179
|
-
const testCanvas = canvas ?? document.createElement('canvas')
|
|
180
|
-
|
|
181
|
-
// 某些浏览器即使有 WebGL2,也会在隐私模式/低电量模式下返回 null
|
|
182
|
-
const contextNames: string[] = ['webgl2', 'experimental-webgl2']
|
|
183
|
-
let gl: WebGL2RenderingContext | null = null
|
|
184
|
-
|
|
185
|
-
for (const name of contextNames) {
|
|
186
|
-
try {
|
|
187
|
-
gl = testCanvas.getContext(name) as WebGL2RenderingContext | null
|
|
188
|
-
if (gl) break
|
|
189
|
-
} catch (_) {
|
|
190
|
-
// ignore
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
_webgl2Supported = !!gl
|
|
195
|
-
_webgl2Checked = true
|
|
196
|
-
return _webgl2Supported
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/* ==================== 3. WebGL1 ==================== */
|
|
200
|
-
let _webgl1Checked = false
|
|
201
|
-
let _webgl1Supported = false
|
|
202
|
-
|
|
203
|
-
export const isWebGL1Supported = (canvas?: HTMLCanvasElement): boolean => {
|
|
204
|
-
if (_webgl1Checked) return _webgl1Supported
|
|
205
|
-
|
|
206
|
-
const testCanvas = canvas ?? document.createElement('canvas')
|
|
207
|
-
|
|
208
|
-
// 兼容老的 experimental-webgl
|
|
209
|
-
const contextNames: string[] = [
|
|
210
|
-
'webgl',
|
|
211
|
-
'experimental-webgl',
|
|
212
|
-
'webkit-3d',
|
|
213
|
-
'moz-webgl',
|
|
214
|
-
]
|
|
215
|
-
|
|
216
|
-
let gl: WebGLRenderingContext | null = null
|
|
217
|
-
for (const name of contextNames) {
|
|
218
|
-
try {
|
|
219
|
-
gl = testCanvas.getContext(name) as WebGLRenderingContext | null
|
|
220
|
-
if (gl) break
|
|
221
|
-
} catch (_) {}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// 额外检查:某些浏览器会返回 context 但实际被禁用(返回黑色画面)
|
|
225
|
-
if (gl) {
|
|
226
|
-
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')
|
|
227
|
-
if (debugInfo) {
|
|
228
|
-
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
|
|
229
|
-
if (renderer && /llvmpipe|software/i.test(renderer)) {
|
|
230
|
-
// 软件渲染器,通常性能极差,建议当做不支持
|
|
231
|
-
gl = null
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
_webgl1Supported = !!gl
|
|
237
|
-
_webgl1Checked = true
|
|
238
|
-
return _webgl1Supported
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/* ==================== 4. Canvas 2D ==================== */
|
|
242
|
-
let _canvas2dChecked = false
|
|
243
|
-
let _canvas2dSupported = false
|
|
244
|
-
|
|
245
|
-
export const isCanvas2DSupported = (canvas?: HTMLCanvasElement): boolean => {
|
|
246
|
-
if (_canvas2dChecked) return _canvas2dSupported
|
|
247
|
-
|
|
248
|
-
const testCanvas = canvas ?? document.createElement('canvas')
|
|
249
|
-
let ctx: CanvasRenderingContext2D | null = null
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
ctx = testCanvas.getContext('2d', {
|
|
253
|
-
// 防止某些极端隐私模式下返回 null
|
|
254
|
-
alpha: true,
|
|
255
|
-
desynchronized: false,
|
|
256
|
-
})
|
|
257
|
-
} catch (_) {
|
|
258
|
-
ctx = null
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// 极少数浏览器会返回 ctx 但实际被禁用
|
|
262
|
-
if (ctx && typeof ctx.fillRect === 'function') {
|
|
263
|
-
_canvas2dSupported = true
|
|
264
|
-
} else {
|
|
265
|
-
_canvas2dSupported = false
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
_canvas2dChecked = true
|
|
269
|
-
return _canvas2dSupported
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/* ==================== 统一检测入口(推荐在程序启动时调用一次)=================== */
|
|
273
|
-
export const detectRenderingCapabilities = async (): Promise<RenderingCapabilities> => {
|
|
274
|
-
const [webgpu, webgl2, webgl1, canvas2d] = await Promise.all([
|
|
275
|
-
isWebGPUSupported(),
|
|
276
|
-
Promise.resolve(isWebGL2Supported()),
|
|
277
|
-
Promise.resolve(isWebGL1Supported()),
|
|
278
|
-
Promise.resolve(isCanvas2DSupported()),
|
|
279
|
-
]);
|
|
280
|
-
|
|
281
|
-
// 按优先级选择最佳渲染方式
|
|
282
|
-
const best: RendererType =
|
|
283
|
-
(webgpu && 'webgpu') ||
|
|
284
|
-
(webgl2 && 'webgl2') ||
|
|
285
|
-
(webgl1 && 'webgl1') ||
|
|
286
|
-
(canvas2d && 'canvas2d') ||
|
|
287
|
-
'none';
|
|
288
|
-
|
|
289
|
-
return { webgpu, webgl2, webgl1, canvas2d, best };
|
|
290
|
-
}
|
|
291
|
-
|
package/src/worker/worker.ts
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import { WebVideoDecoder, WasmVideoDecoder } from "@libmedia/avcodec";
|
|
2
|
-
import {
|
|
3
|
-
addAVPacketData,
|
|
4
|
-
AVCodecID,
|
|
5
|
-
AVCodecParameters,
|
|
6
|
-
avMalloc, AVPacket, AVPacketFlags,
|
|
7
|
-
compileResource, createAVPacket, destroyAVFrame, destroyAVPacket
|
|
8
|
-
} from "@libmedia/avutil";
|
|
9
|
-
import { WebGLRenderer } from "../render/WebGLRenderer";
|
|
10
|
-
import { WebGPURenderer } from "../render/WebGPURenderer";
|
|
11
|
-
import { Canvas2DRenderer } from "../render/Canvas2DRenderer";
|
|
12
|
-
import { RenderMode, WebGLDefault8Render, WebGPUDefault8Render } from "@libmedia/avrender";
|
|
13
|
-
import { memcpyFromUint8Array } from "@libmedia/cheap";
|
|
14
|
-
import { RendererType } from "../types";
|
|
15
|
-
import { getWasm } from "../util/WasmUtil";
|
|
16
|
-
|
|
17
|
-
interface WorkerState {
|
|
18
|
-
supportH265: boolean;
|
|
19
|
-
isHardware: boolean;
|
|
20
|
-
isSoftware: boolean;
|
|
21
|
-
decoderReady: boolean;
|
|
22
|
-
webDecoder: WebVideoDecoder | null;
|
|
23
|
-
wasmDecoder: WasmVideoDecoder | null;
|
|
24
|
-
avPacket: pointer<AVPacket> | null;
|
|
25
|
-
webRenderer: WebGLRenderer | WebGPURenderer | Canvas2DRenderer | null;
|
|
26
|
-
wasmRenderer: WebGLDefault8Render | WebGPUDefault8Render | null;
|
|
27
|
-
renderType: RenderMode | null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const state: WorkerState = {
|
|
31
|
-
supportH265: false,
|
|
32
|
-
isHardware: false,
|
|
33
|
-
isSoftware: false,
|
|
34
|
-
decoderReady: false,
|
|
35
|
-
webDecoder: null,
|
|
36
|
-
wasmDecoder: null,
|
|
37
|
-
avPacket: null,
|
|
38
|
-
webRenderer: null,
|
|
39
|
-
wasmRenderer: null,
|
|
40
|
-
renderType: null
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// -------- pendingFrames 队列管理 --------
|
|
44
|
-
const MAX_PENDING_FRAMES = 100;
|
|
45
|
-
const PENDING_FRAMES_TIMEOUT = 5000; // 5 秒
|
|
46
|
-
const pendingFrames: any[] = [];
|
|
47
|
-
let pendingFramesTimer: number | null = null;
|
|
48
|
-
|
|
49
|
-
function startPendingFramesTimer() {
|
|
50
|
-
if (pendingFramesTimer) return;
|
|
51
|
-
pendingFramesTimer = setTimeout(() => {
|
|
52
|
-
pendingFrames.length = 0;
|
|
53
|
-
pendingFramesTimer = null;
|
|
54
|
-
console.warn("[Worker] pendingFrames cleared due to timeout");
|
|
55
|
-
}, PENDING_FRAMES_TIMEOUT);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function resetPendingFramesTimer() {
|
|
59
|
-
if (pendingFramesTimer) {
|
|
60
|
-
clearTimeout(pendingFramesTimer);
|
|
61
|
-
pendingFramesTimer = null;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function enqueueFrame(frame: any) {
|
|
66
|
-
if (pendingFrames.length >= MAX_PENDING_FRAMES) pendingFrames.shift(); // 丢弃最老帧
|
|
67
|
-
pendingFrames.push(frame);
|
|
68
|
-
startPendingFramesTimer();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function flushPendingFrames() {
|
|
72
|
-
resetPendingFramesTimer();
|
|
73
|
-
while (pendingFrames.length) {
|
|
74
|
-
handleDecode(pendingFrames.shift());
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function clearPendingFrames() {
|
|
79
|
-
pendingFrames.length = 0;
|
|
80
|
-
resetPendingFramesTimer();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// -------- 初始化解码器 --------
|
|
84
|
-
async function initWasmDecoder() {
|
|
85
|
-
if (state.wasmDecoder) return;
|
|
86
|
-
const codecpar = make<AVCodecParameters>();
|
|
87
|
-
codecpar.codecId = AVCodecID.AV_CODEC_ID_H264;
|
|
88
|
-
|
|
89
|
-
state.wasmDecoder = new WasmVideoDecoder({
|
|
90
|
-
resource: await compileResource(getWasm(codecpar.codecId), true),
|
|
91
|
-
onReceiveAVFrame: frame => {
|
|
92
|
-
state.wasmRenderer?.render(frame);
|
|
93
|
-
destroyAVFrame(frame);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const ret = await state.wasmDecoder.open(addressof(codecpar));
|
|
98
|
-
if (ret) {
|
|
99
|
-
state.decoderReady = false;
|
|
100
|
-
clearPendingFrames();
|
|
101
|
-
self.postMessage({ type: 'decoderError', error: 'WasmDecoder初始化失败' });
|
|
102
|
-
} else {
|
|
103
|
-
console.log('初始化成功 WasmVideoDecoder');
|
|
104
|
-
state.decoderReady = true;
|
|
105
|
-
flushPendingFrames();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function initWebDecoder(isHardware: boolean) {
|
|
110
|
-
if (state.webDecoder) return;
|
|
111
|
-
const codecpar = make<AVCodecParameters>();
|
|
112
|
-
codecpar.codecId = AVCodecID.AV_CODEC_ID_H264;
|
|
113
|
-
|
|
114
|
-
state.webDecoder = new WebVideoDecoder({
|
|
115
|
-
codec: 'avc1.42E01E',
|
|
116
|
-
onReceiveVideoFrame: frame => state.webRenderer?.render(frame),
|
|
117
|
-
onError: error => console.error(`[WebDecoder] decode error: ${error}`),
|
|
118
|
-
enableHardwareAcceleration: isHardware,
|
|
119
|
-
optimizeForLatency: true,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const ret = await state.webDecoder.open(addressof(codecpar));
|
|
123
|
-
if (ret) {
|
|
124
|
-
state.decoderReady = false;
|
|
125
|
-
clearPendingFrames();
|
|
126
|
-
self.postMessage({ type: 'decoderError', error: 'WebDecoder初始化失败' });
|
|
127
|
-
} else {
|
|
128
|
-
console.log('初始化成功 WebVideoDecoder');
|
|
129
|
-
state.decoderReady = true;
|
|
130
|
-
self.postMessage({ type: 'initSuccess' });
|
|
131
|
-
flushPendingFrames();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// -------- 初始化渲染器 --------
|
|
136
|
-
function initWebRenderer(canvas: OffscreenCanvas, renderType: RendererType) {
|
|
137
|
-
if (state.webRenderer) return;
|
|
138
|
-
try {
|
|
139
|
-
switch (renderType) {
|
|
140
|
-
case 'webgpu':
|
|
141
|
-
state.webRenderer = new WebGPURenderer(canvas);
|
|
142
|
-
break
|
|
143
|
-
case 'webgl2':
|
|
144
|
-
state.webRenderer = new WebGLRenderer('webgl2', canvas);
|
|
145
|
-
break;
|
|
146
|
-
case 'webgl1':
|
|
147
|
-
state.webRenderer = new WebGLRenderer('webgl', canvas);
|
|
148
|
-
break;
|
|
149
|
-
case 'canvas2d':
|
|
150
|
-
state.webRenderer = new Canvas2DRenderer(canvas);
|
|
151
|
-
break;
|
|
152
|
-
default:
|
|
153
|
-
throw new Error(`Unsupported render type: ${renderType}`);
|
|
154
|
-
}
|
|
155
|
-
} catch (err) {
|
|
156
|
-
self.postMessage({ type: 'rendererError', error: 'webRenderer初始化失败' });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function initWasmRender(canvas: OffscreenCanvas, pixelRatio: number, renderType: RendererType) {
|
|
161
|
-
if (state.wasmRenderer) return;
|
|
162
|
-
try {
|
|
163
|
-
switch (renderType) {
|
|
164
|
-
case 'webgpu':
|
|
165
|
-
state.wasmRenderer = new WebGPUDefault8Render(canvas, { renderMode: RenderMode.FIT, devicePixelRatio: 1 });
|
|
166
|
-
break
|
|
167
|
-
case 'webgl2':
|
|
168
|
-
case 'webgl1':
|
|
169
|
-
state.wasmRenderer = new WebGLDefault8Render(canvas, { renderMode: RenderMode.FIT, devicePixelRatio: 1 });
|
|
170
|
-
break;
|
|
171
|
-
default:
|
|
172
|
-
throw new Error(`Unsupported WASM render type: ${renderType}`);
|
|
173
|
-
}
|
|
174
|
-
await state.wasmRenderer.init();
|
|
175
|
-
state.wasmRenderer.viewport(canvas.width, canvas.height);
|
|
176
|
-
} catch (err) {
|
|
177
|
-
self.postMessage({ type: 'rendererError', error: 'wasmRenderer初始化失败' });
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// -------- 清理函数 --------
|
|
182
|
-
function clearCanvas(renderer: any) {
|
|
183
|
-
if (!renderer) return;
|
|
184
|
-
if ('clear' in renderer) {
|
|
185
|
-
renderer.clear();
|
|
186
|
-
} else if (renderer.canvas) {
|
|
187
|
-
const ctx = renderer.canvas.getContext('2d') as OffscreenCanvasRenderingContext2D | null;
|
|
188
|
-
ctx?.clearRect(0, 0, renderer.canvas.width, renderer.canvas.height);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function clearRender() {
|
|
193
|
-
if (state.avPacket) {
|
|
194
|
-
destroyAVPacket(state.avPacket);
|
|
195
|
-
state.avPacket = createAVPacket();
|
|
196
|
-
}
|
|
197
|
-
clearCanvas(state.webRenderer);
|
|
198
|
-
clearCanvas(state.wasmRenderer);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async function stopDecoding() {
|
|
202
|
-
clearRender();
|
|
203
|
-
if (state.avPacket) {
|
|
204
|
-
destroyAVPacket(state.avPacket);
|
|
205
|
-
state.avPacket = null;
|
|
206
|
-
}
|
|
207
|
-
state.webRenderer?.destroy?.();
|
|
208
|
-
state.webRenderer = null;
|
|
209
|
-
state.wasmRenderer?.destroy?.();
|
|
210
|
-
state.wasmRenderer = null;
|
|
211
|
-
|
|
212
|
-
if (state.webDecoder) {
|
|
213
|
-
await state.webDecoder.flush();
|
|
214
|
-
state.webDecoder.close();
|
|
215
|
-
state.webDecoder = null;
|
|
216
|
-
}
|
|
217
|
-
if (state.wasmDecoder) {
|
|
218
|
-
await state.wasmDecoder.flush();
|
|
219
|
-
state.wasmDecoder.close();
|
|
220
|
-
state.wasmDecoder = null;
|
|
221
|
-
}
|
|
222
|
-
state.decoderReady = false;
|
|
223
|
-
clearPendingFrames();
|
|
224
|
-
self.close();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// -------- 统一解码函数 --------
|
|
228
|
-
async function handleDecode(frame: any) {
|
|
229
|
-
if (!state.decoderReady || !state.avPacket) {
|
|
230
|
-
enqueueFrame(frame);
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
resetPendingFramesTimer();
|
|
235
|
-
|
|
236
|
-
const dataP: pointer<uint8> = avMalloc(frame.data.length);
|
|
237
|
-
memcpyFromUint8Array(dataP, frame.data.length, frame.data);
|
|
238
|
-
addAVPacketData(state.avPacket, dataP, frame.data.length);
|
|
239
|
-
state.avPacket.dts = state.avPacket.pts = BigInt(Math.round(performance.now() * 1000));
|
|
240
|
-
if (frame.label === 'key') state.avPacket.flags |= AVPacketFlags.AV_PKT_FLAG_KEY;
|
|
241
|
-
let ret
|
|
242
|
-
if (state.supportH265 && (state.isHardware || state.isSoftware)) {
|
|
243
|
-
ret = state.webDecoder?.decode(state.avPacket);
|
|
244
|
-
if(ret){
|
|
245
|
-
console.log(`webDecoder decode===>fail ${ret}`)
|
|
246
|
-
}else{
|
|
247
|
-
console.log(`webDecoder decode===>success ${ret}`)
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
ret = state.wasmDecoder?.decode(state.avPacket);
|
|
251
|
-
if(ret){
|
|
252
|
-
console.log(`wasmDecoder decode===>fail ${ret}`)
|
|
253
|
-
}else{
|
|
254
|
-
console.log(`wasmDecoder decode===>success ${ret}`)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// -------- Worker 消息处理 --------
|
|
260
|
-
self.onmessage = async (e: MessageEvent) => {
|
|
261
|
-
const { type, ...data } = e.data;
|
|
262
|
-
switch (type) {
|
|
263
|
-
case 'init':
|
|
264
|
-
state.supportH265 = data.supportH265;
|
|
265
|
-
state.isHardware = data.isHardware;
|
|
266
|
-
state.avPacket = createAVPacket();
|
|
267
|
-
|
|
268
|
-
if (state.supportH265 && (state.isHardware || state.isSoftware)) {
|
|
269
|
-
await initWebDecoder(state.isHardware);
|
|
270
|
-
initWebRenderer(data.canvas, data.renderType);
|
|
271
|
-
} else {
|
|
272
|
-
await initWasmDecoder();
|
|
273
|
-
await initWasmRender(data.canvas, data.pixelRatio, data.renderType);
|
|
274
|
-
}
|
|
275
|
-
break;
|
|
276
|
-
|
|
277
|
-
case 'decode':
|
|
278
|
-
await handleDecode(data);
|
|
279
|
-
break;
|
|
280
|
-
|
|
281
|
-
case 'stopDecode':
|
|
282
|
-
await stopDecoding();
|
|
283
|
-
break;
|
|
284
|
-
|
|
285
|
-
case 'clearRender':
|
|
286
|
-
clearRender();
|
|
287
|
-
break;
|
|
288
|
-
|
|
289
|
-
default:
|
|
290
|
-
console.warn("[Worker] Unknown message type:", type);
|
|
291
|
-
}
|
|
292
|
-
};
|