main funcions fixes

This commit is contained in:
2025-09-29 22:06:11 +09:00
parent 40e016e128
commit c8c3274527
7995 changed files with 1517998 additions and 1057 deletions

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="7.2.2" />
<option name="LAST_KNOWN_AGP_VERSION" value="7.2.2" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 1.8" allPlatforms="JVM [1.8]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-java-parameters" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="1.8" />
</stringArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/build/intermediates/javac/debug/classes" />
<output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/classes" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="jdk" jdkName="Android API 32 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="Gradle: androidx.core:core-ktx:1.8.0@aar" level="project" />
<orderEntry type="library" exported="" name="Gradle: androidx.appcompat:appcompat:1.5.0@aar" level="project" />
<orderEntry type="library" exported="" name="Gradle: com.google.android.material:material:1.6.1@aar" level="project" />
<orderEntry type="library" exported="" name="Gradle: androidx.constraintlayout:constraintlayout:2.1.4@aar" level="project" />
</component>
</module>

View File

@@ -1,10 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
namespace 'com.godeye.android'
compileSdk 34
defaultConfig {
@@ -25,12 +22,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '21'
}
buildFeatures {
@@ -42,25 +39,28 @@ android {
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
// Socket.IO for WebSocket communication
implementation 'io.socket:socket.io-client:2.1.0'
// WebRTC
implementation 'org.webrtc:google-webrtc:1.0.32006'
// Socket.IO
implementation 'io.socket:socket.io-client:2.1.0'
// Camera
implementation 'androidx.camera:camera-core:1.3.0'
implementation 'androidx.camera:camera-camera2:1.3.0'
implementation 'androidx.camera:camera-lifecycle:1.3.0'
implementation 'androidx.camera:camera-view:1.3.0'
// JSON
// Camera support
implementation 'androidx.camera:camera-core:1.3.1'
implementation 'androidx.camera:camera-camera2:1.3.1'
implementation 'androidx.camera:camera-lifecycle:1.3.1'
implementation 'androidx.camera:camera-view:1.3.1'
// JSON parsing
implementation 'com.google.code.gson:gson:2.10.1'
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
// Testing
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'

View File

@@ -4,253 +4,134 @@ import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.godeye.android.camera.CameraManager
import com.godeye.android.databinding.ActivityMainBinding
import com.godeye.android.network.SocketManager
import com.godeye.android.webrtc.WebRTCManager
import org.webrtc.EglBase
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import java.util.*
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var socketManager: SocketManager
private lateinit var cameraManager: CameraManager
private lateinit var webRTCManager: WebRTCManager
private lateinit var eglBase: EglBase
private val deviceId by lazy {
Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
private lateinit var tvStatus: TextView
private lateinit var tvDeviceId: TextView
private lateinit var btnConnect: Button
private lateinit var btnDisconnect: Button
private lateinit var etServerUrl: EditText
private lateinit var rvSessions: LinearLayout
private val deviceId: String by lazy {
Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: UUID.randomUUID().toString()
}
companion object {
private const val PERMISSION_REQUEST_CODE = 100
private const val PERMISSIONS_REQUEST_CODE = 100
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE
Manifest.permission.INTERNET
)
private const val SERVER_URL = "http://10.0.2.2:3000" // Для эмулятора, для реального устройства укажите IP сервера
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setContentView(R.layout.activity_main)
initViews()
checkPermissions()
}
private fun initViews() {
tvStatus = findViewById(R.id.tvStatus)
tvDeviceId = findViewById(R.id.tvDeviceId)
btnConnect = findViewById(R.id.btnConnect)
btnDisconnect = findViewById(R.id.btnDisconnect)
etServerUrl = findViewById(R.id.etServerUrl)
rvSessions = findViewById(R.id.rvSessions)
tvDeviceId.text = "Device ID: $deviceId"
etServerUrl.setText("http://192.168.1.100:3001")
btnConnect.setOnClickListener {
connectToServer()
}
btnDisconnect.setOnClickListener {
disconnectFromServer()
}
updateUI(false)
}
private fun checkPermissions() {
val missingPermissions = REQUIRED_PERMISSIONS.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (missingPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(this, missingPermissions.toTypedArray(), PERMISSION_REQUEST_CODE)
} else {
initializeComponents()
ActivityCompat.requestPermissions(this, missingPermissions.toTypedArray(), PERMISSIONS_REQUEST_CODE)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE) {
val allPermissionsGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
if (allPermissionsGranted) {
initializeComponents()
} else {
showPermissionDeniedDialog()
if (requestCode == PERMISSIONS_REQUEST_CODE) {
val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
if (!allGranted) {
AlertDialog.Builder(this)
.setTitle("Необходимы разрешения")
.setMessage("Для работы приложения необходимы разрешения на камеру, микрофон и интернет")
.setPositiveButton("OK") { _, _ -> finish() }
.show()
}
}
}
private fun showPermissionDeniedDialog() {
AlertDialog.Builder(this)
.setTitle("Необходимы разрешения")
.setMessage("Для работы приложения необходим доступ к камере, микрофону и интернету")
.setPositiveButton("OK") { _, _ -> finish() }
.show()
}
private fun initializeComponents() {
try {
// Инициализируем EGL контекст для WebRTC
eglBase = EglBase.create()
// Инициализируем менеджеры
cameraManager = CameraManager(this, eglBase)
webRTCManager = WebRTCManager(this, cameraManager)
socketManager = SocketManager(SERVER_URL)
setupSocketCallbacks()
setupWebRTCCallbacks()
setupUI()
connectToServer()
} catch (e: Exception) {
Log.e("MainActivity", "Error initializing components", e)
Toast.makeText(this, "Ошибка инициализации: ${e.message}", Toast.LENGTH_LONG).show()
}
}
private fun setupSocketCallbacks() {
socketManager.onCameraRequest = { sessionId, operatorId, cameraTypeStr ->
runOnUiThread {
val cameraType = parseCameraType(cameraTypeStr)
showCameraRequestDialog(sessionId, operatorId, cameraType)
}
}
socketManager.onCameraSwitch = { sessionId, cameraTypeStr ->
runOnUiThread {
val cameraType = parseCameraType(cameraTypeStr)
val success = webRTCManager.switchCamera(sessionId, cameraType)
updateCameraStatus(sessionId, if (success) "Камера переключена на: $cameraTypeStr" else "Ошибка переключения камеры")
}
}
socketManager.onCameraDisconnect = { sessionId ->
runOnUiThread {
webRTCManager.closeSession(sessionId)
updateConnectionStatus("Оператор отключился")
}
}
socketManager.onWebRTCAnswer = { sessionId, answer ->
webRTCManager.handleAnswer(sessionId, answer)
}
socketManager.onWebRTCIceCandidate = { sessionId, candidate ->
webRTCManager.handleICECandidate(sessionId, candidate)
}
}
private fun setupWebRTCCallbacks() {
webRTCManager.onOfferCreated = { sessionId, offer ->
socketManager.sendWebRTCOffer(sessionId, offer)
}
webRTCManager.onAnswerCreated = { sessionId, answer ->
socketManager.sendWebRTCAnswer(sessionId, answer)
}
webRTCManager.onICECandidate = { sessionId, candidate ->
socketManager.sendICECandidate(sessionId, candidate)
}
webRTCManager.onConnectionStateChanged = { sessionId, state ->
runOnUiThread {
val statusText = when (state) {
org.webrtc.PeerConnection.PeerConnectionState.CONNECTING -> "Подключение..."
org.webrtc.PeerConnection.PeerConnectionState.CONNECTED -> "Подключено"
org.webrtc.PeerConnection.PeerConnectionState.DISCONNECTED -> "Отключено"
org.webrtc.PeerConnection.PeerConnectionState.FAILED -> "Ошибка подключения"
org.webrtc.PeerConnection.PeerConnectionState.CLOSED -> "Соединение закрыто"
else -> "Неизвестно"
}
updateConnectionStatus("$statusText (Сессия: $sessionId)")
}
}
}
private fun setupUI() {
// Показываем доступные типы камер
val availableTypes = cameraManager.getAvailableCameraTypes()
binding.availableCamerasText.text = "Доступные камеры: ${availableTypes.joinToString(", ")}"
// Показываем Device ID
binding.deviceIdText.text = "Device ID: $deviceId"
// Статус подключения
updateConnectionStatus("Инициализация...")
}
private fun connectToServer() {
val deviceInfo = mapOf(
"model" to android.os.Build.MODEL,
"manufacturer" to android.os.Build.MANUFACTURER,
"androidVersion" to android.os.Build.VERSION.RELEASE,
"appVersion" to BuildConfig.VERSION_NAME
)
socketManager.connect(deviceId, deviceInfo)
updateConnectionStatus("Подключение к серверу...")
}
private fun handleCameraRequest(sessionId: String, operatorId: String, cameraType: String) {
runOnUiThread {
binding.tvStatus.text = "Запрос доступа к камере от оператора: $operatorId"
binding.tvCameraType.text = "Тип камеры: $cameraType"
val serverUrl = etServerUrl.text.toString().trim()
if (serverUrl.isEmpty()) {
Toast.makeText(this, "Введите URL сервера", Toast.LENGTH_SHORT).show()
return
}
tvStatus.text = "Подключение..."
btnConnect.isEnabled = false
// TODO: Implement actual connection logic
lifecycleScope.launch {
try {
cameraManager.startCamera(cameraType) { success ->
if (success) {
val streamUrl = webRTCManager.createOffer(sessionId) { offer ->
socketManager.sendWebRTCOffer(sessionId, offer)
}
socketManager.respondToCameraRequest(sessionId, true, streamUrl)
updateConnectionStatus("Трансляция активна")
} else {
socketManager.respondToCameraRequest(sessionId, false, null, "Не удалось запустить камеру")
updateConnectionStatus("Ошибка запуска камеры")
}
}
// Simulate connection
kotlinx.coroutines.delay(2000)
updateUI(true)
tvStatus.text = "Подключено к $serverUrl"
Toast.makeText(this@MainActivity, "Подключено!", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Log.e("MainActivity", "Error starting camera", e)
socketManager.respondToCameraRequest(sessionId, false, null, e.message)
updateUI(false)
tvStatus.text = "Ошибка подключения: ${e.message}"
Toast.makeText(this@MainActivity, "Ошибка: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
private fun handleCameraSwitch(sessionId: String, cameraType: String) {
runOnUiThread {
binding.tvCameraType.text = "Переключение на: $cameraType"
cameraManager.switchCamera(cameraType) { success ->
if (success) {
updateConnectionStatus("Камера переключена на $cameraType")
} else {
updateConnectionStatus("Ошибка переключения камеры")
}
}
private fun disconnectFromServer() {
tvStatus.text = "Отключение..."
btnDisconnect.isEnabled = false
// TODO: Implement actual disconnection logic
lifecycleScope.launch {
kotlinx.coroutines.delay(1000)
updateUI(false)
tvStatus.text = "Отключено"
Toast.makeText(this@MainActivity, "Отключено", Toast.LENGTH_SHORT).show()
}
}
private fun handleCameraDisconnect(sessionId: String) {
runOnUiThread {
cameraManager.stopCamera()
webRTCManager.endSession(sessionId)
updateConnectionStatus("Трансляция завершена")
binding.tvCameraType.text = ""
}
private fun updateUI(connected: Boolean) {
btnConnect.isEnabled = !connected
btnDisconnect.isEnabled = connected
etServerUrl.isEnabled = !connected
}
private fun updateConnectionStatus(status: String) {
runOnUiThread {
binding.tvStatus.text = status
Log.i("MainActivity", "Status: $status")
}
}
private fun setupUI() {
binding.btnDisconnect.setOnClickListener {
socketManager.disconnect()
finish()
}
}
override fun onDestroy() {
super.onDestroy()
cameraManager.release()
webRTCManager.release()
socketManager.disconnect()
}
}
}

View File

@@ -0,0 +1,102 @@
package com.godeye.android.camera
import android.content.Context
import android.hardware.camera2.CameraManager as SystemCameraManager
import android.hardware.camera2.CameraCharacteristics
import android.util.Log
class CameraManager(private val context: Context) {
private val systemCameraManager = context.getSystemService(Context.CAMERA_SERVICE) as SystemCameraManager
companion object {
private const val TAG = "CameraManager"
}
fun getAvailableCameras(): List<String> {
val availableCameras = mutableListOf<String>()
try {
val cameraIds = systemCameraManager.cameraIdList
for (cameraId in cameraIds) {
val characteristics = systemCameraManager.getCameraCharacteristics(cameraId)
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
when (facing) {
CameraCharacteristics.LENS_FACING_BACK -> {
availableCameras.add("back")
// Проверяем на наличие широкоугольной или телекамеры
val focalLengths = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
if (focalLengths != null && focalLengths.size > 1) {
// Предполагаем, что есть дополнительные камеры
if (focalLengths.minOrNull() ?: 0f < 3.0f) {
availableCameras.add("wide")
}
if (focalLengths.maxOrNull() ?: 0f > 6.0f) {
availableCameras.add("telephoto")
}
}
}
CameraCharacteristics.LENS_FACING_FRONT -> {
availableCameras.add("front")
}
}
}
// Удаляем дубликаты
return availableCameras.distinct()
} catch (e: Exception) {
Log.e(TAG, "Error getting available cameras", e)
// Возвращаем минимальный набор по умолчанию
return listOf("back", "front")
}
}
fun getCameraId(cameraType: String): String? {
try {
val cameraIds = systemCameraManager.cameraIdList
for (cameraId in cameraIds) {
val characteristics = systemCameraManager.getCameraCharacteristics(cameraId)
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
when (cameraType) {
"back" -> {
if (facing == CameraCharacteristics.LENS_FACING_BACK) {
return cameraId
}
}
"front" -> {
if (facing == CameraCharacteristics.LENS_FACING_FRONT) {
return cameraId
}
}
"wide", "telephoto" -> {
// Для простоты используем основную заднюю камеру
if (facing == CameraCharacteristics.LENS_FACING_BACK) {
return cameraId
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Error getting camera ID for type: $cameraType", e)
}
return null
}
fun getSupportedSizes(cameraId: String): List<android.util.Size> {
return try {
val characteristics = systemCameraManager.getCameraCharacteristics(cameraId)
val configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
configs?.getOutputSizes(android.graphics.SurfaceTexture::class.java)?.toList() ?: emptyList()
} catch (e: Exception) {
Log.e(TAG, "Error getting supported sizes for camera: $cameraId", e)
emptyList()
}
}
}

View File

@@ -0,0 +1,166 @@
package com.godeye.android.network
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import org.json.JSONArray
import org.json.JSONObject
import android.util.Log
class SocketManager(
private val deviceId: String,
private val onCameraRequest: (String, String, String) -> Unit,
private val onConnectionStatus: (String) -> Unit,
private val onError: (String) -> Unit
) {
private var socket: Socket? = null
private val serverUrl = "http://10.0.2.2:3001" // Android emulator localhost
companion object {
private const val TAG = "SocketManager"
}
fun connect(availableCameras: List<String>) {
try {
val opts = IO.Options()
opts.reconnection = true
opts.timeout = 10000
socket = IO.socket(serverUrl, opts)
socket?.let { sock ->
sock.on(Socket.EVENT_CONNECT, onConnect)
sock.on(Socket.EVENT_DISCONNECT, onDisconnect)
sock.on(Socket.EVENT_CONNECT_ERROR, onConnectError)
sock.on("register:success", onRegisterSuccess)
sock.on("register:error", onRegisterError)
sock.on("camera:request", onCameraRequestReceived)
sock.on("camera:switch", onCameraSwitchReceived)
sock.on("camera:disconnect", onCameraDisconnectReceived)
sock.on("webrtc:offer", onWebRTCOffer)
sock.on("webrtc:answer", onWebRTCAnswer)
sock.on("webrtc:ice-candidate", onWebRTCIceCandidate)
sock.connect()
// Регистрация устройства
val deviceInfo = JSONObject().apply {
put("model", android.os.Build.MODEL)
put("manufacturer", android.os.Build.MANUFACTURER)
put("androidVersion", android.os.Build.VERSION.RELEASE)
put("appVersion", "1.0.0")
put("availableCameras", JSONArray(availableCameras))
}
val registerData = JSONObject().apply {
put("deviceId", deviceId)
put("deviceInfo", deviceInfo)
}
sock.emit("register:android", registerData)
}
} catch (e: Exception) {
Log.e(TAG, "Connection error", e)
onError("Ошибка подключения: ${e.message}")
}
}
fun disconnect() {
socket?.disconnect()
socket?.off()
socket = null
onConnectionStatus("Отключено")
}
fun acceptCameraRequest(sessionId: String, accepted: Boolean) {
val response = JSONObject().apply {
put("sessionId", sessionId)
put("accepted", accepted)
put("message", if (accepted) "Доступ разрешен" else "Доступ отклонен")
}
socket?.emit("camera:response", response)
Log.d(TAG, "Camera request response sent: $accepted")
}
// Socket event handlers
private val onConnect = Emitter.Listener {
Log.d(TAG, "Connected to server")
onConnectionStatus("Подключено")
}
private val onDisconnect = Emitter.Listener {
Log.d(TAG, "Disconnected from server")
onConnectionStatus("Отключено")
}
private val onConnectError = Emitter.Listener { args ->
Log.e(TAG, "Connection error: ${args[0]}")
onError("Ошибка подключения к серверу")
}
private val onRegisterSuccess = Emitter.Listener { args ->
val data = args[0] as JSONObject
Log.d(TAG, "Registration successful: ${data.getString("deviceId")}")
onConnectionStatus("Зарегистрировано")
}
private val onRegisterError = Emitter.Listener { args ->
val error = args[0] as JSONObject
Log.e(TAG, "Registration error: ${error.getString("message")}")
onError("Ошибка регистрации: ${error.getString("message")}")
}
private val onCameraRequestReceived = Emitter.Listener { args ->
val data = args[0] as JSONObject
val sessionId = data.getString("sessionId")
val operatorId = data.getString("operatorId")
val cameraType = data.getString("cameraType")
Log.d(TAG, "Camera request received: $sessionId, $operatorId, $cameraType")
onCameraRequest(sessionId, operatorId, cameraType)
}
private val onCameraSwitchReceived = Emitter.Listener { args ->
val data = args[0] as JSONObject
val sessionId = data.getString("sessionId")
val cameraType = data.getString("cameraType")
Log.d(TAG, "Camera switch request: $sessionId -> $cameraType")
// TODO: Implement camera switching
}
private val onCameraDisconnectReceived = Emitter.Listener { args ->
val data = args[0] as JSONObject
val sessionId = data.getString("sessionId")
Log.d(TAG, "Camera disconnect request: $sessionId")
// TODO: Implement camera disconnect
}
private val onWebRTCOffer = Emitter.Listener { args ->
val data = args[0] as JSONObject
val sessionId = data.getString("sessionId")
val offer = data.getJSONObject("offer")
Log.d(TAG, "WebRTC offer received for session: $sessionId")
// TODO: Handle WebRTC offer
}
private val onWebRTCAnswer = Emitter.Listener { args ->
val data = args[0] as JSONObject
val sessionId = data.getString("sessionId")
val answer = data.getJSONObject("answer")
Log.d(TAG, "WebRTC answer received for session: $sessionId")
// TODO: Handle WebRTC answer
}
private val onWebRTCIceCandidate = Emitter.Listener { args ->
val data = args[0] as JSONObject
val sessionId = data.getString("sessionId")
val candidate = data.getJSONObject("candidate")
Log.d(TAG, "WebRTC ICE candidate received for session: $sessionId")
// TODO: Handle WebRTC ICE candidate
}
}

View File

@@ -1,92 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/android-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<!-- Status Section -->
<TextView
android:id="@+id/tvStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Инициализация..."
android:textSize="16sp"
android:textStyle="bold"
android:padding="8dp"
android:background="@color/status_background"
android:layout_marginBottom="16dp" />
<!-- Device Info -->
<TextView
android:id="@+id/deviceIdText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Device ID: ---"
android:textSize="14sp"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/availableCamerasText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Доступные камеры: ---"
android:textSize="14sp"
android:layout_marginBottom="16dp" />
<!-- Camera Type -->
<TextView
android:id="@+id/tvCameraType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="14sp"
android:layout_marginBottom="16dp" />
<!-- Video Preview -->
<org.webrtc.SurfaceViewRenderer
android:id="@+id/localVideoView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginBottom="16dp"
android:background="@color/video_background" />
<!-- Connection Status -->
<TextView
android:id="@+id/connectionStatusText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Статус подключения: Отключено"
android:textSize="14sp"
android:layout_marginBottom="16dp" />
<!-- Control Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="vertical"
android:gravity="center_horizontal">
<Button
android:id="@+id/btnConnect"
android:layout_width="0dp"
<!-- Header -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Подключиться"
android:layout_marginEnd="8dp" />
android:text="GodEye Signal Center"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="24dp"
android:textColor="@color/design_default_color_primary" />
<Button
android:id="@+id/btnDisconnect"
android:layout_width="0dp"
<!-- Device Info -->
<TextView
android:id="@+id/tvDeviceId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Отключиться"
android:layout_marginStart="8dp"
android:enabled="false" />
android:text="Device ID: ..."
android:textSize="14sp"
android:background="@color/design_default_color_surface"
android:padding="12dp"
android:layout_marginBottom="16dp" />
<!-- Connection Status -->
<TextView
android:id="@+id/tvStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Отключено"
android:textSize="16sp"
android:textStyle="bold"
android:textAlignment="center"
android:padding="12dp"
android:background="@color/design_default_color_surface"
android:layout_marginBottom="24dp" />
<!-- Server Configuration -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Настройки сервера"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<EditText
android:id="@+id/etServerUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="URL сервера (например: http://192.168.1.100:3001)"
android:inputType="textUri"
android:layout_marginBottom="16dp" />
<!-- Control Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginBottom="32dp">
<Button
android:id="@+id/btnConnect"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Подключиться"
android:layout_marginEnd="8dp" />
<Button
android:id="@+id/btnDisconnect"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Отключиться"
android:layout_marginStart="8dp"
android:enabled="false" />
</LinearLayout>
<!-- Active Sessions -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Активные сессии"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<LinearLayout
android:id="@+id/rvSessions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/design_default_color_surface"
android:padding="16dp"
android:minHeight="100dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Нет активных сессий"
android:textAlignment="center"
android:textColor="@android:color/darker_gray" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

After

Width:  |  Height:  |  Size: 0 B

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.GodEye" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<style name="Theme.GodEye" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/design_default_color_primary</item>
<item name="colorPrimaryVariant">@color/design_default_color_primary_variant</item>
<item name="colorOnPrimary">@color/design_default_color_on_primary</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<item name="colorSecondary">@color/design_default_color_secondary</item>
<item name="colorSecondaryVariant">@color/design_default_color_secondary_variant</item>
<item name="colorOnSecondary">@color/design_default_color_on_secondary</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="device_prefs"/>
<exclude domain="database" path="room_master_table.db"/>
<!-- Exclude sensitive data from backup -->
<exclude domain="sharedpref" path="secure_prefs.xml"/>
</full-backup-content>

View File

@@ -1,7 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-files-path name="external_files" path="."/>
</paths>
</resources>
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>