This repository has no description
0

Configure Feed

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

fix: handle ios model parsing error

+92 -58
+92 -58
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/CameraEngine.kt
··· 89 89 import platform.darwin.dispatch_get_global_queue 90 90 import platform.darwin.dispatch_get_main_queue 91 91 import platform.darwin.dispatch_queue_create 92 + import platform.darwin.dispatch_sync 93 + import platform.darwin.dispatch_time 92 94 import platform.posix.memcpy 93 95 import kotlin.math.abs 94 96 import kotlin.native.runtime.NativeRuntimeApi ··· 160 162 161 163 @OptIn(ExperimentalForeignApi::class) 162 164 private fun setupCamera() { 165 + // Run session setup synchronously so callers can safely start the session afterwards 163 166 cameraController.setupSession() 164 167 cameraController.setupPreviewLayer(view) 165 168 startSession() ··· 289 292 var drawSkeleton: Boolean = true 290 293 var drawObjects: ((List<AnalysisObject>) -> List<DrawableObject>)? = null 291 294 295 + // Serial queue to serialize session configuration and start/stop calls 296 + private val sessionQueue = dispatch_queue_create("com.performancecoachlab.captureSessionQueue", null) 297 + 292 298 sealed class CameraException : Exception() { 293 299 class DeviceNotAvailable : CameraException() 294 300 class ConfigurationError(message: String) : CameraException() ··· 296 302 } 297 303 298 304 fun startRecording(): String? { 299 - if (movieFileOutput == null) { 300 - movieFileOutput = AVCaptureMovieFileOutput() 301 - captureSession?.addOutput(movieFileOutput!!) 305 + // Ensure we add outputs on the session queue to avoid races 306 + dispatch_sync(sessionQueue) { 307 + if (movieFileOutput == null) { 308 + val candidate = AVCaptureMovieFileOutput() 309 + movieFileOutput = candidate 310 + if (captureSession?.canAddOutput(candidate) == true) { 311 + captureSession?.addOutput(movieFileOutput!!) 312 + } 313 + } 302 314 } 315 + 303 316 val outputURL = generateSegmentURL() 304 317 movieFileOutput?.connections?.firstOrNull()?.let { connection -> 305 318 (connection as AVCaptureConnection).let { avConnection -> ··· 345 358 } 346 359 347 360 fun setupSession() { 348 - try { 349 - captureSession = AVCaptureSession() 350 - captureSession?.beginConfiguration() 351 - captureSession?.sessionPreset = AVCaptureSessionPresetMedium 361 + // Run synchronously on sessionQueue so callers (like setupCamera) can safely call startSession afterwards 362 + dispatch_sync(sessionQueue) { 363 + var configurationBegan = false 364 + try { 365 + captureSession = AVCaptureSession() 366 + captureSession?.beginConfiguration() 367 + configurationBegan = true 368 + captureSession?.sessionPreset = AVCaptureSessionPresetMedium 369 + 370 + if (!setupInputs()) { 371 + throw CameraException.DeviceNotAvailable() 372 + } 373 + setupVideoOutput() 352 374 353 - if (!setupInputs()) { 354 - throw CameraException.DeviceNotAvailable() 375 + // Add movie file output for recording only if it can be added 376 + if (movieFileOutput == null) { 377 + val candidate = AVCaptureMovieFileOutput() 378 + if (captureSession?.canAddOutput(candidate) == true) { 379 + movieFileOutput = candidate 380 + captureSession?.addOutput(movieFileOutput!!) 381 + } else { 382 + // keep the instance (not added) so it can be added later when appropriate 383 + movieFileOutput = candidate 384 + } 385 + } 386 + } catch (e: CameraException) { 387 + cleanupSession() 388 + onError?.invoke(e) 389 + return@dispatch_sync 390 + } finally { 391 + if (configurationBegan) { 392 + captureSession?.commitConfiguration() 393 + } 355 394 } 356 - setupVideoOutput() 357 - // Add movie file output for recording 358 - if (movieFileOutput == null) { 359 - movieFileOutput = AVCaptureMovieFileOutput() 360 - captureSession?.addOutput(movieFileOutput!!) 361 - } 362 - captureSession?.commitConfiguration() 363 - startRecording() 364 - } catch (e: CameraException) { 365 - cleanupSession() 366 - onError?.invoke(e) 367 395 } 396 + // Do not start recording here; startRecording() should be called explicitly after session is running 368 397 } 369 398 370 399 private fun setupVideoOutput() { ··· 416 445 } 417 446 418 447 fun startSession() { 419 - if (captureSession?.isRunning() == false) { 420 - dispatch_async( 421 - dispatch_get_global_queue( 422 - DISPATCH_QUEUE_PRIORITY_HIGH.toLong(), 0u 423 - ) 424 - ) { 448 + // Run on sessionQueue to avoid overlapping with configuration 449 + dispatch_async(sessionQueue) { 450 + if (captureSession?.isRunning() == false) { 425 451 captureSession?.startRunning() 426 452 } 427 453 } 428 454 } 429 455 430 456 fun stopSession() { 431 - if (captureSession?.isRunning() == true) { 432 - captureSession?.stopRunning() 457 + dispatch_async(sessionQueue) { 458 + if (captureSession?.isRunning() == true) { 459 + captureSession?.stopRunning() 460 + } 433 461 } 434 462 } 435 463 ··· 480 508 fun switchCamera() { 481 509 guard(captureSession != null) { return@guard } 482 510 483 - captureSession?.beginConfiguration() 511 + // Run camera switch on sessionQueue to serialize with setupSession/startSession 512 + dispatch_sync(sessionQueue) { 513 + var configurationBegan = false 514 + try { 515 + captureSession?.beginConfiguration() 516 + configurationBegan = true 517 + 518 + captureSession?.inputs?.firstOrNull()?.let { input -> 519 + captureSession?.removeInput(input as AVCaptureInput) 520 + } 484 521 485 - try { 486 - captureSession?.inputs?.firstOrNull()?.let { input -> 487 - captureSession?.removeInput(input as AVCaptureInput) 488 - } 522 + isUsingFrontCamera = !isUsingFrontCamera 523 + currentCamera = if (isUsingFrontCamera) frontCamera else backCamera 489 524 490 - isUsingFrontCamera = !isUsingFrontCamera 491 - currentCamera = if (isUsingFrontCamera) frontCamera else backCamera 525 + val newCamera = currentCamera ?: throw CameraException.DeviceNotAvailable() 492 526 493 - val newCamera = currentCamera ?: throw CameraException.DeviceNotAvailable() 527 + val newInput = AVCaptureDeviceInput.deviceInputWithDevice( 528 + newCamera, null 529 + ) ?: throw CameraException.ConfigurationError("Failed to create input") 494 530 495 - val newInput = AVCaptureDeviceInput.deviceInputWithDevice( 496 - newCamera, null 497 - ) ?: throw CameraException.ConfigurationError("Failed to create input") 531 + if (captureSession?.canAddInput(newInput) == true) { 532 + captureSession?.addInput(newInput) 533 + } else { 534 + throw CameraException.ConfigurationError("Cannot add input") 535 + } 498 536 499 - if (captureSession?.canAddInput(newInput) == true) { 500 - captureSession?.addInput(newInput) 501 - } else { 502 - throw CameraException.ConfigurationError("Cannot add input") 503 - } 537 + cameraPreviewLayer?.connection?.let { connection -> 538 + if (connection.isVideoMirroringSupported()) { 539 + connection.automaticallyAdjustsVideoMirroring = false 540 + connection.setVideoMirrored(isUsingFrontCamera) 541 + } 542 + } 504 543 505 - cameraPreviewLayer?.connection?.let { connection -> 506 - if (connection.isVideoMirroringSupported()) { 507 - connection.automaticallyAdjustsVideoMirroring = false 508 - connection.setVideoMirrored(isUsingFrontCamera) 544 + } catch (e: CameraException) { 545 + onError?.invoke(e) 546 + } catch (e: Exception) { 547 + onError?.invoke(CameraException.ConfigurationError(e.message ?: "Unknown error")) 548 + } finally { 549 + if (configurationBegan) { 550 + captureSession?.commitConfiguration() 509 551 } 510 552 } 511 - 512 - captureSession?.commitConfiguration() 513 - } catch (e: CameraException) { 514 - captureSession?.commitConfiguration() 515 - onError?.invoke(e) 516 - } catch (e: Exception) { 517 - captureSession?.commitConfiguration() 518 - onError?.invoke(CameraException.ConfigurationError(e.message ?: "Unknown error")) 519 553 } 520 554 } 521 555 ··· 573 607 ).let { FrameSize(it.width.toInt(), it.height.toInt()) } 574 608 }) 575 609 } 576 - previewObjects?.also { objects -> 610 + previewObjects.also { objects -> 577 611 customObjectRepository?.updateCustomObject(objects) 578 612 } 579 613 preview.bounds.useContents { ··· 607 641 } 608 642 } 609 643 } catch (e: Exception) { 610 - //println(e.message ?: "Unknown error in frame processing") 644 + // ignore frame processing errors 611 645 } 612 646 } 613 647 }