Issues Starting Real-Time Streaming onClick of an Icon in an Android App

15 Views Asked by At

I am developing an Android app designed to record video streams in real-time and display them simultaneously on a VLC-like player. The app features a camera icon, and I intend for the streaming to start upon clicking this icon.

Currently, I'm facing difficulties in integrating the streaming functionality correctly. I attempted to set up image analysis to handle the streaming, but my approach seems utterly wrong for this purpose. Below, I've attached the code for the setupImageAnalysis function I developed, which evidently needs significant adjustments to fit my goal.


package com.android.example.streamingcamera

import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaFormat
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXThreads.TAG
import androidx.camera.core.ImageAnalysis
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.Recording
import androidx.camera.view.CameraController
import androidx.camera.view.LifecycleCameraController
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cameraswitch
import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.android.example.streamingcamera.ui.theme.StreamingCameraTheme
import java.io.IOException
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors


class MainActivity : ComponentActivity() {

    private var recording: Recording? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Verifica se le autorizzazioni necessarie per CameraX sono state già concesse.
        if(!hasRequiredPermissions()) {
            // Se non tutte le autorizzazioni necessarie sono state concesse,
            // richiede all'utente di concederle.
            // this: Context dell'attività corrente che richiede le autorizzazioni.
            // CAMERAX_PERMISSIONS: Array delle autorizzazioni richieste.
            // 0: Codice di richiesta per la callback di risposta
            ActivityCompat.requestPermissions(
                this, CAMERAX_PERMISSIONS, 0
            )
        }
        setContent {
            StreamingCameraTheme {
                val controller = remember {
                    LifecycleCameraController(applicationContext).apply {
                        // Configura il controller per utilizzare solo la cattura video.
                        setEnabledUseCases(
                            CameraController.VIDEO_CAPTURE
                        )
                    }
                }

                // Box utilizzato per contenere l'anteprima della camera, facendole riempire tutto lo spazio disponibile.
                    Box(
                        modifier = Modifier
                            .fillMaxSize()
                        
                    ){

                        CameraPreview(
                            controller = controller,
                            modifier = Modifier
                                .fillMaxSize()
                        )

                        IconButton(
                            onClick = {
                                controller.cameraSelector =
                                    if (controller.cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
                                        CameraSelector.DEFAULT_FRONT_CAMERA
                                    } else CameraSelector.DEFAULT_BACK_CAMERA

                            },
                            modifier = Modifier
                                .offset(16.dp, 16.dp)
                        ) {

                            Icon(imageVector = Icons.Default.Cameraswitch,
                                contentDescription = "Switch Camera"
                            )
                        }

                        IconButton(
                            onClick = {
                                setupImageAnalysis()
                                      },
                            modifier = Modifier
                                .align(Alignment.BottomCenter)
                        ) {

                            Icon(imageVector = Icons.Default.Videocam,
                                contentDescription = "Record Video")
                        }
                }
                    
                }
        }
    }



    private lateinit var cameraExecutor: ExecutorService


    @SuppressLint("RestrictedApi")
    private fun setupImageAnalysis() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Configura solo ImageAnalysis per l'elaborazione dei frame.
            val imageAnalysis = ImageAnalysis.Builder()
                .build().also {
                    it.setAnalyzer(cameraExecutor) { imageProxy ->
                        val buffer = imageProxy.planes[0].buffer
                        val data = ByteArray(buffer.remaining())
                        buffer.get(data)
                        val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)

                        // Qui potresti voler codificare il bitmap in un formato adatto per lo streaming.
                        // Questo potrebbe richiedere l'uso di MediaCodec per la codifica video in H.264 o simili.

                        // NOTA: Questo è un esempio teorico; la decodifica diretta dei byte di ImageProxy
                        // in un bitmap potrebbe non funzionare come previsto a causa del formato dell'immagine.
                        // Potresti dover convertire i dati YUV_420_888 (formato comune di ImageProxy) in RGB prima.

                        // Dopo la codifica, invia il frame codificato tramite RTP.


                        imageProxy.close()
                    }
                }

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                // Disaccoppia tutti i casi d'uso esistenti per evitare conflitti.
                cameraProvider.unbindAll()

                // Lega solo ImageAnalysis al ciclo di vita, senza la Preview qui.
                cameraProvider.bindToLifecycle(this, cameraSelector, imageAnalysis)
            } catch (exc: Exception) {
                Log.e(TAG, "Errore nel binding dei casi d'uso della fotocamera", exc)
            }
        }, ContextCompat.getMainExecutor(this))
    }




    /**
     * Controlla se tutte le autorizzazioni necessarie per CameraX sono state concesse.
     *
     * Utilizza [CAMERAX_PERMISSIONS] per elencare tutte le autorizzazioni richieste.
     * Itera su ciascuna autorizzazione e verifica se è stata concessa.
     *
     * @return true se tutte le autorizzazioni sono state concesse, altrimenti false.
     */
    private fun hasRequiredPermissions(): Boolean {
        return CAMERAX_PERMISSIONS.all {
            ContextCompat.checkSelfPermission(
                applicationContext,
                it
            ) == PackageManager.PERMISSION_GRANTED
        }
    }

    companion object {
        // Elenco delle autorizzazioni necessarie per l'uso di CameraX.
        private val CAMERAX_PERMISSIONS = arrayOf(
            Manifest.permission.CAMERA, // Autorizzazione per accedere alla fotocamera.
            Manifest.permission.RECORD_AUDIO, // Autorizzazione per registrare l'audio.
        )
    }
}



package com.android.example.streamingcamera

import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView

/**
 * Composable funzione per visualizzare un'anteprima della fotocamera.
 *
 * Utilizza il controller della fotocamera legato al ciclo di vita ([LifecycleCameraController])
 * per gestire automaticamente il ciclo di vita dell'anteprima della fotocamera in base al ciclo di vita
 * del composable in cui è inserito.
 *
 * @param controller Il controller della fotocamera che gestisce l'anteprima.
 * @param modifier [Modifier] per personalizzare il layout dell'anteprima della fotocamera.
 */

@Composable
fun CameraPreview(
    controller: LifecycleCameraController,
    modifier: Modifier = Modifier
) {

    // Ottiene l'istanza corrente di LifecycleOwner dal contesto di Compose.
    // Necessario per permettere al controller della fotocamera di legarsi
    // al ciclo di vita dell'UI composable.
    val lifecycleOwner = LocalLifecycleOwner.current

    // AndroidView è una funzione di Compose utilizzata per incorporare una view Android
    // tradizionale all'interno della UI dichiarativa di Compose.
    // Qui, viene usata per inserire PreviewView, che è il componente della UI Android
    // per visualizzare l'anteprima della fotocamera.
    AndroidView(
        factory = {
            PreviewView(it).apply {
                this.controller = controller
                controller.bindToLifecycle(lifecycleOwner)
            }
        },
        modifier = modifier
    )
    
}
0

There are 0 best solutions below