feat: seekbar preview for downloaded videos

This commit is contained in:
Bnyro 2023-07-29 18:00:30 +02:00
parent 6314b7fcf7
commit 84bcd28c2a
4 changed files with 56 additions and 7 deletions

View File

@ -33,7 +33,10 @@ import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams
import com.github.libretube.helpers.WindowHelper
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.interfaces.TimeFrameReceiver
import com.github.libretube.ui.listeners.SeekbarPreviewListener
import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.util.OfflineTimeFrameReceiver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -45,6 +48,7 @@ class OfflinePlayerActivity : BaseActivity() {
private lateinit var player: ExoPlayer
private lateinit var playerView: PlayerView
private lateinit var trackSelector: DefaultTrackSelector
private var timeFrameReceiver: TimeFrameReceiver? = null
private lateinit var playerBinding: ExoStyledPlayerControlViewBinding
private val playerViewModel: PlayerViewModel by viewModels()
@ -89,6 +93,20 @@ class OfflinePlayerActivity : BaseActivity() {
player.duration / 1000
)
}
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
// setup seekbar preview
if (playbackState == Player.STATE_READY) {
binding.player.binding.exoProgress.addListener(
SeekbarPreviewListener(
timeFrameReceiver ?: return,
binding.player.binding,
player.duration
)
)
}
}
})
}
.loadPlaybackParams()
@ -134,6 +152,10 @@ class OfflinePlayerActivity : BaseActivity() {
setPreferredTextLanguage("en")
}
timeFrameReceiver = video?.path?.let {
OfflineTimeFrameReceiver(this@OfflinePlayerActivity, it)
}
player.prepare()
player.play()
}

View File

@ -1,6 +1,7 @@
package com.github.libretube.ui.listeners
import android.text.format.DateUtils
import android.util.Log
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.isVisible
@ -13,16 +14,18 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.absoluteValue
@UnstableApi
class SeekbarPreviewListener(
private val timeFrameReceiver: TimeFrameReceiver,
private val playerBinding: ExoStyledPlayerControlViewBinding,
private val duration: Long,
private val onScrub: (position: Long) -> Unit,
private val onScrubEnd: (position: Long) -> Unit
private val onScrub: (position: Long) -> Unit = {},
private val onScrubEnd: (position: Long) -> Unit = {}
) : TimeBar.OnScrubListener {
private var scrubInProgress = false
private var lastPreviewPosition = Long.MAX_VALUE
override fun onScrubStart(timeBar: TimeBar, position: Long) {
scrubInProgress = true
@ -39,6 +42,13 @@ class SeekbarPreviewListener(
scrubInProgress = true
playerBinding.seekbarPreviewPosition.text = DateUtils.formatElapsedTime(position / 1000)
// minimum of five seconds of additional seeking in order to show a preview
if ((lastPreviewPosition - position).absoluteValue < 5000) {
updatePreviewX(position)
return
}
CoroutineScope(Dispatchers.IO).launch {
processPreview(position)
}
@ -72,10 +82,7 @@ class SeekbarPreviewListener(
* Make a request to get the image frame and update its position
*/
private suspend fun processPreview(position: Long) {
// update the offset of the preview image view
withContext(Dispatchers.Main) {
updatePreviewX(position)
}
lastPreviewPosition = position
val frame = timeFrameReceiver.getFrameAtTime(position)
if (!scrubInProgress) return
@ -83,6 +90,7 @@ class SeekbarPreviewListener(
withContext(Dispatchers.Main) {
playerBinding.seekbarPreviewImage.setImageBitmap(frame)
playerBinding.seekbarPreview.isVisible = true
updatePreviewX(position)
}
}

View File

@ -0,0 +1,20 @@
package com.github.libretube.util
import android.content.Context
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import com.github.libretube.extensions.toAndroidUri
import com.github.libretube.ui.interfaces.TimeFrameReceiver
import java.nio.file.Path
class OfflineTimeFrameReceiver(
private val context: Context,
private val videoSource: Path
): TimeFrameReceiver() {
private val metadataRetriever = MediaMetadataRetriever().apply {
setDataSource(context, videoSource.toAndroidUri())
}
override suspend fun getFrameAtTime(position: Long): Bitmap? {
return metadataRetriever.getFrameAtTime(position * 1000)
}
}

View File

@ -5,7 +5,6 @@ import android.graphics.Bitmap
import androidx.core.graphics.drawable.toBitmap
import com.github.libretube.api.obj.PreviewFrames
import com.github.libretube.helpers.ImageHelper
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.obj.PreviewFrame
import com.github.libretube.ui.interfaces.TimeFrameReceiver