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