如何使用 Kotlin 在 Android 中的 Worker 上捕获一个小时的视频?

问题描述 投票:0回答:0

我正在编写一个应用程序来在预定义的时间录制视频。 (注意这个应用程序仅供个人使用。我会把手机放在一个偏远的地方,它应该每天在预定的时间录制一个 1 小时的视频)记录计划使用一个工作人员来确保任务被执行,即使设备在睡觉。

我实现了 Worker,它在定义的时间执行得很好。但是,我在此线程中录制视频时遇到问题。我想使用 CameraX API,但这需要一个我不知道如何在 Worker 类中访问的生命周期对象。这就是我现在使用 MediaRecorder 使用 camera2 API 的原因。但我在 recorder.start() 上收到 -22 错误。有谁知道如何修复它或可以推荐更好的方法?任何帮助将不胜感激!

这是我的 MainActivity.kt 中的代码

package com.android.example.workerrecordingscheduler

import android.Manifest
import android.content.ContentValues.TAG
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.work.*

import com.android.example.workerrecordingscheduler.databinding.ActivityMainBinding
import java.util.*
import java.util.concurrent.TimeUnit


class MainActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding
    private val eventTimes = listOf(
        Calendar.getInstance().apply {
            set(Calendar.YEAR, 2023)
            set(Calendar.MONTH, Calendar.MARCH)
            set(Calendar.DAY_OF_MONTH, 17)
            set(Calendar.HOUR_OF_DAY, 19)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
        }
//        Calendar.getInstance().apply {
//            set(Calendar.YEAR, 2023)
//            set(Calendar.MONTH, Calendar.MARCH)
//            set(Calendar.DAY_OF_MONTH, 10)
//            set(Calendar.HOUR_OF_DAY, 18)
//            set(Calendar.MINUTE, 20)
//            set(Calendar.SECOND, 0)
//        },
//        Calendar.getInstance().apply {
//            set(Calendar.YEAR, 2023)
//            set(Calendar.MONTH, Calendar.MARCH)
//            set(Calendar.DAY_OF_MONTH, 10)
//            set(Calendar.HOUR_OF_DAY, 18)
//            set(Calendar.MINUTE, 30)
//            set(Calendar.SECOND, 0)
//        },
//        Calendar.getInstance().apply {
//            set(Calendar.YEAR, 2023)
//            set(Calendar.MONTH, Calendar.MAY)
//            set(Calendar.DAY_OF_MONTH, 10)
//            set(Calendar.HOUR_OF_DAY, 18)
//            set(Calendar.MINUTE, 50)
//            set(Calendar.SECOND, 0)
//        }
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Request camera permissions
        if (!allPermissionsGranted()) {
            ActivityCompat.requestPermissions(
                this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }

        // Permission variables
        val hasStoragePermission = ContextCompat.checkSelfPermission(
            applicationContext,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
        )
        val hasCameraPermission = ContextCompat.checkSelfPermission(
            applicationContext,
            Manifest.permission.CAMERA
        )
        val hasMicPermission = ContextCompat.checkSelfPermission(
            applicationContext,
            Manifest.permission.RECORD_AUDIO
        )
        val hasReadStoragePermission = ContextCompat.checkSelfPermission(
            applicationContext,
            Manifest.permission.READ_EXTERNAL_STORAGE)


        if (hasStoragePermission == PackageManager.PERMISSION_GRANTED &&
            hasCameraPermission == PackageManager.PERMISSION_GRANTED &&
            hasMicPermission == PackageManager.PERMISSION_GRANTED &&
            hasReadStoragePermission == PackageManager.PERMISSION_GRANTED) {
            // Permissions are granted, start the worker
            Log.d(TAG, "Permissions all granted")

        } else {
            // Permissions are not granted, request them
            ActivityCompat.requestPermissions(
                this,
                arrayOf(
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.CAMERA,
                    Manifest.permission.RECORD_AUDIO,
                    Manifest.permission.READ_EXTERNAL_STORAGE
                ),
                REQUEST_CODE_PERMISSIONS
            )
        }


        viewBinding.startButton.setOnClickListener(){myWork()}
    }

    private fun myWork() {
        Log.d(TAG, "Lets start")
        val lifecycleOwner = this
        val workManager = WorkManager.getInstance(applicationContext)
        workManager.cancelAllWork()

        for (eventTime in eventTimes) {
            val now = Calendar.getInstance()
            if (now.before(eventTime)) {
                val delay = eventTime.timeInMillis - now.timeInMillis

                val data = Data.Builder()
                    .putLong("eventTime", eventTime.timeInMillis)
                    .build()


                val workRequest = OneTimeWorkRequest.Builder(RecordingWorker::class.java)
                    .setInputData(data)
                    .setInitialDelay(delay, TimeUnit.MILLISECONDS)
                    .build()

                val uniqueWorkName = "record_${eventTime.timeInMillis}"

                workManager.enqueueUniqueWork(
                    uniqueWorkName,
                    ExistingWorkPolicy.REPLACE,  // Replace previous work request with the same name
                    workRequest

                )
            }
        }
    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(
            baseContext, it) == PackageManager.PERMISSION_GRANTED
    }

    companion object {
        private const val TAG = "CameraXApp"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS =
            mutableListOf (
                Manifest.permission.CAMERA,
            ).apply {
            }.toTypedArray()
    }
}

