@siteed/expo-audio-stream 1.7.2 → 1.8.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/CHANGELOG.md +17 -1
- package/README.md +6 -1
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +39 -0
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +124 -12
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +26 -2
- package/build/AudioRecorder.provider.d.ts.map +1 -1
- package/build/AudioRecorder.provider.js +1 -0
- package/build/AudioRecorder.provider.js.map +1 -1
- package/build/ExpoAudioStream.types.d.ts +35 -1
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +14 -3
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +102 -38
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/WebRecorder.web.d.ts +11 -2
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +178 -43
- package/build/WebRecorder.web.js.map +1 -1
- package/build/events.d.ts +6 -0
- package/build/events.d.ts.map +1 -1
- package/build/events.js.map +1 -1
- package/build/useAudioRecorder.d.ts +3 -2
- package/build/useAudioRecorder.d.ts.map +1 -1
- package/build/useAudioRecorder.js +46 -5
- package/build/useAudioRecorder.js.map +1 -1
- package/ios/AudioStreamManager.swift +127 -8
- package/ios/AudioStreamManagerDelegate.swift +8 -2
- package/ios/ExpoAudioStreamModule.swift +61 -46
- package/ios/RecordingResult.swift +2 -0
- package/ios/RecordingSettings.swift +63 -3
- package/package.json +1 -1
- package/src/AudioRecorder.provider.tsx +1 -0
- package/src/ExpoAudioStream.types.ts +38 -1
- package/src/ExpoAudioStream.web.ts +114 -38
- package/src/WebRecorder.web.ts +210 -64
- package/src/events.ts +7 -0
- package/src/useAudioRecorder.tsx +70 -8
|
@@ -9,6 +9,7 @@ struct RecordingResult {
|
|
|
9
9
|
var channels: Int
|
|
10
10
|
var bitDepth: Int
|
|
11
11
|
var sampleRate: Double
|
|
12
|
+
var compression: CompressedRecordingInfo?
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
struct StartRecordingResult {
|
|
@@ -17,4 +18,5 @@ struct StartRecordingResult {
|
|
|
17
18
|
var channels: Int
|
|
18
19
|
var bitDepth: Int
|
|
19
20
|
var sampleRate: Double
|
|
21
|
+
var compression: CompressedRecordingInfo?
|
|
20
22
|
}
|
|
@@ -17,6 +17,27 @@ struct IOSNotificationConfig {
|
|
|
17
17
|
var categoryIdentifier: String?
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
struct CompressedRecordingInfo {
|
|
21
|
+
var fileUri: String
|
|
22
|
+
var mimeType: String
|
|
23
|
+
var bitrate: Int
|
|
24
|
+
var format: String
|
|
25
|
+
|
|
26
|
+
static func validate(format: String, bitrate: Int) -> Result<Void, Error> {
|
|
27
|
+
// Validate format
|
|
28
|
+
guard ["aac", "opus"].contains(format.lowercased()) else {
|
|
29
|
+
return .failure(RecordingError.unsupportedFormat(format))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Validate bitrate
|
|
33
|
+
guard (8000...960000).contains(bitrate) else {
|
|
34
|
+
return .failure(RecordingError.invalidBitrate(bitrate))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return .success(())
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
20
41
|
struct NotificationConfig {
|
|
21
42
|
var title: String?
|
|
22
43
|
var text: String?
|
|
@@ -28,6 +49,20 @@ struct IOSConfig {
|
|
|
28
49
|
var audioSession: IOSAudioSessionConfig?
|
|
29
50
|
}
|
|
30
51
|
|
|
52
|
+
enum RecordingError: Error {
|
|
53
|
+
case unsupportedFormat(String)
|
|
54
|
+
case invalidBitrate(Int)
|
|
55
|
+
|
|
56
|
+
var localizedDescription: String {
|
|
57
|
+
switch self {
|
|
58
|
+
case .unsupportedFormat(let format):
|
|
59
|
+
return "Unsupported compression format: \(format). iOS only supports AAC."
|
|
60
|
+
case .invalidBitrate(let bitrate):
|
|
61
|
+
return "Invalid bitrate: \(bitrate). Must be between 8000 and 960000 bps."
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
31
66
|
struct RecordingSettings {
|
|
32
67
|
// Core recording settings
|
|
33
68
|
var sampleRate: Double
|
|
@@ -52,10 +87,35 @@ struct RecordingSettings {
|
|
|
52
87
|
// Notification configuration
|
|
53
88
|
var notification: NotificationConfig?
|
|
54
89
|
|
|
55
|
-
|
|
90
|
+
let enableCompressedOutput: Bool
|
|
91
|
+
let compressedFormat: String // "aac" or "opus"
|
|
92
|
+
let compressedBitRate: Int
|
|
93
|
+
|
|
94
|
+
static func fromDictionary(_ dict: [String: Any]) -> Result<RecordingSettings, Error> {
|
|
95
|
+
// Extract compression settings
|
|
96
|
+
let compression = dict["compression"] as? [String: Any]
|
|
97
|
+
let enableCompressedOutput = compression?["enabled"] as? Bool ?? false
|
|
98
|
+
let compressedFormat = (compression?["format"] as? String)?.lowercased() ?? "opus"
|
|
99
|
+
let compressedBitRate = compression?["bitrate"] as? Int ?? 24000
|
|
100
|
+
|
|
101
|
+
// Validate compression settings if enabled
|
|
102
|
+
if enableCompressedOutput {
|
|
103
|
+
// Validate format and bitrate
|
|
104
|
+
if case .failure(let error) = CompressedRecordingInfo.validate(
|
|
105
|
+
format: compressedFormat,
|
|
106
|
+
bitrate: compressedBitRate
|
|
107
|
+
) {
|
|
108
|
+
return .failure(error)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create settings
|
|
56
113
|
var settings = RecordingSettings(
|
|
57
114
|
sampleRate: dict["sampleRate"] as? Double ?? 44100.0,
|
|
58
|
-
desiredSampleRate: dict["desiredSampleRate"] as? Double ?? 44100.0
|
|
115
|
+
desiredSampleRate: dict["desiredSampleRate"] as? Double ?? 44100.0,
|
|
116
|
+
enableCompressedOutput: enableCompressedOutput,
|
|
117
|
+
compressedFormat: compressedFormat,
|
|
118
|
+
compressedBitRate: compressedBitRate
|
|
59
119
|
)
|
|
60
120
|
|
|
61
121
|
// Parse core settings
|
|
@@ -152,6 +212,6 @@ struct RecordingSettings {
|
|
|
152
212
|
settings.notification = notificationConfig
|
|
153
213
|
}
|
|
154
214
|
|
|
155
|
-
return settings
|
|
215
|
+
return .success(settings)
|
|
156
216
|
}
|
|
157
217
|
}
|
package/package.json
CHANGED
|
@@ -6,6 +6,13 @@ import {
|
|
|
6
6
|
} from './AudioAnalysis/AudioAnalysis.types'
|
|
7
7
|
import { AudioAnalysisEvent } from './events'
|
|
8
8
|
|
|
9
|
+
export interface CompressionInfo {
|
|
10
|
+
size: number
|
|
11
|
+
mimeType: string
|
|
12
|
+
bitrate: number
|
|
13
|
+
format: string
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export interface AudioStreamStatus {
|
|
10
17
|
isRecording: boolean
|
|
11
18
|
isPaused: boolean
|
|
@@ -13,6 +20,7 @@ export interface AudioStreamStatus {
|
|
|
13
20
|
size: number
|
|
14
21
|
interval: number
|
|
15
22
|
mimeType: string
|
|
23
|
+
compression?: CompressionInfo
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
export interface AudioDataEvent {
|
|
@@ -21,6 +29,9 @@ export interface AudioDataEvent {
|
|
|
21
29
|
fileUri: string
|
|
22
30
|
eventDataSize: number
|
|
23
31
|
totalSize: number
|
|
32
|
+
compression?: CompressionInfo & {
|
|
33
|
+
data?: string | Blob // Base64 (native) or Float32Array (web) encoded compressed data chunk
|
|
34
|
+
}
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
export type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'
|
|
@@ -60,6 +71,9 @@ export interface AudioRecording {
|
|
|
60
71
|
transcripts?: TranscriberData[]
|
|
61
72
|
wavPCMData?: Float32Array // Full PCM data for the recording in WAV format (only on web, for native use the fileUri)
|
|
62
73
|
analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag
|
|
74
|
+
compression?: CompressionInfo & {
|
|
75
|
+
compressedFileUri: string
|
|
76
|
+
}
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
export interface StartRecordingResult {
|
|
@@ -68,6 +82,9 @@ export interface StartRecordingResult {
|
|
|
68
82
|
channels?: number
|
|
69
83
|
bitDepth?: BitDepth
|
|
70
84
|
sampleRate?: SampleRate
|
|
85
|
+
compression?: CompressionInfo & {
|
|
86
|
+
compressedFileUri: string
|
|
87
|
+
}
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
export interface AudioSessionConfig {
|
|
@@ -147,6 +164,12 @@ export interface RecordingConfig {
|
|
|
147
164
|
|
|
148
165
|
// Callback function to handle audio features extraction results
|
|
149
166
|
onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void>
|
|
167
|
+
|
|
168
|
+
compression?: {
|
|
169
|
+
enabled: boolean
|
|
170
|
+
format: 'aac' | 'opus' | 'mp3'
|
|
171
|
+
bitrate?: number
|
|
172
|
+
}
|
|
150
173
|
}
|
|
151
174
|
|
|
152
175
|
export interface NotificationConfig {
|
|
@@ -216,14 +239,28 @@ export interface WaveformConfig {
|
|
|
216
239
|
height?: number // Height of the waveform view in dp (default: 64)
|
|
217
240
|
}
|
|
218
241
|
|
|
242
|
+
export interface WebRecordingOptions {
|
|
243
|
+
/**
|
|
244
|
+
* Web-specific option to skip the final audio data consolidation process.
|
|
245
|
+
* When true, it will:
|
|
246
|
+
* - Skip the time-consuming process of concatenating all audio chunks
|
|
247
|
+
* - Return immediately with the compressed audio (if compression is enabled)
|
|
248
|
+
* - Improve performance when stopping large recordings
|
|
249
|
+
* - Useful when only the compressed audio is needed (e.g., when not using transcription)
|
|
250
|
+
* @default false
|
|
251
|
+
*/
|
|
252
|
+
skipFinalConsolidation?: boolean
|
|
253
|
+
}
|
|
254
|
+
|
|
219
255
|
export interface UseAudioRecorderState {
|
|
220
256
|
startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>
|
|
221
|
-
stopRecording: () => Promise<AudioRecording | null>
|
|
257
|
+
stopRecording: (options?: WebRecordingOptions) => Promise<AudioRecording | null>
|
|
222
258
|
pauseRecording: () => Promise<void>
|
|
223
259
|
resumeRecording: () => Promise<void>
|
|
224
260
|
isRecording: boolean
|
|
225
261
|
isPaused: boolean
|
|
226
262
|
durationMs: number // Duration of the recording
|
|
227
263
|
size: number // Size in bytes of the recorded audio
|
|
264
|
+
compression?: CompressionInfo
|
|
228
265
|
analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag
|
|
229
266
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ConsoleLike,
|
|
10
10
|
RecordingConfig,
|
|
11
11
|
StartRecordingResult,
|
|
12
|
+
WebRecordingOptions,
|
|
12
13
|
} from './ExpoAudioStream.types'
|
|
13
14
|
import { WebRecorder } from './WebRecorder.web'
|
|
14
15
|
import { AudioEventPayload } from './events'
|
|
@@ -18,6 +19,14 @@ import { writeWavHeader } from './utils/writeWavHeader'
|
|
|
18
19
|
export interface EmitAudioEventProps {
|
|
19
20
|
data: Float32Array
|
|
20
21
|
position: number
|
|
22
|
+
compression?: {
|
|
23
|
+
data: Blob
|
|
24
|
+
size: number
|
|
25
|
+
totalSize: number
|
|
26
|
+
mimeType: string
|
|
27
|
+
format: string
|
|
28
|
+
bitrate: number
|
|
29
|
+
}
|
|
21
30
|
}
|
|
22
31
|
export type EmitAudioEventFunction = (_: EmitAudioEventProps) => void
|
|
23
32
|
export type EmitAudioAnalysisFunction = (_: AudioAnalysis) => void
|
|
@@ -40,6 +49,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
40
49
|
currentInterval: number
|
|
41
50
|
lastEmittedSize: number
|
|
42
51
|
lastEmittedTime: number
|
|
52
|
+
lastEmittedCompressionSize: number
|
|
43
53
|
streamUuid: string | null
|
|
44
54
|
extension: 'webm' | 'wav' = 'wav' // Default extension is 'webm'
|
|
45
55
|
recordingConfig?: RecordingConfig
|
|
@@ -47,6 +57,8 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
47
57
|
audioWorkletUrl: string
|
|
48
58
|
featuresExtratorUrl: string
|
|
49
59
|
logger?: ConsoleLike
|
|
60
|
+
latestPosition: number = 0
|
|
61
|
+
totalCompressedSize: number = 0
|
|
50
62
|
|
|
51
63
|
constructor({
|
|
52
64
|
audioWorkletUrl,
|
|
@@ -76,6 +88,8 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
76
88
|
this.currentInterval = 1000 // Default interval in ms
|
|
77
89
|
this.lastEmittedSize = 0
|
|
78
90
|
this.lastEmittedTime = 0
|
|
91
|
+
this.latestPosition = 0
|
|
92
|
+
this.lastEmittedCompressionSize = 0
|
|
79
93
|
this.streamUuid = null // Initialize UUID on first recording start
|
|
80
94
|
this.audioWorkletUrl = audioWorkletUrl
|
|
81
95
|
this.featuresExtratorUrl = featuresExtratorUrl
|
|
@@ -86,7 +100,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
86
100
|
try {
|
|
87
101
|
return await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
88
102
|
} catch (error) {
|
|
89
|
-
|
|
103
|
+
this.logger?.error('Failed to get media stream:', error)
|
|
90
104
|
throw error
|
|
91
105
|
}
|
|
92
106
|
}
|
|
@@ -110,6 +124,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
110
124
|
const source = audioContext.createMediaStreamSource(stream)
|
|
111
125
|
|
|
112
126
|
this.customRecorder = new WebRecorder({
|
|
127
|
+
logger: this.logger,
|
|
113
128
|
audioContext,
|
|
114
129
|
source,
|
|
115
130
|
recordingConfig,
|
|
@@ -117,12 +132,14 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
117
132
|
emitAudioEventCallback: ({
|
|
118
133
|
data,
|
|
119
134
|
position,
|
|
135
|
+
compression,
|
|
120
136
|
}: EmitAudioEventProps) => {
|
|
121
137
|
this.audioChunks.push(new Float32Array(data))
|
|
122
138
|
this.currentSize += data.byteLength
|
|
123
|
-
this.emitAudioEvent({ data, position })
|
|
139
|
+
this.emitAudioEvent({ data, position, compression })
|
|
124
140
|
this.lastEmittedTime = Date.now()
|
|
125
141
|
this.lastEmittedSize = this.currentSize
|
|
142
|
+
this.lastEmittedCompressionSize = compression?.size ?? 0
|
|
126
143
|
},
|
|
127
144
|
emitAudioAnalysisCallback: (audioAnalysisData: AudioAnalysis) => {
|
|
128
145
|
this.logger?.log(`Emitted AudioAnalysis:`, audioAnalysisData)
|
|
@@ -146,6 +163,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
146
163
|
this.isPaused = false
|
|
147
164
|
this.lastEmittedSize = 0
|
|
148
165
|
this.lastEmittedTime = 0
|
|
166
|
+
this.lastEmittedCompressionSize = 0
|
|
149
167
|
this.streamUuid = Date.now().toString()
|
|
150
168
|
const fileUri = `${this.streamUuid}.${this.extension}`
|
|
151
169
|
const streamConfig: StartRecordingResult = {
|
|
@@ -154,69 +172,118 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
154
172
|
bitDepth: this.bitDepth,
|
|
155
173
|
channels: recordingConfig.channels ?? 1,
|
|
156
174
|
sampleRate: recordingConfig.sampleRate ?? 44100,
|
|
175
|
+
compression: recordingConfig.compression
|
|
176
|
+
? {
|
|
177
|
+
...recordingConfig.compression,
|
|
178
|
+
bitrate: recordingConfig.compression?.bitrate ?? 128000,
|
|
179
|
+
size: 0,
|
|
180
|
+
mimeType: 'audio/webm',
|
|
181
|
+
format: recordingConfig.compression?.format ?? 'opus',
|
|
182
|
+
compressedFileUri: '',
|
|
183
|
+
}
|
|
184
|
+
: undefined,
|
|
157
185
|
}
|
|
158
186
|
return streamConfig
|
|
159
187
|
}
|
|
160
188
|
|
|
161
|
-
emitAudioEvent({ data, position }: EmitAudioEventProps) {
|
|
189
|
+
emitAudioEvent({ data, position, compression }: EmitAudioEventProps) {
|
|
162
190
|
const fileUri = `${this.streamUuid}.${this.extension}`
|
|
191
|
+
if (compression?.size) {
|
|
192
|
+
this.lastEmittedCompressionSize = compression.size
|
|
193
|
+
this.totalCompressedSize = compression.totalSize
|
|
194
|
+
}
|
|
195
|
+
this.latestPosition = position
|
|
196
|
+
this.currentDurationMs = position * 1000 // Convert position (in seconds) to ms
|
|
197
|
+
|
|
163
198
|
const audioEventPayload: AudioEventPayload = {
|
|
164
199
|
fileUri,
|
|
165
200
|
mimeType: `audio/${this.extension}`,
|
|
166
|
-
lastEmittedSize: this.lastEmittedSize,
|
|
201
|
+
lastEmittedSize: this.lastEmittedSize,
|
|
167
202
|
deltaSize: data.byteLength,
|
|
168
203
|
position,
|
|
169
204
|
totalSize: this.currentSize,
|
|
170
205
|
buffer: data,
|
|
171
|
-
streamUuid: this.streamUuid ?? '',
|
|
206
|
+
streamUuid: this.streamUuid ?? '',
|
|
207
|
+
compression: compression
|
|
208
|
+
? {
|
|
209
|
+
data: compression?.data,
|
|
210
|
+
totalSize: this.totalCompressedSize,
|
|
211
|
+
eventDataSize: compression?.size ?? 0,
|
|
212
|
+
position,
|
|
213
|
+
}
|
|
214
|
+
: undefined,
|
|
172
215
|
}
|
|
173
216
|
|
|
174
217
|
this.emit('AudioData', audioEventPayload)
|
|
175
218
|
}
|
|
176
219
|
|
|
177
220
|
// Stop recording
|
|
178
|
-
async stopRecording(): Promise<AudioRecording> {
|
|
221
|
+
async stopRecording(options?: WebRecordingOptions): Promise<AudioRecording> {
|
|
179
222
|
if (!this.customRecorder) {
|
|
180
223
|
throw new Error('Recorder is not initialized')
|
|
181
224
|
}
|
|
182
225
|
|
|
183
|
-
|
|
226
|
+
// Create a promise to handle the PCM data processing
|
|
227
|
+
return new Promise<AudioRecording>((resolve) => {
|
|
228
|
+
// Use requestAnimationFrame to avoid blocking the UI
|
|
229
|
+
requestAnimationFrame(() => {
|
|
230
|
+
// Move the async work inside a self-executing async function
|
|
231
|
+
(async () => {
|
|
232
|
+
const { pcmData, compressedBlob } = await this.customRecorder!.stop(options)
|
|
184
233
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
234
|
+
this.logger?.debug(`Stopped recording`, pcmData)
|
|
235
|
+
this.isRecording = false
|
|
236
|
+
this.isPaused = false
|
|
237
|
+
this.currentDurationMs = Date.now() - this.recordingStartTime
|
|
189
238
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
239
|
+
// Process in the next frame to avoid blocking
|
|
240
|
+
requestAnimationFrame(() => {
|
|
241
|
+
// Rest of the code remains the same
|
|
242
|
+
const wavBuffer = writeWavHeader({
|
|
243
|
+
buffer: pcmData.buffer,
|
|
244
|
+
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
245
|
+
numChannels: this.recordingConfig?.channels ?? 1,
|
|
246
|
+
bitDepth: this.bitDepth,
|
|
247
|
+
})
|
|
197
248
|
|
|
198
|
-
|
|
199
|
-
const cloneableBuffer = wavBuffer.slice(0)
|
|
249
|
+
const cloneableBuffer = wavBuffer.slice(0)
|
|
200
250
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const fileUri = URL.createObjectURL(blob)
|
|
251
|
+
const blob = new Blob([cloneableBuffer], {
|
|
252
|
+
type: `audio/${this.extension}`,
|
|
253
|
+
})
|
|
254
|
+
const fileUri = URL.createObjectURL(blob)
|
|
206
255
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
256
|
+
let compression: AudioRecording['compression']
|
|
257
|
+
if (compressedBlob && this.recordingConfig?.compression?.enabled) {
|
|
258
|
+
const compressedUri = URL.createObjectURL(compressedBlob)
|
|
259
|
+
compression = {
|
|
260
|
+
compressedFileUri: compressedUri,
|
|
261
|
+
size: compressedBlob.size,
|
|
262
|
+
mimeType: 'audio/webm',
|
|
263
|
+
format: 'opus',
|
|
264
|
+
bitrate: this.recordingConfig.compression.bitrate ?? 128000,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
218
267
|
|
|
219
|
-
|
|
268
|
+
resolve({
|
|
269
|
+
fileUri,
|
|
270
|
+
filename: `${this.streamUuid}.${this.extension}`,
|
|
271
|
+
wavPCMData: new Float32Array(cloneableBuffer),
|
|
272
|
+
bitDepth: this.bitDepth,
|
|
273
|
+
channels: this.recordingConfig?.channels ?? 1,
|
|
274
|
+
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
275
|
+
durationMs: this.currentDurationMs,
|
|
276
|
+
size: this.currentSize,
|
|
277
|
+
mimeType: `audio/${this.extension}`,
|
|
278
|
+
compression,
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
})().catch((error) => {
|
|
282
|
+
this.logger?.error('Error in stopRecording:', error)
|
|
283
|
+
throw error
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
})
|
|
220
287
|
}
|
|
221
288
|
|
|
222
289
|
// Pause recording
|
|
@@ -250,10 +317,19 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
250
317
|
const status: AudioStreamStatus = {
|
|
251
318
|
isRecording: this.isRecording,
|
|
252
319
|
isPaused: this.isPaused,
|
|
253
|
-
durationMs:
|
|
320
|
+
durationMs: this.currentDurationMs,
|
|
254
321
|
size: this.currentSize,
|
|
255
322
|
interval: this.currentInterval,
|
|
256
323
|
mimeType: `audio/${this.extension}`,
|
|
324
|
+
compression: this.recordingConfig?.compression?.enabled
|
|
325
|
+
? {
|
|
326
|
+
size: this.totalCompressedSize,
|
|
327
|
+
mimeType: 'audio/webm',
|
|
328
|
+
format: this.recordingConfig.compression.format ?? 'opus',
|
|
329
|
+
bitrate:
|
|
330
|
+
this.recordingConfig.compression.bitrate ?? 128000,
|
|
331
|
+
}
|
|
332
|
+
: undefined,
|
|
257
333
|
}
|
|
258
334
|
return status
|
|
259
335
|
}
|