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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.kimgo.posefit.sender.CameraFacing
|
||||||
import com.kimgo.posefit.sender.WebRtcSenderClient
|
import com.kimgo.posefit.sender.WebRtcSenderClient
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -23,7 +24,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||||
if (granted) {
|
if (granted) {
|
||||||
val url = getSavedSignalingUrl()
|
val url = getSavedSignalingUrl()
|
||||||
startWebRtc(url)
|
val cameraFacing = getSavedCameraFacing()
|
||||||
|
startWebRtc(url, cameraFacing)
|
||||||
} else {
|
} else {
|
||||||
Timber.w("Camera permission denied")
|
Timber.w("Camera permission denied")
|
||||||
}
|
}
|
||||||
@@ -48,6 +50,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun ConfigScreen() {
|
fun ConfigScreen() {
|
||||||
var url by remember { mutableStateOf(getSavedSignalingUrl()) }
|
var url by remember { mutableStateOf(getSavedSignalingUrl()) }
|
||||||
|
var cameraFacing by remember { mutableStateOf(getSavedCameraFacing()) }
|
||||||
var isStreaming by remember { mutableStateOf(false) }
|
var isStreaming by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -71,6 +74,32 @@ class MainActivity : ComponentActivity() {
|
|||||||
enabled = !isStreaming
|
enabled = !isStreaming
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
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(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
@@ -78,6 +107,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
isStreaming = false
|
isStreaming = false
|
||||||
} else {
|
} else {
|
||||||
saveSignalingUrl(url)
|
saveSignalingUrl(url)
|
||||||
|
saveCameraFacing(cameraFacing)
|
||||||
requestPermission.launch(Manifest.permission.CAMERA)
|
requestPermission.launch(Manifest.permission.CAMERA)
|
||||||
isStreaming = true
|
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 {
|
private fun getSavedSignalingUrl(): String {
|
||||||
val prefs = getSharedPreferences("posefit_prefs", Context.MODE_PRIVATE)
|
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"
|
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()
|
prefs.edit().putString("signaling_url", url).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startWebRtc(url: String) {
|
private fun getSavedCameraFacing(): CameraFacing {
|
||||||
Timber.i("startWebRtc: %s", url)
|
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?.release()
|
||||||
webRtcClient = WebRtcSenderClient(
|
webRtcClient = WebRtcSenderClient(
|
||||||
context = this,
|
context = this,
|
||||||
signalingUrl = url
|
signalingUrl = url,
|
||||||
|
cameraFacing = cameraFacing
|
||||||
)
|
)
|
||||||
webRtcClient?.start()
|
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(
|
class WebRtcSenderClient(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val signalingUrl: String
|
private val signalingUrl: String,
|
||||||
|
private val cameraFacing: CameraFacing = CameraFacing.FRONT
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val eglBase = EglBase.create()
|
private val eglBase = EglBase.create()
|
||||||
@@ -24,7 +25,7 @@ class WebRtcSenderClient(
|
|||||||
private var surfaceTextureHelper: SurfaceTextureHelper? = null
|
private var surfaceTextureHelper: SurfaceTextureHelper? = null
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
Timber.i("WebRTC starting, signalingUrl=%s", signalingUrl)
|
Timber.i("WebRTC starting, signalingUrl=%s, cameraFacing=%s", signalingUrl, cameraFacing)
|
||||||
initPeerConnectionFactory()
|
initPeerConnectionFactory()
|
||||||
connectSignaling()
|
connectSignaling()
|
||||||
}
|
}
|
||||||
@@ -129,9 +130,7 @@ class WebRtcSenderClient(
|
|||||||
private fun startCamera() {
|
private fun startCamera() {
|
||||||
val enumerator = Camera2Enumerator(context)
|
val enumerator = Camera2Enumerator(context)
|
||||||
|
|
||||||
val cameraName = enumerator.deviceNames.firstOrNull {
|
val cameraName = selectCamera(enumerator)
|
||||||
enumerator.isFrontFacing(it)
|
|
||||||
} ?: enumerator.deviceNames.first()
|
|
||||||
|
|
||||||
videoCapturer = enumerator.createCapturer(cameraName, null)
|
videoCapturer = enumerator.createCapturer(cameraName, null)
|
||||||
|
|
||||||
@@ -160,6 +159,22 @@ class WebRtcSenderClient(
|
|||||||
Timber.d("Camera started: %s, resolution=1280x720@30fps", cameraName)
|
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() {
|
private fun createOffer() {
|
||||||
val constraints = MediaConstraints().apply {
|
val constraints = MediaConstraints().apply {
|
||||||
mandatory.add(
|
mandatory.add(
|
||||||
@@ -256,4 +271,4 @@ class WebRtcSenderClient(
|
|||||||
|
|
||||||
Timber.d("WebRTC released")
|
Timber.d("WebRTC released")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user