@foundbyte/uni-agent 1.0.0-alpha.0

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 (32) hide show
  1. package/components/bubble-list/index.scss +254 -0
  2. package/components/bubble-list/index.vue +505 -0
  3. package/components/bubble-list/types.ts +110 -0
  4. package/components/bubble-list/use-typewriter.ts +193 -0
  5. package/components/bubble-wrapper/index.scss +34 -0
  6. package/components/bubble-wrapper/index.vue +97 -0
  7. package/components/history/index.scss +87 -0
  8. package/components/history/index.vue +118 -0
  9. package/components/history/types.ts +16 -0
  10. package/components/index.ts +4 -0
  11. package/components/prompts/index.scss +30 -0
  12. package/components/prompts/index.vue +46 -0
  13. package/components/prompts/types.ts +11 -0
  14. package/components/sender/image-picker.ts +100 -0
  15. package/components/sender/index.scss +288 -0
  16. package/components/sender/index.vue +374 -0
  17. package/components/sender/types.ts +56 -0
  18. package/composables/index.ts +6 -0
  19. package/composables/use-inner-audio/audio-manager.ts +22 -0
  20. package/composables/use-inner-audio/enums.ts +15 -0
  21. package/composables/use-inner-audio/types.ts +8 -0
  22. package/composables/use-inner-audio/use-inner-audio.ts +149 -0
  23. package/composables/use-prefix.ts +54 -0
  24. package/composables/use-recorder/h5-recorder-manager.ts +492 -0
  25. package/composables/use-recorder/native-recorder-manager.ts +181 -0
  26. package/composables/use-recorder/recorder-manager.ts +40 -0
  27. package/composables/use-recorder/types.ts +73 -0
  28. package/composables/use-recorder/use-recorder.ts +205 -0
  29. package/configs/index.ts +5 -0
  30. package/index.ts +3 -0
  31. package/package.json +90 -0
  32. package/styles/_base.scss +8 -0
