global fix

This commit is contained in:
2025-12-03 20:46:36 +09:00
parent 74bafd4cb1
commit 752b2fb1ca
8 changed files with 95 additions and 68 deletions

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,7 @@
android:required="false" /> android:required="false" />
<application <application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

View File

@@ -27,8 +27,10 @@ class CameraManager(private val context: Context) {
cameraProviderFuture.addListener( cameraProviderFuture.addListener(
{ {
Log.d("CameraManager", "cameraProviderFuture listener invoked")
try { try {
cameraProvider = cameraProviderFuture.get() cameraProvider = cameraProviderFuture.get()
Log.d("CameraManager", "cameraProvider obtained: $cameraProvider")
// Create preview // Create preview
val preview = Preview.Builder() val preview = Preview.Builder()
@@ -45,6 +47,7 @@ class CameraManager(private val context: Context) {
// Select back camera // Select back camera
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
Log.d("CameraManager", "Using camera selector: $cameraSelector")
// Unbind all use cases // Unbind all use cases
cameraProvider?.unbindAll() cameraProvider?.unbindAll()
@@ -57,6 +60,7 @@ class CameraManager(private val context: Context) {
imageCapture imageCapture
) )
Log.d("CameraManager", "bindToLifecycle called")
Log.d("CameraManager", "Camera started successfully") Log.d("CameraManager", "Camera started successfully")
} catch (exc: Exception) { } catch (exc: Exception) {
Log.e("CameraManager", "Use case binding failed", exc) Log.e("CameraManager", "Use case binding failed", exc)
@@ -105,4 +109,3 @@ class CameraManager(private val context: Context) {
} }
} }
} }

View File

@@ -6,7 +6,6 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.camera.view.PreviewView
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -20,7 +19,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Call
import androidx.compose.material.icons.filled.CallEnd import androidx.compose.material.icons.filled.CallEnd
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
@@ -31,19 +29,22 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.DisposableEffect
import android.util.Log
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.camera.view.PreviewView
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -85,13 +86,8 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun StreamingApp(modifier: Modifier = Modifier) { fun StreamingApp(modifier: Modifier = Modifier) {
val context = LocalContext.current
PermissionChecker( PermissionChecker(
onPermissionsGranted = { onPermissionsGranted = { /* permissions granted callback - do not call composables here */ }
// Разрешения выданы, показываем основное приложение
StreamingAppContent(modifier)
}
) { ) {
// Если разрешения не выданы, PermissionChecker сам покажет запрос // Если разрешения не выданы, PermissionChecker сам покажет запрос
StreamingAppContent(modifier) StreamingAppContent(modifier)
@@ -112,8 +108,6 @@ fun StreamingAppContent(modifier: Modifier = Modifier) {
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
var showConnectionForm by remember { mutableStateOf(true) } var showConnectionForm by remember { mutableStateOf(true) }
val context = LocalContext.current
Surface( Surface(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
@@ -276,24 +270,19 @@ fun StreamingScreen(
onDisconnect: () -> Unit onDisconnect: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val cameraManager = remember { CameraManager(context) } val cameraManager = remember { CameraManager(context) }
// Проверяем разрешения на камеру // Проверяем разрешения на камеру
val hasCameraPermission = PermissionManager.hasCameraPermission(context) val hasCameraPermission = PermissionManager.hasCameraPermission(context)
val hasInternetPermission = PermissionManager.hasInternetPermission(context) val hasInternetPermission = PermissionManager.hasInternetPermission(context)
LaunchedEffect(isCameraRunning) {
if (isCameraRunning && hasCameraPermission) {
// Start camera preview when connected
// In a real app, would bind to lifecycle and PreviewView
}
}
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween verticalArrangement = Arrangement.SpaceBetween
) { ) {
// Camera preview placeholder // Camera preview area using PreviewView
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -340,13 +329,36 @@ fun StreamingScreen(
) )
} }
} else if (isCameraRunning) { } else if (isCameraRunning) {
// Camera preview would be rendered here // Render PreviewView via AndroidView and start camera
// Using AndroidView with PreviewView in a real implementation var previewViewRef: PreviewView? by remember { mutableStateOf(null) }
Text(
text = "🎥 Camera Preview", AndroidView(
color = Color.White, factory = { ctx ->
style = MaterialTheme.typography.headlineSmall PreviewView(ctx).apply {
scaleType = PreviewView.ScaleType.FILL_CENTER
// Keep reference for starting camera
previewViewRef = this
}
},
modifier = Modifier
.fillMaxSize()
) )
// Start/stop camera based on lifecycle and preview availability
DisposableEffect(previewViewRef, isCameraRunning) {
val pv = previewViewRef
if (pv != null && isCameraRunning) {
cameraManager.startCamera(
lifecycleOwner,
pv.surfaceProvider,
onError = { err -> Log.e("CameraManager", "Camera error: $err") }
)
}
onDispose {
cameraManager.stopCamera()
}
}
} else { } else {
Text( Text(
text = "Camera Inactive", text = "Camera Inactive",

View File

@@ -9,7 +9,10 @@ data class ServerConnectionConfig(
val password: String val password: String
) { ) {
fun getWebSocketUrl(): String { fun getWebSocketUrl(): String {
return "ws://$serverHost:$serverPort/ws/client/$roomId/$password" // URL-encode roomId and password to handle special characters and avoid accidental mismatches
val encRoom = try { java.net.URLEncoder.encode(roomId, "UTF-8") } catch (e: Exception) { roomId }
val encPass = try { java.net.URLEncoder.encode(password, "UTF-8") } catch (e: Exception) { password }
return "ws://$serverHost:$serverPort/ws/client/$encRoom/$encPass"
} }
} }
@@ -41,4 +44,3 @@ object VideoCommands {
fun adjustQuality(quality: Int) = VideoCommand(type = "adjust_quality", quality = quality) fun adjustQuality(quality: Int) = VideoCommand(type = "adjust_quality", quality = quality)
fun reset() = VideoCommand(type = "reset") fun reset() = VideoCommand(type = "reset")
} }

View File

@@ -98,10 +98,36 @@ class StreamViewModel : ViewModel() {
private fun onMessage(message: String) { private fun onMessage(message: String) {
Log.d("StreamViewModel", "Message received: $message") Log.d("StreamViewModel", "Message received: $message")
viewModelScope.launch { viewModelScope.launch {
try {
// Try to parse as ConnectionResponse
val gson = com.google.gson.Gson()
val connectionResponse = try {
gson.fromJson(message, ConnectionResponse::class.java)
} catch (e: Exception) {
null
}
if (connectionResponse != null && (connectionResponse.client_id != null || connectionResponse.success)) {
Log.d("StreamViewModel", "ConnectionResponse: success=${connectionResponse.success}, client_id=${connectionResponse.client_id}, room_id=${connectionResponse.room_id}, error=${connectionResponse.error}")
if (connectionResponse.success) {
updateStatus("Сессия создана. Client ID: ${connectionResponse.client_id ?: "-"}")
} else {
updateStatus("Ошибка на сервере: ${connectionResponse.error ?: "unknown"}")
_connectionState.value = ConnectionState.Error(connectionResponse.error ?: "Server error")
}
} else {
// General message handling
if (!message.contains("ping")) { if (!message.contains("ping")) {
updateStatus("Получено: $message") updateStatus("Получено: $message")
} }
} }
} catch (e: Exception) {
Log.e("StreamViewModel", "Failed to handle message: ${e.message}")
if (!message.contains("ping")) {
updateStatus("Получено: $message")
}
}
}
} }
fun sendVideoFrame(frameData: ByteArray) { fun sendVideoFrame(frameData: ByteArray) {
@@ -173,4 +199,3 @@ sealed class ConnectionState {
object Disconnected : ConnectionState() object Disconnected : ConnectionState()
data class Error(val message: String) : ConnectionState() data class Error(val message: String) : ConnectionState()
} }

View File

@@ -79,7 +79,16 @@ class WebSocketManager(
fun isConnected(): Boolean = webSocket != null fun isConnected(): Boolean = webSocket != null
override fun onOpen(webSocket: WebSocket, response: Response) { override fun onOpen(webSocket: WebSocket, response: Response) {
Log.d("WebSocket", "Connected!") Log.d("WebSocket", "Connected! Response code=${response.code}, message=${response.message}")
try {
val headers = response.headers
for (i in 0 until headers.size) {
Log.d("WebSocket", "Header: ${headers.name(i)}=${headers.value(i)}")
}
Log.d("WebSocket", "Request url: ${response.request.url}")
} catch (e: Exception) {
Log.w("WebSocket", "Failed to log headers: ${e.message}")
}
onConnected() onConnected()
} }
@@ -105,4 +114,3 @@ class WebSocketManager(
onDisconnected() onDisconnected()
} }
} }

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Allow cleartext for local development host and base-config for debug devices -->
<base-config cleartextTrafficPermitted="true" />
<!-- Allow cleartext specifically for the local server IP (if needed) -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.0.112</domain>
</domain-config>
</network-security-config>