package com.safeluck.floatwindow import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.Bundle import android.os.IBinder import android.os.RemoteException import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.safeluck.floatwindow.ui.theme.AnyunVideoTheme import timber.log.Timber class MainActivity : ComponentActivity() { private var mediaAidlInterface: IMediaAidlInterface? = null private var serviceConnection: ServiceConnection? = null private var isServiceBound = false private val isServiceBoundState = mutableStateOf(false) private val callback = object : IMyCallback.Stub() { @Throws(RemoteException::class) override fun onResult(re: ResponseVO?) { re?.let { Timber.d("Callback received: type=${it.type}, errCode=${it.errCode}, message=${it.message}") } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { mediaAidlInterface = IMediaAidlInterface.Stub.asInterface(service) isServiceBound = true isServiceBoundState.value = true Timber.d("FloatingService connected") } override fun onServiceDisconnected(name: ComponentName?) { mediaAidlInterface = null isServiceBound = false isServiceBoundState.value = false Timber.d("FloatingService disconnected") } } setContent { AnyunVideoTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { MainScreen( isServiceBound = isServiceBoundState.value, onBindService = { if (!isServiceBoundState.value && serviceConnection != null) { val intent = Intent(this@MainActivity, FloatingService::class.java) bindService(intent, serviceConnection!!, Context.BIND_AUTO_CREATE) Timber.d("Binding FloatingService") } }, onUnbindService = { unbindServiceInternal() }, onStartAndroidRecord = { startAndroidRecord() }, onStopAndroidRecord = { stopAndroidRecord() }, onStartUsbRecord = { startUsbRecord() }, onStopUsbRecord = { stopUsbRecord() }, onStartUsbPush = { startUsbPush() }, onStopUsbPush = { stopUsbPush() }, onStartP2Push = { startP2Push() }, onStopP2Push = { stopP2Push() }, onStartP2Record = { startP2Record() }, onStopP2Record = { stopP2Record() } ) } } } } private fun startAndroidRecord() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot start Android record") return } try { val mediaArgu = MediaArgu().apply { isPush = false isUsedOutCamera = false // Android 内置摄像头 codeRate = 0 frameRate = 0 m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率 recordTime = 0 tfCardFlag = 0 // 内部存储 } mediaAidlInterface?.registerCallback(callback) mediaAidlInterface?.startMedia(mediaArgu) Timber.d("Started Android camera record") } catch (e: RemoteException) { Timber.e(e, "Error starting Android record") } } private fun stopAndroidRecord() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot stop Android record") return } try { val mediaArgu = MediaArgu().apply { isPush = false isUsedOutCamera = false // Android 内置摄像头 usbCameraId = 0 // Android 相机,usbCameraId 为 0 } mediaAidlInterface?.stopMedia(mediaArgu) Timber.d("Stopped Android camera record") } catch (e: RemoteException) { Timber.e(e, "Error stopping Android record") } } private fun startUsbRecord() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot start USB record") return } try { val mediaArgu = MediaArgu().apply { isPush = false isUsedOutCamera = true // USB 摄像头 usbCameraId = 1 // P1 USB 摄像头 codeRate = 0 frameRate = 0 m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率 recordTime = 0 tfCardFlag = 0 // 内部存储 } mediaAidlInterface?.registerCallback(callback) mediaAidlInterface?.startMedia(mediaArgu) Timber.d("Started USB camera record (P1)") } catch (e: RemoteException) { Timber.e(e, "Error starting USB record") } } private fun stopUsbRecord() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot stop USB record") return } try { val mediaArgu = MediaArgu().apply { isPush = false isUsedOutCamera = true // USB 摄像头 usbCameraId = 1 // P1 USB 摄像头 } mediaAidlInterface?.stopMedia(mediaArgu) Timber.d("Stopped USB camera record (P1)") } catch (e: RemoteException) { Timber.e(e, "Error stopping USB record") } } private fun startUsbPush() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot start USB push") return } try { val mediaArgu = MediaArgu().apply { usbCameraId = 1 // P1 USB 摄像头 isPush = true isUsedOutCamera = true // USB 摄像头 codeRate = 0 frameRate = 0 m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率 url = "rtmp://192.168.16.143/live/livestream" // TODO: 需要设置实际的推流地址 userName = "" pwd = "" } mediaAidlInterface?.registerCallback(callback) mediaAidlInterface?.startMedia(mediaArgu) Timber.d("Started USB camera push (P1)") } catch (e: RemoteException) { Timber.e(e, "Error starting USB push") } } private fun stopUsbPush() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot stop USB push") return } try { val mediaArgu = MediaArgu().apply { usbCameraId = 1 // P1 USB 摄像头 isPush = true isUsedOutCamera = true // USB 摄像头 } mediaAidlInterface?.stopMedia(mediaArgu) Timber.d("Stopped USB camera push (P1)") } catch (e: RemoteException) { Timber.e(e, "Error stopping USB push") } } private fun startP2Push() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot start P2 push") return } try { val mediaArgu = MediaArgu().apply { usbCameraId = 2 // P2 USB 摄像头 isPush = true isUsedOutCamera = true // USB 摄像头 codeRate = 0 frameRate = 0 m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率 url = "rtmp://192.168.16.143/live/livestream" // TODO: 需要设置实际的推流地址 userName = "" pwd = "" } mediaAidlInterface?.registerCallback(callback) mediaAidlInterface?.startMedia(mediaArgu) Timber.d("Started P2 USB camera push") } catch (e: RemoteException) { Timber.e(e, "Error starting P2 push") } } private fun stopP2Push() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot stop P2 push") return } try { val mediaArgu = MediaArgu().apply { usbCameraId = 2 // P2 USB 摄像头 isPush = true isUsedOutCamera = true // USB 摄像头 } mediaAidlInterface?.stopMedia(mediaArgu) Timber.d("Stopped P2 USB camera push") } catch (e: RemoteException) { Timber.e(e, "Error stopping P2 push") } } private fun startP2Record() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot start P2 record") return } try { val mediaArgu = MediaArgu().apply { isPush = false isUsedOutCamera = true // USB 摄像头 usbCameraId = 2 // P2 USB 摄像头 codeRate = 0 frameRate = 0 m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率 recordTime = 0 tfCardFlag = 0 // 内部存储 } mediaAidlInterface?.registerCallback(callback) mediaAidlInterface?.startMedia(mediaArgu) Timber.d("Started P2 USB camera record") } catch (e: RemoteException) { Timber.e(e, "Error starting P2 record") } } private fun stopP2Record() { if (mediaAidlInterface == null) { Timber.w("Service not bound, cannot stop P2 record") return } try { val mediaArgu = MediaArgu().apply { isPush = false isUsedOutCamera = true // USB 摄像头 usbCameraId = 2 // P2 USB 摄像头 } mediaAidlInterface?.stopMedia(mediaArgu) Timber.d("Stopped P2 USB camera record") } catch (e: RemoteException) { Timber.e(e, "Error stopping P2 record") } } private fun unbindServiceInternal() { if (isServiceBound && serviceConnection != null) { try { mediaAidlInterface?.unregisterCallback(callback) } catch (e: RemoteException) { Timber.e(e, "Error unregistering callback") } try { unbindService(serviceConnection!!) Timber.d("Unbinding FloatingService") } catch (e: Exception) { Timber.e(e, "Error unbinding service") } isServiceBound = false isServiceBoundState.value = false mediaAidlInterface = null } } override fun onDestroy() { super.onDestroy() unbindServiceInternal() } } @Composable fun MainScreen( isServiceBound: Boolean, onBindService: () -> Unit, onUnbindService: () -> Unit, onStartAndroidRecord: () -> Unit, onStopAndroidRecord: () -> Unit, onStartUsbRecord: () -> Unit, onStopUsbRecord: () -> Unit, onStartUsbPush: () -> Unit, onStopUsbPush: () -> Unit, onStartP2Push: () -> Unit, onStopP2Push: () -> Unit, onStartP2Record: () -> Unit, onStopP2Record: () -> Unit ) { Column( modifier = Modifier.fillMaxSize() ) { // 固定顶部:标题和状态 Column( modifier = Modifier .fillMaxWidth() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "FloatingService 控制", style = MaterialTheme.typography.headlineMedium, modifier = Modifier.padding(bottom = 8.dp) ) Text( text = if (isServiceBound) "服务状态: 已绑定" else "服务状态: 未绑定", style = MaterialTheme.typography.bodyMedium, color = if (isServiceBound) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error, modifier = Modifier.padding(bottom = 16.dp) ) } Divider() // 可滚动的按钮列表 Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp) ) { Spacer(modifier = Modifier.height(12.dp)) // 按钮 1: 绑定服务 Button( onClick = onBindService, modifier = Modifier.fillMaxWidth(), enabled = !isServiceBound ) { Text("1. 绑定 FloatingService") } // 按钮 2: 解绑服务 Button( onClick = onUnbindService, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("2. 解绑 FloatingService") } Divider(modifier = Modifier.padding(vertical = 8.dp)) // 按钮 3: 开始 Android 相机录像 Button( onClick = onStartAndroidRecord, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("3. 开始 Android 相机录像") } // 按钮 4: 结束 Android 相机录像 Button( onClick = onStopAndroidRecord, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("4. 结束 Android 相机录像") } Divider(modifier = Modifier.padding(vertical = 8.dp)) // 按钮 5: 开始 USB 相机录像 Button( onClick = onStartUsbRecord, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("5. 开始 USB 相机录像") } // 按钮 6: 结束 USB 相机录像 Button( onClick = onStopUsbRecord, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("6. 结束 USB 相机录像") } Divider(modifier = Modifier.padding(vertical = 8.dp)) // 按钮 7: 开始 USB 推流 (P1) Button( onClick = onStartUsbPush, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("7. 开始 USB 推流 (P1)") } // 按钮 8: 结束 USB 推流 (P1) Button( onClick = onStopUsbPush, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("8. 结束 USB 推流 (P1)") } Divider(modifier = Modifier.padding(vertical = 8.dp)) // 按钮 9: 开始 P2 USB 推流 Button( onClick = onStartP2Push, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("9. 开始 P2 USB 推流") } // 按钮 10: 结束 P2 USB 推流 Button( onClick = onStopP2Push, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("10. 结束 P2 USB 推流") } Divider(modifier = Modifier.padding(vertical = 8.dp)) // 按钮 11: 开始 P2 USB 录像 Button( onClick = onStartP2Record, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("11. 开始 P2 USB 录像") } // 按钮 12: 结束 P2 USB 录像 Button( onClick = onStopP2Record, modifier = Modifier.fillMaxWidth(), enabled = isServiceBound ) { Text("12. 结束 P2 USB 录像") } Spacer(modifier = Modifier.height(12.dp)) } } }