@siteed/expo-audio-stream 1.11.5 → 1.12.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 +3 -323
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +30 -4
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +32 -2
- package/build/ExpoAudioStream.types.d.ts +3 -0
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +17 -4
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +4 -0
- package/build/WebRecorder.web.js.map +1 -1
- package/ios/AudioStreamManager.swift +114 -45
- package/ios/AudioStreamManagerDelegate.swift +1 -0
- package/ios/ExpoAudioStreamModule.swift +20 -2
- package/ios/RecordingSettings.swift +36 -0
- package/package.json +2 -2
- package/src/ExpoAudioStream.types.ts +5 -0
- package/src/ExpoAudioStream.web.ts +23 -5
- package/src/WebRecorder.web.ts +4 -0
|
@@ -380,11 +380,51 @@ class AudioStreamManager: NSObject {
|
|
|
380
380
|
|
|
381
381
|
/// Creates a new recording file.
|
|
382
382
|
/// - Returns: The URL of the newly created recording file, or nil if creation failed.
|
|
383
|
-
private func createRecordingFile() -> URL? {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
383
|
+
private func createRecordingFile(isCompressed: Bool = false) -> URL? {
|
|
384
|
+
// Add debug logging
|
|
385
|
+
Logger.debug("Creating recording file - settings filename: \(recordingSettings?.filename ?? "nil")")
|
|
386
|
+
|
|
387
|
+
// Get base directory - use default if no custom directory provided
|
|
388
|
+
let baseDirectory: URL
|
|
389
|
+
if let customDir = recordingSettings?.outputDirectory {
|
|
390
|
+
baseDirectory = URL(fileURLWithPath: customDir)
|
|
391
|
+
Logger.debug("Using custom directory: \(customDir)")
|
|
392
|
+
} else {
|
|
393
|
+
// Use existing default behavior
|
|
394
|
+
baseDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
395
|
+
Logger.debug("Using default directory: \(baseDirectory.path)")
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Use custom filename if provided, otherwise generate UUID
|
|
399
|
+
let baseFilename = recordingSettings?.filename ?? UUID().uuidString
|
|
400
|
+
Logger.debug("Using base filename: \(baseFilename)")
|
|
401
|
+
|
|
402
|
+
// Remove any existing extension from the filename
|
|
403
|
+
let filenameWithoutExtension = baseFilename.replacingOccurrences(
|
|
404
|
+
of: "\\.[^\\.]+$",
|
|
405
|
+
with: "",
|
|
406
|
+
options: .regularExpression
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
// Choose extension based on whether this is a compressed file
|
|
410
|
+
let fileExtension: String
|
|
411
|
+
if isCompressed {
|
|
412
|
+
fileExtension = recordingSettings?.compressedFormat.lowercased() ?? "aac"
|
|
413
|
+
} else {
|
|
414
|
+
fileExtension = "wav"
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let fullFilename = "\(filenameWithoutExtension).\(fileExtension)"
|
|
418
|
+
Logger.debug("Full filename: \(fullFilename)")
|
|
419
|
+
|
|
420
|
+
let fileURL = baseDirectory.appendingPathComponent(fullFilename)
|
|
421
|
+
Logger.debug("Final file URL: \(fileURL.path)")
|
|
422
|
+
|
|
423
|
+
// Check if file already exists
|
|
424
|
+
if fileManager.fileExists(atPath: fileURL.path) {
|
|
425
|
+
Logger.debug("File already exists at: \(fileURL.path)")
|
|
426
|
+
return nil
|
|
427
|
+
}
|
|
388
428
|
|
|
389
429
|
if !fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) {
|
|
390
430
|
Logger.debug("Failed to create file at: \(fileURL.path)")
|
|
@@ -479,6 +519,20 @@ class AudioStreamManager: NSObject {
|
|
|
479
519
|
/// - intervalMilliseconds: The interval in milliseconds for emitting audio data.
|
|
480
520
|
/// - Returns: A StartRecordingResult object if recording starts successfully, or nil otherwise.
|
|
481
521
|
func startRecording(settings: RecordingSettings, intervalMilliseconds: Int) -> StartRecordingResult? {
|
|
522
|
+
// Check for active call first
|
|
523
|
+
let callCenter = CXCallObserver()
|
|
524
|
+
if callCenter.calls.contains(where: { $0.hasEnded == false }) {
|
|
525
|
+
Logger.debug("Cannot start recording during an active call")
|
|
526
|
+
delegate?.audioStreamManager(self, didFailWithError: "Cannot start recording during an active call")
|
|
527
|
+
return nil
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Store settings first before doing anything else
|
|
531
|
+
recordingSettings = settings
|
|
532
|
+
|
|
533
|
+
// Add debug logging to verify settings
|
|
534
|
+
Logger.debug("Starting recording with settings - filename: \(settings.filename ?? "nil"), directory: \(settings.outputDirectory ?? "nil")")
|
|
535
|
+
|
|
482
536
|
// Update auto-resume preference from settings
|
|
483
537
|
autoResumeAfterInterruption = settings.autoResumeAfterInterruption
|
|
484
538
|
|
|
@@ -606,37 +660,26 @@ class AudioStreamManager: NSObject {
|
|
|
606
660
|
|
|
607
661
|
Logger.debug("Initializing compressed recording with settings: \(compressedSettings)")
|
|
608
662
|
|
|
609
|
-
|
|
610
|
-
|
|
663
|
+
// Use createRecordingFile for consistency in file handling
|
|
664
|
+
compressedFileURL = createRecordingFile(isCompressed: true)
|
|
611
665
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
compressedFileURL = tempDirectory.appendingPathComponent(recordingUUID.uuidString)
|
|
615
|
-
.appendingPathExtension(settings.compressedFormat)
|
|
666
|
+
if let url = compressedFileURL {
|
|
667
|
+
Logger.debug("Using compressed file URL: \(url.path)")
|
|
616
668
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
Logger.debug("Failed to create empty file at: \(url.path)")
|
|
623
|
-
}
|
|
669
|
+
// Initialize recorder
|
|
670
|
+
compressedRecorder = try AVAudioRecorder(url: url, settings: compressedSettings)
|
|
671
|
+
if let recorder = compressedRecorder {
|
|
672
|
+
let prepared = recorder.prepareToRecord()
|
|
673
|
+
Logger.debug("Recorder prepared: \(prepared)")
|
|
624
674
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
Logger.debug("Recorder current time: \(recorder.currentTime)")
|
|
635
|
-
|
|
636
|
-
compressedFormat = settings.compressedFormat
|
|
637
|
-
compressedBitRate = settings.compressedBitRate
|
|
638
|
-
Logger.debug("Compressed recording initialized - Format: \(compressedFormat), Bitrate: \(compressedBitRate)")
|
|
639
|
-
}
|
|
675
|
+
let started = recorder.record()
|
|
676
|
+
Logger.debug("Recorder started: \(started)")
|
|
677
|
+
|
|
678
|
+
Logger.debug("Recorder current time: \(recorder.currentTime)")
|
|
679
|
+
|
|
680
|
+
compressedFormat = settings.compressedFormat
|
|
681
|
+
compressedBitRate = settings.compressedBitRate
|
|
682
|
+
Logger.debug("Compressed recording initialized - Format: \(compressedFormat), Bitrate: \(compressedBitRate)")
|
|
640
683
|
}
|
|
641
684
|
}
|
|
642
685
|
} catch {
|
|
@@ -700,18 +743,27 @@ class AudioStreamManager: NSObject {
|
|
|
700
743
|
isPaused = false
|
|
701
744
|
Logger.debug("Debug: Recording started successfully.")
|
|
702
745
|
|
|
746
|
+
var compression = compressedRecorder != nil ? CompressedRecordingInfo(
|
|
747
|
+
fileUri: compressedFileURL?.absoluteString ?? "",
|
|
748
|
+
mimeType: compressedFormat == "aac" ? "audio/aac" : "audio/opus",
|
|
749
|
+
bitrate: compressedBitRate,
|
|
750
|
+
format: compressedFormat
|
|
751
|
+
) : nil
|
|
752
|
+
|
|
753
|
+
// Get the size separately since it's not part of the initializer
|
|
754
|
+
if let compressedPath = compressedFileURL?.path,
|
|
755
|
+
let attributes = try? FileManager.default.attributesOfItem(atPath: compressedPath),
|
|
756
|
+
let fileSize = attributes[.size] as? Int64 {
|
|
757
|
+
compression?.size = fileSize
|
|
758
|
+
}
|
|
759
|
+
|
|
703
760
|
return StartRecordingResult(
|
|
704
761
|
fileUri: recordingFileURL!.path,
|
|
705
762
|
mimeType: mimeType,
|
|
706
763
|
channels: settings.numberOfChannels,
|
|
707
764
|
bitDepth: settings.bitDepth,
|
|
708
765
|
sampleRate: settings.sampleRate,
|
|
709
|
-
compression:
|
|
710
|
-
fileUri: compressedFileURL!.absoluteString,
|
|
711
|
-
mimeType: compressedFormat == "aac" ? "audio/aac" : "audio/opus",
|
|
712
|
-
bitrate: compressedBitRate,
|
|
713
|
-
format: compressedFormat
|
|
714
|
-
) : nil
|
|
766
|
+
compression: compression
|
|
715
767
|
)
|
|
716
768
|
|
|
717
769
|
} catch {
|
|
@@ -792,6 +844,14 @@ class AudioStreamManager: NSObject {
|
|
|
792
844
|
|
|
793
845
|
/// Resumes the current audio recording.
|
|
794
846
|
func resumeRecording() {
|
|
847
|
+
// Check for active call first
|
|
848
|
+
let callCenter = CXCallObserver()
|
|
849
|
+
if callCenter.calls.contains(where: { $0.hasEnded == false }) {
|
|
850
|
+
Logger.debug("Cannot resume recording during an active call")
|
|
851
|
+
delegate?.audioStreamManager(self, didFailWithError: "Cannot resume recording during an active call")
|
|
852
|
+
return
|
|
853
|
+
}
|
|
854
|
+
|
|
795
855
|
guard isRecording && isPaused else { return }
|
|
796
856
|
|
|
797
857
|
lastValidDuration = nil // Clear the stored duration when resuming
|
|
@@ -920,6 +980,20 @@ class AudioStreamManager: NSObject {
|
|
|
920
980
|
// Update the WAV header with the correct file size
|
|
921
981
|
updateWavHeader(fileURL: fileURL, totalDataSize: fileSize - 44)
|
|
922
982
|
|
|
983
|
+
var compression = compressedRecorder != nil ? CompressedRecordingInfo(
|
|
984
|
+
fileUri: compressedFileURL?.absoluteString ?? "",
|
|
985
|
+
mimeType: compressedFormat == "aac" ? "audio/aac" : "audio/opus",
|
|
986
|
+
bitrate: compressedBitRate,
|
|
987
|
+
format: compressedFormat
|
|
988
|
+
) : nil
|
|
989
|
+
|
|
990
|
+
// Get the size separately since it's not part of the initializer
|
|
991
|
+
if let compressedPath = compressedFileURL?.path,
|
|
992
|
+
let attributes = try? FileManager.default.attributesOfItem(atPath: compressedPath),
|
|
993
|
+
let fileSize = attributes[.size] as? Int64 {
|
|
994
|
+
compression?.size = fileSize
|
|
995
|
+
}
|
|
996
|
+
|
|
923
997
|
let result = RecordingResult(
|
|
924
998
|
fileUri: fileURL.absoluteString,
|
|
925
999
|
filename: fileURL.lastPathComponent,
|
|
@@ -929,12 +1003,7 @@ class AudioStreamManager: NSObject {
|
|
|
929
1003
|
channels: settings.numberOfChannels,
|
|
930
1004
|
bitDepth: settings.bitDepth,
|
|
931
1005
|
sampleRate: settings.sampleRate,
|
|
932
|
-
compression:
|
|
933
|
-
fileUri: compressedFileURL!.absoluteString,
|
|
934
|
-
mimeType: compressedFormat == "aac" ? "audio/aac" : "audio/opus",
|
|
935
|
-
bitrate: compressedBitRate,
|
|
936
|
-
format: compressedFormat
|
|
937
|
-
) : nil
|
|
1006
|
+
compression: compression
|
|
938
1007
|
)
|
|
939
1008
|
|
|
940
1009
|
// Cleanup
|
|
@@ -12,4 +12,5 @@ protocol AudioStreamManagerDelegate: AnyObject {
|
|
|
12
12
|
func audioStreamManager(_ manager: AudioStreamManager, didResumeRecording resumeTime: Date)
|
|
13
13
|
func audioStreamManager(_ manager: AudioStreamManager, didUpdateNotificationState isPaused: Bool)
|
|
14
14
|
func audioStreamManager(_ manager: AudioStreamManager, didReceiveInterruption info: [String: Any])
|
|
15
|
+
func audioStreamManager(_ manager: AudioStreamManager, didFailWithError error: String)
|
|
15
16
|
}
|
|
@@ -213,8 +213,7 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
213
213
|
/// - promise: A promise to resolve with the recording result or reject with an error.
|
|
214
214
|
AsyncFunction("stopRecording") { (promise: Promise) in
|
|
215
215
|
if let recordingResult = self.streamManager.stopRecording() {
|
|
216
|
-
|
|
217
|
-
let resultDict: [String: Any] = [
|
|
216
|
+
var resultDict: [String: Any] = [
|
|
218
217
|
"fileUri": recordingResult.fileUri,
|
|
219
218
|
"filename": recordingResult.filename,
|
|
220
219
|
"durationMs": recordingResult.duration,
|
|
@@ -224,6 +223,18 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
224
223
|
"sampleRate": recordingResult.sampleRate,
|
|
225
224
|
"mimeType": recordingResult.mimeType,
|
|
226
225
|
]
|
|
226
|
+
|
|
227
|
+
// Add compression info if available
|
|
228
|
+
if let compression = recordingResult.compression {
|
|
229
|
+
resultDict["compression"] = [
|
|
230
|
+
"fileUri": compression.fileUri,
|
|
231
|
+
"mimeType": compression.mimeType,
|
|
232
|
+
"bitrate": compression.bitrate,
|
|
233
|
+
"format": compression.format,
|
|
234
|
+
"size": compression.size
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
|
|
227
238
|
promise.resolve(resultDict)
|
|
228
239
|
} else {
|
|
229
240
|
promise.reject("ERROR", "Failed to stop recording or no recording in progress.")
|
|
@@ -459,4 +470,11 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
459
470
|
func audioStreamManager(_ manager: AudioStreamManager, didReceiveInterruption info: [String: Any]) {
|
|
460
471
|
sendEvent(recordingInterruptedEvent, info)
|
|
461
472
|
}
|
|
473
|
+
|
|
474
|
+
func audioStreamManager(_ manager: AudioStreamManager, didFailWithError error: String) {
|
|
475
|
+
// Send error event to JavaScript
|
|
476
|
+
sendEvent("error", [
|
|
477
|
+
"message": error
|
|
478
|
+
])
|
|
479
|
+
}
|
|
462
480
|
}
|
|
@@ -22,6 +22,7 @@ struct CompressedRecordingInfo {
|
|
|
22
22
|
var mimeType: String
|
|
23
23
|
var bitrate: Int
|
|
24
24
|
var format: String
|
|
25
|
+
var size: Int64 = 0 // Add size with default value
|
|
25
26
|
|
|
26
27
|
static func validate(format: String, bitrate: Int) -> Result<(String, Int), Error> {
|
|
27
28
|
// Validate format
|
|
@@ -59,6 +60,7 @@ struct IOSConfig {
|
|
|
59
60
|
enum RecordingError: Error {
|
|
60
61
|
case unsupportedFormat(String)
|
|
61
62
|
case invalidBitrate(Int)
|
|
63
|
+
case invalidOutputDirectory(String)
|
|
62
64
|
|
|
63
65
|
var localizedDescription: String {
|
|
64
66
|
switch self {
|
|
@@ -66,6 +68,8 @@ enum RecordingError: Error {
|
|
|
66
68
|
return "Unsupported compression format: \(format). iOS only supports AAC."
|
|
67
69
|
case .invalidBitrate(let bitrate):
|
|
68
70
|
return "Invalid bitrate: \(bitrate). Must be between 8000 and 960000 bps."
|
|
71
|
+
case .invalidOutputDirectory(let directory):
|
|
72
|
+
return "Invalid output directory: \(directory). Directory does not exist, is not a directory, or is not writable."
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
}
|
|
@@ -100,6 +104,10 @@ struct RecordingSettings {
|
|
|
100
104
|
|
|
101
105
|
let autoResumeAfterInterruption: Bool
|
|
102
106
|
|
|
107
|
+
// Make these optional with nil default values
|
|
108
|
+
var outputDirectory: String? = nil
|
|
109
|
+
var filename: String? = nil
|
|
110
|
+
|
|
103
111
|
static func fromDictionary(_ dict: [String: Any]) -> Result<RecordingSettings, Error> {
|
|
104
112
|
// Extract compression settings
|
|
105
113
|
let compression = dict["compression"] as? [String: Any]
|
|
@@ -222,6 +230,34 @@ struct RecordingSettings {
|
|
|
222
230
|
settings.notification = notificationConfig
|
|
223
231
|
}
|
|
224
232
|
|
|
233
|
+
// Parse output settings (they remain nil if not provided)
|
|
234
|
+
if let directory = dict["outputDirectory"] as? String {
|
|
235
|
+
// Only validate if a custom directory is provided
|
|
236
|
+
let fileManager = FileManager.default
|
|
237
|
+
var isDirectory: ObjCBool = false
|
|
238
|
+
|
|
239
|
+
// Clean up the directory path by removing file:// protocol if present
|
|
240
|
+
let cleanDirectory = directory.replacingOccurrences(of: "file://", with: "")
|
|
241
|
+
.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
|
242
|
+
.replacingOccurrences(of: "//", with: "/")
|
|
243
|
+
|
|
244
|
+
if !fileManager.fileExists(atPath: cleanDirectory, isDirectory: &isDirectory) {
|
|
245
|
+
return .failure(RecordingError.invalidOutputDirectory("Directory does not exist: \(cleanDirectory)"))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if !isDirectory.boolValue {
|
|
249
|
+
return .failure(RecordingError.invalidOutputDirectory("Path is not a directory: \(cleanDirectory)"))
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if !fileManager.isWritableFile(atPath: cleanDirectory) {
|
|
253
|
+
return .failure(RecordingError.invalidOutputDirectory("Directory is not writable: \(cleanDirectory)"))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
settings.outputDirectory = cleanDirectory
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
settings.filename = dict["filename"] as? String
|
|
260
|
+
|
|
225
261
|
return .success(settings)
|
|
226
262
|
}
|
|
227
263
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteed/expo-audio-stream",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "stream audio crossplatform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@expo/config-plugins": "~9.0.0",
|
|
70
|
-
"@siteed/publisher": "^0.4.
|
|
70
|
+
"@siteed/publisher": "^0.4.18",
|
|
71
71
|
"@size-limit/preset-big-lib": "^11.1.4",
|
|
72
72
|
"@types/jest": "^29.5.12",
|
|
73
73
|
"@types/node": "^20.12.7",
|
|
@@ -11,6 +11,7 @@ export interface CompressionInfo {
|
|
|
11
11
|
mimeType: string
|
|
12
12
|
bitrate: number
|
|
13
13
|
format: string
|
|
14
|
+
compressedFileUri?: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface AudioStreamStatus {
|
|
@@ -188,6 +189,10 @@ export interface RecordingConfig {
|
|
|
188
189
|
|
|
189
190
|
// Optional callback to handle recording interruptions
|
|
190
191
|
onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void
|
|
192
|
+
|
|
193
|
+
// Optional output configuration
|
|
194
|
+
outputDirectory?: string // If not provided, uses default app directory
|
|
195
|
+
filename?: string // If not provided, uses UUID
|
|
191
196
|
}
|
|
192
197
|
|
|
193
198
|
export interface NotificationConfig {
|
|
@@ -50,7 +50,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
50
50
|
lastEmittedTime: number
|
|
51
51
|
lastEmittedCompressionSize: number
|
|
52
52
|
streamUuid: string | null
|
|
53
|
-
extension: 'webm' | 'wav' = 'wav' // Default extension is '
|
|
53
|
+
extension: 'webm' | 'wav' = 'wav' // Default extension is 'wav'
|
|
54
54
|
recordingConfig?: RecordingConfig
|
|
55
55
|
bitDepth: BitDepth // Bit depth of the audio
|
|
56
56
|
audioWorkletUrl: string
|
|
@@ -108,7 +108,9 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// Start recording with options
|
|
111
|
-
async startRecording(
|
|
111
|
+
async startRecording(
|
|
112
|
+
recordingConfig: RecordingConfig = {}
|
|
113
|
+
): Promise<StartRecordingResult> {
|
|
112
114
|
if (this.isRecording) {
|
|
113
115
|
throw new Error('Recording is already in progress')
|
|
114
116
|
}
|
|
@@ -170,7 +172,15 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
170
172
|
this.lastEmittedSize = 0
|
|
171
173
|
this.lastEmittedTime = 0
|
|
172
174
|
this.lastEmittedCompressionSize = 0
|
|
173
|
-
|
|
175
|
+
|
|
176
|
+
// Use custom filename if provided, otherwise fallback to timestamp
|
|
177
|
+
if (recordingConfig.filename) {
|
|
178
|
+
// Remove any existing extension from the filename
|
|
179
|
+
this.streamUuid = recordingConfig.filename.replace(/\.[^/.]+$/, '')
|
|
180
|
+
} else {
|
|
181
|
+
this.streamUuid = Date.now().toString()
|
|
182
|
+
}
|
|
183
|
+
|
|
174
184
|
const fileUri = `${this.streamUuid}.${this.extension}`
|
|
175
185
|
const streamConfig: StartRecordingResult = {
|
|
176
186
|
fileUri,
|
|
@@ -266,9 +276,11 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
266
276
|
}
|
|
267
277
|
)
|
|
268
278
|
|
|
269
|
-
|
|
279
|
+
// Use the stored streamUuid (which contains our custom filename) for the final filename
|
|
280
|
+
const filename = `${this.streamUuid}.${this.extension}`
|
|
281
|
+
const result: AudioRecording = {
|
|
270
282
|
fileUri,
|
|
271
|
-
filename
|
|
283
|
+
filename, // This will now use our custom filename
|
|
272
284
|
bitDepth: this.bitDepth,
|
|
273
285
|
channels: this.recordingConfig?.channels ?? 1,
|
|
274
286
|
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
@@ -277,6 +289,11 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
277
289
|
mimeType,
|
|
278
290
|
compression,
|
|
279
291
|
}
|
|
292
|
+
|
|
293
|
+
// Reset after creating the result
|
|
294
|
+
this.streamUuid = null
|
|
295
|
+
|
|
296
|
+
return result
|
|
280
297
|
} catch (error) {
|
|
281
298
|
this.logger?.error('[Stop] Error stopping recording:', error)
|
|
282
299
|
throw error
|
|
@@ -325,6 +342,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
325
342
|
format: this.recordingConfig.compression.format ?? 'opus',
|
|
326
343
|
bitrate:
|
|
327
344
|
this.recordingConfig.compression.bitrate ?? 128000,
|
|
345
|
+
compressedFileUri: `${this.streamUuid}.webm`,
|
|
328
346
|
}
|
|
329
347
|
: undefined,
|
|
330
348
|
}
|
package/src/WebRecorder.web.ts
CHANGED
|
@@ -351,6 +351,10 @@ export class WebRecorder {
|
|
|
351
351
|
return { pcmData: new Float32Array() }
|
|
352
352
|
} finally {
|
|
353
353
|
this.cleanup()
|
|
354
|
+
// Reset the chunks array
|
|
355
|
+
this.compressedChunks = []
|
|
356
|
+
this.compressedSize = 0
|
|
357
|
+
this.pendingCompressedChunk = null
|
|
354
358
|
}
|
|
355
359
|
}
|
|
356
360
|
|