Skip to content

Commit 6d659b6

Browse files
Adjust filp() to handle multiple calls without breaking (#1483)
1 parent 568933b commit 6d659b6

File tree

1 file changed

+75
-14
lines changed
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core

1 file changed

+75
-14
lines changed

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,23 @@ import io.getstream.video.android.core.utils.buildAudioConstraints
4343
import io.getstream.video.android.core.utils.mapState
4444
import io.getstream.video.android.core.utils.safeCall
4545
import kotlinx.coroutines.CoroutineScope
46+
import kotlinx.coroutines.Job
4647
import kotlinx.coroutines.delay
4748
import kotlinx.coroutines.flow.MutableStateFlow
4849
import kotlinx.coroutines.flow.StateFlow
4950
import kotlinx.coroutines.launch
51+
import kotlinx.coroutines.suspendCancellableCoroutine
5052
import org.webrtc.Camera2Capturer
5153
import org.webrtc.Camera2Enumerator
5254
import org.webrtc.CameraEnumerationAndroid
55+
import org.webrtc.CameraVideoCapturer
5356
import org.webrtc.EglBase
5457
import org.webrtc.MediaStreamTrack
5558
import org.webrtc.ScreenCapturerAndroid
5659
import org.webrtc.SurfaceTextureHelper
5760
import stream.video.sfu.models.VideoDimension
5861
import java.util.UUID
62+
import kotlin.coroutines.resumeWithException
5963

6064
sealed class DeviceStatus {
6165
data object NotSelected : DeviceStatus()
@@ -169,6 +173,7 @@ class SpeakerManager(
169173
selectedBeforeSpeaker != null &&
170174
selectedBeforeSpeaker !is StreamAudioDevice.Speakerphone &&
171175
devices.contains(selectedBeforeSpeaker) -> selectedBeforeSpeaker
176+
172177
else -> firstNonSpeaker
173178
}
174179

@@ -659,20 +664,76 @@ class CameraManager(
659664
}
660665
}
661666

662-
/**
663-
* Flips the camera
664-
*/
665-
fun flip() {
666-
if (isCapturingVideo) {
667-
setup()
668-
val newDirection = when (_direction.value) {
669-
CameraDirection.Front -> CameraDirection.Back
670-
CameraDirection.Back -> CameraDirection.Front
667+
private var job: Job? = null
668+
669+
private suspend fun flipInternal(): Boolean =
670+
suspendCancellableCoroutine { cont ->
671+
videoCapturer.switchCamera(object : CameraVideoCapturer.CameraSwitchHandler {
672+
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
673+
if (cont.isActive) cont.resumeWith(Result.success(isFrontCamera))
674+
}
675+
676+
override fun onCameraSwitchError(error: String?) {
677+
if (cont.isActive) {
678+
cont.resumeWithException(
679+
RuntimeException(
680+
error ?: "Unknown",
681+
),
682+
)
683+
}
684+
}
685+
})
686+
cont.invokeOnCancellation {
687+
// No direct cancel for switchCamera; nothing to do here.
671688
}
672-
val device = devices.firstOrNull { it.direction == newDirection }
673-
device?.let { select(it.id, triggeredByFlip = true) }
689+
}
690+
691+
/** Flips the camera */
692+
fun flip() {
693+
logger.v { "[flip] no args" }
694+
if (!isCapturingVideo) {
695+
return
696+
}
674697

675-
videoCapturer.switchCamera(null)
698+
if (job != null) {
699+
logger.v { "[flip] job already running" }
700+
return
701+
}
702+
703+
job = mediaManager.scope.launch {
704+
logger.v { "[flip] launched" }
705+
try {
706+
val isFront = flipInternal()
707+
logger.v { "[flip] isFront: $isFront" }
708+
709+
// 3) Update state ONLY after success; no select()/restart.
710+
val newDir = if (isFront) CameraDirection.Front else CameraDirection.Back
711+
_direction.value = newDir
712+
713+
val dev = devices.firstOrNull { it.direction == newDir }
714+
_selectedDevice.value = dev
715+
_availableResolutions.value = dev?.supportedFormats?.toList().orEmpty()
716+
717+
_resolution.value = selectDesiredResolution(
718+
dev?.supportedFormats,
719+
mediaManager.call.state.settings.value?.video,
720+
)
721+
if (_resolution.value != null) {
722+
videoCapturer.changeCaptureFormat(
723+
_resolution.value!!.width,
724+
_resolution.value!!.height,
725+
_resolution.value!!.framerate.max,
726+
)
727+
}
728+
729+
// preview.mirror = (newDir == CameraDirection.Front)
730+
} catch (t: Throwable) {
731+
logger.e(t) { "[flip] failed" }
732+
} finally {
733+
logger.v { "[flip] finally, wait to settle" }
734+
delay(500)
735+
job = null
736+
}
676737
}
677738
}
678739

@@ -752,9 +813,9 @@ class CameraManager(
752813
* Handle the setup of the camera manager and enumerator
753814
* You should only call this once the permissions have been granted
754815
*/
755-
internal fun setup(force: Boolean = false) {
816+
internal fun setup(force: Boolean = false): Result<Unit> = runCatching {
756817
if (setupCompleted && !force) {
757-
return
818+
return@runCatching
758819
}
759820

760821
initDeviceList()

0 commit comments

Comments
 (0)