@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.
- package/components/bubble-list/index.scss +254 -0
- package/components/bubble-list/index.vue +505 -0
- package/components/bubble-list/types.ts +110 -0
- package/components/bubble-list/use-typewriter.ts +193 -0
- package/components/bubble-wrapper/index.scss +34 -0
- package/components/bubble-wrapper/index.vue +97 -0
- package/components/history/index.scss +87 -0
- package/components/history/index.vue +118 -0
- package/components/history/types.ts +16 -0
- package/components/index.ts +4 -0
- package/components/prompts/index.scss +30 -0
- package/components/prompts/index.vue +46 -0
- package/components/prompts/types.ts +11 -0
- package/components/sender/image-picker.ts +100 -0
- package/components/sender/index.scss +288 -0
- package/components/sender/index.vue +374 -0
- package/components/sender/types.ts +56 -0
- package/composables/index.ts +6 -0
- package/composables/use-inner-audio/audio-manager.ts +22 -0
- package/composables/use-inner-audio/enums.ts +15 -0
- package/composables/use-inner-audio/types.ts +8 -0
- package/composables/use-inner-audio/use-inner-audio.ts +149 -0
- package/composables/use-prefix.ts +54 -0
- package/composables/use-recorder/h5-recorder-manager.ts +492 -0
- package/composables/use-recorder/native-recorder-manager.ts +181 -0
- package/composables/use-recorder/recorder-manager.ts +40 -0
- package/composables/use-recorder/types.ts +73 -0
- package/composables/use-recorder/use-recorder.ts +205 -0
- package/configs/index.ts +5 -0
- package/index.ts +3 -0
- package/package.json +90 -0
- 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
|
+
}
|