我正在编写一个应用程序来在预定义的时间录制视频。 (注意这个应用程序仅供个人使用。我会把手机放在一个偏远的地方,它应该每天在预定的时间录制一个 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>