@siteed/expo-audio-stream 1.0.2 → 1.0.5
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/.size-limit.json +6 -0
- package/README.md +18 -176
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +1 -0
- package/app.plugin.js +1 -1
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts +74 -0
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
- package/build/AudioAnalysis/AudioAnalysis.types.js +3 -0
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts +20 -0
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
- package/build/AudioAnalysis/extractAudioAnalysis.js +88 -0
- package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
- package/build/AudioAnalysis/extractWaveform.d.ts +8 -0
- package/build/AudioAnalysis/extractWaveform.d.ts.map +1 -0
- package/build/AudioAnalysis/extractWaveform.js +14 -0
- package/build/AudioAnalysis/extractWaveform.js.map +1 -0
- package/build/AudioRecorder.provider.d.ts +15 -2
- package/build/AudioRecorder.provider.d.ts.map +1 -1
- package/build/AudioRecorder.provider.js +21 -8
- package/build/AudioRecorder.provider.js.map +1 -1
- package/build/ExpoAudioStream.native.d.ts.map +1 -1
- package/build/ExpoAudioStream.native.js +2 -2
- package/build/ExpoAudioStream.native.js.map +1 -1
- package/build/ExpoAudioStream.types.d.ts +33 -89
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +10 -9
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +44 -25
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/ExpoAudioStreamModule.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.js +13 -8
- package/build/ExpoAudioStreamModule.js.map +1 -1
- package/build/{WebRecorder.d.ts → WebRecorder.web.d.ts} +13 -9
- package/build/WebRecorder.web.d.ts.map +1 -0
- package/build/{WebRecorder.js → WebRecorder.web.js} +118 -63
- package/build/WebRecorder.web.js.map +1 -0
- package/build/constants.d.ts +11 -0
- package/build/constants.d.ts.map +1 -0
- package/build/constants.js +14 -0
- package/build/constants.js.map +1 -0
- package/build/events.d.ts +18 -0
- package/build/events.d.ts.map +1 -0
- package/build/events.js +15 -0
- package/build/events.js.map +1 -0
- package/build/index.d.ts +9 -17
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -113
- package/build/index.js.map +1 -1
- package/build/logger.d.ts +9 -0
- package/build/logger.d.ts.map +1 -0
- package/build/logger.js +13 -0
- package/build/logger.js.map +1 -0
- package/build/useAudioRecorder.d.ts +20 -0
- package/build/useAudioRecorder.d.ts.map +1 -0
- package/build/{useAudioRecording.js → useAudioRecorder.js} +90 -86
- package/build/useAudioRecorder.js.map +1 -0
- package/build/utils/BlobFix.d.ts +9 -0
- package/build/utils/BlobFix.d.ts.map +1 -0
- package/build/utils/BlobFix.js +494 -0
- package/build/utils/BlobFix.js.map +1 -0
- package/build/utils/concatenateBuffers.d.ts +8 -0
- package/build/utils/concatenateBuffers.d.ts.map +1 -0
- package/build/utils/concatenateBuffers.js +21 -0
- package/build/utils/concatenateBuffers.js.map +1 -0
- package/build/utils/convertPCMToFloat32.d.ts +11 -0
- package/build/utils/convertPCMToFloat32.d.ts.map +1 -0
- package/build/utils/convertPCMToFloat32.js +54 -0
- package/build/utils/convertPCMToFloat32.js.map +1 -0
- package/build/utils/encodingToBitDepth.d.ts +5 -0
- package/build/utils/encodingToBitDepth.d.ts.map +1 -0
- package/build/utils/encodingToBitDepth.js +13 -0
- package/build/utils/encodingToBitDepth.js.map +1 -0
- package/build/utils/getWavFileInfo.d.ts +26 -0
- package/build/utils/getWavFileInfo.d.ts.map +1 -0
- package/build/utils/getWavFileInfo.js +92 -0
- package/build/utils/getWavFileInfo.js.map +1 -0
- package/build/utils/writeWavHeader.d.ts +9 -0
- package/build/utils/writeWavHeader.d.ts.map +1 -0
- package/build/utils/writeWavHeader.js +41 -0
- package/build/utils/writeWavHeader.js.map +1 -0
- package/build/workers/InlineFeaturesExtractor.web.d.ts +2 -0
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
- package/build/workers/InlineFeaturesExtractor.web.js +303 -0
- package/build/workers/InlineFeaturesExtractor.web.js.map +1 -0
- package/build/workers/inlineAudioWebWorker.web.d.ts +2 -0
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
- package/build/workers/inlineAudioWebWorker.web.js +243 -0
- package/build/workers/inlineAudioWebWorker.web.js.map +1 -0
- package/expo-module.config.json +8 -17
- package/ios/AudioStreamManager.swift +40 -2
- package/ios/ExpoAudioStreamModule.swift +11 -0
- package/ios/RecordingResult.swift +1 -0
- package/package.json +72 -64
- package/plugin/build/index.d.ts +1 -1
- package/plugin/build/index.js +7 -7
- package/plugin/src/index.ts +47 -47
- package/plugin/tsconfig.json +8 -13
- package/publish.sh +0 -0
- package/src/AudioAnalysis/AudioAnalysis.types.ts +84 -0
- package/src/AudioAnalysis/extractAudioAnalysis.ts +147 -0
- package/src/AudioAnalysis/extractWaveform.ts +25 -0
- package/src/AudioRecorder.provider.tsx +59 -31
- package/src/ExpoAudioStream.native.ts +2 -2
- package/src/ExpoAudioStream.types.ts +58 -116
- package/src/ExpoAudioStream.web.ts +233 -205
- package/src/ExpoAudioStreamModule.ts +18 -12
- package/src/WebRecorder.web.ts +433 -0
- package/src/constants.ts +18 -0
- package/src/events.ts +39 -0
- package/src/index.ts +15 -176
- package/src/logger.ts +23 -0
- package/src/useAudioRecorder.tsx +420 -0
- package/src/utils/BlobFix.ts +550 -0
- package/src/utils/concatenateBuffers.ts +24 -0
- package/src/utils/convertPCMToFloat32.ts +75 -0
- package/src/utils/encodingToBitDepth.ts +18 -0
- package/src/utils/getWavFileInfo.ts +132 -0
- package/src/utils/writeWavHeader.ts +56 -0
- package/src/workers/InlineFeaturesExtractor.web.tsx +302 -0
- package/src/workers/inlineAudioWebWorker.web.tsx +242 -0
- package/tsconfig.json +12 -7
- package/build/WebRecorder.d.ts.map +0 -1
- package/build/WebRecorder.js.map +0 -1
- package/build/inlineAudioWebWorker.d.ts +0 -3
- package/build/inlineAudioWebWorker.d.ts.map +0 -1
- package/build/inlineAudioWebWorker.js +0 -340
- package/build/inlineAudioWebWorker.js.map +0 -1
- package/build/useAudioRecording.d.ts +0 -38
- package/build/useAudioRecording.d.ts.map +0 -1
- package/build/useAudioRecording.js.map +0 -1
- package/build/utils.d.ts +0 -31
- package/build/utils.d.ts.map +0 -1
- package/build/utils.js +0 -143
- package/build/utils.js.map +0 -1
- package/src/WebRecorder.ts +0 -364
- package/src/inlineAudioWebWorker.tsx +0 -340
- package/src/useAudioRecording.tsx +0 -410
- package/src/utils.ts +0 -189
|
@@ -1,135 +1,77 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
totalSize: number;
|
|
9
|
-
mimeType: string;
|
|
10
|
-
streamUuid: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface AudioStreamResult {
|
|
14
|
-
fileUri: string;
|
|
15
|
-
webAudioUri?: string;
|
|
16
|
-
durationMs: number;
|
|
17
|
-
size: number;
|
|
18
|
-
mimeType: string;
|
|
19
|
-
channels?: number;
|
|
20
|
-
bitDepth?: number;
|
|
21
|
-
sampleRate?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface StartAudioStreamResult {
|
|
25
|
-
fileUri: string;
|
|
26
|
-
mimeType: string;
|
|
27
|
-
channels?: number;
|
|
28
|
-
bitDepth?: number;
|
|
29
|
-
sampleRate?: number;
|
|
30
|
-
}
|
|
1
|
+
// packages/expo-audio-stream/src/ExpoAudioStream.types.ts
|
|
2
|
+
import {
|
|
3
|
+
AmplitudeAlgorithm,
|
|
4
|
+
AudioAnalysis,
|
|
5
|
+
AudioFeaturesOptions,
|
|
6
|
+
} from './AudioAnalysis/AudioAnalysis.types'
|
|
7
|
+
import { AudioAnalysisEvent } from './events'
|
|
31
8
|
|
|
32
9
|
export interface AudioStreamStatus {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
10
|
+
isRecording: boolean
|
|
11
|
+
isPaused: boolean
|
|
12
|
+
durationMs: number
|
|
13
|
+
size: number
|
|
14
|
+
interval: number
|
|
15
|
+
mimeType: string
|
|
39
16
|
}
|
|
40
17
|
|
|
41
18
|
export interface AudioDataEvent {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
19
|
+
data: string | Float32Array
|
|
20
|
+
position: number
|
|
21
|
+
fileUri: string
|
|
22
|
+
eventDataSize: number
|
|
23
|
+
totalSize: number
|
|
47
24
|
}
|
|
48
25
|
|
|
49
|
-
export
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
spectralCentroid: number;
|
|
57
|
-
spectralFlatness: number;
|
|
58
|
-
spectralRolloff: number;
|
|
59
|
-
spectralBandwidth: number;
|
|
60
|
-
chromagram: number[];
|
|
61
|
-
tempo: number;
|
|
62
|
-
hnr: number;
|
|
26
|
+
export type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'
|
|
27
|
+
export type SampleRate = 16000 | 44100 | 48000
|
|
28
|
+
export type BitDepth = 8 | 16 | 32
|
|
29
|
+
|
|
30
|
+
export interface Chunk {
|
|
31
|
+
text: string
|
|
32
|
+
timestamp: [number, number | null]
|
|
63
33
|
}
|
|
64
34
|
|
|
65
|
-
export interface
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
zcr?: boolean;
|
|
70
|
-
spectralCentroid?: boolean;
|
|
71
|
-
spectralFlatness?: boolean;
|
|
72
|
-
spectralRolloff?: boolean;
|
|
73
|
-
spectralBandwidth?: boolean;
|
|
74
|
-
chromagram?: boolean;
|
|
75
|
-
tempo?: boolean;
|
|
76
|
-
hnr?: boolean;
|
|
35
|
+
export interface TranscriberData {
|
|
36
|
+
isBusy: boolean
|
|
37
|
+
text: string
|
|
38
|
+
chunks: Chunk[]
|
|
77
39
|
}
|
|
78
40
|
|
|
79
|
-
export interface
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// number of audio samples for this point (samples size depends on bit depth)
|
|
92
|
-
samples?: number;
|
|
93
|
-
// Id of the speaker for this point
|
|
94
|
-
speaker?: number;
|
|
41
|
+
export interface AudioRecording {
|
|
42
|
+
fileUri: string
|
|
43
|
+
filename: string
|
|
44
|
+
durationMs: number
|
|
45
|
+
size: number
|
|
46
|
+
mimeType: string
|
|
47
|
+
channels: number
|
|
48
|
+
bitDepth: BitDepth
|
|
49
|
+
sampleRate: SampleRate
|
|
50
|
+
transcripts?: TranscriberData[]
|
|
51
|
+
wavPCMData?: Float32Array // Full PCM data for the recording in WAV format (only on web, for native use the fileUri)
|
|
52
|
+
analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag
|
|
95
53
|
}
|
|
96
54
|
|
|
97
|
-
export interface
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
sampleRate: number; // Sample rate of the audio
|
|
104
|
-
dataPoints: DataPoint[];
|
|
105
|
-
amplitudeRange: {
|
|
106
|
-
min: number;
|
|
107
|
-
max: number;
|
|
108
|
-
};
|
|
109
|
-
speakerChanges?: {
|
|
110
|
-
timestamp: number;
|
|
111
|
-
speaker: number;
|
|
112
|
-
}[];
|
|
55
|
+
export interface StartRecordingResult {
|
|
56
|
+
fileUri: string
|
|
57
|
+
mimeType: string
|
|
58
|
+
channels?: number
|
|
59
|
+
bitDepth?: BitDepth
|
|
60
|
+
sampleRate?: SampleRate
|
|
113
61
|
}
|
|
114
62
|
|
|
115
|
-
export type EncodingType = "pcm_32bit" | "pcm_16bit" | "pcm_8bit";
|
|
116
|
-
export type SampleRate = 16000 | 44100 | 48000;
|
|
117
63
|
export interface RecordingConfig {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// Optional parameters for audio processing
|
|
124
|
-
//TODO remove maxRecentDataDuration - should be replaced by maxDataPoints to 100.
|
|
125
|
-
maxRecentDataDuration?: number; // Maximum duration of recent data to keep for processing (default is 10.0 seconds)
|
|
126
|
-
enableProcessing?: boolean; // Boolean to enable/disable audio processing (default is false)
|
|
127
|
-
pointsPerSecond?: number; // Number of data points to extract per second of audio (default is 1000)
|
|
128
|
-
algorithm?: string; // Algorithm to use for extraction (default is "rms")
|
|
129
|
-
features?: AudioFeaturesOptions; // Feature options to extract (default is empty)
|
|
64
|
+
sampleRate?: SampleRate // Sample rate for recording
|
|
65
|
+
channels?: 1 | 2 // 1 or 2 (MONO or STEREO)
|
|
66
|
+
encoding?: EncodingType // Encoding type for the recording
|
|
67
|
+
interval?: number // Interval in milliseconds at which to emit recording data
|
|
130
68
|
|
|
131
|
-
|
|
69
|
+
// Optional parameters for audio processing
|
|
70
|
+
enableProcessing?: boolean // Boolean to enable/disable audio processing (default is false)
|
|
71
|
+
pointsPerSecond?: number // Number of data points to extract per second of audio (default is 1000)
|
|
72
|
+
algorithm?: AmplitudeAlgorithm // Algorithm to use for amplitude computation (default is "rms")
|
|
73
|
+
features?: AudioFeaturesOptions // Feature options to extract (default is empty)
|
|
132
74
|
|
|
133
|
-
|
|
134
|
-
|
|
75
|
+
onAudioStream?: (_: AudioDataEvent) => Promise<void> // Callback function to handle audio stream
|
|
76
|
+
onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void> // Callback function to handle audio features extraction results
|
|
135
77
|
}
|
|
@@ -1,228 +1,256 @@
|
|
|
1
1
|
// src/ExpoAudioStreamModule.web.ts
|
|
2
|
-
import
|
|
3
|
-
import { EventEmitter } from "expo-modules-core";
|
|
2
|
+
import { EventEmitter } from 'expo-modules-core'
|
|
4
3
|
|
|
4
|
+
import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
6
|
+
AudioRecording,
|
|
7
|
+
AudioStreamStatus,
|
|
8
|
+
BitDepth,
|
|
9
|
+
RecordingConfig,
|
|
10
|
+
StartRecordingResult,
|
|
11
|
+
} from './ExpoAudioStream.types'
|
|
12
|
+
import { WebRecorder } from './WebRecorder.web'
|
|
13
|
+
import { AudioEventPayload } from './events'
|
|
14
|
+
import { getLogger } from './logger'
|
|
15
|
+
import { concatenateBuffers } from './utils/concatenateBuffers'
|
|
16
|
+
import { encodingToBitDepth } from './utils/encodingToBitDepth'
|
|
17
|
+
import { writeWavHeader } from './utils/writeWavHeader'
|
|
15
18
|
|
|
16
19
|
export interface EmitAudioEventProps {
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
data: Float32Array
|
|
21
|
+
position: number
|
|
19
22
|
}
|
|
20
|
-
export type EmitAudioEventFunction = (_: EmitAudioEventProps) => void
|
|
21
|
-
export type EmitAudioAnalysisFunction = (_:
|
|
23
|
+
export type EmitAudioEventFunction = (_: EmitAudioEventProps) => void
|
|
24
|
+
export type EmitAudioAnalysisFunction = (_: AudioAnalysis) => void
|
|
22
25
|
|
|
23
26
|
export interface ExpoAudioStreamWebProps {
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
audioWorkletUrl: string
|
|
28
|
+
featuresExtratorUrl: string
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
const logger = getLogger('ExpoAudioStreamWeb')
|
|
32
|
+
|
|
30
33
|
export class ExpoAudioStreamWeb extends EventEmitter {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Utility to handle user media stream
|
|
81
|
-
async getMediaStream() {
|
|
82
|
-
try {
|
|
83
|
-
return await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error("Failed to get media stream:", error);
|
|
86
|
-
throw error;
|
|
34
|
+
customRecorder: WebRecorder | null
|
|
35
|
+
audioChunks: ArrayBuffer[]
|
|
36
|
+
isRecording: boolean
|
|
37
|
+
isPaused: boolean
|
|
38
|
+
recordingStartTime: number
|
|
39
|
+
pausedTime: number
|
|
40
|
+
currentDurationMs: number
|
|
41
|
+
currentSize: number
|
|
42
|
+
currentInterval: number
|
|
43
|
+
lastEmittedSize: number
|
|
44
|
+
lastEmittedTime: number
|
|
45
|
+
streamUuid: string | null
|
|
46
|
+
extension: 'webm' | 'wav' = 'wav' // Default extension is 'webm'
|
|
47
|
+
recordingConfig?: RecordingConfig
|
|
48
|
+
bitDepth: BitDepth // Bit depth of the audio
|
|
49
|
+
audioWorkletUrl: string
|
|
50
|
+
featuresExtratorUrl: string
|
|
51
|
+
|
|
52
|
+
constructor({
|
|
53
|
+
audioWorkletUrl,
|
|
54
|
+
featuresExtratorUrl,
|
|
55
|
+
}: ExpoAudioStreamWebProps) {
|
|
56
|
+
const mockNativeModule = {
|
|
57
|
+
addListener: () => {
|
|
58
|
+
// Not used on web
|
|
59
|
+
},
|
|
60
|
+
removeListeners: () => {
|
|
61
|
+
// Not used on web
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
super(mockNativeModule) // Pass the mock native module to the parent class
|
|
65
|
+
|
|
66
|
+
this.customRecorder = null
|
|
67
|
+
this.audioChunks = []
|
|
68
|
+
this.isRecording = false
|
|
69
|
+
this.isPaused = false
|
|
70
|
+
this.recordingStartTime = 0
|
|
71
|
+
this.pausedTime = 0
|
|
72
|
+
this.currentDurationMs = 0
|
|
73
|
+
this.currentSize = 0
|
|
74
|
+
this.bitDepth = 32 // Default
|
|
75
|
+
this.currentInterval = 1000 // Default interval in ms
|
|
76
|
+
this.lastEmittedSize = 0
|
|
77
|
+
this.lastEmittedTime = 0
|
|
78
|
+
this.streamUuid = null // Initialize UUID on first recording start
|
|
79
|
+
this.audioWorkletUrl = audioWorkletUrl
|
|
80
|
+
this.featuresExtratorUrl = featuresExtratorUrl
|
|
87
81
|
}
|
|
88
|
-
}
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
// Utility to handle user media stream
|
|
84
|
+
async getMediaStream() {
|
|
85
|
+
try {
|
|
86
|
+
return await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Failed to get media stream:', error)
|
|
89
|
+
throw error
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Start recording with options
|
|
94
|
+
async startRecording(recordingConfig: RecordingConfig = {}) {
|
|
95
|
+
if (this.isRecording) {
|
|
96
|
+
throw new Error('Recording is already in progress')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.bitDepth = encodingToBitDepth({
|
|
100
|
+
encoding: recordingConfig.encoding ?? 'pcm_32bit',
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const audioContext = new (window.AudioContext ||
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
105
|
+
// @ts-ignore - Allow webkitAudioContext for Safari
|
|
106
|
+
window.webkitAudioContext)()
|
|
107
|
+
const stream = await this.getMediaStream()
|
|
108
|
+
|
|
109
|
+
const source = audioContext.createMediaStreamSource(stream)
|
|
110
|
+
|
|
111
|
+
this.customRecorder = new WebRecorder({
|
|
112
|
+
audioContext,
|
|
113
|
+
source,
|
|
114
|
+
recordingConfig,
|
|
115
|
+
audioWorkletUrl: this.audioWorkletUrl,
|
|
116
|
+
emitAudioEventCallback: ({
|
|
117
|
+
data,
|
|
118
|
+
position,
|
|
119
|
+
}: EmitAudioEventProps) => {
|
|
120
|
+
this.audioChunks.push(data)
|
|
121
|
+
this.currentSize += data.byteLength
|
|
122
|
+
this.emitAudioEvent({ data, position })
|
|
123
|
+
this.lastEmittedTime = Date.now()
|
|
124
|
+
this.lastEmittedSize = this.currentSize
|
|
125
|
+
},
|
|
126
|
+
emitAudioAnalysisCallback: (audioAnalysisData: AudioAnalysis) => {
|
|
127
|
+
logger.log(`Emitted AudioAnalysis:`, audioAnalysisData)
|
|
128
|
+
this.emit('AudioAnalysis', audioAnalysisData)
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
await this.customRecorder.init()
|
|
132
|
+
this.customRecorder.start()
|
|
133
|
+
|
|
134
|
+
// // Set a timer to stop recording after 5 seconds
|
|
135
|
+
// setTimeout(() => {
|
|
136
|
+
// logger.log("AUTO Stopping recording");
|
|
137
|
+
// this.customRecorder?.stopAndPlay();
|
|
138
|
+
// this.isRecording = false;
|
|
139
|
+
// }, 3000);
|
|
140
|
+
|
|
141
|
+
this.isRecording = true
|
|
142
|
+
this.recordingConfig = recordingConfig
|
|
143
|
+
this.recordingStartTime = Date.now()
|
|
144
|
+
this.pausedTime = 0
|
|
145
|
+
this.lastEmittedSize = 0
|
|
146
|
+
this.lastEmittedTime = 0
|
|
147
|
+
this.streamUuid = Date.now().toString()
|
|
148
|
+
const fileUri = `${this.streamUuid}.${this.extension}`
|
|
149
|
+
const streamConfig: StartRecordingResult = {
|
|
150
|
+
fileUri,
|
|
151
|
+
mimeType: `audio/${this.extension}`,
|
|
152
|
+
bitDepth: this.bitDepth,
|
|
153
|
+
channels: recordingConfig.channels ?? 1,
|
|
154
|
+
sampleRate: recordingConfig.sampleRate ?? 44100,
|
|
155
|
+
}
|
|
156
|
+
return streamConfig
|
|
94
157
|
}
|
|
95
158
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
recordingConfig,
|
|
111
|
-
audioWorkletUrl: this.audioWorkletUrl,
|
|
112
|
-
featuresExtratorUrl: this.featuresExtratorUrl,
|
|
113
|
-
emitAudioEventCallback: ({ data, position }: EmitAudioEventProps) => {
|
|
114
|
-
this.audioChunks.push(data);
|
|
115
|
-
this.currentSize += data.byteLength;
|
|
116
|
-
this.emitAudioEvent({ data, position });
|
|
117
|
-
this.lastEmittedTime = Date.now();
|
|
118
|
-
this.lastEmittedSize = this.currentSize;
|
|
119
|
-
},
|
|
120
|
-
emitAudioAnalysisCallback: (audioAnalysisData: AudioAnalysisData) => {
|
|
121
|
-
console.log(`Emitted AudioAnalysis:`, audioAnalysisData);
|
|
122
|
-
this.emit("AudioAnalysis", audioAnalysisData);
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
await this.customRecorder.init();
|
|
126
|
-
this.customRecorder.start();
|
|
127
|
-
|
|
128
|
-
// // Set a timer to stop recording after 5 seconds
|
|
129
|
-
// setTimeout(() => {
|
|
130
|
-
// console.log("AUTO Stopping recording");
|
|
131
|
-
// this.customRecorder?.stopAndPlay();
|
|
132
|
-
// this.isRecording = false;
|
|
133
|
-
// }, 3000);
|
|
134
|
-
|
|
135
|
-
this.isRecording = true;
|
|
136
|
-
this.recordingConfig = recordingConfig;
|
|
137
|
-
this.recordingStartTime = Date.now();
|
|
138
|
-
this.pausedTime = 0;
|
|
139
|
-
this.lastEmittedSize = 0;
|
|
140
|
-
this.lastEmittedTime = 0;
|
|
141
|
-
this.streamUuid = Date.now().toString();
|
|
142
|
-
const fileUri = `${this.streamUuid}.${this.extension}`;
|
|
143
|
-
const streamConfig: StartAudioStreamResult = {
|
|
144
|
-
fileUri,
|
|
145
|
-
mimeType: `audio/${this.extension}`,
|
|
146
|
-
bitDepth: this.bitDepth,
|
|
147
|
-
channels: recordingConfig.channels ?? 1,
|
|
148
|
-
sampleRate: recordingConfig.sampleRate ?? 44100,
|
|
149
|
-
};
|
|
150
|
-
return streamConfig;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
emitAudioEvent({ data, position }: EmitAudioEventProps) {
|
|
154
|
-
const fileUri = `${this.streamUuid}.${this.extension}`;
|
|
155
|
-
const audioEventPayload: AudioEventPayload = {
|
|
156
|
-
fileUri,
|
|
157
|
-
mimeType: `audio/${this.extension}`,
|
|
158
|
-
lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
|
|
159
|
-
deltaSize: data.byteLength,
|
|
160
|
-
position,
|
|
161
|
-
totalSize: this.currentSize,
|
|
162
|
-
buffer: data,
|
|
163
|
-
streamUuid: this.streamUuid ?? "", // Generate or manage UUID for stream identification
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
this.emit("AudioData", audioEventPayload);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Stop recording
|
|
170
|
-
async stopRecording(): Promise<AudioStreamResult | null> {
|
|
171
|
-
if (this.customRecorder) {
|
|
172
|
-
const fullPcmBuffer = await this.customRecorder.stop();
|
|
173
|
-
log.debug(`Stopped recording`, fullPcmBuffer);
|
|
159
|
+
emitAudioEvent({ data, position }: EmitAudioEventProps) {
|
|
160
|
+
const fileUri = `${this.streamUuid}.${this.extension}`
|
|
161
|
+
const audioEventPayload: AudioEventPayload = {
|
|
162
|
+
fileUri,
|
|
163
|
+
mimeType: `audio/${this.extension}`,
|
|
164
|
+
lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
|
|
165
|
+
deltaSize: data.byteLength,
|
|
166
|
+
position,
|
|
167
|
+
totalSize: this.currentSize,
|
|
168
|
+
buffer: data,
|
|
169
|
+
streamUuid: this.streamUuid ?? '', // Generate or manage UUID for stream identification
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.emit('AudioData', audioEventPayload)
|
|
174
173
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
174
|
+
|
|
175
|
+
// Stop recording
|
|
176
|
+
async stopRecording(): Promise<AudioRecording> {
|
|
177
|
+
if (!this.customRecorder) {
|
|
178
|
+
throw new Error('Recorder is not initialized')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const fullPcmBufferArray = await this.customRecorder.stop()
|
|
182
|
+
|
|
183
|
+
// concat all audio chunks
|
|
184
|
+
logger.debug(`Stopped recording`, fullPcmBufferArray)
|
|
185
|
+
this.isRecording = false
|
|
186
|
+
this.currentDurationMs = Date.now() - this.recordingStartTime
|
|
187
|
+
|
|
188
|
+
const wavConfig = {
|
|
189
|
+
buffer: fullPcmBufferArray.buffer,
|
|
190
|
+
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
191
|
+
numChannels: this.recordingConfig?.channels ?? 1,
|
|
192
|
+
bitDepth: this.bitDepth,
|
|
193
|
+
}
|
|
194
|
+
logger.debug(`Writing wav header`, wavConfig)
|
|
195
|
+
const wavBuffer = writeWavHeader(wavConfig).slice(0)
|
|
196
|
+
|
|
197
|
+
// Create blob fileUri from audio chunks
|
|
198
|
+
const blob = new Blob([wavBuffer], {
|
|
199
|
+
type: `audio/${this.extension}`,
|
|
200
|
+
})
|
|
201
|
+
const fileUri = URL.createObjectURL(blob)
|
|
202
|
+
|
|
203
|
+
const result: AudioRecording = {
|
|
204
|
+
fileUri,
|
|
205
|
+
filename: `${this.streamUuid}.${this.extension}`,
|
|
206
|
+
wavPCMData: fullPcmBufferArray,
|
|
207
|
+
bitDepth: this.bitDepth,
|
|
208
|
+
channels: this.recordingConfig?.channels ?? 1,
|
|
209
|
+
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
210
|
+
durationMs: this.currentDurationMs,
|
|
211
|
+
size: this.currentSize,
|
|
212
|
+
mimeType: `audio/${this.extension}`,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result
|
|
194
216
|
}
|
|
195
217
|
|
|
196
|
-
|
|
197
|
-
|
|
218
|
+
// Pause recording
|
|
219
|
+
async pauseRecording() {
|
|
220
|
+
if (!this.isRecording || this.isPaused) {
|
|
221
|
+
throw new Error('Recording is not active or already paused')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (this.customRecorder) {
|
|
225
|
+
this.customRecorder.pause()
|
|
226
|
+
}
|
|
227
|
+
this.isPaused = true
|
|
228
|
+
this.pausedTime = Date.now()
|
|
198
229
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
230
|
+
|
|
231
|
+
// Resume recording
|
|
232
|
+
async resumeRecording() {
|
|
233
|
+
if (!this.isPaused) {
|
|
234
|
+
throw new Error('Recording is not paused')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (this.customRecorder) {
|
|
238
|
+
this.customRecorder.resume()
|
|
239
|
+
}
|
|
240
|
+
this.isPaused = false
|
|
241
|
+
this.recordingStartTime += Date.now() - this.pausedTime
|
|
207
242
|
}
|
|
208
243
|
|
|
209
|
-
|
|
210
|
-
|
|
244
|
+
// Get current status
|
|
245
|
+
status() {
|
|
246
|
+
const status: AudioStreamStatus = {
|
|
247
|
+
isRecording: this.isRecording,
|
|
248
|
+
isPaused: this.isPaused,
|
|
249
|
+
durationMs: Date.now() - this.recordingStartTime,
|
|
250
|
+
size: this.currentSize,
|
|
251
|
+
interval: this.currentInterval,
|
|
252
|
+
mimeType: `audio/${this.extension}`,
|
|
253
|
+
}
|
|
254
|
+
return status
|
|
211
255
|
}
|
|
212
|
-
this.isPaused = false;
|
|
213
|
-
this.recordingStartTime += Date.now() - this.pausedTime;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Get current status
|
|
217
|
-
status() {
|
|
218
|
-
const status: AudioStreamStatus = {
|
|
219
|
-
isRecording: this.isRecording,
|
|
220
|
-
isPaused: this.isPaused,
|
|
221
|
-
durationMs: Date.now() - this.recordingStartTime,
|
|
222
|
-
size: this.currentSize,
|
|
223
|
-
interval: this.currentInterval,
|
|
224
|
-
mimeType: `audio/${this.extension}`,
|
|
225
|
-
};
|
|
226
|
-
return status;
|
|
227
|
-
}
|
|
228
256
|
}
|