我的 Worker 长这样(我知道视频还没录一个小时):

package com.android.example.workerrecordingscheduler

import android.Manifest
import android.content.ContentValues.TAG
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.ImageFormat
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.media.MediaRecorder
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import java.io.File
import java.lang.Thread.sleep


class RecordingWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {

    override fun doWork(): Result {

        // Put the camera operation on a separate thread
        val handlerThread = HandlerThread("CameraHandlerThread")
        handlerThread.start()
        val handler = Handler(handlerThread.looper)

        // Enable camera manager
        val cameraManager = applicationContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager

        // Get the list of available cameras
        val cameraIds = cameraManager.cameraIdList

        // Select the back camera (or front camera if there is no back camera)
        var cameraId = cameraIds[0]

        // Check if all required permissions are granted
        if (ActivityCompat.checkSelfPermission(
                applicationContext,
                Manifest.permission.CAMERA
            ) != PackageManager.PERMISSION_GRANTED ||
            ActivityCompat.checkSelfPermission(
                applicationContext,
                Manifest.permission.RECORD_AUDIO
            ) != PackageManager.PERMISSION_GRANTED ||
            ActivityCompat.checkSelfPermission(
                applicationContext,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            Log.d(TAG, "Permissions granted")
        }

        cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
                @RequiresApi(Build.VERSION_CODES.S)
                override fun onOpened(camera: CameraDevice) {
                    Log.d(TAG, "Camera is opened")

                    //val builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).build()

                    // Get the directory for the user's public videos
                    val outputDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
                    // Create a new file in the directory with a unique name
                    val outputFile = File(outputDir, "myvideo_${System.currentTimeMillis()}.mp4")

                    camera.close()  // close to make sure the recorder doesn't crash because the camera is opened

                    val recorder = MediaRecorder(applicationContext)
                    recorder.setAudioSource(MediaRecorder.AudioSource.MIC)
                    recorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT)
                    recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
                    recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
                    recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
                    recorder.setOutputFile(outputFile.absolutePath)
                    recorder.prepare()
                    recorder.setOnErrorListener {_, what, extra ->
                        Log.e(TAG, "MediaRecorder error: what=$what, extra=$extra")
                    }

                    recorder.start()
                    recorder.stop()
                }

                override fun onDisconnected(camera: CameraDevice) {
                    Log.d(TAG, "Camera is disconnected")
                }

                override fun onError(camera: CameraDevice, p1: Int) {
                    Log.d(TAG, "Camera has an error")
                    camera.close() // close the camera device on error
                }
            }, handler)
        return Result.success()
    }

这是我的 AndroidManifest.xml:

<?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-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.WorkerRecordingScheduler"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
android kotlin android-camera2 android-workmanager video-recording
© www.soinside.com 2019 - 2024. All rights reserved.