feat: add camera preview

This commit is contained in:
Fritz Heiden 2025-03-22 13:32:02 +01:00
parent 04c93a0ce7
commit 0c1f4801c3
8 changed files with 121 additions and 17 deletions

View File

@ -52,6 +52,13 @@ dependencies {
implementation(libs.androidx.navigation.compose)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.video)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.camera.mlkit.vision)
implementation(libs.androidx.camera.extensions)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

View File

@ -1,10 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"

View File

@ -29,15 +29,20 @@ import com.example.tvcontroller.ui.theme.TVControllerTheme
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.res.painterResource
import androidx.core.app.ActivityCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.tvcontroller.services.BluetoothService
import com.example.tvcontroller.services.CameraService
import com.example.tvcontroller.services.DeviceService
import com.example.tvcontroller.ui.AppViewModel
import com.example.tvcontroller.ui.views.CameraView
import com.example.tvcontroller.ui.views.SettingsView
class MainActivity : ComponentActivity() {
private lateinit var bluetoothService: BluetoothService
private lateinit var deviceService: DeviceService
private lateinit var cameraService: CameraService
private val appViewModel by viewModels<AppViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
@ -46,6 +51,8 @@ class MainActivity : ComponentActivity() {
bluetoothService.onBluetoothStateChanged { state -> appViewModel.setBluetoothEnabled(state == BluetoothAdapter.STATE_ON) }
appViewModel.setBluetoothEnabled(bluetoothService.isBluetoothEnabled())
deviceService = DeviceService(this.applicationContext)
cameraService = CameraService(this.applicationContext)
checkPermissions()
enableEdgeToEdge()
setContent {
TVControllerTheme {
@ -57,6 +64,12 @@ class MainActivity : ComponentActivity() {
}
}
}
private fun checkPermissions() {
if (!cameraService.hasRequiredPermissions()) {
ActivityCompat.requestPermissions(this, CameraService.CAMERAX_PERMISSIONS, 0)
}
}
}
@Composable
@ -115,31 +128,19 @@ fun TvControllerApp(
modifier = Modifier.padding(innerPadding)
) {
composable(route = Screen.Camera.name) {
CameraScreen()
CameraView()
}
composable(route = Screen.Remote.name) {
RemoteScreen()
}
composable(route = Screen.Settings.name) {
SettingsScreen(appViewModel = appViewModel, deviceService = deviceService)
SettingsView(appViewModel = appViewModel, deviceService = deviceService)
}
}
}
}
}
@Composable
fun CameraScreen(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Camera Screen", modifier = modifier)
}
}
@Composable
fun RemoteScreen(modifier: Modifier = Modifier) {
Column(

View File

@ -0,0 +1,19 @@
package com.example.tvcontroller.services
import android.content.Context
import android.content.pm.PackageManager
class CameraService(private val context: Context) {
fun hasRequiredPermissions(): Boolean {
return CAMERAX_PERMISSIONS.all {
context.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED
}
}
companion object {
val CAMERAX_PERMISSIONS = arrayOf(
android.Manifest.permission.CAMERA,
android.Manifest.permission.RECORD_AUDIO
)
}
}

View File

@ -0,0 +1,25 @@
package com.example.tvcontroller.ui.components
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.LocalLifecycleOwner
@Composable
fun CameraPreview(
controller: LifecycleCameraController,
modifier: Modifier = Modifier
) {
val lifecycleOwner = LocalLifecycleOwner.current
AndroidView(
modifier = modifier,
factory = {
PreviewView(it).apply {
this.controller = controller
controller.bindToLifecycle(lifecycleOwner)
}
},
)
}

View File

@ -0,0 +1,34 @@
package com.example.tvcontroller.ui.views
import androidx.camera.view.CameraController
import androidx.camera.view.LifecycleCameraController
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.tvcontroller.ui.components.CameraPreview
@Composable
fun CameraView() {
val context = LocalContext.current
val controller = remember {
LifecycleCameraController(context).apply {
setEnabledUseCases(CameraController.VIDEO_CAPTURE)
}
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(all = 16.dp),
) {
CameraPreview(controller = controller, modifier = Modifier.fillMaxSize())
}
}

View File

@ -1,4 +1,4 @@
package com.example.tvcontroller
package com.example.tvcontroller.ui.views
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -16,13 +16,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.tvcontroller.R
import com.example.tvcontroller.Settings
import com.example.tvcontroller.SettingsViewModel
import com.example.tvcontroller.services.DeviceService
import com.example.tvcontroller.ui.AppViewModel
@Composable
fun SettingsScreen(deviceService: DeviceService, appViewModel: AppViewModel) {
fun SettingsView(deviceService: DeviceService, appViewModel: AppViewModel) {
val viewModel =
viewModel<SettingsViewModel>(factory = SettingsViewModel.provideFactory(deviceService))
viewModel<SettingsViewModel>(factory = SettingsViewModel.Companion.provideFactory(deviceService))
Column(
modifier = Modifier

View File

@ -1,5 +1,6 @@
[versions]
agp = "8.9.0"
cameraCore = "1.4.1"
kotlin = "2.0.0"
coreKtx = "1.10.1"
junit = "4.13.2"
@ -12,6 +13,13 @@ composeBom = "2024.04.01"
navigationCompose = "2.8.4"
[libraries]
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" }
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCore" }
androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "cameraCore" }
androidx-camera-mlkit-vision = { module = "androidx.camera:camera-mlkit-vision", version.ref = "cameraCore" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" }
androidx-camera-video = { module = "androidx.camera:camera-video", version.ref = "cameraCore" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCore" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
junit = { group = "junit", name = "junit", version.ref = "junit" }