global fix
This commit is contained in:
35
.idea/copilotDiffState.xml
generated
35
.idea/copilotDiffState.xml
generated
File diff suppressed because one or more lines are too long
@@ -19,6 +19,7 @@
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
|
||||
@@ -27,8 +27,10 @@ class CameraManager(private val context: Context) {
|
||||
|
||||
cameraProviderFuture.addListener(
|
||||
{
|
||||
Log.d("CameraManager", "cameraProviderFuture listener invoked")
|
||||
try {
|
||||
cameraProvider = cameraProviderFuture.get()
|
||||
Log.d("CameraManager", "cameraProvider obtained: $cameraProvider")
|
||||
|
||||
// Create preview
|
||||
val preview = Preview.Builder()
|
||||
@@ -45,6 +47,7 @@ class CameraManager(private val context: Context) {
|
||||
|
||||
// Select back camera
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
Log.d("CameraManager", "Using camera selector: $cameraSelector")
|
||||
|
||||
// Unbind all use cases
|
||||
cameraProvider?.unbindAll()
|
||||
@@ -57,6 +60,7 @@ class CameraManager(private val context: Context) {
|
||||
imageCapture
|
||||
)
|
||||
|
||||
Log.d("CameraManager", "bindToLifecycle called")
|
||||
Log.d("CameraManager", "Camera started successfully")
|
||||
} catch (exc: Exception) {
|
||||
Log.e("CameraManager", "Use case binding failed", exc)
|
||||
@@ -105,4 +109,3 @@ class CameraManager(private val context: Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Call
|
||||
import androidx.compose.material.icons.filled.CallEnd
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
@@ -31,19 +29,22 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -85,13 +86,8 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
@Composable
|
||||
fun StreamingApp(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
|
||||
PermissionChecker(
|
||||
onPermissionsGranted = {
|
||||
// Разрешения выданы, показываем основное приложение
|
||||
StreamingAppContent(modifier)
|
||||
}
|
||||
onPermissionsGranted = { /* permissions granted callback - do not call composables here */ }
|
||||
) {
|
||||
// Если разрешения не выданы, PermissionChecker сам покажет запрос
|
||||
StreamingAppContent(modifier)
|
||||
@@ -112,8 +108,6 @@ fun StreamingAppContent(modifier: Modifier = Modifier) {
|
||||
var password by remember { mutableStateOf("") }
|
||||
var showConnectionForm by remember { mutableStateOf(true) }
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
Surface(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
@@ -276,24 +270,19 @@ fun StreamingScreen(
|
||||
onDisconnect: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val cameraManager = remember { CameraManager(context) }
|
||||
|
||||
// Проверяем разрешения на камеру
|
||||
val hasCameraPermission = PermissionManager.hasCameraPermission(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(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
// Camera preview placeholder
|
||||
// Camera preview area using PreviewView
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -340,13 +329,36 @@ fun StreamingScreen(
|
||||
)
|
||||
}
|
||||
} else if (isCameraRunning) {
|
||||
// Camera preview would be rendered here
|
||||
// Using AndroidView with PreviewView in a real implementation
|
||||
Text(
|
||||
text = "🎥 Camera Preview",
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
// Render PreviewView via AndroidView and start camera
|
||||
var previewViewRef: PreviewView? by remember { mutableStateOf(null) }
|
||||
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
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 {
|
||||
Text(
|
||||
text = "Camera Inactive",
|
||||
|
||||
@@ -9,7 +9,10 @@ data class ServerConnectionConfig(
|
||||
val password: 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 reset() = VideoCommand(type = "reset")
|
||||
}
|
||||
|
||||
|
||||
@@ -98,10 +98,36 @@ class StreamViewModel : ViewModel() {
|
||||
private fun onMessage(message: String) {
|
||||
Log.d("StreamViewModel", "Message received: $message")
|
||||
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")) {
|
||||
updateStatus("Получено: $message")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("StreamViewModel", "Failed to handle message: ${e.message}")
|
||||
if (!message.contains("ping")) {
|
||||
updateStatus("Получено: $message")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendVideoFrame(frameData: ByteArray) {
|
||||
@@ -173,4 +199,3 @@ sealed class ConnectionState {
|
||||
object Disconnected : ConnectionState()
|
||||
data class Error(val message: String) : ConnectionState()
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,16 @@ class WebSocketManager(
|
||||
fun isConnected(): Boolean = webSocket != null
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -105,4 +114,3 @@ class WebSocketManager(
|
||||
onDisconnected()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
app/src/main/res/xml/network_security_config.xml
Normal file
11
app/src/main/res/xml/network_security_config.xml
Normal 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>
|
||||
|
||||
Reference in New Issue
Block a user