Allow cutting milliseconds
This commit is contained in:
parent
1a597240fd
commit
0eabeb0556
2 changed files with 181 additions and 150 deletions
|
|
@ -13,7 +13,12 @@ import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
|
@ -30,12 +35,12 @@ import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.models.ChapterItem
|
import com.deniscerri.ytdl.database.models.ChapterItem
|
||||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
|
|
||||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdl.util.Extensions.convertToTimestamp
|
import com.deniscerri.ytdl.util.Extensions.convertToTimestamp
|
||||||
import com.deniscerri.ytdl.util.Extensions.setTextAndRecalculateWidth
|
import com.deniscerri.ytdl.util.Extensions.setTextAndRecalculateWidth
|
||||||
import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||||
import com.deniscerri.ytdl.util.Extensions.toStringTimeStamp
|
import com.deniscerri.ytdl.util.Extensions.toStringTimeStamp
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.tryConvertToTimestamp
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.deniscerri.ytdl.util.VideoPlayerUtil
|
import com.deniscerri.ytdl.util.VideoPlayerUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
|
@ -71,8 +76,8 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
private lateinit var forwardBtn : MaterialButton
|
private lateinit var forwardBtn : MaterialButton
|
||||||
private lateinit var muteBtn : MaterialButton
|
private lateinit var muteBtn : MaterialButton
|
||||||
private lateinit var rangeSlider : RangeSlider
|
private lateinit var rangeSlider : RangeSlider
|
||||||
private lateinit var fromTextInput : EditText
|
private lateinit var startTextInput : EditText
|
||||||
private lateinit var toTextInput : EditText
|
private lateinit var endTextInput : EditText
|
||||||
private lateinit var cancelBtn : Button
|
private lateinit var cancelBtn : Button
|
||||||
private lateinit var okBtn : Button
|
private lateinit var okBtn : Button
|
||||||
private lateinit var forceKeyframes: MaterialSwitch
|
private lateinit var forceKeyframes: MaterialSwitch
|
||||||
|
|
@ -89,6 +94,9 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
private var itemDurationTimestamp = 0L
|
private var itemDurationTimestamp = 0L
|
||||||
private lateinit var selectedCuts: MutableList<String>
|
private lateinit var selectedCuts: MutableList<String>
|
||||||
|
|
||||||
|
private var startTimestamp = 0L
|
||||||
|
private var endTimestamp = 0L
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -135,10 +143,18 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
forwardBtn = view.findViewById(R.id.forward)
|
forwardBtn = view.findViewById(R.id.forward)
|
||||||
muteBtn = view.findViewById(R.id.mute)
|
muteBtn = view.findViewById(R.id.mute)
|
||||||
rangeSlider = view.findViewById(R.id.rangeSlider)
|
rangeSlider = view.findViewById(R.id.rangeSlider)
|
||||||
fromTextInput = view.findViewById(R.id.from_textinput_edittext)
|
|
||||||
fromTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
startTextInput = view.findViewById(R.id.from_textinput_edittext)
|
||||||
toTextInput = view.findViewById(R.id.to_textinput_edittext)
|
startTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
||||||
toTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
startTextInput.imeOptions = EditorInfo.IME_ACTION_DONE
|
||||||
|
startTextInput.inputType = EditorInfo.TYPE_NUMBER_FLAG_DECIMAL
|
||||||
|
startTextInput.maxLines = 1
|
||||||
|
endTextInput = view.findViewById(R.id.to_textinput_edittext)
|
||||||
|
endTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
||||||
|
endTextInput.imeOptions = EditorInfo.IME_ACTION_DONE
|
||||||
|
endTextInput.inputType = EditorInfo.TYPE_NUMBER_FLAG_DECIMAL
|
||||||
|
endTextInput.maxLines = 1
|
||||||
|
|
||||||
cancelBtn = view.findViewById(R.id.cancelButton)
|
cancelBtn = view.findViewById(R.id.cancelButton)
|
||||||
okBtn = view.findViewById(R.id.okButton)
|
okBtn = view.findViewById(R.id.okButton)
|
||||||
suggestedChips = view.findViewById(R.id.chapters)
|
suggestedChips = view.findViewById(R.id.chapters)
|
||||||
|
|
@ -211,14 +227,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//poll video progress
|
//poll video progress
|
||||||
|
val pollProgressInterval = 200L
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
videoProgress(player).collect { p ->
|
videoProgress(player, pollProgressInterval).collect { currentTime ->
|
||||||
val currentTime = p.toStringDuration(Locale.US)
|
durationText.text = "${currentTime.toStringTimeStamp()} / ${item.duration}"
|
||||||
durationText.text = "$currentTime / ${item.duration}"
|
if (endTextInput.text.isNotBlank()) {
|
||||||
val startTimestamp = fromTextInput.text.toString().convertToTimestamp()
|
if (currentTime >= endTimestamp || (!player.isPlaying && currentTime >= endTimestamp - pollProgressInterval)) {
|
||||||
if (toTextInput.text.isNotBlank()){
|
|
||||||
val endTimestamp = toTextInput.text.toString().convertToTimestamp()
|
|
||||||
if (p >= endTimestamp / 1000 || (!player.isPlaying && p >= endTimestamp / 1000 - 1)){
|
|
||||||
player.prepare()
|
player.prepare()
|
||||||
player.seekTo(startTimestamp)
|
player.seekTo(startTimestamp)
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +273,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
|
|
||||||
rewindBtn.setOnClickListener {
|
rewindBtn.setOnClickListener {
|
||||||
try {
|
try {
|
||||||
val stmp = fromTextInput.text.toString().convertToTimestamp()
|
val stmp = startTextInput.text.toString().convertToTimestamp()
|
||||||
player.seekTo(stmp)
|
player.seekTo(stmp)
|
||||||
player.play()
|
player.play()
|
||||||
}catch (ignored: Exception) {}
|
}catch (ignored: Exception) {}
|
||||||
|
|
@ -267,10 +281,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
|
|
||||||
forwardBtn.setOnClickListener {
|
forwardBtn.setOnClickListener {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val startTimestamp = fromTextInput.text.toString().convertToTimestamp()
|
player.seekTo(max(startTimestamp, endTimestamp - 1500))
|
||||||
var endTimestamp = toTextInput.text.toString().convertToTimestamp() - 1500
|
|
||||||
if (endTimestamp < startTimestamp) endTimestamp = startTimestamp
|
|
||||||
player.seekTo(endTimestamp)
|
|
||||||
player.play()
|
player.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -286,14 +297,44 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
private fun setStartTimestamp(
|
||||||
private fun initCutSection(){
|
millis: Long,
|
||||||
fromTextInput.setTextAndRecalculateWidth("0:00")
|
updateTextInput: Boolean = true,
|
||||||
toTextInput.setTextAndRecalculateWidth(item.duration)
|
updateSlider: Boolean = true
|
||||||
|
) {
|
||||||
|
startTimestamp = millis
|
||||||
|
if (updateSlider) rangeSlider.setValues(millis.toFloat(), endTimestamp.toFloat())
|
||||||
|
if (updateTextInput) {
|
||||||
|
startTextInput.setTextAndRecalculateWidth(millis.toStringTimeStamp(forceMillis = true))
|
||||||
|
}
|
||||||
|
okBtn.isEnabled = startTimestamp != 0L || endTimestamp != itemDurationTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
rangeSlider.valueFrom = 0f
|
private fun setEndTimestamp(
|
||||||
rangeSlider.valueTo = (itemDurationTimestamp / 1000).toFloat()
|
millis: Long,
|
||||||
rangeSlider.setValues(0F, (itemDurationTimestamp / 1000).toFloat())
|
updateTextInput: Boolean = true,
|
||||||
|
updateSlider: Boolean = true
|
||||||
|
) {
|
||||||
|
endTimestamp = millis
|
||||||
|
if (updateSlider) rangeSlider.setValues(startTimestamp.toFloat(), millis.toFloat())
|
||||||
|
if (updateTextInput) {
|
||||||
|
endTextInput.setTextAndRecalculateWidth(millis.toStringTimeStamp(forceMillis = true))
|
||||||
|
}
|
||||||
|
okBtn.isEnabled = startTimestamp != 0L || endTimestamp != itemDurationTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCutTimestamps(startTimestamp: Long, endTimestamp: Long) {
|
||||||
|
setStartTimestamp(startTimestamp)
|
||||||
|
setEndTimestamp(endTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetCutTimestamps() = setCutTimestamps(0L, itemDurationTimestamp)
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||||
|
private fun initCutSection() {
|
||||||
|
rangeSlider.valueFrom = 0F
|
||||||
|
rangeSlider.valueTo = itemDurationTimestamp.toFloat()
|
||||||
|
resetCutTimestamps()
|
||||||
rangeSlider.setOnTouchListener { _, event -> // Handle touch events here
|
rangeSlider.setOnTouchListener { _, event -> // Handle touch events here
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
|
@ -352,77 +393,42 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fromTextInput.setOnKeyListener(object : View.OnKeyListener {
|
startTextInput.setOnFocusChangeListener { view, hasFocus ->
|
||||||
|
if (!hasFocus) {
|
||||||
override fun onKey(p0: View?, keyCode: Int, event: KeyEvent?): Boolean {
|
updateFromStartTextInput(startTextInput.text.toString())
|
||||||
if ((event!!.action == KeyEvent.ACTION_DOWN) &&
|
|
||||||
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
|
||||||
|
|
||||||
var startTimestamp = rangeSlider.valueFrom.toInt()
|
|
||||||
|
|
||||||
fromTextInput.clearFocus()
|
|
||||||
var timestamp = fromTextInput.text.toString().convertToTimestamp()
|
|
||||||
val endstamp = toTextInput.text.toString().convertToTimestamp()
|
|
||||||
|
|
||||||
if (timestamp == 0L) {
|
|
||||||
fromTextInput.setTextAndRecalculateWidth(startTimestamp.toStringDuration(Locale.US))
|
|
||||||
}else if (timestamp > endstamp){
|
|
||||||
startTimestamp = 0
|
|
||||||
timestamp = 0
|
|
||||||
fromTextInput.setTextAndRecalculateWidth(startTimestamp.toStringDuration(Locale.US))
|
|
||||||
}else{
|
|
||||||
fromTextInput.setTextAndRecalculateWidth(fromTextInput.text.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
rangeSlider.setValues((timestamp / 1000).toFloat(), (endstamp / 1000).toFloat())
|
|
||||||
okBtn.isEnabled = timestamp != 0L || endstamp != itemDurationTimestamp
|
|
||||||
try {
|
|
||||||
player.seekTo(timestamp)
|
|
||||||
player.play()
|
|
||||||
}catch (ignored: Exception) {}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
startTextInput.setOnEditorActionListener { view, actionId, event ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
|
||||||
toTextInput.setOnKeyListener(object : View.OnKeyListener {
|
actionId == EditorInfo.IME_ACTION_DONE ||
|
||||||
|
event != null &&
|
||||||
override fun onKey(p0: View?, keyCode: Int, event: KeyEvent?): Boolean {
|
event.action == KeyEvent.ACTION_DOWN &&
|
||||||
if ((event!!.action == KeyEvent.ACTION_DOWN) &&
|
event.keyCode == KeyEvent.KEYCODE_ENTER
|
||||||
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
) {
|
||||||
|
updateFromStartTextInput(startTextInput.text.toString())
|
||||||
toTextInput.clearFocus()
|
true
|
||||||
val timestamp = fromTextInput.text.toString().convertToTimestamp()
|
} else {
|
||||||
var endstamp = toTextInput.text.toString().convertToTimestamp()
|
false
|
||||||
|
|
||||||
if (endstamp > itemDurationTimestamp){
|
|
||||||
endstamp = itemDurationTimestamp
|
|
||||||
}
|
|
||||||
if (endstamp == 0L) {
|
|
||||||
endstamp = itemDurationTimestamp
|
|
||||||
}
|
|
||||||
if (endstamp <= timestamp){
|
|
||||||
endstamp = timestamp + 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
toTextInput.setTextAndRecalculateWidth(endstamp.toStringTimeStamp())
|
|
||||||
|
|
||||||
rangeSlider.setValues((timestamp / 1000).toFloat(), (endstamp / 1000).toFloat())
|
|
||||||
okBtn.isEnabled = timestamp != 0L || endstamp != itemDurationTimestamp
|
|
||||||
try {
|
|
||||||
player.seekTo(endstamp - 1500)
|
|
||||||
player.play()
|
|
||||||
}catch (ignored: Exception) {}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
endTextInput.setOnFocusChangeListener { view, hasFocus ->
|
||||||
|
if (!hasFocus) {
|
||||||
|
updateFromEndTextInput(endTextInput.text.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endTextInput.setOnEditorActionListener { view, actionId, event ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
|
||||||
|
actionId == EditorInfo.IME_ACTION_DONE ||
|
||||||
|
event != null &&
|
||||||
|
event.action == KeyEvent.ACTION_DOWN &&
|
||||||
|
event.keyCode == KeyEvent.KEYCODE_ENTER
|
||||||
|
) {
|
||||||
|
updateFromEndTextInput(endTextInput.text.toString())
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cancelBtn.setOnClickListener {
|
cancelBtn.setOnClickListener {
|
||||||
if (chipGroup.childCount == 0){
|
if (chipGroup.childCount == 0){
|
||||||
|
|
@ -437,7 +443,11 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
okBtn.isEnabled = false
|
okBtn.isEnabled = false
|
||||||
okBtn.setOnClickListener {
|
okBtn.setOnClickListener {
|
||||||
forceKeyframes.isVisible = true
|
forceKeyframes.isVisible = true
|
||||||
val chip = createChip("${fromTextInput.text}-${toTextInput.text}")
|
updateFromStartTextInput(startTextInput.text.toString())
|
||||||
|
updateFromEndTextInput(endTextInput.text.toString())
|
||||||
|
val chip = createChip(
|
||||||
|
startTimestamp.toStringTimeStamp(showMillisIfNonZero = true) +
|
||||||
|
"-${endTimestamp.toStringTimeStamp(showMillisIfNonZero = true)}")
|
||||||
chip.performClick()
|
chip.performClick()
|
||||||
cutSection.visibility = View.GONE
|
cutSection.visibility = View.GONE
|
||||||
cutListSection.visibility = View.VISIBLE
|
cutListSection.visibility = View.VISIBLE
|
||||||
|
|
@ -446,26 +456,41 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
populateSuggestedChapters()
|
populateSuggestedChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFromSlider(){
|
private fun updateFromStartTextInput(text: String) {
|
||||||
val values = rangeSlider.values
|
val timestamp = text.tryConvertToTimestamp()
|
||||||
val startTimestamp = values[0].toLong() * 1000
|
if (timestamp == null) {
|
||||||
val endTimestamp = values[1].toLong() * 1000
|
startTextInput.setTextAndRecalculateWidth(startTimestamp.toStringTimeStamp(forceMillis = true))
|
||||||
|
} else if (timestamp != startTimestamp) {
|
||||||
|
setStartTimestamp(timestamp, updateTextInput = false)
|
||||||
|
player.seekTo(timestamp)
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val startTimestampString = startTimestamp.toStringTimeStamp()
|
private fun updateFromEndTextInput(text: String) {
|
||||||
val endTimestampString = endTimestamp.toStringTimeStamp()
|
val timestamp = text.tryConvertToTimestamp()
|
||||||
|
if (timestamp == null) {
|
||||||
|
endTextInput.setTextAndRecalculateWidth(endTimestamp.toStringTimeStamp(forceMillis = true))
|
||||||
|
} else if (timestamp != endTimestamp) {
|
||||||
|
setEndTimestamp(timestamp, updateTextInput = false)
|
||||||
|
player.seekTo(timestamp-1500)
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateFromSlider() {
|
||||||
val draggedFromBeginning = rangeSlider.focusedThumbIndex != 1
|
val draggedFromBeginning = rangeSlider.focusedThumbIndex != 1
|
||||||
|
|
||||||
fromTextInput.setTextAndRecalculateWidth(startTimestampString)
|
if (draggedFromBeginning) {
|
||||||
toTextInput.setTextAndRecalculateWidth(endTimestampString)
|
setStartTimestamp(rangeSlider.values[0].toLong(), updateSlider = false)
|
||||||
|
} else {
|
||||||
|
setEndTimestamp(rangeSlider.values[1].toLong(), updateSlider = false)
|
||||||
okBtn.isEnabled = values[0] != 0F || values[1] != (itemDurationTimestamp / 1000).toFloat()
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (draggedFromBeginning){
|
if (draggedFromBeginning) {
|
||||||
player.seekTo(startTimestamp)
|
player.seekTo(startTimestamp)
|
||||||
}else{
|
} else {
|
||||||
player.seekTo(max(startTimestamp, endTimestamp - 1500))
|
player.seekTo(max(startTimestamp, endTimestamp - 1500))
|
||||||
}
|
}
|
||||||
player.play()
|
player.play()
|
||||||
|
|
@ -509,7 +534,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
newCutBtn.setOnClickListener {
|
newCutBtn.setOnClickListener {
|
||||||
cutSection.visibility = View.VISIBLE
|
cutSection.visibility = View.VISIBLE
|
||||||
cutListSection.visibility = View.GONE
|
cutListSection.visibility = View.GONE
|
||||||
rangeSlider.setValues(0F, ( itemDurationTimestamp / 1000).toFloat())
|
resetCutTimestamps()
|
||||||
player.seekTo(0)
|
player.seekTo(0)
|
||||||
suggestedChips.children.apply {
|
suggestedChips.children.apply {
|
||||||
this.forEach {
|
this.forEach {
|
||||||
|
|
@ -551,11 +576,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
|
|
||||||
chip.setOnClickListener {
|
chip.setOnClickListener {
|
||||||
if (chip.isChecked) {
|
if (chip.isChecked) {
|
||||||
rangeSlider.setValues((startTimestamp / 1000).toFloat(), (endTimestamp / 1000).toFloat())
|
setCutTimestamps(startTimestamp, endTimestamp)
|
||||||
player.prepare()
|
player.prepare()
|
||||||
player.seekTo(startTimestamp)
|
player.seekTo(startTimestamp)
|
||||||
player.play()
|
player.play()
|
||||||
}else {
|
}else {
|
||||||
|
// TODO reset timestamps?
|
||||||
player.seekTo(0)
|
player.seekTo(0)
|
||||||
player.pause()
|
player.pause()
|
||||||
}
|
}
|
||||||
|
|
@ -635,11 +661,13 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
return chip
|
return chip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
private fun videoProgress(player: ExoPlayer?) = flow {
|
* Emits current video timestamp (in milliseconds) every `interval` milliseconds.
|
||||||
|
*/
|
||||||
|
private fun videoProgress(player: ExoPlayer?, interval: Long = 200) = flow {
|
||||||
while (true) {
|
while (true) {
|
||||||
emit((player!!.currentPosition / 1000).toInt())
|
emit(player!!.currentPosition)
|
||||||
delay(1000)
|
delay(interval)
|
||||||
}
|
}
|
||||||
}.flowOn(Dispatchers.Main)
|
}.flowOn(Dispatchers.Main)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Outline
|
import android.graphics.Outline
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
|
|
@ -65,6 +64,11 @@ import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
||||||
object Extensions {
|
object Extensions {
|
||||||
|
|
@ -346,45 +350,44 @@ object Extensions {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Long.toStringTimeStamp() : String {
|
fun Long.toStringTimeStamp(forceMillis: Boolean = false, showMillisIfNonZero : Boolean = false): String {
|
||||||
var tmp = this
|
return this.milliseconds.toComponents { hours, minutes, seconds, nanoseconds ->
|
||||||
val millis = ((tmp % 1000) / 100).toInt()
|
buildString {
|
||||||
tmp /= 1000
|
if (hours > 0) {
|
||||||
val hours = (tmp / 3600).toInt()
|
append(hours)
|
||||||
tmp %= 3600
|
append(':')
|
||||||
val minutes = (tmp / 60).toInt()
|
}
|
||||||
tmp %= 60
|
append(minutes.toString().padStart(if (hours > 0) 2 else 1, '0'))
|
||||||
val seconds = tmp.toInt()
|
append(':')
|
||||||
|
append(seconds.toString().padStart(2, '0'))
|
||||||
|
|
||||||
var res = "${minutes.toString().padStart(if (hours > 0) 2 else 1, '0')}:${seconds.toString().padStart(2, '0')}"
|
val millis = nanoseconds / 1_000_000
|
||||||
if (hours > 0){
|
if (forceMillis || showMillisIfNonZero && millis > 0) {
|
||||||
res = "${hours}:" + res
|
append('.')
|
||||||
|
var millisString = millis.toString().padStart(3, '0')
|
||||||
|
if (showMillisIfNonZero) millisString = millisString.trimEnd('0')
|
||||||
|
append(millisString)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (millis > 0){
|
|
||||||
res += ".${millis}"
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.convertToTimestamp() : Long {
|
fun String.convertToTimestamp(): Long =
|
||||||
return try {
|
kotlin.runCatching { tryConvertToTimestamp() }.getOrNull() ?: 0L
|
||||||
val timeArray = this.split(":")
|
|
||||||
val secondsMillis = timeArray[timeArray.lastIndex]
|
|
||||||
var timeSeconds = secondsMillis.split(".")[0].toLong()
|
|
||||||
val millis = kotlin.runCatching { secondsMillis.split(".")[1].toInt() }.getOrElse { 0 }
|
|
||||||
|
|
||||||
var times = 60
|
private val timeRegex =
|
||||||
for (i in timeArray.lastIndex - 1 downTo 0) {
|
Regex("""^(?:(?:(?<hours>\d+):)?(?<minutes>\d{1,2}):)?(?<seconds>\d+)(?:\.(?<decimals>\d+))?$""")
|
||||||
timeSeconds += timeArray[i].toInt() * times
|
|
||||||
times *= 60
|
|
||||||
}
|
|
||||||
|
|
||||||
(timeSeconds * 1000) + millis * 100
|
fun String.tryConvertToTimestamp(): Long? {
|
||||||
}catch (e: Exception){
|
val match = timeRegex.matchEntire(this.trim()) ?: return null
|
||||||
e.printStackTrace()
|
|
||||||
0L
|
val hours = match.groups["hours"]?.value?.toInt() ?: 0
|
||||||
}
|
val minutes = match.groups["minutes"]?.value?.toInt() ?: 0
|
||||||
|
val seconds = match.groups["seconds"]!!.value.toInt()
|
||||||
|
val millis = match.groups["decimals"]?.value
|
||||||
|
?.take(3)?.padEnd(3,'0')?.toInt() ?: 0
|
||||||
|
|
||||||
|
return (hours.hours + minutes.minutes + seconds.seconds + millis.milliseconds).inWholeMilliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.convertNetscapeToSetCookie(): String {
|
fun String.convertNetscapeToSetCookie(): String {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue