Skip to content

Commit fd6f241

Browse files
committed
Handle App Crash from Audio Transcription
1 parent be802d1 commit fd6f241

8 files changed

Lines changed: 273 additions & 209 deletions

File tree

shared/src/androidMain/kotlin/com/module/notelycompose/platform/Downloader.android.kt

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,27 @@ actual class Downloader(
2929

3030

3131
actual suspend fun startDownload(url: String, fileName: String) {
32-
val request = DownloadManager.Request(url.toUri())
33-
.setTitle("Downloading $fileName")
34-
.setDestinationInExternalFilesDir(
35-
mainContext,
36-
Environment.DIRECTORY_DOWNLOADS,
37-
fileName
38-
)
39-
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)
40-
41-
val downloadManager =
42-
mainContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
43-
val downloadId = downloadManager.enqueue(request)
44-
45-
preferencesRepository.setModelDownloadId(downloadId)
32+
try {
33+
val request = DownloadManager.Request(url.toUri())
34+
.setTitle("Downloading $fileName")
35+
.setDestinationInExternalFilesDir(
36+
mainContext,
37+
Environment.DIRECTORY_DOWNLOADS,
38+
fileName
39+
)
40+
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)
41+
42+
val downloadManager =
43+
mainContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
44+
val downloadId = downloadManager.enqueue(request)
45+
46+
preferencesRepository.setModelDownloadId(downloadId)
47+
48+
} catch (e: NullPointerException) {
49+
debugPrintln {"Invalid download URL $url: ${e.message}"}
50+
} catch (e: Exception) {
51+
debugPrintln {"Failed to start download: ${e.message}"}
52+
}
4653
}
4754

