main fixes

This commit is contained in:
2025-12-09 21:21:26 +09:00
parent 752b2fb1ca
commit 568ca73a11
33 changed files with 4353 additions and 345 deletions

View File

@@ -5,10 +5,6 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Hardware features -->
<uses-feature

View File

@@ -9,7 +9,6 @@ import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.Executors
class CameraManager(private val context: Context) {
@@ -17,12 +16,15 @@ class CameraManager(private val context: Context) {
private var cameraProvider: ProcessCameraProvider? = null
private var imageCapture: ImageCapture? = null
private val cameraExecutor = Executors.newSingleThreadExecutor()
private var onFrameAvailable: ((ByteArray) -> Unit)? = null
fun startCamera(
lifecycleOwner: LifecycleOwner,
previewSurfaceProvider: Preview.SurfaceProvider,
onError: (String) -> Unit
onError: (String) -> Unit,
onFrame: ((ByteArray) -> Unit)? = null
) {
onFrameAvailable = onFrame
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener(
@@ -45,6 +47,11 @@ class CameraManager(private val context: Context) {
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// Create image analysis for frame processing
// NOTE: ImageAnalysis is DISABLED because it causes buffer overflow
// with ImageProcessingUtil.convertYUVToRGB in background thread
// For video streaming, we don't need real-time frame processing
// Select back camera
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
Log.d("CameraManager", "Using camera selector: $cameraSelector")
@@ -52,7 +59,7 @@ class CameraManager(private val context: Context) {
// Unbind all use cases
cameraProvider?.unbindAll()
// Bind use cases to camera
// Bind use cases to camera (Preview + ImageCapture only)
cameraProvider?.bindToLifecycle(
lifecycleOwner,
cameraSelector,
@@ -61,7 +68,7 @@ class CameraManager(private val context: Context) {
)
Log.d("CameraManager", "bindToLifecycle called")
Log.d("CameraManager", "Camera started successfully")
Log.d("CameraManager", "Camera started successfully with video streaming")
} catch (exc: Exception) {
Log.e("CameraManager", "Use case binding failed", exc)
onError("Failed to start camera: ${exc.message}")
@@ -71,6 +78,7 @@ class CameraManager(private val context: Context) {
)
}
fun captureFrame(onFrameCaptured: (ByteArray) -> Unit, onError: (String) -> Unit) {
val imageCapture = imageCapture ?: return
@@ -103,6 +111,7 @@ class CameraManager(private val context: Context) {
try {
cameraProvider?.unbindAll()
cameraExecutor.shutdown()
onFrameAvailable = null
Log.d("CameraManager", "Camera stopped")
} catch (exc: Exception) {
Log.e("CameraManager", "Error stopping camera", exc)

View File

@@ -351,7 +351,11 @@ fun StreamingScreen(
cameraManager.startCamera(
lifecycleOwner,
pv.surfaceProvider,
onError = { err -> Log.e("CameraManager", "Camera error: $err") }
onError = { err -> Log.e("CameraManager", "Camera error: $err") },
onFrame = { frameData ->
// Send video frame to ViewModel for transmission to server
viewModel.sendVideoFrame(frameData)
}
)
}

View File

@@ -13,8 +13,7 @@ object PermissionManager {
val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.RECORD_AUDIO
Manifest.permission.ACCESS_NETWORK_STATE
)
/**

View File

@@ -24,6 +24,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -47,8 +48,16 @@ fun PermissionChecker(
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestMultiplePermissions()
) { result ->
permissionsStatus = result
permissionsGranted = result.values.all { it }
// Обновляем состояние с новыми результатами
permissionsStatus = permissionsStatus.toMutableMap().apply {
putAll(result)
}
// Проверяем, выданы ли все обязательные разрешения
permissionsGranted = PermissionManager.REQUIRED_PERMISSIONS.all { permission ->
permissionsStatus[permission] == true
}
if (permissionsGranted) {
onPermissionsGranted()
}
@@ -106,8 +115,7 @@ fun PermissionsRequestScreen(
val requiredPermissions = mapOf(
Manifest.permission.CAMERA to "📷 Доступ к камере",
Manifest.permission.INTERNET to "🌐 Доступ в интернет",
Manifest.permission.ACCESS_NETWORK_STATE to "📡 Проверка состояния сети",
Manifest.permission.RECORD_AUDIO to "🎤 Запись аудио"
Manifest.permission.ACCESS_NETWORK_STATE to "📡 Проверка состояния сети"
)
requiredPermissions.forEach { (permission, label) ->

View File

@@ -32,6 +32,8 @@ class StreamViewModel : ViewModel() {
private var frameCount = 0
private var lastFpsTime = System.currentTimeMillis()
private var totalBytesTransferred = 0L
private var lastFrameTime = System.currentTimeMillis()
private val frameIntervalMs = 100 // ограничиваем до 10 FPS (100мс между фреймами)
fun initializeConnection(
serverHost: String,
@@ -132,6 +134,15 @@ class StreamViewModel : ViewModel() {
fun sendVideoFrame(frameData: ByteArray) {
try {
val currentTime = System.currentTimeMillis()
// Ограничиваем частоту отправки до 10 FPS (100мс между фреймами)
if (currentTime - lastFrameTime < frameIntervalMs) {
return // Пропускаем фрейм если он пришёл слишком рано
}
lastFrameTime = currentTime
wsManager?.sendBinary(frameData)
// Update statistics
@@ -140,14 +151,14 @@ class StreamViewModel : ViewModel() {
_bytesTransferred.value = totalBytesTransferred
// Update FPS every second
val currentTime = System.currentTimeMillis()
if (currentTime - lastFpsTime >= 1000) {
Log.d("StreamViewModel", "FPS: $frameCount, Total bytes sent: $totalBytesTransferred, Frame size: ${frameData.size}")
_fps.value = frameCount
frameCount = 0
lastFpsTime = currentTime
}
} catch (e: Exception) {
Log.e("StreamViewModel", "Failed to send frame: ${e.message}")
Log.e("StreamViewModel", "Failed to send frame: ${e.message}", e)
}
}

View File

@@ -1,6 +1,7 @@
package com.example.camcontrol
import android.util.Log
import okio.ByteString.Companion.toByteString
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.WebSocket
@@ -51,14 +52,8 @@ class WebSocketManager(
fun sendBinary(data: ByteArray) {
try {
// Create ByteString from ByteArray using reflection to avoid import issues
val byteStringClass = Class.forName("okhttp3.ByteString")
val ofMethod = byteStringClass.getMethod("of", ByteArray::class.java)
val byteString = ofMethod.invoke(null, data)
val sendMethod = WebSocket::class.java.getMethod("send", byteStringClass)
sendMethod.invoke(webSocket, byteString)
val byteString = data.toByteString()
webSocket?.send(byteString)
Log.d("WebSocket", "Binary data sent: ${data.size} bytes")
} catch (e: Exception) {
Log.e("WebSocket", "Binary send error: ${e.message}")