@xmov/avatar 2.0.0-alpha.38

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.
Files changed (160) hide show
  1. package/README.md +139 -0
  2. package/dist/agent/__tests__/agent.test.d.ts +1 -0
  3. package/dist/agent/audio-debug.d.ts +3 -0
  4. package/dist/agent/audio-uplink.d.ts +62 -0
  5. package/dist/agent/avatar.cjs +2 -0
  6. package/dist/agent/avatar.cjs.map +1 -0
  7. package/dist/agent/avatar.modern.js +2 -0
  8. package/dist/agent/avatar.modern.js.map +1 -0
  9. package/dist/agent/avatar.module.js +2 -0
  10. package/dist/agent/avatar.module.js.map +1 -0
  11. package/dist/agent/avatar.umd.js +2 -0
  12. package/dist/agent/avatar.umd.js.map +1 -0
  13. package/dist/agent/e2e-client.d.ts +32 -0
  14. package/dist/agent/fixed-audio-track.d.ts +48 -0
  15. package/dist/agent/index.cjs +5 -0
  16. package/dist/agent/index.d.ts +104 -0
  17. package/dist/agent/index.umd.js +10 -0
  18. package/dist/agent/microphone.d.ts +49 -0
  19. package/dist/agent/types.d.ts +153 -0
  20. package/dist/baseRender/AudioRenderer.d.ts +46 -0
  21. package/dist/baseRender/AudioWorklet.d.ts +78 -0
  22. package/dist/baseRender/AvatarRenderer.d.ts +226 -0
  23. package/dist/baseRender/MSEAudioPlayer.d.ts +51 -0
  24. package/dist/baseRender/UIRenderer.d.ts +39 -0
  25. package/dist/baseRender/pcm-audio-processor.d.ts +63 -0
  26. package/dist/control/APIForwarder.d.ts +23 -0
  27. package/dist/control/DataCacheQueue.d.ts +69 -0
  28. package/dist/control/EventDispatcher.d.ts +35 -0
  29. package/dist/control/RenderScheduler.d.ts +162 -0
  30. package/dist/control/SaveAndDownload.d.ts +14 -0
  31. package/dist/control/StreamingClient.d.ts +33 -0
  32. package/dist/control/VideoPlayer.d.ts +47 -0
  33. package/dist/control/ttsa.d.ts +111 -0
  34. package/dist/encoding/media-recorder-encoder.d.ts +33 -0
  35. package/dist/encoding/pcm-webm-encoder.d.ts +13 -0
  36. package/dist/encoding/webcodec-opus-encoder.d.ts +41 -0
  37. package/dist/encoding/webm-muxer.d.ts +90 -0
  38. package/dist/index.cjs +2 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.ts +231 -0
  41. package/dist/index.modern.js +2 -0
  42. package/dist/index.modern.js.map +1 -0
  43. package/dist/index.module.js +2 -0
  44. package/dist/index.module.js.map +1 -0
  45. package/dist/index.umd.js +2 -0
  46. package/dist/index.umd.js.map +1 -0
  47. package/dist/modules/Composition.d.ts +28 -0
  48. package/dist/modules/ResourceManager.d.ts +295 -0
  49. package/dist/modules/TrackRenderer/base-track.d.ts +4 -0
  50. package/dist/modules/TrackRenderer/index.d.ts +12 -0
  51. package/dist/modules/TrackRenderer/render-implements.d.ts +19 -0
  52. package/dist/modules/TrackRenderer/track-pic.d.ts +8 -0
  53. package/dist/modules/TrackRenderer/track-subtitle.d.ts +8 -0
  54. package/dist/modules/cache-manager.d.ts +5 -0
  55. package/dist/modules/decoder.d.ts +84 -0
  56. package/dist/modules/error-handle.d.ts +17 -0
  57. package/dist/modules/network.d.ts +21 -0
  58. package/dist/proto/face_data_pb.d.ts +0 -0
  59. package/dist/types/capability.d.ts +15 -0
  60. package/dist/types/error.d.ts +70 -0
  61. package/dist/types/event.d.ts +25 -0
  62. package/dist/types/frame-data.d.ts +107 -0
  63. package/dist/types/index.d.ts +187 -0
  64. package/dist/types/render.d.ts +18 -0
  65. package/dist/utils/DataInterface.d.ts +113 -0
  66. package/dist/utils/GLDevice.d.ts +46 -0
  67. package/dist/utils/GLPipeline.d.ts +84 -0
  68. package/dist/utils/GLPipelineDebugTools.d.ts +5 -0
  69. package/dist/utils/Math.d.ts +21 -0
  70. package/dist/utils/__tests__/capability-checker.test.d.ts +1 -0
  71. package/dist/utils/audio-session.d.ts +11 -0
  72. package/dist/utils/audio.d.ts +1 -0
  73. package/dist/utils/capability-checker.d.ts +23 -0
  74. package/dist/utils/encodeToken.d.ts +7 -0
  75. package/dist/utils/face.d.ts +1 -0
  76. package/dist/utils/float32-decoder.d.ts +26 -0
  77. package/dist/utils/fpsTracker.d.ts +27 -0
  78. package/dist/utils/index.d.ts +22 -0
  79. package/dist/utils/logger.d.ts +9 -0
  80. package/dist/utils/media-recorder-timestamp.d.ts +15 -0
  81. package/dist/utils/pcmAnalyzer.d.ts +40 -0
  82. package/dist/utils/perfermance.d.ts +15 -0
  83. package/dist/utils/request.d.ts +19 -0
  84. package/dist/utils/requestAnimateFrames.d.ts +31 -0
  85. package/dist/utils/time.d.ts +9 -0
  86. package/dist/view/DebugOverlay.d.ts +54 -0
  87. package/dist/worker/streaming-video.d.ts +1 -0
  88. package/package.json +82 -0
  89. package/src/agent/__tests__/agent.test.ts +3787 -0
  90. package/src/agent/audio-debug.ts +36 -0
  91. package/src/agent/audio-uplink.ts +392 -0
  92. package/src/agent/e2e-client.ts +215 -0
  93. package/src/agent/fixed-audio-track.ts +547 -0
  94. package/src/agent/index.ts +1393 -0
  95. package/src/agent/microphone.ts +534 -0
  96. package/src/agent/types.ts +197 -0
  97. package/src/baseRender/AudioRenderer.ts +317 -0
  98. package/src/baseRender/AudioWorklet.ts +776 -0
  99. package/src/baseRender/AvatarRenderer.ts +1559 -0
  100. package/src/baseRender/MSEAudioPlayer.ts +243 -0
  101. package/src/baseRender/UIRenderer.ts +195 -0
  102. package/src/baseRender/pcm-audio-processor.js +267 -0
  103. package/src/control/APIForwarder.ts +67 -0
  104. package/src/control/DataCacheQueue.ts +372 -0
  105. package/src/control/EventDispatcher.ts +106 -0
  106. package/src/control/RenderScheduler.ts +945 -0
  107. package/src/control/SaveAndDownload.js +77 -0
  108. package/src/control/StreamingClient.ts +138 -0
  109. package/src/control/VideoPlayer.ts +306 -0
  110. package/src/control/ttsa.ts +676 -0
  111. package/src/encoding/media-recorder-encoder.ts +175 -0
  112. package/src/encoding/pcm-webm-encoder.ts +72 -0
  113. package/src/encoding/webcodec-opus-encoder.ts +258 -0
  114. package/src/encoding/webm-muxer.ts +337 -0
  115. package/src/global.d.ts +105 -0
  116. package/src/index.ts +1920 -0
  117. package/src/modules/Composition.ts +79 -0
  118. package/src/modules/ResourceManager.ts +899 -0
  119. package/src/modules/TrackRenderer/base-track.ts +7 -0
  120. package/src/modules/TrackRenderer/index.ts +40 -0
  121. package/src/modules/TrackRenderer/render-implements.ts +269 -0
  122. package/src/modules/TrackRenderer/track-pic.ts +19 -0
  123. package/src/modules/TrackRenderer/track-subtitle.ts +16 -0
  124. package/src/modules/cache-manager.ts +10 -0
  125. package/src/modules/decoder.ts +515 -0
  126. package/src/modules/error-handle.ts +20 -0
  127. package/src/modules/network.ts +71 -0
  128. package/src/proto/face_data.proto +45 -0
  129. package/src/proto/face_data_pb.js +1297 -0
  130. package/src/proto/protobuf.min.js +8 -0
  131. package/src/types/capability.ts +23 -0
  132. package/src/types/error.ts +211 -0
  133. package/src/types/event.ts +38 -0
  134. package/src/types/frame-data.ts +121 -0
  135. package/src/types/index.ts +207 -0
  136. package/src/types/render.ts +18 -0
  137. package/src/utils/DataInterface.ts +746 -0
  138. package/src/utils/GLDevice.ts +286 -0
  139. package/src/utils/GLPipeline.ts +1293 -0
  140. package/src/utils/GLPipelineDebugTools.ts +174 -0
  141. package/src/utils/Math.ts +266 -0
  142. package/src/utils/__tests__/capability-checker.test.ts +159 -0
  143. package/src/utils/audio-session.ts +144 -0
  144. package/src/utils/audio.ts +44 -0
  145. package/src/utils/blueimp-md5.d.ts +3 -0
  146. package/src/utils/capability-checker.ts +379 -0
  147. package/src/utils/encodeToken.ts +100 -0
  148. package/src/utils/face.ts +29 -0
  149. package/src/utils/float32-decoder.js +130 -0
  150. package/src/utils/fpsTracker.ts +100 -0
  151. package/src/utils/index.ts +182 -0
  152. package/src/utils/logger.js +36 -0
  153. package/src/utils/media-recorder-timestamp.ts +79 -0
  154. package/src/utils/pcmAnalyzer.ts +154 -0
  155. package/src/utils/perfermance.ts +141 -0
  156. package/src/utils/request.ts +95 -0
  157. package/src/utils/requestAnimateFrames.ts +195 -0
  158. package/src/utils/time.ts +32 -0
  159. package/src/view/DebugOverlay.ts +404 -0
  160. package/src/worker/streaming-video.ts +4647 -0
