alpha
Login
or
Join now
nateholland.bsky.social
/
PoseDetection
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
fix: ios camera flickering on toggle
author
nate
committer
tangled.org
date
5 months ago
(Jan 15, 2026, 8:43 PM UTC)
commit
f5a7d093
f5a7d093ac2c52429d5ebbc336c741909e62263b
parent
346c9137
346c9137477dc9fc5c9e19fe3bd8bf18e671b824
+54
-9
3 changed files
Expand all
Collapse all
Unified
Split
posedetection
src
commonMain
kotlin
com
performancecoachlab
posedetection
camera
Utils.kt
iosMain
kotlin
com
performancecoachlab
posedetection
camera
CameraEngine.kt
CameraView.ios.kt
-1
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/camera/Utils.kt
Reviewed
···
114
114
val canvas = Canvas(bitmap)
115
115
val drawScope = CanvasDrawScope()
116
116
val size = Size(it.width.toFloat(), it.height.toFloat())
117
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
Reviewed
···
548
548
if (isSwitchingCamera) return@dispatch_async
549
549
isSwitchingCamera = true
550
550
551
551
+
// Hide preview while switching to avoid transient rotated frames being displayed.
552
552
+
dispatch_async(dispatch_get_main_queue()) {
553
553
+
cameraPreviewLayer?.hidden = true
554
554
+
}
555
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
576
-
cameraPreviewLayer?.connection?.let { connection ->
577
577
-
if (connection.isVideoMirroringSupported()) {
578
578
-
connection.automaticallyAdjustsVideoMirroring = false
579
579
-
connection.setVideoMirrored(isUsingFrontCamera)
581
581
+
// Apply orientation + mirroring on main queue while still hidden.
582
582
+
dispatch_async(dispatch_get_main_queue()) {
583
583
+
cameraPreviewLayer?.connection?.let { connection ->
584
584
+
connection.videoOrientation = interfaceOrientationToVideoOrientation()
585
585
+
if (connection.isVideoMirroringSupported()) {
586
586
+
connection.automaticallyAdjustsVideoMirroring = false
587
587
+
connection.setVideoMirrored(isUsingFrontCamera)
588
588
+
}
580
589
}
581
590
}
582
591
} catch (e: CameraException) {
···
587
596
if (configurationBegan) {
588
597
captureSession?.commitConfiguration()
589
598
}
599
599
+
600
600
+
// Ensure the connection is correct after commit, then show the preview again.
601
601
+
dispatch_async(dispatch_get_main_queue()) {
602
602
+
cameraPreviewLayer?.connection?.let { connection ->
603
603
+
connection.videoOrientation = interfaceOrientationToVideoOrientation()
604
604
+
if (connection.isVideoMirroringSupported()) {
605
605
+
connection.automaticallyAdjustsVideoMirroring = false
606
606
+
connection.setVideoMirrored(isUsingFrontCamera)
607
607
+
}
608
608
+
}
609
609
+
610
610
+
cameraPreviewLayer?.apply {
611
611
+
// Use a tiny fade-in to avoid a harsh blink.
612
612
+
opacity = 0f
613
613
+
hidden = false
614
614
+
val anim = platform.QuartzCore.CABasicAnimation.animationWithKeyPath("opacity")
615
615
+
anim.fromValue = 0.0
616
616
+
anim.toValue = 1.0
617
617
+
anim.duration = 0.12
618
618
+
addAnimation(anim, forKey = "fadeIn")
619
619
+
opacity = 1f
620
620
+
}
621
621
+
}
622
622
+
590
623
isSwitchingCamera = false
591
624
}
592
625
}
···
604
637
runCatching {
605
638
dispatch_async(frameProcessingQueue) {
606
639
try {
640
640
+
// If we're switching cameras, don't publish overlay frames.
641
641
+
// This avoids brief flashes of stale frames from the previous lens.
642
642
+
if (isSwitchingCamera) return@dispatch_async
643
643
+
607
644
var detectedSkeleton: Skeleton? = null
608
645
var detectedObjects: List<AnalysisObject> = emptyList()
646
646
+
609
647
frameProcessor.analyseBufferForAll(
610
648
CMSampleBufferGetImageBuffer(didOutputSampleBuffer),
611
649
timestamp,
···
617
655
detectedObjects = objects
618
656
}
619
657
)
658
658
+
620
659
cameraPreviewLayer?.also { preview ->
621
660
val previewSkeleton = detectedSkeleton?.let {
622
661
mapSkeletonToPreview(
···
626
665
height = 360f
627
666
)
628
667
}
668
668
+
629
669
previewSkeleton?.also {
630
670
skeletonRepository?.updateSkeleton(it)
631
671
}
672
672
+
632
673
val previewObjects = detectedObjects.map {
633
674
it.copy(
634
675
boundingBox = mapBoxToPreview(
···
649
690
}
650
691
)
651
692
}
693
693
+
652
694
customObjectRepository?.updateCustomObject(previewObjects)
653
695
654
696
preview.bounds.useContents {
···
672
714
}
673
715
)
674
716
}
675
675
-
frameListener?.updateFrame(drawn)
717
717
+
718
718
+
// One more guard right at publish time, in case a switch started mid-frame.
719
719
+
if (!isSwitchingCamera) {
720
720
+
frameListener?.updateFrame(drawn)
721
721
+
}
676
722
}
677
723
}
678
724
}
···
682
728
}
683
729
}
684
730
}
731
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
Reviewed
···
9
9
import androidx.compose.runtime.getValue
10
10
import androidx.compose.runtime.mutableStateOf
11
11
import androidx.compose.runtime.remember
12
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
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
115
-
kotlin.native.runtime.GC.collect()
113
113
+
// IMPORTANT: Do not force GC from composition; it can cause visible flicker/jitter.
114
114
+
// (GC.collect() is still performed in the capture pipeline.)
116
115
frameBitmap?.also {
117
116
Image(
118
117
bitmap = it,