@@ -0,0 +1,492 @@
1
+ import type {
2
+ ErrorCallback,
3
+ FrameRecordedCallback,
4
+ InterruptionCallback,
5
+ PauseCallback,
6
+ RecorderCallbacks,
7
+ RecorderFrameResult,
8
+ RecorderManagerOptions,
9
+ RecorderStopResult,
10
+ ResumeCallback,
11
+ StartCallback,
12
+ StopCallback,
13
+ } from './types'
14
+
15
+ // H5录音管理器实现
16
+ export class H5RecorderManager {
17
+ /** MediaRecorder 实例 */
18
+ private mediaRecorder: MediaRecorder | null = null
19
+ /** AudioContext 实例 */
20
+ private audioContext: AudioContext | null = null
21
+ /** 音频数据块 */
22
+ private audioChunks: Blob[] = []
23
+ /** 录音开始时间 */
24
+ private startTime: number | null = null
25
+ /** 是否暂停 */
26
+ private isPaused: boolean = false
27
+ /** 是否正在录音 */
28
+ private isRecording: boolean = false
29
+
30
+ /** 事件回调函数 */
31
+ private callbacks: RecorderCallbacks = {
32
+ onStart: [],
33
+ onPause: [],
34
+ onResume: [],
35
+ onStop: [],
36
+ onError: [],
37
+ onFrameRecorded: [],
38
+ onInterruptionBegin: [],
39
+ onInterruptionEnd: [],
40
+ }
41
+
42
+ /** 默认配置 */
43
+ private readonly defaultOptions: Required<RecorderManagerOptions> = {
44
+ duration: 60000,
45
+ sampleRate: 44100,
46
+ numberOfChannels: 1,
47
+ encodeBitRate: 192000,
48
+ format: 'mp3',
49
+ frameSize: 0,
50
+ }
51
+
52
+ /**
53
+ * 开始录音
54
+ * @param options 录音配置选项
55
+ */
56
+ async start(options: RecorderManagerOptions): Promise<void> {
57
+ if (this.isRecording) {
58
+ this._triggerError('录音已在进行中')
59
+ return
60
+ }
61
+
62
+ const config: Required<RecorderManagerOptions> = { ...this.defaultOptions, ...options }
63
+
64
+ // 检查浏览器支持
65
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
66
+ this._triggerError('当前浏览器不支持录音功能')
67
+ return
68
+ }
69
+
70
+ // 请求麦克风权限并开始录音
71
+ try {
72
+ const stream: MediaStream = await navigator.mediaDevices.getUserMedia({
73
+ audio: {
74
+ sampleRate: config.sampleRate,
75
+ channelCount: config.numberOfChannels,
76
+ },
77
+ })
78
+ this._initRecorder(stream, config)
79
+ } catch (err) {
80
+ const error: Error = err instanceof Error ? err : new Error(String(err))
81
+ console.error('获取麦克风权限失败:', error)
82
+ this._triggerError(`获取麦克风权限失败: ${error.message}`)
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 初始化录音器
88
+ * @param stream 媒体流
89
+ * @param config 录音配置
90
+ */
91
+ private _initRecorder(stream: MediaStream, config: Required<RecorderManagerOptions>): void {
92
+ try {
93
+ // 创建AudioContext用于音频处理
94
+ const AudioContextClass =
95
+ window.AudioContext ||
96
+ (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext
97
+ this.audioContext = new AudioContextClass()
98
+
99
+ // 创建MediaRecorder
100
+ const mimeType: string = this._getMimeType(config.format)
101
+
102
+ if (!MediaRecorder.isTypeSupported(mimeType)) {
103
+ console.warn(`不支持${config.format}格式,将使用默认格式`)
104
+ }
105
+
106
+ this.mediaRecorder = new MediaRecorder(stream, {
107
+ mimeType: MediaRecorder.isTypeSupported(mimeType) ? mimeType : undefined,
108
+ audioBitsPerSecond: config.encodeBitRate,
109
+ })
110
+
111
+ this.audioChunks = []
112
+ this.startTime = Date.now()
113
+ this.isRecording = true
114
+ this.isPaused = false
115
+
116
+ // 设置事件监听
117
+ this.mediaRecorder.ondataavailable = (event: BlobEvent) => {
118
+ if (event.data.size > 0) {
119
+ this.audioChunks.push(event.data)
120
+
121
+ // 触发帧录制回调
122
+ if (config.frameSize && this.callbacks.onFrameRecorded.length > 0) {
123
+ this._triggerFrameRecorded(event.data, false)
124
+ }
125
+ }
126
+ }
127
+
128
+ this.mediaRecorder.onstop = () => {
129
+ this._handleRecordStop(config)
130
+ }
131
+
132
+ this.mediaRecorder.onerror = (event: ErrorEvent) => {
133
+ this._triggerError(`录音过程中出现错误: ${event.error}`)
134
+ }
135
+
136
+ // 开始录音
137
+ if (config.frameSize) {
138
+ // 如果设置了frameSize,定时收集数据
139
+ this.mediaRecorder.start(config.frameSize * 1024)
140
+ } else {
141
+ this.mediaRecorder.start()
142
+ }
143
+
144
+ // 设置录音时长限制
145
+ if (config.duration && config.duration > 0) {
146
+ setTimeout(() => {
147
+ if (this.isRecording && !this.isPaused) {
148
+ this.stop()
149
+ }
150
+ }, config.duration)
151
+ }
152
+
153
+ // 触发开始事件
154
+ this._triggerStart()
155
+ } catch (err) {
156
+ console.error('初始化录音器失败:', err)
157
+ const error: Error = err instanceof Error ? err : new Error(String(err))
158
+ this._triggerError(`初始化录音器失败: ${error.message}`)
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 获取MIME类型
164
+ * @param format 音频格式
165
+ * @returns MIME类型字符串
166
+ */
167
+ private _getMimeType(format: string): string {
168
+ const mimeTypes: Record<string, string> = {
169
+ mp3: 'audio/mpeg',
170
+ wav: 'audio/wav',
171
+ aac: 'audio/aac',
172
+ PCM: 'audio/wav',
173
+ }
174
+ return mimeTypes[format] || 'audio/webm'
175
+ }
176
+
177
+ /**
178
+ * 暂停录音
179
+ */
180
+ pause(): void {
181
+ if (!this.isRecording || this.isPaused) {
182
+ return
183
+ }
184
+
185
+ if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
186
+ this.mediaRecorder.pause()
187
+ this.isPaused = true
188
+ this._triggerPause()
189
+ }
190
+ }
191
+
192
+ /**
193
+ * 继续录音
194
+ */
195
+ resume(): void {
196
+ if (!this.isRecording || !this.isPaused) {
197
+ return
198
+ }
199
+
200
+ if (this.mediaRecorder && this.mediaRecorder.state === 'paused') {
201
+ this.mediaRecorder.resume()
202
+ this.isPaused = false
203
+ this._triggerResume()
204
+ }
205
+ }
206
+
207
+ /**
208
+ * 停止录音
209
+ */
210
+ stop(): void {
211
+ if (!this.isRecording) {
212
+ return
213
+ }
214
+
215
+ if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
216
+ this.mediaRecorder.stop()
217
+ }
218
+
219
+ // 停止所有音频轨道
220
+ if (this.mediaRecorder && this.mediaRecorder.stream) {
221
+ this.mediaRecorder.stream.getTracks().forEach((track: MediaStreamTrack) => track.stop())
222
+ }
223
+
224
+ this.isRecording = false
225
+ this.isPaused = false
226
+ }
227
+
228
+ /**
229
+ * 处理录音停止
230
+ * @param config 录音配置
231
+ */
232
+ private _handleRecordStop(config: Required<RecorderManagerOptions>): void {
233
+ if (this.audioChunks.length === 0) {
234
+ this._triggerError('没有录制到音频数据')
235
+ return
236
+ }
237
+
238
+ // 创建音频文件
239
+ const audioBlob: Blob = new Blob(this.audioChunks, {
240
+ type: this._getMimeType(config.format),
241
+ })
242
+
243
+ // 创建临时文件URL
244
+ const tempFilePath: string = URL.createObjectURL(audioBlob)
245
+
246
+ // 计算录音时长
247
+ const duration: number = this.startTime ? (Date.now() - this.startTime) / 1000 : 0
248
+
249
+ // 触发停止事件
250
+ this._triggerStop({
251
+ tempFilePath,
252
+ duration,
253
+ fileSize: audioBlob.size,
254
+ })
255
+
256
+ // 清理资源
257
+ this._cleanup()
258
+ }
259
+
260
+ /**
261
+ * 清理资源
262
+ */
263
+ private _cleanup(): void {
264
+ if (this.audioContext) {
265
+ this.audioContext.close()
266
+ this.audioContext = null
267
+ }
268
+
269
+ this.mediaRecorder = null
270
+ this.audioChunks = []
271
+ this.startTime = null
272
+ }
273
+
274
+ /**
275
+ * 监听录音开始事件
276
+ * @param callback 回调函数
277
+ */
278
+ onStart(callback: StartCallback): void {
279
+ if (typeof callback === 'function') {
280
+ this.callbacks.onStart.push(callback)
281
+ }
282
+ }
283
+
284
+ /**
285
+ * 监听录音暂停事件
286
+ * @param callback 回调函数
287
+ */
288
+ onPause(callback: PauseCallback): void {
289
+ if (typeof callback === 'function') {
290
+ this.callbacks.onPause.push(callback)
291
+ }
292
+ }
293
+
294
+ /**
295
+ * 监听录音继续事件
296
+ * @param callback 回调函数
297
+ */
298
+ onResume(callback: ResumeCallback): void {
299
+ if (typeof callback === 'function') {
300
+ this.callbacks.onResume.push(callback)
301
+ }
302
+ }
303
+
304
+ /**
305
+ * 监听录音停止事件
306
+ * @param callback 回调函数
307
+ */
308
+ onStop(callback: StopCallback): void {
309
+ if (typeof callback === 'function') {
310
+ this.callbacks.onStop.push(callback)
311
+ }
312
+ }
313
+
314
+ /**
315
+ * 监听录音错误事件
316
+ * @param callback 回调函数
317
+ */
318
+ onError(callback: ErrorCallback): void {
319
+ if (typeof callback === 'function') {
320
+ this.callbacks.onError.push(callback)
321
+ }
322
+ }
323
+
324
+ /**
325
+ * 监听录音帧录制事件
326
+ * @param callback 回调函数
327
+ */
328
+ onFrameRecorded(callback: FrameRecordedCallback): void {
329
+ if (typeof callback === 'function') {
330
+ this.callbacks.onFrameRecorded.push(callback)
331
+ }
332
+ }
333
+
334
+ /**
335
+ * 监听录音中断开始事件
336
+ * @param callback 回调函数
337
+ */
338
+ onInterruptionBegin(callback: InterruptionCallback): void {
339
+ if (typeof callback === 'function') {
340
+ this.callbacks.onInterruptionBegin.push(callback)
341
+ }
342
+ }
343
+
344
+ /**
345
+ * 监听录音中断结束事件
346
+ * @param callback 回调函数
347
+ */
348
+ onInterruptionEnd(callback: InterruptionCallback): void {
349
+ if (typeof callback === 'function') {
350
+ this.callbacks.onInterruptionEnd.push(callback)
351
+ }
352
+ }
353
+
354
+ /**
355
+ * 移除录音开始事件监听(支付宝小程序兼容)
356
+ * @param callback 回调函数
357
+ */
358
+ offStart(callback?: () => void): void {
359
+ this._removeCallback('onStart', callback)
360
+ }
361
+
362
+ /**
363
+ * 移除录音暂停事件监听(支付宝小程序兼容)
364
+ * @param callback 回调函数
365
+ */
366
+ offPause(callback?: () => void): void {
367
+ this._removeCallback('onPause', callback)
368
+ }
369
+
370
+ /**
371
+ * 移除录音继续事件监听(支付宝小程序兼容)
372
+ * @param callback 回调函数
373
+ */
374
+ offResume(callback?: () => void): void {
375
+ this._removeCallback('onResume', callback)
376
+ }
377
+
378
+ /**
379
+ * 移除录音停止事件监听(支付宝小程序兼容)
380
+ * @param callback 回调函数
381
+ */
382
+ offStop(callback?: (result: RecorderStopResult) => void): void {
383
+ this._removeCallback('onStop', callback)
384
+ }
385
+
386
+ /**
387
+ * 移除录音帧录制事件监听(支付宝小程序兼容)
388
+ * @param callback 回调函数
389
+ */
390
+ offFrameRecorded(callback?: (result: RecorderFrameResult) => void): void {
391
+ this._removeCallback('onFrameRecorded', callback)
392
+ }
393
+
394
+ /**
395
+ * 移除回调函数
396
+ * @param eventName 事件名称
397
+ * @param callback 回调函数
398
+ */
399
+ private _removeCallback(eventName: keyof RecorderCallbacks, callback?: unknown): void {
400
+ if (!callback) return
401
+ const callbacks: Array<unknown> = this.callbacks[eventName]
402
+ const index = callbacks.indexOf(callback)
403
+ if (index > -1) {
404
+ callbacks.splice(index, 1)
405
+ }
406
+ }
407
+
408
+ /**
409
+ * 触发开始事件
410
+ */
411
+ private _triggerStart(): void {
412
+ this.callbacks.onStart.forEach((callback: StartCallback) => {
413
+ try {
414
+ callback()
415
+ } catch (error) {
416
+ console.error('onStart callback error:', error)
417
+ }
418
+ })
419
+ }
420
+
421
+ /**
422
+ * 触发暂停事件
423
+ */
424
+ private _triggerPause(): void {
425
+ this.callbacks.onPause.forEach((callback: PauseCallback) => {
426
+ try {
427
+ callback()
428
+ } catch (error) {
429
+ console.error('onPause callback error:', error)
430
+ }
431
+ })
432
+ }
433
+
434
+ /**
435
+ * 触发继续事件
436
+ */
437
+ private _triggerResume(): void {
438
+ this.callbacks.onResume.forEach((callback: ResumeCallback) => {
439
+ try {
440
+ callback()
441
+ } catch (error) {
442
+ console.error('onResume callback error:', error)
443
+ }
444
+ })
445
+ }
446
+
447
+ /**
448
+ * 触发停止事件
449
+ * @param result 录音结果
450
+ */
451
+ private _triggerStop(result: RecorderStopResult): void {
452
+ this.callbacks.onStop.forEach((callback: StopCallback) => {
453
+ try {
454
+ callback(result)
455
+ } catch (error) {
456
+ console.error('onStop callback error:', error)
457
+ }
458
+ })
459
+ }
460
+
461
+ /**
462
+ * 触发错误事件
463
+ * @param errMsg 错误信息
464
+ */
465
+ private _triggerError(errMsg: string): void {
466
+ this.callbacks.onError.forEach((callback: ErrorCallback) => {
467
+ try {
468
+ callback({ errMsg })
469
+ } catch (error) {
470
+ console.error('onError callback error:', error)
471
+ }
472
+ })
473
+ }
474
+
475
+ /**
476
+ * 触发帧录制事件
477
+ * @param frameBuffer 帧数据
478
+ * @param isLastFrame 是否最后一帧
479
+ */
480
+ private _triggerFrameRecorded(frameBuffer: Blob, isLastFrame: boolean = false): void {
481
+ this.callbacks.onFrameRecorded.forEach((callback: FrameRecordedCallback) => {
482
+ try {
483
+ callback({
484
+ frameBuffer,
485
+ isLastFrame,
486
+ })
487
+ } catch (error) {
488
+ console.error('onFrameRecorded callback error:', error)
489
+ }
490
+ })
491
+ }
492
+ }
@@ -0,0 +1,181 @@
1
+ import type { H5RecorderManager } from './h5-recorder-manager'
2
+ import type {
3
+ ErrorCallback,
4
+ FrameRecordedCallback,
5
+ InterruptionCallback,
6
+ PauseCallback,
7
+ RecorderFrameResult,
8
+ RecorderManagerOptions,
9
+ RecorderStopResult,
10
+ ResumeCallback,
11
+ StartCallback,
12
+ StopCallback,
13
+ } from './types'
14
+
15
+ interface RecorderManager
16
+ extends
17
+ UniApp.RecorderManager,
18
+ Pick<
19
+ H5RecorderManager,
20
+ | 'onResume'
21
+ | 'onInterruptionBegin'
22
+ | 'onInterruptionEnd'
23
+ | 'offStart'
24
+ | 'offPause'
25
+ | 'offResume'
26
+ | 'offStop'
27
+ | 'offFrameRecorded'
28
+ > {}
29
+
30
+ // 原生平台录音管理器包装器
31
+ export class NativeRecorderManager {
32
+ /** 原生录音管理器 */
33
+ private nativeManager: RecorderManager
34
+
35
+ constructor() {
36
+ this.nativeManager = uni.getRecorderManager() as RecorderManager
37
+ }
38
+
39
+ /**
40
+ * 开始录音
41
+ * @param options 录音配置选项
42
+ */
43
+ start = (options: RecorderManagerOptions = {}): void => {
44
+ return this.nativeManager.start(options)
45
+ }
46
+
47
+ /**
48
+ * 暂停录音
49
+ */
50
+ pause = (): void => {
51
+ return this.nativeManager.pause()
52
+ }
53
+
54
+ /**
55
+ * 继续录音
56
+ */
57
+ resume = (): void => {
58
+ return this.nativeManager.resume()
59
+ }
60
+
61
+ /**
62
+ * 停止录音
63
+ */
64
+ stop = (): void => {
65
+ return this.nativeManager.stop()
66
+ }
67
+
68
+ /**
69
+ * 监听录音开始事件
70
+ * @param callback 回调函数
71
+ */
72
+ onStart = (callback: StartCallback): void => {
73
+ return this.nativeManager.onStart(callback)
74
+ }
75
+
76
+ /**
77
+ * 监听录音暂停事件
78
+ * @param callback 回调函数
79
+ */
80
+ onPause = (callback: PauseCallback): void => {
81
+ return this.nativeManager.onPause(callback)
82
+ }
83
+
84
+ /**
85
+ * 监听录音继续事件
86
+ * @param callback 回调函数
87
+ */
88
+ onResume = (callback: ResumeCallback): void => {
89
+ return this.nativeManager.onResume(callback)
90
+ }
91
+
92
+ /**
93
+ * 监听录音停止事件
94
+ * @param callback 回调函数
95
+ */
96
+ onStop = (callback: StopCallback): void => {
97
+ return this.nativeManager.onStop(callback)
98
+ }
99
+
100
+ /**
101
+ * 监听录音错误事件
102
+ * @param callback 回调函数
103
+ */
104
+ onError = (callback: ErrorCallback): void => {
105
+ return this.nativeManager.onError(callback)
106
+ }
107
+
108
+ /**
109
+ * 监听录音帧录制事件
110
+ * @param callback 回调函数
111
+ */
112
+ onFrameRecorded = (callback: FrameRecordedCallback): void => {
113
+ return this.nativeManager.onFrameRecorded(callback)
114
+ }
115
+
116
+ /**
117
+ * 监听录音中断开始事件
118
+ * @param callback 回调函数
119
+ */
120
+ onInterruptionBegin = (callback: InterruptionCallback): void => {
121
+ return this.nativeManager.onInterruptionBegin(callback)
122
+ }
123
+
124
+ /**
125
+ * 监听录音中断结束事件
126
+ * @param callback 回调函数
127
+ */
128
+ onInterruptionEnd = (callback: InterruptionCallback): void => {
129
+ return this.nativeManager.onInterruptionEnd(callback)
130
+ }
131
+
132
+ /**
133
+ * 移除录音开始事件监听(支付宝小程序兼容)
134
+ * @param callback 回调函数
135
+ */
136
+ offStart = (callback?: () => void): void => {
137
+ if (this.nativeManager.offStart) {
138
+ return this.nativeManager.offStart(callback)
139
+ }
140
+ }
141
+
142
+ /**
143
+ * 移除录音暂停事件监听(支付宝小程序兼容)
144
+ * @param callback 回调函数
145
+ */
146
+ offPause = (callback?: () => void): void => {
147
+ if (this.nativeManager.offPause) {
148
+ return this.nativeManager.offPause(callback)
149
+ }
150
+ }
151
+
152
+ /**
153
+ * 移除录音继续事件监听(支付宝小程序兼容)
154
+ * @param callback 回调函数
155
+ */
156
+ offResume = (callback?: () => void): void => {
157
+ if (this.nativeManager.offResume) {
158
+ return this.nativeManager.offResume(callback)
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 移除录音停止事件监听(支付宝小程序兼容)
164
+ * @param callback 回调函数
165
+ */
166
+ offStop = (callback?: (result: RecorderStopResult) => void): void => {
167
+ if (this.nativeManager.offStop) {
168
+ return this.nativeManager.offStop(callback)
169
+ }
170
+ }
171
+
172
+ /**
173
+ * 移除录音帧录制事件监听(支付宝小程序兼容)
174
+ * @param callback 回调函数
175
+ */
176
+ offFrameRecorded = (callback?: (result: RecorderFrameResult) => void): void => {
177
+ if (this.nativeManager.offFrameRecorded) {
178
+ return this.nativeManager.offFrameRecorded(callback)
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,40 @@
1
+ // H5录音管理插件
2
+ // 完全兼容 uni.getRecorderManager API
3
+ // https://gitcode.com/Etangzeng/jz-h5-getRecorderManager
4
+
5
+ import { H5RecorderManager } from './h5-recorder-manager'
6
+ import { NativeRecorderManager } from './native-recorder-manager'
7
+
8
+ // 检测当前平台
9
+ function isH5Platform(): boolean {
10
+ // #ifdef H5
11
+ return true
12
+ // #endif
13
+ return false
14
+ }
15
+
16
+ // 全局实例
17
+ let recorderManagerInstance: H5RecorderManager | NativeRecorderManager | null = null
18
+
19
+ /** 录音管理器类型 */
20
+ export type RecorderManager = H5RecorderManager | NativeRecorderManager
21
+
22
+ /**
23
+ * 获取录音管理器
24
+ * @returns 录音管理器实例
25
+ */
26
+ export function getRecorderManager(): RecorderManager {
27
+ if (isH5Platform()) {
28
+ // H5平台使用自定义实现
29
+ if (!recorderManagerInstance) {
30
+ recorderManagerInstance = new H5RecorderManager()
31
+ }
32
+ return recorderManagerInstance
33
+ } else {
34
+ // 非H5平台使用原生管理器包装器,确保单例
35
+ if (!recorderManagerInstance) {
36
+ recorderManagerInstance = new NativeRecorderManager()
37
+ }
38
+ return recorderManagerInstance
39
+ }
40
+ }