mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 14:20:30 +05:30
feat: seekbar preview for downloaded videos
This commit is contained in:
parent
6314b7fcf7
commit
84bcd28c2a
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user