Add camera facing selection

This commit is contained in:
2026-06-02 01:04:38 +08:00
parent 1031b20d29
commit aea5efec56
3 changed files with 108 additions and 10 deletions
@@ -12,6 +12,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.kimgo.posefit.sender.CameraFacing
import com.kimgo.posefit.sender.WebRtcSenderClient
import timber.log.Timber
@@ -23,7 +24,8 @@ class MainActivity : ComponentActivity() {
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
val url = getSavedSignalingUrl()
startWebRtc(url)
val cameraFacing = getSavedCameraFacing()
startWebRtc(url, cameraFacing)
} else {
Timber.w("Camera permission denied")
}
@@ -48,6 +50,7 @@ class MainActivity : ComponentActivity() {
@Composable
fun ConfigScreen() {
var url by remember { mutableStateOf(getSavedSignalingUrl()) }
var cameraFacing by remember { mutableStateOf(getSavedCameraFacing()) }
var isStreaming by remember { mutableStateOf(false) }
Column(
@@ -71,6 +74,32 @@ class MainActivity : ComponentActivity() {
enabled = !isStreaming
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "选择摄像头",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
CameraFacingButton(
text = "前置摄像头",
selected = cameraFacing == CameraFacing.FRONT,
enabled = !isStreaming,
modifier = Modifier.weight(1f),
onClick = { cameraFacing = CameraFacing.FRONT }
)
CameraFacingButton(
text = "后置摄像头",
selected = cameraFacing == CameraFacing.BACK,
enabled = !isStreaming,
modifier = Modifier.weight(1f),
onClick = { cameraFacing = CameraFacing.BACK }
)
}
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
if (isStreaming) {
@@ -78,6 +107,7 @@ class MainActivity : ComponentActivity() {
isStreaming = false
} else {
saveSignalingUrl(url)
saveCameraFacing(cameraFacing)
requestPermission.launch(Manifest.permission.CAMERA)
isStreaming = true
}
@@ -89,6 +119,40 @@ class MainActivity : ComponentActivity() {
}
}
@Composable
private fun CameraFacingButton(
text: String,
selected: Boolean,
enabled: Boolean,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
val colors = if (selected) {
ButtonDefaults.buttonColors()
} else {
ButtonDefaults.outlinedButtonColors()
}
if (selected) {
Button(
onClick = onClick,
enabled = enabled,
modifier = modifier
) {
Text(text)
}
} else {
OutlinedButton(
onClick = onClick,
enabled = enabled,
modifier = modifier,
colors = colors
) {
Text(text)
}
}
}
private fun getSavedSignalingUrl(): String {
val prefs = getSharedPreferences("posefit_prefs", Context.MODE_PRIVATE)
return prefs.getString("signaling_url", "ws://192.168.2.10:8765") ?: "ws://192.168.2.10:8765"
@@ -99,12 +163,25 @@ class MainActivity : ComponentActivity() {
prefs.edit().putString("signaling_url", url).apply()
}
private fun startWebRtc(url: String) {
Timber.i("startWebRtc: %s", url)
private fun getSavedCameraFacing(): CameraFacing {
val prefs = getSharedPreferences("posefit_prefs", Context.MODE_PRIVATE)
val value = prefs.getString("camera_facing", CameraFacing.FRONT.name)
return runCatching { CameraFacing.valueOf(value ?: CameraFacing.FRONT.name) }
.getOrDefault(CameraFacing.FRONT)
}
private fun saveCameraFacing(cameraFacing: CameraFacing) {
val prefs = getSharedPreferences("posefit_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("camera_facing", cameraFacing.name).apply()
}
private fun startWebRtc(url: String, cameraFacing: CameraFacing) {
Timber.i("startWebRtc: %s, cameraFacing=%s", url, cameraFacing)
webRtcClient?.release()
webRtcClient = WebRtcSenderClient(
context = this,
signalingUrl = url
signalingUrl = url,
cameraFacing = cameraFacing
)
webRtcClient?.start()
}
@@ -0,0 +1,6 @@
package com.kimgo.posefit.sender
enum class CameraFacing {
FRONT,
BACK
}
@@ -8,7 +8,8 @@ import timber.log.Timber
class WebRtcSenderClient(
private val context: Context,
private val signalingUrl: String
private val signalingUrl: String,
private val cameraFacing: CameraFacing = CameraFacing.FRONT
) {
private val eglBase = EglBase.create()
@@ -24,7 +25,7 @@ class WebRtcSenderClient(
private var surfaceTextureHelper: SurfaceTextureHelper? = null
fun start() {
Timber.i("WebRTC starting, signalingUrl=%s", signalingUrl)
Timber.i("WebRTC starting, signalingUrl=%s, cameraFacing=%s", signalingUrl, cameraFacing)
initPeerConnectionFactory()
connectSignaling()
}
@@ -129,9 +130,7 @@ class WebRtcSenderClient(
private fun startCamera() {
val enumerator = Camera2Enumerator(context)
val cameraName = enumerator.deviceNames.firstOrNull {
enumerator.isFrontFacing(it)
} ?: enumerator.deviceNames.first()
val cameraName = selectCamera(enumerator)
videoCapturer = enumerator.createCapturer(cameraName, null)
@@ -160,6 +159,22 @@ class WebRtcSenderClient(
Timber.d("Camera started: %s, resolution=1280x720@30fps", cameraName)
}
private fun selectCamera(enumerator: Camera2Enumerator): String {
val preferredCamera = enumerator.deviceNames.firstOrNull { cameraName ->
when (cameraFacing) {
CameraFacing.FRONT -> enumerator.isFrontFacing(cameraName)
CameraFacing.BACK -> enumerator.isBackFacing(cameraName)
}
}
if (preferredCamera != null) {
return preferredCamera
}
Timber.w("Preferred camera not found: %s, falling back to first available camera", cameraFacing)
return enumerator.deviceNames.first()
}
private fun createOffer() {
val constraints = MediaConstraints().apply {
mandatory.add(
@@ -256,4 +271,4 @@ class WebRtcSenderClient(
Timber.d("WebRTC released")
}
}
}