Не могу сохранить видео с водяным знаком
ХЕЛП! Очень долго искал ошибку и в итоге через дэбаг обнаружил "Object is being initialized", есть код:
import android.Manifest
import android.annotation.SuppressLint
import android.content.ContentValues.TAG
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.media.MediaPlayer
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.camera.core.CameraSelector
import androidx.camera.video.FileOutputOptions
import androidx.camera.video.Recording
import androidx.camera.video.VideoRecordEvent
import androidx.camera.view.CameraController
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.video.AudioConfig
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
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.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.daasuu.mp4compose.FillMode
import com.daasuu.mp4compose.composer.Mp4Composer
import com.daasuu.mp4compose.filter.GlWatermarkFilter
import ru.horekdev.uniade.R
import ru.horekdev.uniade.cameraPreview
import ru.horekdev.uniade.ui.theme.UniadeTheme
import java.io.File
class VideoRecordActivity : ComponentActivity() {
private var recording: Recording? = null
private val random = java.util.Random()
private lateinit var mediaPlayer: MediaPlayer
private val num = random.nextInt(100000)
private val file = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES
), "uniade$num.mp4"
)
private val textFile = File(file.parentFile!!, "${file.nameWithoutExtension}-t.mp4")
private val watermarkFile = File(textFile.parentFile!!, "${textFile.nameWithoutExtension}-w.mp4")
companion object {
private val CAMERAX_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
lateinit var userName: String
lateinit var userSurname: String
lateinit var userDateOfBirth: String
}
private fun hasRequiredPermissionsCamera(): Boolean {
return CAMERAX_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
applicationContext,
it
) == PackageManager.PERMISSION_GRANTED
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityCompat.requestPermissions(
this, arrayOf(arrayOf(CAMERAX_PERMISSIONS).toString()), 0
)
setContent {
UniadeTheme { menu() }
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun menu() {
val scaffoldState = rememberBottomSheetScaffoldState()
val controller = remember {
LifecycleCameraController(applicationContext).apply {
setEnabledUseCases(CameraController.VIDEO_CAPTURE)
}
}
BottomSheetScaffold(
scaffoldState = scaffoldState,
sheetPeekHeight = 0.dp,
sheetContent = {
}) { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
cameraPreview(controller = controller, modifier = Modifier.fillMaxSize())
IconButton(modifier = Modifier.offset(20.dp, 20.dp),
onClick = {
controller.cameraSelector =
if (controller.cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
CameraSelector.DEFAULT_FRONT_CAMERA
} else {
CameraSelector.DEFAULT_BACK_CAMERA
}
}) {
Image(
modifier = Modifier
.height(30.dp)
.width(30.dp),
painter = painterResource(id = R.drawable.switch_camera),
contentDescription = "switch camera"
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceAround
) {
IconButton(onClick = { onRecordVideo(controller) }) {
Image(
modifier = Modifier
.height(30.dp)
.width(30.dp),
painter = painterResource(id = R.drawable.video_camera),
contentDescription = "record video"
)
}
}
}
}
}
@SuppressLint("MissingPermission")
private fun onRecordVideo(controller: LifecycleCameraController) {
if (recording != null) {
recording?.stop()
recording = null
return
}
if (!hasRequiredPermissionsCamera()) {
closeContextMenu()
Toast.makeText(
applicationContext,
getString(R.string.your_don_t_accept_camera_permissions), Toast.LENGTH_LONG
).show()
return
}
mediaPlayer = MediaPlayer.create(applicationContext, R.raw.phone_video_record)
mediaPlayer.setVolume(0.1f, 0.1f)
mediaPlayer.start()
recording = controller.startRecording(
FileOutputOptions.Builder(file).build(),
AudioConfig.create(true),
ContextCompat.getMainExecutor(applicationContext),
) { event ->
when (event) {
is VideoRecordEvent.Finalize -> {
if (event.hasError()) {
recording?.stop()
recording = null
println(event.cause)
Toast.makeText(
applicationContext,
getString(R.string.toast8), Toast.LENGTH_LONG
).show()
} else {
addText(file)
}
}
}
}
}
private fun textToBitmap(text: String): Bitmap {
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.textSize = 24.0f
paint.color = Color.WHITE
paint.textAlign = Paint.Align.LEFT
val baseline = -paint.ascent()
val width = paint.measureText(text) + 0.5f
val height = baseline + paint.descent() + 0.5f
val image = Bitmap.createBitmap(width.toInt(), height.toInt(),
Bitmap.Config.ARGB_8888)
val canvas = Canvas(image)
canvas.drawText(text, 0.0f, baseline, paint)
return image
}
private fun addText(file: File) {
Mp4Composer(file.absolutePath, textFile.absolutePath)
.fillMode(FillMode.PRESERVE_ASPECT_FIT)
.filter(
GlWatermarkFilter(
textToBitmap("$userSurname $userName / $userDateOfBirth"),
GlWatermarkFilter.Position.RIGHT_BOTTOM
)
)
.listener(object : Mp4Composer.Listener {
override fun onProgress(progress: Double) {
Log.d(TAG, "onProgress = $progress")
}
override fun onCurrentWrittenVideoTime(timeUs: Long) {}
override fun onCompleted() {
Log.d(TAG, "onCompleted()")
file.delete()
}
override fun onCanceled() {
Log.d(TAG, "onCanceled")
}
override fun onFailed(exception: Exception) {
Log.e(TAG, "onFailed()", exception)
}
}).start()
addWatermark(textFile)
}
private fun addWatermark(textFile: File) {
Mp4Composer(textFile.absolutePath, watermarkFile.absolutePath)
.fillMode(FillMode.PRESERVE_ASPECT_FIT)
.filter(
GlWatermarkFilter(
BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.logo_with_watermark
), GlWatermarkFilter.Position.LEFT_BOTTOM
)
)
.listener(object : Mp4Composer.Listener {
override fun onProgress(progress: Double) {
Log.d(TAG, "onProgress = $progress")
}
override fun onCurrentWrittenVideoTime(timeUs: Long) {}
override fun onCompleted() {
Log.d(TAG, "onCompleted()")
textFile.delete()
Toast.makeText(
applicationContext,
getString(R.string.toast9), Toast.LENGTH_LONG
).show()
}
override fun onCanceled() {
Log.d(TAG, "onCanceled")
}
override fun onFailed(exception: Exception) {
Log.e(TAG, "onFailed()", exception)
}
}).start()
}
}