Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ class MainActivity : ComponentActivity() {
private lateinit var viewModel: ModelSettingsViewModel
private lateinit var downloadViewModel: ModelDownloadViewModel

// Cached ParakeetModule: loaded once and reused across transcription calls.
private var parakeetModule: ParakeetModule? = null
private var loadedModelPath: String = ""
private var loadedTokenizerPath: String = ""
private var loadedDataPath: String? = null

enum class Screen {
DOWNLOAD,
MAIN,
Expand Down Expand Up @@ -211,6 +217,54 @@ class MainActivity : ComponentActivity() {
}.start()
}

/**
* Returns the cached ParakeetModule, creating or recreating it only if
* the model settings have changed since the last load.
*/
private fun getOrCreateModule(settings: ModelSettings): ParakeetModule {
val dataPath = settings.dataPath.ifBlank { null }
if (parakeetModule != null &&
loadedModelPath == settings.modelPath &&
loadedTokenizerPath == settings.tokenizerPath &&
loadedDataPath == dataPath
) {
return parakeetModule!!
Comment on lines +224 to +231
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

getOrCreateModule() reads/writes parakeetModule and the loaded*Path fields without any synchronization. Since runParakeetOnWavFile() can be invoked from different threads (UI thread via runParakeet() and a background Thread via runParakeetFromFile()), this can race with module creation/close and/or concurrent transcribe() calls. Consider guarding module access with a lock/single-thread executor, and ensure only one transcription can run at a time across all entry points.

Copilot uses AI. Check for mistakes.
}

// Settings changed or first load — construct the new module first,
// then swap it in and close the old one only after successful creation.
val oldModule = parakeetModule

// Clear cached module and paths so a failed load never returns a closed or stale module.
parakeetModule = null
loadedModelPath = null
loadedTokenizerPath = null
loadedDataPath = null

Log.v(TAG, "Loading model: ${settings.modelPath}")
val newModule = try {
ParakeetModule(
modelPath = settings.modelPath,
tokenizerPath = settings.tokenizerPath,
dataPath = dataPath
)
} catch (e: Exception) {
// Leave cache cleared; do not close the old module here since it may still be in use.
throw e
}

// Successfully created the new module; now it is safe to close the old one
// and update the cached references and paths.
oldModule?.close()

parakeetModule = newModule
loadedModelPath = settings.modelPath
loadedTokenizerPath = settings.tokenizerPath
loadedDataPath = dataPath

return newModule
}

/**
* Common method to run Parakeet on a WAV file path.
*/
Expand All @@ -232,24 +286,18 @@ class MainActivity : ComponentActivity() {
buttonEnabled = false
}

val parakeetModule = ParakeetModule(
modelPath = settings.modelPath,
tokenizerPath = settings.tokenizerPath,
dataPath = settings.dataPath.ifBlank { null }
)
val module = getOrCreateModule(settings)

Log.v(TAG, "Starting transcribe for: $wavFilePath")
runOnUiThread {
statusText = "Transcribing..."
}
val startTime = System.currentTimeMillis()
val result = parakeetModule.transcribe(wavFilePath)
val result = module.transcribe(wavFilePath)
val elapsedTime = System.currentTimeMillis() - startTime
val elapsedSeconds = elapsedTime / 1000.0
Log.v(TAG, "Finished transcribe in ${elapsedSeconds}s")

parakeetModule.close()

runOnUiThread {
transcriptionOutput = result
statusText = "Transcription complete (%.2fs)".format(elapsedSeconds)
Expand Down
Loading