408 lines
11 KiB
Markdown
408 lines
11 KiB
Markdown
и# 📝 Полный список изменений
|
||
|
||
## Дата: 2025-12-03
|
||
## Статус: ✅ Завершено и скомпилировано
|
||
|
||
---
|
||
|
||
## 1️⃣ CameraManager.kt
|
||
|
||
### Добавлены импорты
|
||
```kotlin
|
||
import androidx.camera.core.ImageAnalysis
|
||
import androidx.camera.core.ImageProxy
|
||
```
|
||
|
||
### Добавлены новые поля
|
||
```kotlin
|
||
private val analysisExecutor = Executors.newSingleThreadExecutor()
|
||
private var onFrameAvailable: ((ByteArray) -> Unit)? = null
|
||
private var frameCount = 0
|
||
private var lastLogTime = System.currentTimeMillis()
|
||
```
|
||
|
||
### Изменена функция startCamera()
|
||
**Было:**
|
||
```kotlin
|
||
fun startCamera(
|
||
lifecycleOwner: LifecycleOwner,
|
||
previewSurfaceProvider: Preview.SurfaceProvider,
|
||
onError: (String) -> Unit
|
||
)
|
||
```
|
||
|
||
**Стало:**
|
||
```kotlin
|
||
fun startCamera(
|
||
lifecycleOwner: LifecycleOwner,
|
||
previewSurfaceProvider: Preview.SurfaceProvider,
|
||
onError: (String) -> Unit,
|
||
onFrame: ((ByteArray) -> Unit)? = null // ← НОВЫЙ ПАРАМЕТР
|
||
)
|
||
```
|
||
|
||
### Добавлен ImageAnalysis при привязке к камере
|
||
```kotlin
|
||
// Create image analysis for frame processing
|
||
val imageAnalysis = ImageAnalysis.Builder()
|
||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
|
||
.build()
|
||
.apply {
|
||
setAnalyzer(analysisExecutor) { imageProxy ->
|
||
processFrame(imageProxy)
|
||
}
|
||
}
|
||
|
||
// Bind with imageAnalysis
|
||
cameraProvider?.bindToLifecycle(
|
||
lifecycleOwner,
|
||
cameraSelector,
|
||
preview,
|
||
imageCapture,
|
||
imageAnalysis // ← ДОБАВЛЕН
|
||
)
|
||
```
|
||
|
||
### Добавлена новая функция processFrame()
|
||
```kotlin
|
||
private fun processFrame(imageProxy: ImageProxy) {
|
||
try {
|
||
frameCount++
|
||
val currentTime = System.currentTimeMillis()
|
||
|
||
// Log every 5 seconds
|
||
if (currentTime - lastLogTime > 5000) {
|
||
Log.d("CameraManager", "Processing $frameCount frames/5s, sending to server")
|
||
frameCount = 0
|
||
lastLogTime = currentTime
|
||
}
|
||
|
||
// Convert image to byte array
|
||
val buffer = imageProxy.planes[0].buffer
|
||
buffer.rewind()
|
||
val frameData = ByteArray(buffer.remaining())
|
||
buffer.get(frameData)
|
||
|
||
// Send frame to callback
|
||
onFrameAvailable?.invoke(frameData)
|
||
|
||
imageProxy.close()
|
||
} catch (e: Exception) {
|
||
Log.e("CameraManager", "Error processing frame: ${e.message}")
|
||
imageProxy.close()
|
||
}
|
||
}
|
||
```
|
||
|
||
### Обновлена функция stopCamera()
|
||
```kotlin
|
||
fun stopCamera() {
|
||
try {
|
||
cameraProvider?.unbindAll()
|
||
analysisExecutor.shutdown() // ← ДОБАВЛЕНА
|
||
cameraExecutor.shutdown()
|
||
onFrameAvailable = null // ← ДОБАВЛЕНА
|
||
Log.d("CameraManager", "Camera stopped")
|
||
} catch (exc: Exception) {
|
||
Log.e("CameraManager", "Error stopping camera", exc)
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 2️⃣ MainActivity.kt
|
||
|
||
### Изменен вызов startCamera()
|
||
|
||
**Было:**
|
||
```kotlin
|
||
cameraManager.startCamera(
|
||
lifecycleOwner,
|
||
pv.surfaceProvider,
|
||
onError = { err -> Log.e("CameraManager", "Camera error: $err") }
|
||
)
|
||
```
|
||
|
||
**Стало:**
|
||
```kotlin
|
||
cameraManager.startCamera(
|
||
lifecycleOwner,
|
||
pv.surfaceProvider,
|
||
onError = { err -> Log.e("CameraManager", "Camera error: $err") },
|
||
onFrame = { frameData -> // ← НОВЫЙ CALLBACK
|
||
// Send video frame to ViewModel for transmission to server
|
||
viewModel.sendVideoFrame(frameData)
|
||
}
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 3️⃣ WebSocketManager.kt
|
||
|
||
### Изменены импорты
|
||
|
||
**Было:**
|
||
```kotlin
|
||
import okhttp3.OkHttpClient
|
||
import okhttp3.Request
|
||
import okhttp3.WebSocket
|
||
import okhttp3.WebSocketListener
|
||
import okhttp3.Response
|
||
```
|
||
|
||
**Стало:**
|
||
```kotlin
|
||
import android.util.Log
|
||
import okio.ByteString.Companion.toByteString // ← ИЗМЕНЕНО
|
||
import okhttp3.OkHttpClient
|
||
import okhttp3.Request
|
||
import okhttp3.WebSocket
|
||
import okhttp3.WebSocketListener
|
||
import okhttp3.Response
|
||
import java.util.concurrent.TimeUnit
|
||
```
|
||
|
||
### Переписана функция sendBinary()
|
||
|
||
**Было:**
|
||
```kotlin
|
||
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)
|
||
|
||
Log.d("WebSocket", "Binary data sent: ${data.size} bytes")
|
||
} catch (e: Exception) {
|
||
Log.e("WebSocket", "Binary send error: ${e.message}")
|
||
onError(e.message ?: "Failed to send binary data")
|
||
}
|
||
}
|
||
```
|
||
|
||
**Стало:**
|
||
```kotlin
|
||
fun sendBinary(data: ByteArray) {
|
||
try {
|
||
val byteString = data.toByteString() // ← CLEAN API
|
||
webSocket?.send(byteString)
|
||
Log.d("WebSocket", "Binary data sent: ${data.size} bytes")
|
||
} catch (e: Exception) {
|
||
Log.e("WebSocket", "Binary send error: ${e.message}")
|
||
onError(e.message ?: "Failed to send binary data")
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4️⃣ StreamViewModel.kt
|
||
|
||
### Улучшена функция sendVideoFrame()
|
||
|
||
**Было:**
|
||
```kotlin
|
||
fun sendVideoFrame(frameData: ByteArray) {
|
||
try {
|
||
wsManager?.sendBinary(frameData)
|
||
|
||
// Update statistics
|
||
frameCount++
|
||
totalBytesTransferred += frameData.size
|
||
_bytesTransferred.value = totalBytesTransferred
|
||
|
||
// Update FPS every second
|
||
val currentTime = System.currentTimeMillis()
|
||
if (currentTime - lastFpsTime >= 1000) {
|
||
_fps.value = frameCount
|
||
frameCount = 0
|
||
lastFpsTime = currentTime
|
||
}
|
||
} catch (e: Exception) {
|
||
Log.e("StreamViewModel", "Failed to send frame: ${e.message}")
|
||
}
|
||
}
|
||
```
|
||
|
||
**Стало:**
|
||
```kotlin
|
||
fun sendVideoFrame(frameData: ByteArray) {
|
||
try {
|
||
wsManager?.sendBinary(frameData)
|
||
|
||
// Update statistics
|
||
frameCount++
|
||
totalBytesTransferred += frameData.size
|
||
_bytesTransferred.value = totalBytesTransferred
|
||
|
||
// Update FPS every second
|
||
val currentTime = System.currentTimeMillis()
|
||
if (currentTime - lastFpsTime >= 1000) {
|
||
Log.d("StreamViewModel", "FPS: $frameCount, Total bytes sent: $totalBytesTransferred") // ← ДОБАВЛЕНО
|
||
_fps.value = frameCount
|
||
frameCount = 0
|
||
lastFpsTime = currentTime
|
||
}
|
||
} catch (e: Exception) {
|
||
Log.e("StreamViewModel", "Failed to send frame: ${e.message}", e) // ← УЛУЧШЕНО
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5️⃣ AndroidManifest.xml
|
||
|
||
### Удалены ненужные разрешения
|
||
|
||
**Было:**
|
||
```xml
|
||
<!-- Required permissions -->
|
||
<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" />
|
||
```
|
||
|
||
**Стало:**
|
||
```xml
|
||
<!-- Required permissions -->
|
||
<uses-permission android:name="android.permission.CAMERA" />
|
||
<uses-permission android:name="android.permission.INTERNET" />
|
||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 Статистика изменений
|
||
|
||
| Файл | Строк добавлено | Строк удалено | Изменено |
|
||
|------|-----------------|---------------|----------|
|
||
| CameraManager.kt | 45 | 5 | Функция startCamera(), метод processFrame() |
|
||
| MainActivity.kt | 5 | 1 | Вызов startCamera() |
|
||
| WebSocketManager.kt | 8 | 15 | Метод sendBinary() |
|
||
| StreamViewModel.kt | 2 | 1 | Функция sendVideoFrame() |
|
||
| AndroidManifest.xml | 0 | 4 | Разрешения |
|
||
| **ВСЕГО** | **60** | **26** | **5 файлов** |
|
||
|
||
---
|
||
|
||
## 🔍 Детализация по функциям
|
||
|
||
### CameraManager - Добавлены строки (примерно)
|
||
|
||
**Новые поля:**
|
||
```kotlin
|
||
line 18: private val analysisExecutor = Executors.newSingleThreadExecutor()
|
||
line 19: private var onFrameAvailable: ((ByteArray) -> Unit)? = null
|
||
line 20: private var frameCount = 0
|
||
line 21: private var lastLogTime = System.currentTimeMillis()
|
||
```
|
||
|
||
**Параметр startCamera:**
|
||
```kotlin
|
||
line 24: onFrame: ((ByteArray) -> Unit)? = null
|
||
```
|
||
|
||
**Новый ImageAnalysis:**
|
||
```kotlin
|
||
line 49-58: val imageAnalysis = ImageAnalysis.Builder()
|
||
```
|
||
|
||
**Новая функция processFrame:**
|
||
```kotlin
|
||
line 89-110: private fun processFrame(imageProxy: ImageProxy) { ... }
|
||
```
|
||
|
||
**В bindToLifecycle:**
|
||
```kotlin
|
||
line 76: imageAnalysis
|
||
```
|
||
|
||
**В stopCamera:**
|
||
```kotlin
|
||
line 124: analysisExecutor.shutdown()
|
||
line 125: onFrameAvailable = null
|
||
```
|
||
|
||
### MainActivity - Добавлены строки
|
||
|
||
**Новый callback в startCamera:**
|
||
```kotlin
|
||
line X: onFrame = { frameData ->
|
||
line X+1: viewModel.sendVideoFrame(frameData)
|
||
line X+2: }
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ Компиляция
|
||
|
||
```
|
||
BUILD SUCCESSFUL in 6s
|
||
97 actionable tasks: 15 executed, 82 up-to-date
|
||
```
|
||
|
||
### Никаких ошибок:
|
||
- ✅ No compilation errors
|
||
- ✅ All imports resolved
|
||
- ✅ Type safety verified
|
||
|
||
---
|
||
|
||
## 📦 Артефакты
|
||
|
||
### Созданные файлы документации
|
||
1. `VIDEO_STREAMING_FIX.md` - Детальное объяснение
|
||
2. `DEBUGGING_SUMMARY.md` - Полный анализ
|
||
3. `TESTING_GUIDE.md` - Инструкции тестирования
|
||
4. `QUICK_SUMMARY.md` - Краткое резюме
|
||
5. `LOGS_COMPARISON.md` - Сравнение логов
|
||
6. `CHANGES.md` - Этот файл
|
||
|
||
### Скомпилированные APK файлы
|
||
- `/app/build/outputs/apk/debug/app-debug.apk`
|
||
- `/app/build/outputs/apk/release/app-release.apk`
|
||
|
||
---
|
||
|
||
## 🎯 Следующие шаги
|
||
|
||
### Немедленно
|
||
1. ✅ Скомпилировать: `./gradlew build` (ГОТОВО)
|
||
2. Установить: `./gradlew installDebug`
|
||
3. Запустить на устройстве
|
||
4. Проверить логи в logcat
|
||
|
||
### Для оптимизации
|
||
1. Добавить H.264 видеокодирование
|
||
2. Масштабировать фреймы перед отправкой
|
||
3. Реализовать переподключение при разрыве
|
||
|
||
---
|
||
|
||
## 🔗 Связанные файлы
|
||
|
||
- `app/src/main/java/com/example/camcontrol/CameraManager.kt`
|
||
- `app/src/main/java/com/example/camcontrol/MainActivity.kt`
|
||
- `app/src/main/java/com/example/camcontrol/WebSocketManager.kt`
|
||
- `app/src/main/java/com/example/camcontrol/StreamViewModel.kt`
|
||
- `app/src/main/AndroidManifest.xml`
|
||
|
||
---
|
||
|
||
**Создано:** 2025-12-03
|
||
**Проверено:** ✅ Успешно компилируется
|
||
**Статус:** 🚀 Готово к тестированию
|
||
|