PixelDroid-App-Android/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt

274 lines
9.9 KiB
Kotlin
Raw Normal View History

2022-06-10 23:41:29 +02:00
package org.pixeldroid.app.postCreation.photoEdit
2022-06-18 22:21:19 +02:00
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.media.AudioManager
2022-06-10 23:41:29 +02:00
import android.net.Uri
import android.os.Bundle
2022-06-18 22:21:19 +02:00
import android.os.Handler
import android.os.Looper
import android.text.format.DateUtils
2022-06-10 23:41:29 +02:00
import android.util.Log
2022-06-18 22:21:19 +02:00
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
2022-06-10 23:41:29 +02:00
import androidx.core.net.toUri
2022-06-18 22:21:19 +02:00
import androidx.core.os.HandlerCompat
import androidx.media.AudioAttributesCompat
import androidx.media2.common.MediaMetadata
import androidx.media2.common.UriMediaItem
import androidx.media2.player.MediaPlayer
2022-06-10 23:41:29 +02:00
import com.arthenica.ffmpegkit.*
import com.bumptech.glide.Glide
2022-06-18 22:21:19 +02:00
import com.google.android.material.slider.RangeSlider
import org.pixeldroid.app.R
2022-06-10 23:41:29 +02:00
import org.pixeldroid.app.databinding.ActivityVideoEditBinding
2022-06-18 22:21:19 +02:00
import org.pixeldroid.app.postCreation.PostCreationActivity
import org.pixeldroid.app.postCreation.carousel.dpToPx
2022-07-10 13:42:19 +02:00
import org.pixeldroid.app.utils.BaseThemedWithBarActivity
2022-08-21 00:31:36 +02:00
import org.pixeldroid.app.utils.ffmpegCompliantUri
2022-06-10 23:41:29 +02:00
import java.io.File
2022-07-10 13:42:19 +02:00
class VideoEditActivity : BaseThemedWithBarActivity() {
2022-06-18 22:21:19 +02:00
private lateinit var mediaPlayer: MediaPlayer
private var videoPosition: Int = -1
private lateinit var binding: ActivityVideoEditBinding
// Map photoData indexes to FFmpeg Session IDs
private val sessionList: ArrayList<Long> = arrayListOf()
private val tempFiles: ArrayList<File> = ArrayList()
2022-06-10 23:41:29 +02:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
2022-06-18 22:21:19 +02:00
binding = ActivityVideoEditBinding.inflate(layoutInflater)
2022-06-10 23:41:29 +02:00
setContentView(binding.root)
2022-06-18 22:21:19 +02:00
supportActionBar?.setTitle(R.string.toolbar_title_edit)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
binding.videoRangeSeekBar.setCustomThumbDrawablesForValues(R.drawable.thumb_left,R.drawable.double_circle,R.drawable.thumb_right)
binding.videoRangeSeekBar.thumbRadius = 20.dpToPx(this)
val resultHandler: Handler = HandlerCompat.createAsync(Looper.getMainLooper())
val uri = intent.getParcelableExtra<Uri>(PhotoEditActivity.PICTURE_URI)!!
videoPosition = intent.getIntExtra(PhotoEditActivity.PICTURE_POSITION, -1)
2022-08-21 00:31:36 +02:00
val inputVideoPath = ffmpegCompliantUri(uri)
2022-06-10 23:41:29 +02:00
val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(inputVideoPath).mediaInformation
2022-06-18 22:21:19 +02:00
binding.muter.setOnClickListener {
binding.muter.isSelected = !binding.muter.isSelected
}
//Duration in seconds, or null
val duration: Float? = mediaInformation?.duration?.toFloatOrNull()
binding.videoRangeSeekBar.valueFrom = 0f
binding.videoRangeSeekBar.valueTo = duration ?: 100f
binding.videoRangeSeekBar.values = listOf(0f,(duration?: 100f) / 2, duration ?: 100f)
2022-06-10 23:41:29 +02:00
2022-06-18 22:21:19 +02:00
val mediaItem: UriMediaItem = UriMediaItem.Builder(uri).build()
mediaItem.metadata = MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_TITLE, "")
.build()
2022-06-10 23:41:29 +02:00
2022-06-18 22:21:19 +02:00
mediaPlayer = MediaPlayer(this)
mediaPlayer.setMediaItem(mediaItem)
//binding.videoView.mediaControlView?.setMediaController()
// Configure audio
mediaPlayer.setAudioAttributes(AudioAttributesCompat.Builder()
.setLegacyStreamType(AudioManager.STREAM_MUSIC)
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
.setContentType(AudioAttributesCompat.CONTENT_TYPE_MOVIE)
.build()
2022-06-10 23:41:29 +02:00
)
2022-06-18 22:21:19 +02:00
findViewById<FrameLayout?>(R.id.progress_bar)?.visibility = View.GONE
mediaPlayer.prepare()
2022-06-10 23:41:29 +02:00
2022-06-18 22:21:19 +02:00
binding.muter.setOnClickListener {
if(!binding.muter.isSelected) mediaPlayer.playerVolume = 0f
else mediaPlayer.playerVolume = 1f
binding.muter.isSelected = !binding.muter.isSelected
2022-06-10 23:41:29 +02:00
}
2022-06-18 22:21:19 +02:00
binding.videoView.setPlayer(mediaPlayer)
mediaPlayer.seekTo((binding.videoRangeSeekBar.values[1]*1000).toLong())
object : Runnable {
override fun run() {
val getCurrent = mediaPlayer.currentPosition / 1000f
if(getCurrent >= binding.videoRangeSeekBar.values[0] && getCurrent <= binding.videoRangeSeekBar.values[2] ) {
binding.videoRangeSeekBar.values = listOf(binding.videoRangeSeekBar.values[0],getCurrent, binding.videoRangeSeekBar.values[2])
}
Handler(Looper.getMainLooper()).postDelayed(this, 1000)
}
}.run()
binding.videoRangeSeekBar.addOnChangeListener { rangeSlider: RangeSlider, value, fromUser ->
// Responds to when the middle slider's value is changed
if(fromUser && value != rangeSlider.values[0] && value != rangeSlider.values[2]) {
mediaPlayer.seekTo((rangeSlider.values[1]*1000).toLong())
}
}
binding.videoRangeSeekBar.setLabelFormatter { value: Float ->
DateUtils.formatElapsedTime(value.toLong())
}
val thumbInterval: Float? = duration?.div(7)
thumbInterval?.let {
thumbnail(uri, resultHandler, binding.thumbnail1, it)
thumbnail(uri, resultHandler, binding.thumbnail2, it.times(2))
thumbnail(uri, resultHandler, binding.thumbnail3, it.times(3))
thumbnail(uri, resultHandler, binding.thumbnail4, it.times(4))
thumbnail(uri, resultHandler, binding.thumbnail5, it.times(5))
thumbnail(uri, resultHandler, binding.thumbnail6, it.times(6))
thumbnail(uri, resultHandler, binding.thumbnail7, it.times(7))
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.edit_menu, menu)
return true
2022-06-10 23:41:29 +02:00
}
2022-06-18 22:21:19 +02:00
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
android.R.id.home -> onBackPressed()
R.id.action_save -> {
returnWithValues()
}
R.id.action_reset -> {
resetControls()
}
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
if (noEdits()) super.onBackPressed()
else {
val builder = AlertDialog.Builder(this)
builder.apply {
setMessage(R.string.save_before_returning)
setPositiveButton(android.R.string.ok) { _, _ ->
returnWithValues()
}
setNegativeButton(R.string.no_cancel_edit) { _, _ ->
super.onBackPressed()
}
}
// Create the AlertDialog
builder.show()
}
}
private fun noEdits(): Boolean {
val videoPositions = binding.videoRangeSeekBar.values.let {
it[0] == 0f && it[2] == binding.videoRangeSeekBar.valueTo
}
val muted = binding.muter.isSelected
return !muted && videoPositions
}
private fun returnWithValues() {
val intent = Intent(this, PostCreationActivity::class.java)
.apply {
putExtra(PhotoEditActivity.PICTURE_POSITION, videoPosition)
putExtra(MUTED, binding.muter.isSelected)
putExtra(MODIFIED, !noEdits())
putExtra(VIDEO_START, binding.videoRangeSeekBar.values.first())
putExtra(VIDEO_END, binding.videoRangeSeekBar.values[2])
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
}
setResult(Activity.RESULT_OK, intent)
finish()
}
private fun resetControls() {
binding.videoRangeSeekBar.values = listOf(0f, binding.videoRangeSeekBar.valueTo/2, binding.videoRangeSeekBar.valueTo)
binding.muter.isSelected = false
}
override fun onDestroy() {
super.onDestroy()
sessionList.forEach {
FFmpegKit.cancel(it)
}
tempFiles.forEach{
it.delete()
}
mediaPlayer.close()
}
private fun thumbnail(
inputUri: Uri?,
resultHandler: Handler,
thumbnail: ImageView,
thumbTime: Float,
) {
val file = File.createTempFile("temp_img", ".bmp")
tempFiles.add(file)
val fileUri = file.toUri()
2022-08-21 00:31:36 +02:00
val ffmpegCompliantUri = ffmpegCompliantUri(inputUri)
2022-06-18 22:21:19 +02:00
val outputImagePath =
if(fileUri.toString().startsWith("content://"))
FFmpegKitConfig.getSafParameterForWrite(this, fileUri)
else fileUri.toString()
2022-08-21 00:31:36 +02:00
val session = FFmpegKit.executeWithArgumentsAsync(arrayOf(
"-noaccurate_seek", "-ss", "$thumbTime", "-i", ffmpegCompliantUri, "-vf",
"scale=${thumbnail.width}:${thumbnail.height}",
"-frames:v", "1", "-f", "image2", "-y", outputImagePath), { session ->
2022-06-18 22:21:19 +02:00
val state = session.state
val returnCode = session.returnCode
if (ReturnCode.isSuccess(returnCode)) {
// SUCCESS
resultHandler.post {
if(!this.isFinishing)
Glide.with(this).load(outputImagePath).centerCrop().into(thumbnail)
}
}
// CALLED WHEN SESSION IS EXECUTED
Log.d("VideoEditActivity", "FFmpeg process exited with state $state and rc $returnCode.${session.failStackTrace}")
},
2022-08-21 00:31:36 +02:00
{/* CALLED WHEN SESSION PRINTS LOGS */ }, { /*CALLED WHEN SESSION GENERATES STATISTICS*/ })
2022-06-18 22:21:19 +02:00
sessionList.add(session.sessionId)
}
override fun onPause() {
super.onPause()
mediaPlayer.pause()
}
2022-06-10 23:41:29 +02:00
companion object {
const val VIDEO_TAG = "VideoEditTag"
2022-06-18 22:21:19 +02:00
const val MUTED = "VideoEditMutedTag"
const val VIDEO_START = "VideoEditVideoStartTag"
const val VIDEO_END = "VideoEditVideoEndTag"
const val MODIFIED = "VideoEditModifiedTag"
2022-06-10 23:41:29 +02:00
}
}