@@ -0,0 +1,286 @@
1
+ // @ts-nocheck
2
+
3
+ export class GLDevice {
4
+ private _ElementTypeMap_JS2GL!: { readonly [index: string]: number };
5
+ private _FormatTypeMap_Ext2Int!: Map<number, Map<number, number>>;
6
+ private _FormatTypeMap_Ext2Str!: Map<number, string>;
7
+ private shaderStage!: { readonly [index: string]: number };
8
+ private running: boolean = true;
9
+ private syncMedia: HTMLVideoElement | null = null;
10
+ private syncMediaTime: DOMHighResTimeStamp | null = null;
11
+ private _lost_context_sim: WEBGL_lose_context | null = null;
12
+ canvas: HTMLCanvasElement;
13
+ gl!: WebGL2RenderingContext;
14
+ frameID: number | null;
15
+ cbRender: (device: GLDevice) => void;
16
+ private _isDestroyed: boolean = false;
17
+ private _onContextLost: (e: Event) => void;
18
+ private _onContextRestored: () => void;
19
+ private _onContextCreationError: (e: Event) => void;
20
+
21
+ constructor(canvas: HTMLCanvasElement) {
22
+ this.canvas = canvas;
23
+ this.frameID = null;
24
+
25
+ const initContext = () => {
26
+ const gl = this.canvas.getContext('webgl2', { antialias: false, premultipliedAlpha: true, preserveDrawingBuffer: true });
27
+ if (!gl) throw Error("WebGL context cannot be created."); else this.gl = gl;
28
+
29
+ this._lost_context_sim = this.gl.getExtension("WEBGL_lose_context");
30
+
31
+ this.shaderStage = {
32
+ "vertex": this.gl.VERTEX_SHADER,
33
+ "fragment": this.gl.FRAGMENT_SHADER
34
+ }
35
+
36
+ this._ElementTypeMap_JS2GL = {
37
+ "Uint8Array": this.gl.UNSIGNED_BYTE,
38
+ "Uint8ClampedArray": this.gl.UNSIGNED_BYTE,
39
+ "Int8Array": this.gl.BYTE,
40
+ "Uint16Array": this.gl.UNSIGNED_SHORT,
41
+ "Int16Array": this.gl.SHORT,
42
+ "Uint32Array": this.gl.UNSIGNED_INT,
43
+ "Int32Array": this.gl.INT,
44
+ "Float16Array": this.gl.HALF_FLOAT,
45
+ "Float32Array": this.gl.FLOAT
46
+ };
47
+
48
+ const _FormatTypeMap_Ext2Int_Raw: [number, [number, number][]][] = [
49
+ [this.gl.UNSIGNED_BYTE, [[this.gl.RED, this.gl.R8], [this.gl.RED_INTEGER, this.gl.R8UI], [this.gl.RG, this.gl.RG8], [this.gl.RG_INTEGER, this.gl.RG8UI], [this.gl.RGB, this.gl.RGB8], [this.gl.RGB_INTEGER, this.gl.RGB8UI], [this.gl.RGBA, this.gl.RGBA8], [this.gl.RGBA_INTEGER, this.gl.RGBA8UI]]],
50
+ [this.gl.BYTE, [[this.gl.RED, this.gl.R8_SNORM], [this.gl.RED_INTEGER, this.gl.R8I], [this.gl.RG, this.gl.RG8_SNORM], [this.gl.RG_INTEGER, this.gl.RG8I], [this.gl.RGB, this.gl.RGB8_SNORM], [this.gl.RGB_INTEGER, this.gl.RGB8I], [this.gl.RGBA, this.gl.RGBA8_SNORM], [this.gl.RGBA_INTEGER, this.gl.RGBA8I]]],
51
+ [this.gl.UNSIGNED_SHORT, [[this.gl.RED_INTEGER, this.gl.R16UI], [this.gl.RG_INTEGER, this.gl.RG16UI], [this.gl.RGB_INTEGER, this.gl.RGB16UI], [this.gl.RGBA_INTEGER, this.gl.RGBA16UI], [this.gl.DEPTH_COMPONENT, this.gl.DEPTH_COMPONENT16]]],
52
+ [this.gl.SHORT, [[this.gl.RED_INTEGER, this.gl.R16I], [this.gl.RG_INTEGER, this.gl.RG16I], [this.gl.RGB_INTEGER, this.gl.RGB16I], [this.gl.RGBA_INTEGER, this.gl.RGBA16I]]],
53
+ [this.gl.UNSIGNED_INT, [[this.gl.RED_INTEGER, this.gl.R32UI], [this.gl.RG_INTEGER, this.gl.RG32UI], [this.gl.RGB_INTEGER, this.gl.RGB32UI], [this.gl.RGBA_INTEGER, this.gl.RGBA32UI], [this.gl.DEPTH_COMPONENT, this.gl.DEPTH_COMPONENT24]]],
54
+ [this.gl.INT, [[this.gl.RED_INTEGER, this.gl.R32I], [this.gl.RG_INTEGER, this.gl.RG32I], [this.gl.RGB_INTEGER, this.gl.RGB32I], [this.gl.RGBA_INTEGER, this.gl.RGBA32I]]],
55
+ [this.gl.HALF_FLOAT, [[this.gl.RED, this.gl.R16F], [this.gl.RG, this.gl.RG16F], [this.gl.RGB, this.gl.RGB16F], [this.gl.RGBA, this.gl.RGBA16F]]],
56
+ [this.gl.FLOAT, [[this.gl.RED, this.gl.R32F], [this.gl.RG, this.gl.RG32F], [this.gl.RGB, this.gl.RGB32F], [this.gl.RGBA, this.gl.RGBA32F], [this.gl.DEPTH_COMPONENT, this.gl.DEPTH_COMPONENT32F]]],
57
+ [this.gl.UNSIGNED_SHORT_5_6_5, [[this.gl.RGB, this.gl.RGB565]]],
58
+ [this.gl.UNSIGNED_SHORT_4_4_4_4, [[this.gl.RGBA, this.gl.RGBA4]]],
59
+ [this.gl.UNSIGNED_SHORT_5_5_5_1, [[this.gl.RGBA, this.gl.RGB5_A1]]],
60
+ [this.gl.UNSIGNED_INT_2_10_10_10_REV, [[this.gl.RGBA, this.gl.RGB10_A2]]],
61
+ [this.gl.UNSIGNED_INT_10F_11F_11F_REV, [[this.gl.RGB, this.gl.R11F_G11F_B10F]]],
62
+ [this.gl.UNSIGNED_INT_5_9_9_9_REV, [[this.gl.RGB, this.gl.RGB9_E5]]],
63
+ [this.gl.UNSIGNED_INT_24_8, [[this.gl.DEPTH_STENCIL, this.gl.DEPTH24_STENCIL8]]],
64
+ [this.gl.FLOAT_32_UNSIGNED_INT_24_8_REV, [[this.gl.DEPTH_STENCIL, this.gl.DEPTH32F_STENCIL8]]]
65
+ ];
66
+ this._FormatTypeMap_Ext2Int = new Map();
67
+ for (const [elemType, formats] of _FormatTypeMap_Ext2Int_Raw) {
68
+ const formatMap = new Map<number, number>();
69
+ for (const [extFormat, intFormat] of formats) formatMap.set(extFormat, intFormat);
70
+ this._FormatTypeMap_Ext2Int.set(elemType, formatMap);
71
+ }
72
+
73
+ const _FormatTypeMap_Ext2Str_Raw: { [name: string]: number } = {
74
+ "GL_RED": this.gl.RED,
75
+ "GL_RED_INTEGER": this.gl.RED_INTEGER,
76
+ "GL_RG": this.gl.RG,
77
+ "GL_RG_INTEGER": this.gl.RG_INTEGER,
78
+ "GL_RGB": this.gl.RGB,
79
+ "GL_RGB_INTEGER": this.gl.RGB_INTEGER,
80
+ "GL_RGBA": this.gl.RGBA,
81
+ "GL_RGBA_INTEGER": this.gl.RGBA_INTEGER,
82
+ "GL_DEPTH_COMPONENT": this.gl.DEPTH_COMPONENT,
83
+ "GL_DEPTH_STENCIL": this.gl.DEPTH_STENCIL
84
+ }
85
+ this._FormatTypeMap_Ext2Str = new Map();
86
+ for (const [name, value] of Object.entries(_FormatTypeMap_Ext2Str_Raw)) this._FormatTypeMap_Ext2Str.set(value, name);
87
+ };
88
+
89
+ this.frameID = null;
90
+ this.cbRender = () => { };
91
+
92
+ // 使用命名函数引用,以便 destroy() 能正确移除监听器
93
+ this._onContextLost = (e: Event) => {
94
+ e.preventDefault();
95
+ this.stop();
96
+ };
97
+ this._onContextRestored = initContext;
98
+ this._onContextCreationError = (e: Event) => {
99
+ console.error((e as WebGLContextEvent).statusMessage || "WebGL context creation error: unknown error.");
100
+ };
101
+
102
+ this.canvas.addEventListener("webglcontextlost", this._onContextLost, false);
103
+ this.canvas.addEventListener("webglcontextrestored", this._onContextRestored, false);
104
+ this.canvas.addEventListener("webglcontextcreationerror", this._onContextCreationError, false);
105
+
106
+ initContext();
107
+ }
108
+ compileShaderProgram(shaders: { readonly [index: string]: string }): WebGLProgram {
109
+ let shaderProgram = this.gl.createProgram();
110
+ if (!shaderProgram) throw new Error('WebGL shader program creation failed.');
111
+
112
+ for (const [stage, code] of Object.entries(shaders)) {
113
+ const shader = this.gl.createShader(this.shaderStage[stage]);
114
+ if (!shader) throw new Error('WebGL shader creation failed.');
115
+ this.gl.shaderSource(shader, code)
116
+ this.gl.compileShader(shader)
117
+ if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
118
+ const compile_log = this.gl.getShaderInfoLog(shader);
119
+ this.gl.deleteShader(shader);
120
+ throw new Error('WebGL shader compile failed:' + compile_log);
121
+ }
122
+ this.gl.attachShader(shaderProgram, shader);
123
+ }
124
+
125
+ this.gl.linkProgram(shaderProgram);
126
+ if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
127
+ const link_log = this.gl.getProgramInfoLog(shaderProgram);
128
+ this.gl.deleteProgram(shaderProgram);
129
+ throw new Error('WebGL shader compile failed:' + link_log);
130
+ }
131
+
132
+ return shaderProgram;
133
+ }
134
+ getShaderProgramUniformLocation(program: WebGLProgram, attribNames: Array<string>): { [index: string]: WebGLUniformLocation } {
135
+ let result: { [index: string]: WebGLUniformLocation } = {};
136
+ for (const name of attribNames) result[name] = this.gl.getUniformLocation(program, name)!;
137
+ return result;
138
+ }
139
+ getShaderProgramUniformBlockLocation(program: WebGLProgram, attribNames: Array<string>): { [index: string]: GLint } {
140
+ let result: { [index: string]: GLint } = {};
141
+ for (const name of attribNames) result[name] = this.gl.getUniformBlockIndex(program, name)!;
142
+ return result;
143
+ }
144
+ getShaderProgramAttribLocation(program: WebGLProgram, attribNames: Array<string>): { [index: string]: GLint } {
145
+ let result: { [index: string]: GLint } = {};
146
+ for (const name of attribNames) result[name] = this.gl.getAttribLocation(program, name)!;
147
+ return result;
148
+ }
149
+ getGLArrayElementType(arr: ArrayBufferView<ArrayBufferLike>): number | null {
150
+ // @ts-ignore
151
+ const tag = arr[Symbol.toStringTag] as string;
152
+ if (tag in this._ElementTypeMap_JS2GL) return this._ElementTypeMap_JS2GL[tag]; else return null;
153
+ }
154
+ getGLTexInternalFormat(externalFormat: number, elementType: number): number | null {
155
+ const formats = this._FormatTypeMap_Ext2Int.get(elementType);
156
+ if (formats) {
157
+ const internalFormat = formats.get(externalFormat);
158
+ if (internalFormat) return internalFormat;
159
+ }
160
+ return null;
161
+ }
162
+ texImage2D(target: number, level: number, width: number, height: number, format: number, srcData: ArrayBufferView<ArrayBufferLike>) {
163
+ const elementType = this.getGLArrayElementType(srcData);
164
+ // @ts-ignore
165
+ if (!elementType) throw new Error(`Unsupported data format: ${srcData[Symbol.toStringTag] as string}`);
166
+ const internalFormat = this.getGLTexInternalFormat(format, elementType);
167
+ // @ts-ignore
168
+ if (!internalFormat) throw new Error(`No available internal format for ${srcData[Symbol.toStringTag] as string} and ${this._FormatTypeMap_Ext2Str.get(format) ?? "unknown"}.`);
169
+ if (this.gl.HALF_FLOAT == elementType) srcData = new Uint16Array(srcData.buffer); // patch for Float16Array
170
+ this.gl.texImage2D(target, level, internalFormat, width, height, 0, format, elementType, srcData);
171
+ }
172
+ texImage3D(target: number, level: number, width: number, height: number, depth: number, format: number, srcData: ArrayBufferView<ArrayBufferLike>) {
173
+ const elementType = this.getGLArrayElementType(srcData);
174
+ // @ts-ignore
175
+ if (!elementType) throw new Error('Unsupported data format: ' + srcData[Symbol.toStringTag] as string);
176
+ const internalFormat = this.getGLTexInternalFormat(format, elementType);
177
+ // @ts-ignore
178
+ if (!internalFormat) throw new Error(`No available internal format for ${srcData[Symbol.toStringTag] as string} and ${this._FormatTypeMap_Ext2Str.get(format) ?? "unknown"}.`);
179
+ if (this.gl.HALF_FLOAT == elementType) srcData = new Uint16Array(srcData.buffer); // patch for Float16Array
180
+ this.gl.texImage3D(target, level, internalFormat, width, height, depth, 0, format, elementType, srcData);
181
+ }
182
+ run(fn: (device: GLDevice) => void, syncMedia?: HTMLVideoElement) {
183
+ this.running = true;
184
+ this.cbRender = fn;
185
+ if (syncMedia && syncMedia instanceof HTMLVideoElement) {
186
+ this.syncMedia = syncMedia;
187
+ const fnWrapper = (now: DOMHighResTimeStamp, metadata: VideoFrameCallbackMetadata) => {
188
+ if (this.running) {
189
+ this.syncMediaTime = metadata.mediaTime;
190
+ this.cbRender(this);
191
+ this.frameID = syncMedia.requestVideoFrameCallback(fnWrapper);
192
+ }
193
+ };
194
+ this.syncMediaTime = syncMedia.currentTime;
195
+ this.cbRender(this);
196
+ syncMedia.requestVideoFrameCallback(fnWrapper);
197
+ }
198
+ else {
199
+ const fnWrapper = () => {
200
+ if (this.running) {
201
+ this.cbRender(this);
202
+ this.frameID = requestAnimationFrame(fnWrapper);
203
+ }
204
+ };
205
+ fnWrapper();
206
+ }
207
+ }
208
+ refresh() { this.cbRender(this); }
209
+ getSyncMediaTime(): number | null { return this.syncMediaTime; }
210
+ stop() {
211
+ if (null !== this.frameID) {
212
+ if (this.syncMedia) {
213
+ this.syncMedia.cancelVideoFrameCallback(this.frameID);
214
+ this.syncMedia = null;
215
+ this.syncMediaTime = null;
216
+ }
217
+ else cancelAnimationFrame(this.frameID);
218
+ this.frameID = null;
219
+ }
220
+ this.running = false;
221
+ }
222
+ simulateContextFault(contextAvailable: boolean) {
223
+ if (this._lost_context_sim) {
224
+ contextAvailable ? this._lost_context_sim.restoreContext() : this._lost_context_sim.loseContext();
225
+ }
226
+ }
227
+ captureFrame() {
228
+ const width = this.canvas.width
229
+ const height = this.canvas.height
230
+ const pixelData = new Uint8ClampedArray(width * height * 4)
231
+ this.gl.readPixels(0, 0, width, height, this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixelData);
232
+
233
+ return {
234
+ width,
235
+ height,
236
+ data: pixelData
237
+ }
238
+ }
239
+ destroy(): void {
240
+ // 1. 防止重复销毁
241
+ if (this._isDestroyed || !this.gl) {
242
+ return;
243
+ }
244
+
245
+ const gl = this.gl;
246
+
247
+ try {
248
+ // 主动丢失WebGL上下文(关键:通知浏览器回收上下文)
249
+ const loseExtension = gl.getExtension("WEBGL_lose_context");
250
+ if (loseExtension) {
251
+ loseExtension.loseContext(); // 触发上下文丢失,释放浏览器级资源
252
+ }
253
+
254
+ // 清理画布状态
255
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
256
+ gl.flush(); // 确保所有渲染命令执行完毕
257
+
258
+ // 清除DOM/引用(助力垃圾回收)
259
+ if (this.canvas) {
260
+ // 可选:从DOM移除画布(根据业务需求决定是否保留)
261
+ // this.canvas.remove();
262
+ // 清空画布内容
263
+ const ctx2d = this.canvas.getContext("2d");
264
+ if (ctx2d) {
265
+ ctx2d.clearRect(0, 0, this.canvas.width, this.canvas.height);
266
+ }
267
+ }
268
+
269
+ // 移除事件监听器(使用命名函数引用,可正确移除)
270
+ if (this.canvas) {
271
+ this.canvas.removeEventListener("webglcontextlost", this._onContextLost, false);
272
+ this.canvas.removeEventListener("webglcontextrestored", this._onContextRestored, false);
273
+ this.canvas.removeEventListener("webglcontextcreationerror", this._onContextCreationError, false);
274
+ }
275
+
276
+ // 标记上下文为null,防止后续误调用
277
+ this.gl = null;
278
+ this._isDestroyed = true;
279
+
280
+ } catch (error) {
281
+ // 即使出错,也标记为销毁(避免重复尝试)
282
+ this._isDestroyed = true;
283
+ }
284
+ }
285
+
286
+ }