2 years ago

#64927

test-img

confusedstudent

CameraX not launching on physical device and emulator

I'm currently using the cameraX sample code to record a video with audio . I didn't change anything from the samples but the camera simply won't launch. I have a swipe button in my fragment that is supposed to take me to the videoCaptureFragment but all I get are some logs and the fragments don't change. No errors or warnings , the app doesn't crash but it doesn't load the camera view. here's my videoCaptureFragment:

class VideoCaptureFragment : Fragment() {

// UI with ViewBinding
private var _captureViewBinding: VideoCaptureFragmentBinding? = null
private val captureViewBinding get() = _captureViewBinding!!
private val captureLiveStatus = MutableLiveData<String>()

private val cameraCapabilities = mutableListOf<CameraCapability>()

private lateinit var videoCapture: VideoCapture<Recorder>
private var currentRecording: Recording? = null
private lateinit var recordingState:VideoRecordEvent

// Camera UI  states and inputs
enum class UiState {
    IDLE,       // Not recording, all UI controls are active.
    RECORDING,  // Camera is recording, only display Pause/Resume & Stop button.
    FINALIZED,  // Recording just completes, disable all RECORDING UI controls.
    RECOVERY    // For future use.
}
private var cameraIndex = 0
private var qualityIndex = DEFAULT_QUALITY_IDX
private var audioEnabled = false

private val mainThreadExecutor by lazy { ContextCompat.getMainExecutor(requireContext()) }
private var enumerationDeferred:Deferred<Unit>? = null

// main cameraX capture functions
/**
 *   Always bind preview + video capture use case combinations in this sample
 *   (VideoCapture can work on its own). The function should always execute on
 *   the main thread.
 */
private suspend fun bindCaptureUsecase() {
    val cameraProvider = ProcessCameraProvider.getInstance(requireContext()).await()

    val cameraSelector = getCameraSelector(cameraIndex)

    // create the user required QualitySelector (video resolution): we know this is
    // supported, a valid qualitySelector will be created.
    val quality = cameraCapabilities[cameraIndex].qualities[qualityIndex]
    val qualitySelector = QualitySelector.from(quality)

    captureViewBinding.previewView.updateLayoutParams<ConstraintLayout.LayoutParams> {
        val orientation = this@RecordingInterviewFragment.resources.configuration.orientation
        dimensionRatio = quality.getAspectRatioString(quality,
            (orientation == Configuration.ORIENTATION_PORTRAIT))
    }

    val preview = Preview.Builder()
        .setTargetAspectRatio(quality.getAspectRatio(quality))
        .build().apply {
            setSurfaceProvider(captureViewBinding.previewView.surfaceProvider)
        }

    // build a recorder, which can:
    //   - record video/audio to MediaStore(only shown here), File, ParcelFileDescriptor
    //   - be used create recording(s) (the recording performs recording)
    val recorder = Recorder.Builder()
        .setQualitySelector(qualitySelector)
        .build()
    videoCapture = VideoCapture.withOutput(recorder)

    try {
        cameraProvider.unbindAll()
        cameraProvider.bindToLifecycle(
            viewLifecycleOwner,
            cameraSelector,
            videoCapture,
            preview
        )
    } catch (exc: Exception) {
        // we are on main thread, let's reset the controls on the UI.
        Log.e(TAG, "Use case binding failed", exc)
        resetUIandState("bindToLifecycle failed: $exc")
    }
    enableUI(true)
}

/**
 * Kick start the video recording
 *   - config Recorder to capture to MediaStoreOutput
 *   - register RecordEvent Listener
 *   - apply audio request from user
 *   - start recording!
 * After this function, user could start/pause/resume/stop recording and application listens
 * to VideoRecordEvent for the current recording status.
 */
@SuppressLint("MissingPermission")
private fun startRecording() {
    // create MediaStoreOutputOptions for our recorder: resulting our recording!
    val name = "CameraX-recording-" +
            SimpleDateFormat(FILENAME_FORMAT, Locale.US)
                .format(System.currentTimeMillis()) + ".mp4"
    val contentValues = ContentValues().apply {
        put(MediaStore.Video.Media.DISPLAY_NAME, name)
    }
    val mediaStoreOutput = MediaStoreOutputOptions.Builder(
        requireActivity().contentResolver,
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
        .setContentValues(contentValues)
        .build()

    // configure Recorder and Start recording to the mediaStoreOutput.
    currentRecording = videoCapture.output
        .prepareRecording(requireActivity(), mediaStoreOutput)
        .apply { if (audioEnabled) withAudioEnabled() }
        .start(mainThreadExecutor, captureListener)

    Log.i(TAG, "Recording started")
}

/**
 * CaptureEvent listener.
 */
private val captureListener = Consumer<VideoRecordEvent> { event ->
    // cache the recording state
    if (event !is VideoRecordEvent.Status)
        recordingState = event

    updateUI(event)

    if (event is VideoRecordEvent.Finalize) {
        // display the captured video
        lifecycleScope.launch {
            /*navController.navigate(
                CaptureFragmentDirections.actionCaptureToVideoViewer(
                    event.outputResults.outputUri
                )
            )*/
        }
    }
}

/**
 * Retrieve the asked camera's type(lens facing type). In this sample, only 2 types:
 *   idx is even number:  CameraSelector.LENS_FACING_BACK
 *          odd number:   CameraSelector.LENS_FACING_FRONT
 */
private fun getCameraSelector(idx: Int) : CameraSelector {
    if (cameraCapabilities.size == 0) {
        Log.i(TAG, "Error: This device does not have any camera, bailing out")
        requireActivity().finish()
    }
    return (cameraCapabilities[idx % cameraCapabilities.size].camSelector)
}

data class CameraCapability(val camSelector: CameraSelector, val qualities:List<Quality>)
/**
 * Query and cache this platform's camera capabilities, run only once.
 */
init {
    enumerationDeferred = lifecycleScope.async {
        whenCreated {
            val provider = ProcessCameraProvider.getInstance(requireContext()).await()

            provider.unbindAll()
            for (camSelector in arrayOf(
                CameraSelector.DEFAULT_BACK_CAMERA,
                CameraSelector.DEFAULT_FRONT_CAMERA
            )) {
                try {
                    // just get the camera.cameraInfo to query capabilities
                    // we are not binding anything here.
                    if (provider.hasCamera(camSelector)) {
                        val camera = provider.bindToLifecycle(requireActivity(), camSelector)
                        QualitySelector
                            .getSupportedQualities(camera.cameraInfo)
                            .filter { quality ->
                                listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD)
                                    .contains(quality)
                            }.also {
                                cameraCapabilities.add(CameraCapability(camSelector, it))
                            }
                    }
                } catch (exc: java.lang.Exception) {
                    Log.e(TAG, "Camera Face $camSelector is not supported")
                }
            }
        }
    }
}

/**
 * One time initialize for CameraFragment (as a part of fragment layout's creation process).
 * This function performs the following:
 *   - initialize but disable all UI controls except the Quality selection.
 *   - set up the Quality selection recycler view.
 *   - bind use cases to a lifecycle camera, enable UI controls.
 */
private fun initCameraFragment() {
    initializeUI()
    Log.i("Camerx","insisde Init Camera")
    viewLifecycleOwner.lifecycleScope.launch {
        if (enumerationDeferred != null) {
            enumerationDeferred!!.await()
            enumerationDeferred = null
        }
        initializeQualitySectionsUI()

        bindCaptureUsecase()
    }
}

/**
 * Initialize UI. Preview and Capture actions are configured in this function.
 * Note that preview and capture are both initialized either by UI or CameraX callbacks
 * (except the very 1st time upon entering to this fragment in onCreateView()
 */
@SuppressLint("ClickableViewAccessibility", "MissingPermission")
private fun initializeUI() {
    captureViewBinding.cameraButton.apply {
        setOnClickListener {
            cameraIndex = (cameraIndex + 1) % cameraCapabilities.size
            // camera device change is in effect instantly:
            //   - reset quality selection
            //   - restart preview
            qualityIndex = DEFAULT_QUALITY_IDX
            initializeQualitySectionsUI()
            enableUI(false)
            viewLifecycleOwner.lifecycleScope.launch {
                bindCaptureUsecase()
            }
        }
        isEnabled = false
    }

    // audioEnabled by default is disabled.
    captureViewBinding.audioSelection.isChecked = audioEnabled
    captureViewBinding.audioSelection.setOnClickListener {
        audioEnabled = captureViewBinding.audioSelection.isChecked
    }
    Log.i("Camerx","inside init UI")
    // React to user touching the capture button
    captureViewBinding.captureButton.apply {
        setOnClickListener {
            if (!this@RecordingInterviewFragment::recordingState.isInitialized ||
                recordingState is VideoRecordEvent.Finalize)
            {
                enableUI(false)  // Our eventListener will turn on the Recording UI.
                startRecording()
            } else {
                when (recordingState) {
                    is VideoRecordEvent.Start -> {
                        currentRecording?.pause()
                        captureViewBinding.stopButton.visibility = View.VISIBLE
                    }
                    is VideoRecordEvent.Pause -> currentRecording?.resume()
                    is VideoRecordEvent.Resume -> currentRecording?.pause()
                    else -> throw IllegalStateException("recordingState in unknown state")
                }
            }
        }
        isEnabled = false
    }

    captureViewBinding.stopButton.apply {
        setOnClickListener {
            // stopping: hide it after getting a click before we go to viewing fragment
            captureViewBinding.stopButton.visibility = View.INVISIBLE
            if (currentRecording == null || recordingState is VideoRecordEvent.Finalize) {
                return@setOnClickListener
            }

            val recording = currentRecording
            if (recording != null) {
                recording.stop()
                currentRecording = null
            }
            captureViewBinding.captureButton.setImageResource(R.drawable.ic_start)
        }
        // ensure the stop button is initialized disabled & invisible
        visibility = View.INVISIBLE
        isEnabled = false
    }

    captureLiveStatus.observe(viewLifecycleOwner) {
        captureViewBinding.captureStatus.apply {
            post { text = it }
        }
    }
    captureLiveStatus.value = getString(R.string.Idle)
}

/**
 * UpdateUI according to CameraX VideoRecordEvent type:
 *   - user starts capture.
 *   - this app disables all UI selections.
 *   - this app enables capture run-time UI (pause/resume/stop).
 *   - user controls recording with run-time UI, eventually tap "stop" to end.
 *   - this app informs CameraX recording to stop with recording.stop() (or recording.close()).
 *   - CameraX notify this app that the recording is indeed stopped, with the Finalize event.
 *   - this app starts VideoViewer fragment to view the captured result.
 */
private fun updateUI(event: VideoRecordEvent) {
    val state = if (event is VideoRecordEvent.Status) recordingState.getNameString()
    else event.getNameString()
    when (event) {
        is VideoRecordEvent.Status -> {
            // placeholder: we update the UI with new status after this when() block,
            // nothing needs to do here.
            Log.i("Camerx","you're idk tbh")
        }
        is VideoRecordEvent.Start -> {
            showUI(UiState.RECORDING, event.getNameString())
            Log.i("Camerx","you're recording")
        }
        is VideoRecordEvent.Finalize-> {
            showUI(UiState.FINALIZED, event.getNameString())
            Log.i("Camerx","you're done, you're done")
        }
        is VideoRecordEvent.Pause -> {
            captureViewBinding.captureButton.setImageResource(R.drawable.ic_resume)
            Log.i("Camerx","you're paused")
        }
        is VideoRecordEvent.Resume -> {
            captureViewBinding.captureButton.setImageResource(R.drawable.ic_pause)
            Log.i("Camerx","you're resumed")
        }
    }

    val stats = event.recordingStats
    val size = stats.numBytesRecorded / 1000
    val time = java.util.concurrent.TimeUnit.NANOSECONDS.toSeconds(stats.recordedDurationNanos)
    var text = "${state}: recorded ${size}KB, in ${time}second"
    if(event is VideoRecordEvent.Finalize)
        text = "${text}\nFile saved to: ${event.outputResults.outputUri}"

    captureLiveStatus.value = text
    Log.i(TAG, "recording event: $text")
}

/**
 * Enable/disable UI:
 *    User could select the capture parameters when recording is not in session
 *    Once recording is started, need to disable able UI to avoid conflict.
 */
private fun enableUI(enable: Boolean) {

    Log.i("Camerx","insisde enableuia")
    arrayOf(captureViewBinding.cameraButton,
        captureViewBinding.captureButton,
        captureViewBinding.stopButton,
        captureViewBinding.audioSelection,
        captureViewBinding.qualitySelection).forEach {
        it.isEnabled = enable
    }
    // disable the camera button if no device to switch
    if (cameraCapabilities.size <= 1) {
        captureViewBinding.cameraButton.isEnabled = false
    }
    // disable the resolution list if no resolution to switch
    if (cameraCapabilities[cameraIndex].qualities.size <= 1) {
        captureViewBinding.qualitySelection.apply { isEnabled = false }
    }
}

/**
 * initialize UI for recording:
 *  - at recording: hide audio, qualitySelection,change camera UI; enable stop button
 *  - otherwise: show all except the stop button
 */
private fun showUI(state: UiState, status:String = "idle") {
    Log.i("Camerx","insisde show ui")
    captureViewBinding.let {
        when(state) {
            UiState.IDLE -> {
                it.captureButton.setImageResource(R.drawable.ic_start)
                it.stopButton.visibility = View.INVISIBLE

                it.cameraButton.visibility= View.VISIBLE
                it.audioSelection.visibility = View.VISIBLE
                it.qualitySelection.visibility=View.VISIBLE
            }
            UiState.RECORDING -> {
                it.cameraButton.visibility = View.INVISIBLE
                it.audioSelection.visibility = View.INVISIBLE
                it.qualitySelection.visibility = View.INVISIBLE

                it.captureButton.setImageResource(R.drawable.ic_pause)
                it.captureButton.isEnabled = true
                it.stopButton.visibility = View.VISIBLE
                it.stopButton.isEnabled = true
            }
            UiState.FINALIZED -> {
                it.captureButton.setImageResource(R.drawable.ic_start)
                it.stopButton.visibility = View.INVISIBLE
            }
            else -> {
                val errorMsg = "Error: showUI($state) is not supported"
                Log.e(TAG, errorMsg)
                return
            }
        }
        it.captureStatus.text = status
    }
}



override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    initCameraFragment()
}
override fun onDestroyView() {
    _captureViewBinding = null
    super.onDestroyView()
}

companion object {
    // default Quality selection if no input from UI
    const val DEFAULT_QUALITY_IDX = 0
    val TAG:String = RecordingInterviewFragment::class.java.simpleName
    private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
}

Thank you

android

android-camera

android-camerax

0 Answers

Your Answer

Accepted video resources