Add camera facing selection
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user