package com.aivan.camerax
import android.Manifest
import android.annotation.SuppressLint
import android.content.ContentValues
import android.os.Build
import android.os.Bundle
import android.os.CountDownTimer
import android.provider.MediaStore
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.camera.core.AspectRatio
import androidx.camera.core.CameraSelector
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.MediaStoreOutputOptions
import androidx.camera.video.Quality
import androidx.camera.video.QualitySelector
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.camera.video.VideoRecordEvent
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.core.util.Consumer
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.work.await
import com.aivan.camerax.base.BaseActivity
import com.aivan.camerax.databinding.ActivityCamaraxBinding
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.ToastUtils
import com.permissionx.guolindev.PermissionX
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.launch
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
class CameraxActivity: BaseActivity<ActivityCamaraxBinding>(ActivityCamaraxBinding::inflate) {
companion object {
val TAG:String = CameraxActivity::class.java.simpleName
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
}
private var inProgress = false
private val totalTime = 6000L
private var videoPath : String? = null
private var videoArray: ByteArray? = null
private var currentRecording: Recording? = null
private var countDownTimer : CountDownTimer? =null
private var recordingState: VideoRecordEvent? =null
private var enumerationDeferred: Deferred<Unit>? = null
private var videoCapture: VideoCapture<Recorder>? = null
private val mainThreadExecutor by lazy { ContextCompat.getMainExecutor(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
permissionCheck{
initCamera()
}
binding.startButton.setOnClickListener {
permissionCheck{
initStartRecording()
}
}
printWebVersion()
}
private fun printWebVersion(){
packageManager.getInstalledPackages(0)?.forEach {
if("com.android.webview".equals(it.packageName)){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
LogUtils.i("packageName ${it.packageName} versionCode ${it.versionCode} versionName ${it.versionName} longVersionCode ${it.longVersionCode}")
}else{
LogUtils.i("packageName ${it.packageName} versionCode ${it.versionCode} versionName ${it.versionName}")
}
}
if("com.google.android.webview".equals(it.packageName)){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
LogUtils.i("packageName ${it.packageName} versionCode ${it.versionCode} versionName ${it.versionName} longVersionCode ${it.longVersionCode}")
}else{
LogUtils.i("packageName ${it.packageName} versionCode ${it.versionCode} versionName ${it.versionName}")
}
}
}
}
private fun permissionCheck(block:()-> Unit){
val requestList = ArrayList<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestList.add(Manifest.permission.READ_MEDIA_IMAGES)
requestList.add(Manifest.permission.READ_MEDIA_AUDIO)
requestList.add(Manifest.permission.READ_MEDIA_VIDEO)
requestList.add(Manifest.permission.RECORD_AUDIO)
requestList.add(Manifest.permission.CAMERA)
}else{
requestList.add(Manifest.permission.CAMERA)
requestList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
requestList.add(Manifest.permission.READ_EXTERNAL_STORAGE)
requestList.add(Manifest.permission.RECORD_AUDIO)
}
PermissionX.init(this).permissions(requestList)
.request{ allGranted, deniedList, _ ->
if(allGranted){
block()
}
else{
ToastUtils.showLong("你拒绝对应权限")
}
}
}
private fun initCamera(){
setUpCamera(binding.previewView)
}
private fun setUpCamera(previewView: PreviewView) {
lifecycleScope.launch {
if (enumerationDeferred != null) {
enumerationDeferred!!.await()
enumerationDeferred = null
}
bindCaptureUseCase()
}
}
@SuppressLint("RestrictedApi")
private suspend fun bindCaptureUseCase() {
val cameraProvider = ProcessCameraProvider.getInstance(this).await()
val quality = Quality.SD
val qualitySelector = QualitySelector.from(quality)
val preview = Preview.Builder().setTargetAspectRatio(quality.getAspectRatio(quality)).build().apply {
setSurfaceProvider(binding.previewView.surfaceProvider)
}
val recorder = Recorder.Builder().setQualitySelector(qualitySelector).build()
videoCapture = VideoCapture.withOutput(recorder)
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, videoCapture, preview)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}
private fun initStartRecording(){
if(inProgress){
ToastUtils.showLong("视频录制中")
return
}
inProgress = true
starTimer()
}
private fun updateUI(event: VideoRecordEvent) {
val state = if (event is VideoRecordEvent.Status) recordingState?.getNameString()
else event.getNameString()
when (event) {
is VideoRecordEvent.Status -> {
}
is VideoRecordEvent.Start -> {
}
is VideoRecordEvent.Finalize-> {
}
is VideoRecordEvent.Pause -> {
}
is VideoRecordEvent.Resume -> {
}
}
}
private val captureListener = Consumer<VideoRecordEvent> { event ->
if (event !is VideoRecordEvent.Status)
recordingState = event
updateUI(event)
if (event is VideoRecordEvent.Finalize) {
val proj = arrayOf(MediaStore.Images.Media.DATA)
val actualImageCursor = managedQuery(event.outputResults.outputUri, proj, null, null, null)
val actualImageColumnIndex = actualImageCursor?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
actualImageCursor?.moveToFirst()
videoPath = actualImageColumnIndex?.let { actualImageCursor.getString(it) }.toString();
Log.i("TAG", "视频路径: $videoPath");
Log.i("TAG", "视频保存成功: ${event.outputResults.outputUri}")
if(videoPath!=null&& !videoPath.equals("null")){
binding.previewView.isVisible = false
binding.realNameVidePlayView.isVisible = true
videoArray = File(videoPath).readBytes()
permissionCheck{
startVideo()
}
}
}
}
private fun startVideo(){
binding.realNameVidePlayView.setVideoPath(videoPath)
binding.realNameVidePlayView.start()
}
private fun starTimer(){
binding.previewView.isVisible = true
binding.realNameVidePlayView.isVisible = false
countDownTimer=object : CountDownTimer(totalTime,1000){
@SuppressLint("RestrictedApi")
override fun onFinish() {
Log.d(TAG,"==倒计时结束")
currentRecording?.stop()
inProgress = false
binding.startButton.text = "重新开始"
}
@SuppressLint("SetTextI18n")
override fun onTick(millisUntilFinished: Long) {
val time = millisUntilFinished/1000
binding.startButton.text = "请在${time}S内完成识别"
}
}.start()
startRecording()
}
@SuppressLint("MissingPermission")
private fun startRecording(){
val name = "CameraX-recording-" + SimpleDateFormat(FILENAME_FORMAT, Locale.CHINA).format(System.currentTimeMillis()) + ".mp4"
val contentValues = ContentValues().apply {
put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI).setContentValues(contentValues).build()
currentRecording = videoCapture?.output?.prepareRecording(this, mediaStoreOutput)?.apply { withAudioEnabled() }
?.start(mainThreadExecutor, captureListener)
Log.i(TAG, "Recording started")
}
}
fun Quality.getAspectRatio(quality: Quality): Int {
return when {
arrayOf(Quality.UHD, Quality.FHD, Quality.HD)
.contains(quality) -> AspectRatio.RATIO_16_9
(quality == Quality.SD) -> AspectRatio.RATIO_4_3
else -> throw UnsupportedOperationException()
}
}
fun VideoRecordEvent.getNameString() : String {
return when (this) {
is VideoRecordEvent.Status -> "Status"
is VideoRecordEvent.Start -> "Started"
is VideoRecordEvent.Finalize-> "Finalized"
is VideoRecordEvent.Pause -> "Paused"
is VideoRecordEvent.Resume -> "Resumed"
else -> throw IllegalArgumentException("Unknown VideoRecordEvent: $this")
}
}
github.com/AivanUy/Cam…
github.com/android/cam…
juejin.cn/post/725927…