4855
actual suspend fun hasRunningDownload(): Boolean {
@@ -74,38 +81,38 @@ actual class Downloader(
7481

7582
registerDownloadReceiver(downloadId, onSuccess, onFailed)
7683
val downloadManager = mainContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
77-
while (true) {
78-
val query = DownloadManager.Query().setFilterById(downloadId)
79-
val cursor: Cursor = downloadManager.query(query)
80-
if (cursor.moveToFirst()) {
81-
val bytesDownloaded =
82-
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
83-
val bytesTotal =
84-
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
85-
86-
if (bytesTotal > 0) {
87-
val progress = (bytesDownloaded * 100L / bytesTotal).toInt()
88-
89-
// Convert to MB
90-
val downloadedMB =
91-
String.format("%.2f MB", bytesDownloaded / 1024.0 / 1024.0)
92-
val totalMB = String.format("%.2f MB", bytesTotal / 1024.0 / 1024.0)
93-
94-
onProgressUpdated(progress, downloadedMB, totalMB)
95-
}
84+
while (true) {
85+
val query = DownloadManager.Query().setFilterById(downloadId)
86+
val cursor: Cursor = downloadManager.query(query)
87+
if (cursor.moveToFirst()) {
88+
val bytesDownloaded =
89+
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
90+
val bytesTotal =
91+
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
92+
93+
if (bytesTotal > 0) {
94+
val progress = (bytesDownloaded * 100L / bytesTotal).toInt()
95+
96+
// Convert to MB
97+
val downloadedMB =
98+
String.format("%.2f MB", bytesDownloaded / 1024.0 / 1024.0)
99+
val totalMB = String.format("%.2f MB", bytesTotal / 1024.0 / 1024.0)
100+
101+
onProgressUpdated(progress, downloadedMB, totalMB)
102+
}
96103

97-
val status =
98-
cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
99-
cursor.close()
104+
val status =
105+
cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
106+
cursor.close()
100107

101-
if (status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED) {
102-
break
103-
}
104-
} else {
105-
cursor.close()
108+
if (status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED) {
106109
break
107110
}
108-
delay(1000)
111+
} else {
112+
cursor.close()
113+
break
114+
}
115+
delay(1000)
109116
}
110117
}
111118

@@ -171,15 +178,15 @@ actual class Downloader(
171178
}
172179

173180
private fun getErrorTextFromReason(reason: Int) = when (reason) {
174-
DownloadManager.ERROR_CANNOT_RESUME -> "ERROR_CANNOT_RESUME"
175-
DownloadManager.ERROR_DEVICE_NOT_FOUND -> "ERROR_DEVICE_NOT_FOUND"
176-
DownloadManager.ERROR_FILE_ALREADY_EXISTS -> "ERROR_FILE_ALREADY_EXISTS"
177-
DownloadManager.ERROR_FILE_ERROR -> "ERROR_FILE_ERROR"
178-
DownloadManager.ERROR_HTTP_DATA_ERROR -> "ERROR_HTTP_DATA_ERROR"
179-
DownloadManager.ERROR_INSUFFICIENT_SPACE -> "ERROR_INSUFFICIENT_SPACE"
180-
DownloadManager.ERROR_TOO_MANY_REDIRECTS -> "ERROR_TOO_MANY_REDIRECTS"
181-
DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> "ERROR_UNHANDLED_HTTP_CODE"
182-
DownloadManager.ERROR_UNKNOWN -> "ERROR_UNKNOWN"
181+
DownloadManager.ERROR_CANNOT_RESUME -> "ERROR_CANNOT_RESUME"
182+
DownloadManager.ERROR_DEVICE_NOT_FOUND -> "ERROR_DEVICE_NOT_FOUND"
183+
DownloadManager.ERROR_FILE_ALREADY_EXISTS -> "ERROR_FILE_ALREADY_EXISTS"
184+
DownloadManager.ERROR_FILE_ERROR -> "ERROR_FILE_ERROR"
185+
DownloadManager.ERROR_HTTP_DATA_ERROR -> "ERROR_HTTP_DATA_ERROR"
186+
DownloadManager.ERROR_INSUFFICIENT_SPACE -> "ERROR_INSUFFICIENT_SPACE"
187+
DownloadManager.ERROR_TOO_MANY_REDIRECTS -> "ERROR_TOO_MANY_REDIRECTS"
188+
DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> "ERROR_UNHANDLED_HTTP_CODE"
189+
DownloadManager.ERROR_UNKNOWN -> "ERROR_UNKNOWN"
183190
else -> "DOWNLOAD_ERROR"
184191
}
185192

shared/src/androidMain/kotlin/com/module/notelycompose/platform/Transcriper.android.kt

Lines changed: 39 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import android.os.Environment
77
import androidx.core.content.ContextCompat
88
import audio.utils.LauncherHolder
99
import com.module.notelycompose.core.debugPrintln
10-
import com.module.notelycompose.utils.decodeWaveFileStream
10+
import com.module.notelycompose.utils.decodeWaveFile
1111
import com.whispercpp.whisper.WhisperCallback
1212
import com.whispercpp.whisper.WhisperContext
13-
import kotlinx.coroutines.flow.collectIndexed
1413
import kotlinx.coroutines.suspendCancellableCoroutine
1514
import java.io.File
1615
import kotlin.coroutines.resume
@@ -93,66 +92,51 @@ actual class Transcriber(
9392
}
9493

9594
actual suspend fun start(
96-
filePath: String,
97-
language: String,
98-
onProgress: (Int) -> Unit,
99-
onNewSegment: (Long, Long, String) -> Unit,
100-
onComplete: () -> Unit
95+
filePath: String, language: String,
96+
onProgress : (Int) -> Unit,
97+
onNewSegment : (Long, Long,String) -> Unit,
98+
onComplete : () -> Unit,
99+
onError : () -> Unit
101100
) {
102-
if (!canTranscribe) return
101+
if (!canTranscribe) {
102+
return
103+
}
104+
103105
canTranscribe = false
104106

105107
try {
106-
debugPrintln { "Streaming wave samples in chunks..." }
108+
debugPrintln{"Reading wave samples... "}
107109
val file = File(filePath)
108-
109-
// 1 second of audio at 16 kHz
110-
val chunkFrames = 16_000
111-
112-
// track how many frames we've processed so we can report progress
113-
var totalFramesRead = 0L
114-
// approximate total frames from file size (minus 44 byte header):
115-
val totalFrames = ((file.length() - 44) / 2 /*bytes/sample*/)
116-
117-
// 1) collectIndexed gives us the chunk index
118-
decodeWaveFileStream(file, chunkFrames).collectIndexed { chunkIndex, floatChunk ->
119-
totalFramesRead += floatChunk.size
120-
121-
debugPrintln { "Chunk #$chunkIndex${floatChunk.size} samples" }
122-
123-
// 2) send each chunk into Whisper
124-
whisperContext?.transcribeData(
125-
floatChunk,
126-
language,
127-
callback = object : WhisperCallback {
128-
override fun onNewSegment(startMs: Long, endMs: Long, text: String) {
129-
onNewSegment(startMs, endMs, text)
130-
}
131-
132-
override fun onProgress(progress: Int) {
133-
onProgress(progress)
134-
}
135-
136-
override fun onComplete() {
137-
// per‑chunk completion if needed
138-
}
139-
}
140-
)
141-
142-
// 3) fire a rough progress callback in ms
143-
val progressMs =
144-
(totalFramesRead.toFloat() / totalFrames * (file.length() / 16_000)).toInt()
145-
onProgress(progressMs)
146-
}
147-
148-
debugPrintln { "All chunks processed, signaling complete." }
149-
onComplete()
150-
110+
val data = decodeWaveFile(file)
111+
debugPrintln{"${data.size / (16000 / 1000)} ms\n"}
112+
debugPrintln{"Transcribing data...\n"}
113+
val start = System.currentTimeMillis()
114+
val text = whisperContext?.transcribeData(data, language, callback = object : WhisperCallback{
115+
override fun onNewSegment(startMs: Long, endMs: Long, text: String) {
116+
onNewSegment(startMs, endMs, text)
117+
}
118+
119+
override fun onProgress(progress: Int) {
120+
onProgress(progress)
121+
}
122+
123+
override fun onComplete() {
124+
onComplete()
125+
}
126+
127+
})
128+
val elapsed = System.currentTimeMillis() - start
129+
debugPrintln{"Done ($elapsed ms): \n$text\n"}
130+
131+
} catch (e: OutOfMemoryError) {
132+
onError()
133+
debugPrintln{"OutOfMemoryError: File too large to process - ${e.message}\n"}
151134
} catch (e: Exception) {
152135
e.printStackTrace()
153-
debugPrintln { "Error: ${e.localizedMessage}" }
154-
} finally {
155-
canTranscribe = true
136+
debugPrintln{"${e.localizedMessage}\n"}
156137
}
138+
139+
canTranscribe = true
140+
157141
}
158142
}

shared/src/commonMain/composeResources/values/strings.xml

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<string name="error">Error</string>
55
<string name="close">Close</string>
66
<string name="cancel">Cancel</string>
7+
<string name="navigate">Navigate</string>
78

89
<!-- TopBar -->
910
<string name="top_bar_back">Back</string>
@@ -20,6 +21,7 @@
2021
<string name="confirmation_delete">Delete</string>
2122
<string name="confirmation_cancel">Cancel</string>
2223
<string name="recording_replace_error">You already have a recording saved. Creating a new recording will permanently replace it. Do you want to continue?</string>
24+
<string name="recording_import_error">You already have a recording saved. Importing a new record will permanently replace it. Do you want to continue?</string>
2325
<string name="recording_replace_continue">Continue</string>
2426

2527
<!-- BottomNavigationBar -->
@@ -95,6 +97,9 @@
9597
<string name="transcription_dialog_append">Append</string>
9698
<string name="transcription_dialog_summarize">Summarize</string>
9799
<string name="transcription_dialog_original">Original</string>
100+
<string name="transcription_dialog_error_got_it">Got it</string>
101+
<string name="transcription_dialog_error_audio_file_title">Audio file too large</string>
102+
<string name="transcription_dialog_error_audio_file_desc">This audio file is too big to transcribe on your device. Try using a shorter recording or a smaller file size.</string>
98103

99104
<!-- Download Dialog -->
100105
<string name="download_dialog_error">Error while downloading</string>
@@ -137,5 +142,46 @@
137142
<string name="appearance">Appearance</string>
138143
<string name="theme">Theme</string>
139144
<string name="choose_how_the_app_looks">Choose how the app looks and feels</string>
140-
141-
</resources>
145+
<string name="accessibility">Accessibility</string>
146+
<string name="body_text_size">Body Text size</string>
147+
<string name="body_text_preferred_text">Set your preferred body text size for the notes editor to enhance your writing experience.</string>
148+
<string name="body_text_default">Default</string>
149+
<string name="body_text_pt">%1$d pt</string>
150+
151+
<!-- Onboarding Screen -->
152+
<string name="onboarding_get_started">Get Started</string>
153+
<string name="onboarding_next">Next</string>
154+
<string name="onboarding_skip">Skip</string>
155+
<string name="onboarding_screen_one_title">Create Notes\nand Share</string>
156+
<string name="onboarding_screen_one_desc">Write and share your notes\ninstantly with ease</string>
157+
<string name="onboarding_screen_two_title">Record Voice\nNote and Share</string>
158+
<string name="onboarding_screen_two_desc">Capture and share voice notes\non the go</string>
159+
<string name="onboarding_screen_three_title">Transcribe\nand Summarise</string>
160+
<string name="onboarding_screen_three_desc">Convert voice notes to text and\nsummaries without internet</string>
161+
<string name="onboarding_screen_four_title">Supports\nOver 50 languages</string>
162+
<string name="onboarding_screen_four_desc">Create and transcribe notes in\nyour preferred language</string>
163+
164+
<!-- Setting Bottom Sheet -->
165+
<string name="settings_light_theme">Light Theme</string>
166+
<string name="settings_dark_theme">Dark Theme</string>
167+
<string name="settings_system_default">System Default</string>
168+
<string name="settings_change_language">Change Language</string>
169+
<string name="settings_change_default">English</string>
170+
<string name="settings_transcription_language">Transcription Language</string>
171+
<string name="settings_themes">Themes</string>
172+
<string name="settings_title">Settings</string>
173+
174+
<!-- Language Selection -->
175+
<string name="language_selection_no_languages_found">No languages found</string>
176+
<string name="language_selection_supported_languages">SUPPORTED LANGUAGES</string>
177+
<string name="language_selection_search">Search</string>
178+
<string name="language_selection_select_language">Select Language</string>
179+
180+
<!-- Accessibility -->
181+
<string name="accessibility_example">Example %1$d pt</string>
182+
<string name="accessibility_desc">Use the slider to set the preferred writing body size for the notes editor, customise your experience.</string>
183+
<string name="accessibility_a">A</string>
184+
<string name="accessibility_default">Default is 14 pt</string>
185+
186+
187+
</resources>

shared/src/commonMain/kotlin/com/module/notelycompose/platform/Transcriber.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package com.module.notelycompose.platform
22

33
expect class Transcriber {
44
fun doesModelExists():Boolean
5-
suspend fun initialize()
6-
suspend fun finish()
7-
suspend fun stop()
5+
suspend fun initialize()
6+
suspend fun finish()
7+
suspend fun stop()
88
suspend fun start(
99
filePath:String, language:String,
1010
onProgress : (Int) -> Unit,
1111
onNewSegment : (Long, Long,String) -> Unit,
12-
onComplete : () -> Unit
12+
onComplete : () -> Unit,
13+
onError : () -> Unit
1314
)
1415
fun hasRecordingPermission(): Boolean
1516
suspend fun requestRecordingPermission():Boolean

0 commit comments

Comments
 (0)