main fixes
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user