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.MotionEvent
|
||||
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.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
|
|
@ -30,12 +35,12 @@ import androidx.preference.PreferenceManager
|
|||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.ChapterItem
|
||||
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.util.Extensions.convertToTimestamp
|
||||
import com.deniscerri.ytdl.util.Extensions.setTextAndRecalculateWidth
|
||||
import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||
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.VideoPlayerUtil
|
||||
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 muteBtn : MaterialButton
|
||||
private lateinit var rangeSlider : RangeSlider
|
||||
private lateinit var fromTextInput : EditText
|
||||
private lateinit var toTextInput : EditText
|
||||
private lateinit var startTextInput : EditText
|
||||
private lateinit var endTextInput : EditText
|
||||
private lateinit var cancelBtn : Button
|
||||
private lateinit var okBtn : Button
|
||||
private lateinit var forceKeyframes: MaterialSwitch
|
||||
|
|
@ -89,6 +94,9 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
private var itemDurationTimestamp = 0L
|
||||
private lateinit var selectedCuts: MutableList<String>
|
||||
|
||||
private var startTimestamp = 0L
|
||||
private var endTimestamp = 0L
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -135,10 +143,18 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
forwardBtn = view.findViewById(R.id.forward)
|
||||
muteBtn = view.findViewById(R.id.mute)
|
||||
rangeSlider = view.findViewById(R.id.rangeSlider)
|
||||
fromTextInput = view.findViewById(R.id.from_textinput_edittext)
|
||||
fromTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
||||
toTextInput = view.findViewById(R.id.to_textinput_edittext)
|
||||
toTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
||||
|
||||
startTextInput = view.findViewById(R.id.from_textinput_edittext)
|
||||
startTextInput.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)
|
||||
okBtn = view.findViewById(R.id.okButton)
|
||||
suggestedChips = view.findViewById(R.id.chapters)
|
||||
|
|
@ -211,14 +227,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
}
|
||||
}
|
||||
//poll video progress
|
||||
val pollProgressInterval = 200L
|
||||
lifecycleScope.launch {
|
||||
videoProgress(player).collect { p ->
|
||||
val currentTime = p.toStringDuration(Locale.US)
|
||||
durationText.text = "$currentTime / ${item.duration}"
|
||||
val startTimestamp = fromTextInput.text.toString().convertToTimestamp()
|
||||
if (toTextInput.text.isNotBlank()){
|
||||
val endTimestamp = toTextInput.text.toString().convertToTimestamp()
|
||||
if (p >= endTimestamp / 1000 || (!player.isPlaying && p >= endTimestamp / 1000 - 1)){
|
||||
videoProgress(player, pollProgressInterval).collect { currentTime ->
|
||||
durationText.text = "${currentTime.toStringTimeStamp()} / ${item.duration}"
|
||||
if (endTextInput.text.isNotBlank()) {
|
||||
if (currentTime >= endTimestamp || (!player.isPlaying && currentTime >= endTimestamp - pollProgressInterval)) {
|
||||
player.prepare()
|
||||
player.seekTo(startTimestamp)
|
||||
}
|
||||
|
|
@ -259,7 +273,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
|
||||
rewindBtn.setOnClickListener {
|
||||
try {
|
||||
val stmp = fromTextInput.text.toString().convertToTimestamp()
|
||||
val stmp = startTextInput.text.toString().convertToTimestamp()
|
||||
player.seekTo(stmp)
|
||||
player.play()
|
||||
}catch (ignored: Exception) {}
|
||||
|
|
@ -267,10 +281,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
|
||||
forwardBtn.setOnClickListener {
|
||||
kotlin.runCatching {
|
||||
val startTimestamp = fromTextInput.text.toString().convertToTimestamp()
|
||||
var endTimestamp = toTextInput.text.toString().convertToTimestamp() - 1500
|
||||
if (endTimestamp < startTimestamp) endTimestamp = startTimestamp
|
||||
player.seekTo(endTimestamp)
|
||||
player.seekTo(max(startTimestamp, endTimestamp - 1500))
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
|
|
@ -286,14 +297,44 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
private fun initCutSection(){
|
||||
fromTextInput.setTextAndRecalculateWidth("0:00")
|
||||
toTextInput.setTextAndRecalculateWidth(item.duration)
|
||||
private fun setStartTimestamp(
|
||||
millis: Long,
|
||||
updateTextInput: Boolean = true,
|
||||
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
|
||||
rangeSlider.valueTo = (itemDurationTimestamp / 1000).toFloat()
|
||||
rangeSlider.setValues(0F, (itemDurationTimestamp / 1000).toFloat())
|
||||
private fun setEndTimestamp(
|
||||
millis: Long,
|
||||
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
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
|
|
@ -352,77 +393,42 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
// }
|
||||
// }
|
||||
|
||||
fromTextInput.setOnKeyListener(object : View.OnKeyListener {
|
||||
|
||||
override fun onKey(p0: View?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||
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.setOnFocusChangeListener { view, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
updateFromStartTextInput(startTextInput.text.toString())
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
toTextInput.setOnKeyListener(object : View.OnKeyListener {
|
||||
|
||||
override fun onKey(p0: View?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if ((event!!.action == KeyEvent.ACTION_DOWN) &&
|
||||
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||
|
||||
toTextInput.clearFocus()
|
||||
val timestamp = fromTextInput.text.toString().convertToTimestamp()
|
||||
var endstamp = toTextInput.text.toString().convertToTimestamp()
|
||||
|
||||
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
|
||||
}
|
||||
startTextInput.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
|
||||
) {
|
||||
updateFromStartTextInput(startTextInput.text.toString())
|
||||
true
|
||||
} else {
|
||||
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 {
|
||||
if (chipGroup.childCount == 0){
|
||||
|
|
@ -437,7 +443,11 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
okBtn.isEnabled = false
|
||||
okBtn.setOnClickListener {
|
||||
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()
|
||||
cutSection.visibility = View.GONE
|
||||
cutListSection.visibility = View.VISIBLE
|
||||
|
|
@ -446,26 +456,41 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
populateSuggestedChapters()
|
||||
}
|
||||
|
||||
private fun updateFromSlider(){
|
||||
val values = rangeSlider.values
|
||||
val startTimestamp = values[0].toLong() * 1000
|
||||
val endTimestamp = values[1].toLong() * 1000
|
||||
private fun updateFromStartTextInput(text: String) {
|
||||
val timestamp = text.tryConvertToTimestamp()
|
||||
if (timestamp == null) {
|
||||
startTextInput.setTextAndRecalculateWidth(startTimestamp.toStringTimeStamp(forceMillis = true))
|
||||
} else if (timestamp != startTimestamp) {
|
||||
setStartTimestamp(timestamp, updateTextInput = false)
|
||||
player.seekTo(timestamp)
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
|
||||
val startTimestampString = startTimestamp.toStringTimeStamp()
|
||||
val endTimestampString = endTimestamp.toStringTimeStamp()
|
||||
private fun updateFromEndTextInput(text: String) {
|
||||
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
|
||||
|
||||
fromTextInput.setTextAndRecalculateWidth(startTimestampString)
|
||||
toTextInput.setTextAndRecalculateWidth(endTimestampString)
|
||||
|
||||
|
||||
okBtn.isEnabled = values[0] != 0F || values[1] != (itemDurationTimestamp / 1000).toFloat()
|
||||
if (draggedFromBeginning) {
|
||||
setStartTimestamp(rangeSlider.values[0].toLong(), updateSlider = false)
|
||||
} else {
|
||||
setEndTimestamp(rangeSlider.values[1].toLong(), updateSlider = false)
|
||||
}
|
||||
|
||||
try {
|
||||
if (draggedFromBeginning){
|
||||
if (draggedFromBeginning) {
|
||||
player.seekTo(startTimestamp)
|
||||
}else{
|
||||
} else {
|
||||
player.seekTo(max(startTimestamp, endTimestamp - 1500))
|
||||
}
|
||||
player.play()
|
||||
|
|
@ -509,7 +534,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
newCutBtn.setOnClickListener {
|
||||
cutSection.visibility = View.VISIBLE
|
||||
cutListSection.visibility = View.GONE
|
||||
rangeSlider.setValues(0F, ( itemDurationTimestamp / 1000).toFloat())
|
||||
resetCutTimestamps()
|
||||
player.seekTo(0)
|
||||
suggestedChips.children.apply {
|
||||
this.forEach {
|
||||
|
|
@ -551,11 +576,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
|
||||
chip.setOnClickListener {
|
||||
if (chip.isChecked) {
|
||||
rangeSlider.setValues((startTimestamp / 1000).toFloat(), (endTimestamp / 1000).toFloat())
|
||||
setCutTimestamps(startTimestamp, endTimestamp)
|
||||
player.prepare()
|
||||
player.seekTo(startTimestamp)
|
||||
player.play()
|
||||
}else {
|
||||
// TODO reset timestamps?
|
||||
player.seekTo(0)
|
||||
player.pause()
|
||||
}
|
||||
|
|
@ -635,11 +661,13 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
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) {
|
||||
emit((player!!.currentPosition / 1000).toInt())
|
||||
delay(1000)
|
||||
emit(player!!.currentPosition)
|
||||
delay(interval)
|
||||
}
|
||||
}.flowOn(Dispatchers.Main)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import android.content.Context
|
|||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
|
|
@ -65,6 +64,11 @@ import java.util.Calendar
|
|||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
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 {
|
||||
|
|
@ -346,45 +350,44 @@ object Extensions {
|
|||
)
|
||||
}
|
||||
|
||||
fun Long.toStringTimeStamp() : String {
|
||||
var tmp = this
|
||||
val millis = ((tmp % 1000) / 100).toInt()
|
||||
tmp /= 1000
|
||||
val hours = (tmp / 3600).toInt()
|
||||
tmp %= 3600
|
||||
val minutes = (tmp / 60).toInt()
|
||||
tmp %= 60
|
||||
val seconds = tmp.toInt()
|
||||
fun Long.toStringTimeStamp(forceMillis: Boolean = false, showMillisIfNonZero : Boolean = false): String {
|
||||
return this.milliseconds.toComponents { hours, minutes, seconds, nanoseconds ->
|
||||
buildString {
|
||||
if (hours > 0) {
|
||||
append(hours)
|
||||
append(':')
|
||||
}
|
||||
append(minutes.toString().padStart(if (hours > 0) 2 else 1, '0'))
|
||||
append(':')
|
||||
append(seconds.toString().padStart(2, '0'))
|
||||
|
||||
var res = "${minutes.toString().padStart(if (hours > 0) 2 else 1, '0')}:${seconds.toString().padStart(2, '0')}"
|
||||
if (hours > 0){
|
||||
res = "${hours}:" + res
|
||||
val millis = nanoseconds / 1_000_000
|
||||
if (forceMillis || showMillisIfNonZero && millis > 0) {
|
||||
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 {
|
||||
return try {
|
||||
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 }
|
||||
fun String.convertToTimestamp(): Long =
|
||||
kotlin.runCatching { tryConvertToTimestamp() }.getOrNull() ?: 0L
|
||||
|
||||
var times = 60
|
||||
for (i in timeArray.lastIndex - 1 downTo 0) {
|
||||
timeSeconds += timeArray[i].toInt() * times
|
||||
times *= 60
|
||||
}
|
||||
private val timeRegex =
|
||||
Regex("""^(?:(?:(?<hours>\d+):)?(?<minutes>\d{1,2}):)?(?<seconds>\d+)(?:\.(?<decimals>\d+))?$""")
|
||||
|
||||
(timeSeconds * 1000) + millis * 100
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
0L
|
||||
}
|
||||
fun String.tryConvertToTimestamp(): Long? {
|
||||
val match = timeRegex.matchEntire(this.trim()) ?: return null
|
||||
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue