Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions android/src/main/java/com/vosk/VoskModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.vosk.android.RecognitionListener
import org.vosk.android.SpeechService
import org.vosk.android.StorageService
import java.io.IOException
import java.io.FileInputStream

class VoskModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext), RecognitionListener {
Expand Down Expand Up @@ -158,6 +159,48 @@ override fun onResult(hypothesis: String) {
}
}

/**
* Transcribe a 16 kHz mono WAV file from disk.
* @param wavPath absolute path to a .wav file (PCM 16-bit LE, 16 kHz, mono)
* @param promise resolves to the Vosk JSON string
*/
@ReactMethod
fun transcribeFile(wavPath: String, promise: Promise) {
// Ensure model is loaded
if (model == null) {
promise.reject("NO_MODEL", "Call loadModel() first")
return
}

var recognizer: Recognizer? = null
var input: FileInputStream? = null
try {
// Create a new recognizer on the same model & sample rate
recognizer = Recognizer(model, sampleRate)

// Open the WAV file
input = FileInputStream(wavPath)
val buffer = ByteArray(4096)
var bytesRead: Int

// Read & feed each chunk
while (input.read(buffer).also { bytesRead = it } > 0) {
recognizer.acceptWaveForm(buffer, bytesRead)
}
Copy link

Copilot AI Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WAV file is being read from the beginning, which includes the WAV header. The header should be skipped to avoid feeding metadata to the recognizer. Consider using input.skip(44) before the read loop to skip the standard 44-byte WAV header, or implement proper WAV header parsing.

Copilot uses AI. Check for mistakes.

// Pull out the final result JSON
val resultJson = recognizer.finalResult
promise.resolve(resultJson)

} catch (e: Exception) {
promise.reject("TRANSCRIBE_FAIL", e)
} finally {
// Clean up
try { input?.close() } catch (_: IOException) { }
Copy link

Copilot AI Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Silently catching and ignoring IOException when closing the file stream can hide important cleanup issues. Consider logging the exception or using a more specific exception handling approach.

Suggested change
try { input?.close() } catch (_: IOException) { }
try { input?.close() } catch (e: IOException) { Log.e(NAME, "Failed to close input stream", e) }

Copilot uses AI. Check for mistakes.
recognizer?.close()
}
}

private fun cleanRecognizer() {
if (speechService != null) {
speechService!!.stop()
Expand Down
2 changes: 2 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface VoskInterface extends TurboModule {
loadModel: (path: string) => Promise<void>;
unload: () => void;

transcribeFile(wavPath: string): Promise<string>;

start: (options?: VoskOptions) => Promise<void>;
stop: () => void;

Expand Down
8 changes: 8 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ export default class Vosk {
*/
loadModel = (path: string) => VoskModule.loadModel(path);

/**
* Transcribe a 16 kHz mono WAV file you’ve already extracted.
* @param wavPath absolute filesystem path to your .wav
* @returns Promise<string> the Vosk JSON result
*/
transcribeFile = (wavPath: string): Promise<string> =>
VoskModule.transcribeFile(wavPath);

/**
* Asks for recording permissions then starts the recognizer.
*
Expand Down