@@ -66,42 +66,99 @@ const AssemblyView: () => JSX.Element = () => {
66
66
latencyHint : "interactive" ,
67
67
} ) ;
68
68
69
- // Create a MediaStreamSource from the microphone stream
70
- const source = audioContext . createMediaStreamSource ( microphone . stream ) ;
69
+ const setupAudioProcessor = async ( ) => {
70
+ const source = audioContext . createMediaStreamSource ( microphone . stream ) ;
71
+ let processor : AudioWorkletNode | ScriptProcessorNode ;
71
72
72
- // Create a ScriptProcessor to get raw PCM data
73
- // @ts -ignore - ScriptProcessor is deprecated but still works
74
- const processor = audioContext . createScriptProcessor ( 2048 , 1 , 1 ) ;
75
-
76
- processor . onaudioprocess = ( e ) => {
77
- if ( isPaused || ! isConnected ) return ;
73
+ try {
74
+ // Try AudioWorklet first (modern browsers)
75
+ if ( "audioWorklet" in audioContext ) {
76
+ // Define processor code as a string
77
+ const processorCode = `
78
+ class PCMProcessor extends AudioWorkletProcessor {
79
+ process(inputs, outputs) {
80
+ const input = inputs[0];
81
+ const inputChannel = input[0];
82
+
83
+ // Convert to 16-bit PCM
84
+ const pcmData = new Int16Array(inputChannel.length);
85
+ for (let i = 0; i < inputChannel.length; i++) {
86
+ pcmData[i] = Math.min(1, Math.max(-1, inputChannel[i])) * 0x7FFF;
87
+ }
88
+
89
+ // Post the PCM data back to the main thread
90
+ this.port.postMessage(pcmData.buffer, [pcmData.buffer]);
91
+
92
+ return true;
93
+ }
94
+ }
95
+ registerProcessor('pcm-processor', PCMProcessor);
96
+ ` ;
97
+
98
+ // Create a blob URL for the processor code
99
+ const blob = new Blob ( [ processorCode ] , {
100
+ type : "application/javascript" ,
101
+ } ) ;
102
+ const url = URL . createObjectURL ( blob ) ;
103
+
104
+ await audioContext . audioWorklet . addModule ( url ) ;
105
+ URL . revokeObjectURL ( url ) ;
106
+
107
+ const workletNode = new AudioWorkletNode (
108
+ audioContext ,
109
+ "pcm-processor"
110
+ ) ;
111
+ workletNode . port . onmessage = ( e : MessageEvent < ArrayBuffer > ) => {
112
+ if ( isPaused || ! isConnected ) return ;
113
+ const blob = new Blob ( [ e . data ] , { type : "audio/wav" } ) ;
114
+ sendAudio ( blob ) ;
115
+ } ;
116
+ processor = workletNode ;
117
+ } else {
118
+ // Fallback to ScriptProcessor (older browsers)
119
+ console . log ( "[AssemblyAI] Using ScriptProcessor fallback" ) ;
120
+ // @ts -ignore - ScriptProcessor is deprecated but needed for fallback
121
+ const scriptNode = audioContext . createScriptProcessor ( 2048 , 1 , 1 ) ;
122
+ scriptNode . onaudioprocess = ( e : AudioProcessingEvent ) => {
123
+ if ( isPaused || ! isConnected ) return ;
124
+ const inputData = e . inputBuffer . getChannelData ( 0 ) ;
125
+ const pcmData = new Int16Array ( inputData . length ) ;
126
+ for ( let i = 0 ; i < inputData . length ; i ++ ) {
127
+ pcmData [ i ] = Math . min ( 1 , Math . max ( - 1 , inputData [ i ] ) ) * 0x7fff ;
128
+ }
129
+ const blob = new Blob ( [ pcmData ] , { type : "audio/wav" } ) ;
130
+ sendAudio ( blob ) ;
131
+ } ;
132
+ processor = scriptNode ;
133
+ }
78
134
79
- // Get raw PCM data
80
- const inputData = e . inputBuffer . getChannelData ( 0 ) ;
135
+ // Connect the audio nodes
136
+ source . connect ( processor ) ;
137
+ if ( ! ( "audioWorklet" in audioContext ) ) {
138
+ ( processor as ScriptProcessorNode ) . connect (
139
+ ( audioContext as AudioContext ) . destination
140
+ ) ;
141
+ }
81
142
82
- // Convert to 16-bit PCM
83
- const pcmData = new Int16Array ( inputData . length ) ;
84
- for ( let i = 0 ; i < inputData . length ; i ++ ) {
85
- pcmData [ i ] = Math . min ( 1 , Math . max ( - 1 , inputData [ i ] ) ) * 0x7fff ;
143
+ // Store for cleanup
144
+ mainRecorder . current = {
145
+ stop : ( ) => {
146
+ processor . disconnect ( ) ;
147
+ source . disconnect ( ) ;
148
+ audioContext . close ( ) ;
149
+ } ,
150
+ } as any ;
151
+ } catch ( error ) {
152
+ console . error ( "[AssemblyAI] Audio processor setup failed:" , error ) ;
153
+ // Fallback to ScriptProcessor if AudioWorklet fails
154
+ if ( "audioWorklet" in audioContext ) {
155
+ console . log ( "[AssemblyAI] Falling back to ScriptProcessor" ) ;
156
+ setupAudioProcessor ( ) ;
157
+ }
86
158
}
87
-
88
- // Send as blob
89
- const blob = new Blob ( [ pcmData ] , { type : "audio/wav" } ) ;
90
- sendAudio ( blob ) ;
91
159
} ;
92
160
93
- // Connect the audio nodes
94
- source . connect ( processor ) ;
95
- processor . connect ( audioContext . destination ) ;
96
-
97
- // Store for cleanup
98
- mainRecorder . current = {
99
- stop : ( ) => {
100
- processor . disconnect ( ) ;
101
- source . disconnect ( ) ;
102
- audioContext . close ( ) ;
103
- } ,
104
- } as any ;
161
+ setupAudioProcessor ( ) ;
105
162
106
163
return ( ) => {
107
164
if ( mainRecorder . current ) {
@@ -136,7 +193,7 @@ const AssemblyView: () => JSX.Element = () => {
136
193
const transcriptHandler = ( transcript : any ) => {
137
194
if ( ! transcript . text ) return ;
138
195
139
- console . log ( "[AssemblyAI ] transcript:" , {
196
+ console . log ( "[AssemblyView ] transcript:" , {
140
197
type : transcript . message_type ,
141
198
text : transcript . text ,
142
199
confidence : transcript . confidence ,
@@ -145,6 +202,9 @@ const AssemblyView: () => JSX.Element = () => {
145
202
const isFinal = transcript . message_type === "FinalTranscript" ;
146
203
const isPartial = transcript . message_type === "PartialTranscript" ;
147
204
205
+ // Filter out empty or low confidence transcripts
206
+ if ( ! transcript . text || transcript . confidence === 0 ) return ;
207
+
148
208
// Only handle final or partial transcripts
149
209
if ( ! isFinal && ! isPartial ) return ;
150
210
0 commit comments