This repository has no description
0

Configure Feed

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

fix: ios camera flickering on toggle

+54 -9
-1
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/camera/Utils.kt
··· 114 114 val canvas = Canvas(bitmap) 115 115 val drawScope = CanvasDrawScope() 116 116 val size = Size(it.width.toFloat(), it.height.toFloat()) 117 - Logger.d{"Size ${size.width} x ${size.height}"} 118 117 119 118 // Calculate scaling factor based on skeleton size 120 119 val skeletonSize = skeleton?.joints()?.let { joints ->
+52 -5
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/CameraEngine.kt
··· 548 548 if (isSwitchingCamera) return@dispatch_async 549 549 isSwitchingCamera = true 550 550 551 + // Hide preview while switching to avoid transient rotated frames being displayed. 552 + dispatch_async(dispatch_get_main_queue()) { 553 + cameraPreviewLayer?.hidden = true 554 + } 555 + 551 556 var configurationBegan = false 552 557 try { 553 558 captureSession?.beginConfiguration() ··· 573 578 throw CameraException.ConfigurationError("Cannot add input") 574 579 } 575 580 576 - cameraPreviewLayer?.connection?.let { connection -> 577 - if (connection.isVideoMirroringSupported()) { 578 - connection.automaticallyAdjustsVideoMirroring = false 579 - connection.setVideoMirrored(isUsingFrontCamera) 581 + // Apply orientation + mirroring on main queue while still hidden. 582 + dispatch_async(dispatch_get_main_queue()) { 583 + cameraPreviewLayer?.connection?.let { connection -> 584 + connection.videoOrientation = interfaceOrientationToVideoOrientation() 585 + if (connection.isVideoMirroringSupported()) { 586 + connection.automaticallyAdjustsVideoMirroring = false 587 + connection.setVideoMirrored(isUsingFrontCamera) 588 + } 580 589 } 581 590 } 582 591 } catch (e: CameraException) { ··· 587 596 if (configurationBegan) { 588 597 captureSession?.commitConfiguration() 589 598 } 599 + 600 + // Ensure the connection is correct after commit, then show the preview again. 601 + dispatch_async(dispatch_get_main_queue()) { 602 + cameraPreviewLayer?.connection?.let { connection -> 603 + connection.videoOrientation = interfaceOrientationToVideoOrientation() 604 + if (connection.isVideoMirroringSupported()) { 605 + connection.automaticallyAdjustsVideoMirroring = false 606 + connection.setVideoMirrored(isUsingFrontCamera) 607 + } 608 + } 609 + 610 + cameraPreviewLayer?.apply { 611 + // Use a tiny fade-in to avoid a harsh blink. 612 + opacity = 0f 613 + hidden = false 614 + val anim = platform.QuartzCore.CABasicAnimation.animationWithKeyPath("opacity") 615 + anim.fromValue = 0.0 616 + anim.toValue = 1.0 617 + anim.duration = 0.12 618 + addAnimation(anim, forKey = "fadeIn") 619 + opacity = 1f 620 + } 621 + } 622 + 590 623 isSwitchingCamera = false 591 624 } 592 625 } ··· 604 637 runCatching { 605 638 dispatch_async(frameProcessingQueue) { 606 639 try { 640 + // If we're switching cameras, don't publish overlay frames. 641 + // This avoids brief flashes of stale frames from the previous lens. 642 + if (isSwitchingCamera) return@dispatch_async 643 + 607 644 var detectedSkeleton: Skeleton? = null 608 645 var detectedObjects: List<AnalysisObject> = emptyList() 646 + 609 647 frameProcessor.analyseBufferForAll( 610 648 CMSampleBufferGetImageBuffer(didOutputSampleBuffer), 611 649 timestamp, ··· 617 655 detectedObjects = objects 618 656 } 619 657 ) 658 + 620 659 cameraPreviewLayer?.also { preview -> 621 660 val previewSkeleton = detectedSkeleton?.let { 622 661 mapSkeletonToPreview( ··· 626 665 height = 360f 627 666 ) 628 667 } 668 + 629 669 previewSkeleton?.also { 630 670 skeletonRepository?.updateSkeleton(it) 631 671 } 672 + 632 673 val previewObjects = detectedObjects.map { 633 674 it.copy( 634 675 boundingBox = mapBoxToPreview( ··· 649 690 } 650 691 ) 651 692 } 693 + 652 694 customObjectRepository?.updateCustomObject(previewObjects) 653 695 654 696 preview.bounds.useContents { ··· 672 714 } 673 715 ) 674 716 } 675 - frameListener?.updateFrame(drawn) 717 + 718 + // One more guard right at publish time, in case a switch started mid-frame. 719 + if (!isSwitchingCamera) { 720 + frameListener?.updateFrame(drawn) 721 + } 676 722 } 677 723 } 678 724 } ··· 682 728 } 683 729 } 684 730 } 731 + 685 732 MemoryManager.updateMemoryStatus() 686 733 kotlin.native.runtime.GC.collect() 687 734 }
+2 -3
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/CameraView.ios.kt
··· 9 9 import androidx.compose.runtime.getValue 10 10 import androidx.compose.runtime.mutableStateOf 11 11 import androidx.compose.runtime.remember 12 - import androidx.compose.runtime.rememberCoroutineScope 13 12 import androidx.compose.runtime.setValue 14 13 import androidx.compose.ui.Modifier 15 14 import androidx.compose.ui.geometry.Rect ··· 20 19 import com.performancecoachlab.posedetection.recording.AnalysisObject 21 20 import com.performancecoachlab.posedetection.skeleton.SkeletonRepository 22 21 import kotlinx.cinterop.BetaInteropApi 23 - import kotlinx.cinterop.autoreleasepool 24 22 import kotlinx.coroutines.delay 25 23 import kotlin.native.runtime.NativeRuntimeApi 26 24 ··· 112 110 }, 113 111 ) 114 112 if (drawSkeleton || drawObjects!= null) { 115 - kotlin.native.runtime.GC.collect() 113 + // IMPORTANT: Do not force GC from composition; it can cause visible flicker/jitter. 114 + // (GC.collect() is still performed in the capture pipeline.) 116 115 frameBitmap?.also { 117 116 Image( 118 117 bitmap = it,