This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: handle Exceptions correctly and proper Logging

+44 -16
+10 -3
posedetection/src/androidMain/kotlin/com/performancecoachlab/posedetection/recording/VideoUtils.android.kt
··· 8 8 import kotlinx.coroutines.Dispatchers 9 9 import kotlinx.coroutines.withContext 10 10 import androidx.core.net.toUri 11 + import co.touchlab.kermit.Logger 11 12 12 13 actual suspend fun extractFrame( 13 14 videoPath: String, frameTimestamp: Long ··· 62 63 break 63 64 } 64 65 } 65 - if (videoTrack == -1) return@withContext out 66 + 67 + if (videoTrack == -1) { 68 + Logger.i { "No video track found in file: $videoPath" } 69 + return@withContext out 70 + } 66 71 67 72 extractor.selectTrack(videoTrack) 68 73 ··· 77 82 78 83 if (!extractor.advance()) break 79 84 } 80 - } catch (_: Throwable) { 81 - // swallow; return what we have 85 + } catch (e: Exception) { 86 + Logger.i { "Exception extracting frames from $videoPath: ${e::class.simpleName} - ${e.message}" } 87 + } catch (t: Throwable) { 88 + Logger.i { "CRASH prevented during frame extraction for $videoPath: ${t::class.simpleName}" } 82 89 } finally { 83 90 extractor.release() 84 91 }
+26 -12
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/recording/VideoUtils.ios.kt
··· 28 28 import platform.Foundation.NSURL 29 29 import platform.Foundation.NSValue 30 30 import platform.Foundation.NSFileManager 31 + import co.touchlab.kermit.Logger 31 32 32 33 @OptIn(ExperimentalForeignApi::class) 33 34 actual suspend fun extractFrame( ··· 55 56 } 56 57 } 57 58 59 + private fun NSURL.safeDescription(): String { 60 + val scheme = this.scheme ?: "unknown" 61 + val fileName = this.lastPathComponent ?: "unknown" 62 + return "$scheme://.../$fileName" 63 + } 64 + 65 + private fun safeFileName(path: String?): String { 66 + return path?.substringAfterLast("/") ?: "unknown" 67 + } 68 + 58 69 @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) 59 70 actual suspend fun listVideoFrameTimestamps( 60 71 videoPath: String, ··· 63 74 64 75 try { 65 76 val resolvedUrl = createUrl(url = videoPath) 66 - println("[iOS] listVideoFrameTimestamps using URL: ${'$'}{resolvedUrl.absoluteString}") 77 + Logger.i { "[iOS] listVideoFrameTimestamps using URL: ${resolvedUrl.safeDescription()}" } 67 78 68 79 // AVAssetReader requires a local file URL. Bail out early for remote URLs. 69 80 if (!resolvedUrl.isFileURL()) { 70 - println("AVAssetReader requires a local file URL; got: ${'$'}{resolvedUrl.scheme}:// ...") 81 + Logger.i { "AVAssetReader requires a local file URL; got scheme: ${resolvedUrl.scheme}" } 71 82 return@withContext out 72 83 } 73 84 74 85 // Verify the file actually exists at path 75 86 val path = resolvedUrl.path 76 87 if (path == null || !NSFileManager.defaultManager.fileExistsAtPath(path)) { 77 - println("File does not exist at path: ${'$'}path") 88 + Logger.i { "File does not exist: ${safeFileName(path)}" } 78 89 return@withContext out 79 90 } 80 91 ··· 84 95 val videoTracks = asset.tracksWithMediaType(AVMediaTypeVideo) 85 96 86 97 if (videoTracks.isEmpty()) { 87 - println("No video tracks found in video: ${'$'}videoPath") 98 + Logger.i { "No video tracks found in video: ${safeFileName(videoPath)}" } 88 99 return@withContext out 89 100 } 90 101 91 102 val videoTrack = videoTracks.first() as AVAssetTrack 92 - println("Found video track: ${'$'}{videoTrack}") 103 + Logger.i { "Found video track with ID: ${videoTrack.trackID}" } 93 104 94 105 memScoped { 95 106 val errorPtr = alloc<ObjCObjectVar<NSError?>>() ··· 97 108 98 109 if (reader == null) { 99 110 val err = errorPtr.value 100 - println("Failed to create AVAssetReader: ${'$'}{err?.localizedDescription}") 111 + Logger.i { "Failed to create AVAssetReader: ${err?.localizedDescription}" } 101 112 return@withContext out 102 113 } 103 114 ··· 107 118 ) 108 119 109 120 if (!reader.canAddOutput(trackOutput)) { 110 - println("Cannot add track output to reader") 121 + Logger.i { "Cannot add track output to reader" } 111 122 return@withContext out 112 123 } 113 124 114 125 reader.addOutput(trackOutput) 115 126 116 127 if (!reader.startReading()) { 117 - println("Failed to start reading: ${'$'}{reader.error?.localizedDescription}") 128 + Logger.i { "Failed to start reading: ${reader.error?.localizedDescription}" } 118 129 return@withContext out 119 130 } 120 131 121 - println("Started reading video frames...") 132 + Logger.i { "Started reading video frames..." } 122 133 var frameCount = 0 123 134 while (true) { 124 135 val sampleBuffer = trackOutput.copyNextSampleBuffer() ··· 139 150 if (out.size >= Int.MAX_VALUE) break 140 151 } 141 152 142 - println("Extracted ${'$'}frameCount frame timestamps") 153 + Logger.i { "Extracted $frameCount frame timestamps" } 143 154 } 144 155 145 156 } catch (e: Exception) { 146 - println("Exception in listVideoFrameTimestamps: ${'$'}{e.message}") 157 + Logger.i { 158 + val safeName = safeFileName(videoPath) 159 + "Exception extracting frames from $safeName: ${e::class.simpleName} - ${e.message ?: "No message"}" 160 + } 147 161 e.printStackTrace() 148 162 } 149 163 150 - println("Returning ${'$'}{out.size} timestamps") 164 + Logger.i { "Returning ${out.size} timestamps" } 151 165 out 152 166 } 153 167
+8 -1
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/App.kt
··· 66 66 import kotlin.math.roundToLong 67 67 import kotlin.time.Clock 68 68 import kotlin.time.ExperimentalTime 69 + import co.touchlab.kermit.Logger 69 70 70 71 @Composable 71 72 internal fun App() = AppTheme { ··· 228 229 } finally { 229 230 val nextFrame = frameMap[url]?.let { frameTimeStamps -> 230 231 frameTimeStamps.firstOrNull { it > frame} 231 - }?:(frame + 20L) 232 + }?:( 233 + // Fallback: 234 + // If no timestamp is found (e.g., end of list or metadata missing), 235 + // assume a 50 FPS video and increment by 20ms. 236 + // This keeps playback and processing moving forward smoothly. 237 + frame + 20L 238 + ) 232 239 nextFrame.also { 233 240 if (it < timeRange.second) { 234 241 frame = it