preview seekbar

This commit is contained in:
LagradOst 2023-10-02 17:44:06 +02:00
parent bb8cbb5167
commit bd05a67f26
12 changed files with 413 additions and 57 deletions

View file

@ -258,6 +258,8 @@ dependencies {
// color palette for images -> colors // color palette for images -> colors
implementation("androidx.palette:palette-ktx:1.0.0") implementation("androidx.palette:palette-ktx:1.0.0")
// seekbar https://github.com/rubensousa/PreviewSeekBar
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0")
} }
tasks.register("androidSourcesJar", Jar::class) { tasks.register("androidSourcesJar", Jar::class) {

View file

@ -18,10 +18,9 @@ import android.widget.ProgressBar
import android.widget.Toast import android.widget.Toast
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
@ -30,6 +29,10 @@ import androidx.media3.ui.DefaultTimeBar
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
import androidx.media3.ui.SubtitleView import androidx.media3.ui.SubtitleView
import androidx.media3.ui.TimeBar import androidx.media3.ui.TimeBar
import androidx.preference.PreferenceManager
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.github.rubensousa.previewseekbar.PreviewBar
import com.github.rubensousa.previewseekbar.media3.PreviewTimeBar
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.canEnterPipMode import com.lagradost.cloudstream3.CommonActivity.canEnterPipMode
@ -454,6 +457,41 @@ abstract class AbstractPlayerFragment(
) )
if (player is CS3IPlayer) { if (player is CS3IPlayer) {
// preview bar
val progressBar : PreviewTimeBar? = playerView?.findViewById(R.id.exo_progress)
val previewImageView : ImageView? = playerView?.findViewById(R.id.previewImageView)
val previewFrameLayout : FrameLayout? = playerView?.findViewById(R.id.previewFrameLayout)
if(progressBar != null && previewImageView != null && previewFrameLayout != null) {
var resume = false
progressBar.addOnScrubListener(object : PreviewBar.OnScrubListener {
override fun onScrubStart(previewBar: PreviewBar?) {
progressBar.isPreviewEnabled = player.hasPreview()
resume = player.getIsPlaying()
if (resume) player.handleEvent(
CSPlayerEvent.Pause,
PlayerEventSource.Player
)
}
override fun onScrubMove(
previewBar: PreviewBar?,
progress: Int,
fromUser: Boolean
) {
}
override fun onScrubStop(previewBar: PreviewBar?) {
if (resume) player.handleEvent(CSPlayerEvent.Play, PlayerEventSource.Player)
}
})
progressBar.attachPreviewView(previewFrameLayout)
progressBar.setPreviewLoader { currentPosition, max ->
val bitmap = player.getPreview(currentPosition.toFloat().div(max.toFloat()))
previewImageView.isGone = bitmap == null
previewImageView.setImageBitmap(bitmap)
}
}
subView = playerView?.findViewById(R.id.exo_subtitles) subView = playerView?.findViewById(R.id.exo_subtitles)
subStyle = SubtitlesFragment.getCurrentSavedStyle() subStyle = SubtitlesFragment.getCurrentSavedStyle()
player.initSubtitles(subView, subtitleHolder, subStyle) player.initSubtitles(subView, subtitleHolder, subStyle)

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@ -88,7 +89,9 @@ class CS3IPlayer : IPlayer {
private var exoPlayer: ExoPlayer? = null private var exoPlayer: ExoPlayer? = null
set(value) { set(value) {
// If the old value is not null then the player has not been properly released. // If the old value is not null then the player has not been properly released.
debugAssert({ field != null && value != null }, { "Previous player instance should be released!" }) debugAssert(
{ field != null && value != null },
{ "Previous player instance should be released!" })
field = value field = value
} }
@ -96,6 +99,8 @@ class CS3IPlayer : IPlayer {
var simpleCacheSize = 0L var simpleCacheSize = 0L
var videoBufferMs = 0L var videoBufferMs = 0L
private val imageGenerator = PreviewGenerator()
private val seekActionTime = 30000L private val seekActionTime = 30000L
private var ignoreSSL: Boolean = true private var ignoreSSL: Boolean = true
@ -182,6 +187,14 @@ class CS3IPlayer : IPlayer {
subtitleHelper.initSubtitles(subView, subHolder, style) subtitleHelper.initSubtitles(subView, subHolder, style)
} }
override fun getPreview(fraction: Float): Bitmap? {
return imageGenerator.getPreviewImage(fraction)
}
override fun hasPreview(): Boolean {
return imageGenerator.hasPreview()
}
override fun loadPlayer( override fun loadPlayer(
context: Context, context: Context,
sameEpisode: Boolean, sameEpisode: Boolean,
@ -190,7 +203,8 @@ class CS3IPlayer : IPlayer {
startPosition: Long?, startPosition: Long?,
subtitles: Set<SubtitleData>, subtitles: Set<SubtitleData>,
subtitle: SubtitleData?, subtitle: SubtitleData?,
autoPlay: Boolean? autoPlay: Boolean?,
preview : Boolean,
) { ) {
Log.i(TAG, "loadPlayer") Log.i(TAG, "loadPlayer")
if (sameEpisode) { if (sameEpisode) {
@ -210,9 +224,27 @@ class CS3IPlayer : IPlayer {
// release the current exoplayer and cache // release the current exoplayer and cache
releasePlayer() releasePlayer()
if (link != null) { if (link != null) {
// only video support atm
if (link.type == ExtractorLinkType.VIDEO && preview) {
val headers = if (link.referer.isBlank()) {
link.headers
} else {
mapOf("referer" to link.referer) + link.headers
}
imageGenerator.load(sameEpisode, link.url, headers)
} else {
imageGenerator.clear(sameEpisode)
}
loadOnlinePlayer(context, link) loadOnlinePlayer(context, link)
} else if (data != null) { } else if (data != null) {
if (preview) {
imageGenerator.load(sameEpisode, context, data.uri)
} else {
imageGenerator.clear(sameEpisode)
}
loadOfflinePlayer(context, data) loadOfflinePlayer(context, data)
} else {
throw IllegalArgumentException("Requires link or uri")
} }
} }
@ -494,6 +526,7 @@ class CS3IPlayer : IPlayer {
} }
override fun release() { override fun release() {
imageGenerator.release()
releasePlayer() releasePlayer()
} }
@ -871,8 +904,20 @@ class CS3IPlayer : IPlayer {
CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source) CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source) CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
CSPlayerEvent.NextEpisode -> event(EpisodeSeekEvent(offset = 1, source = source)) CSPlayerEvent.NextEpisode -> event(
CSPlayerEvent.PrevEpisode -> event(EpisodeSeekEvent(offset = -1, source = source)) EpisodeSeekEvent(
offset = 1,
source = source
)
)
CSPlayerEvent.PrevEpisode -> event(
EpisodeSeekEvent(
offset = -1,
source = source
)
)
CSPlayerEvent.SkipCurrentChapter -> { CSPlayerEvent.SkipCurrentChapter -> {
//val dur = this@CS3IPlayer.getDuration() ?: return@apply //val dur = this@CS3IPlayer.getDuration() ?: return@apply
getCurrentTimestamp()?.let { lastTimeStamp -> getCurrentTimestamp()?.let { lastTimeStamp ->

View file

@ -180,6 +180,7 @@ class GeneratorPlayer : FullScreenPlayer() {
(if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle( (if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(
currentSubs, settings = true, downloads = true currentSubs, settings = true, downloads = true
), ),
preview = isFullScreenPlayer
) )
} }

View file

@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.ui.player package com.lagradost.cloudstream3.ui.player
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.util.Rational import android.util.Rational
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.EpisodeSkip
@ -246,11 +247,15 @@ interface IPlayer {
startPosition: Long? = null, startPosition: Long? = null,
subtitles: Set<SubtitleData>, subtitles: Set<SubtitleData>,
subtitle: SubtitleData?, subtitle: SubtitleData?,
autoPlay: Boolean? = true autoPlay: Boolean? = true,
preview : Boolean = true,
) )
fun reloadPlayer(context: Context) fun reloadPlayer(context: Context)
fun getPreview(fraction : Float) : Bitmap?
fun hasPreview() : Boolean
fun setActiveSubtitles(subtitles: Set<SubtitleData>) fun setActiveSubtitles(subtitles: Set<SubtitleData>)
fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean // returns true if the player requires a reload, null for nothing fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean // returns true if the player requires a reload, null for nothing
fun getCurrentPreferredSubtitle(): SubtitleData? fun getCurrentPreferredSubtitle(): SubtitleData?

View file

@ -0,0 +1,147 @@
package com.lagradost.cloudstream3.ui.player
import android.content.Context
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.util.Log
import androidx.annotation.WorkerThread
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlin.math.absoluteValue
import kotlin.math.ceil
import kotlin.math.log2
const val MAX_LOD = 6
const val MIN_LOD = 3
class PreviewGenerator {
// lod = level of detail where the number indicates how many ones there is
// 2^(lod-1) = images
private var loadedLod = 0
private var loadedImages = 0
private var images = Array<Bitmap?>((1 shl MAX_LOD) - 1) {
null
}
fun hasPreview(): Boolean {
synchronized(images) {
return loadedLod >= MIN_LOD
}
}
val TAG = "PreviewImg"
fun getPreviewImage(fraction: Float): Bitmap? {
synchronized(images) {
if (loadedLod < MIN_LOD) {
Log.i(TAG, "Requesting preview for $fraction but $loadedLod < $MIN_LOD")
return null
}
Log.i(TAG, "Requesting preview for $fraction")
var bestIdx = 0
var bestDiff = 0.5f.minus(fraction).absoluteValue
// this should be done mathematically, but for now we just loop all images
for (l in 1..loadedLod + 1) {
val items = (1 shl (l - 1))
for (i in 0 until items) {
val idx = items - 1 + i
if (idx > loadedImages) {
break
}
val currentFraction =
(1.0f.div((1 shl l).toFloat()) + i * 1.0f.div(items.toFloat()))
val diff = currentFraction.minus(fraction).absoluteValue
if (diff < bestDiff) {
bestDiff = diff
bestIdx = idx
}
}
}
Log.i(TAG, "Best diff found at ${bestDiff * 100}% diff (${bestIdx})")
return images[bestIdx]
}
}
// also check out https://github.com/wseemann/FFmpegMediaMetadataRetriever
private val retriever: MediaMetadataRetriever = MediaMetadataRetriever()
fun clear(keepCache: Boolean = false) {
if (keepCache) return
synchronized(images) {
loadedLod = 0
loadedImages = 0
images.fill(null)
}
}
private var currentJob: Job? = null
fun load(keepCache: Boolean, url: String, headers: Map<String, String>) {
currentJob?.cancel()
currentJob = ioSafe {
Log.i(TAG, "Loading with url = $url headers = $headers")
clear(keepCache)
retriever.setDataSource(url, headers)
start(this)
}
}
fun load(keepCache: Boolean, context: Context, uri: Uri) {
currentJob?.cancel()
currentJob = ioSafe {
Log.i(TAG, "Loading with uri = $uri")
clear(keepCache)
retriever.setDataSource(context, uri)
start(this)
}
}
fun release() {
currentJob?.cancel()
clear(false)
}
@Throws
@WorkerThread
private fun start(scope: CoroutineScope) {
Log.i(TAG, "Started loading preview")
val durationMs =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong()
?: throw IllegalArgumentException("Bad video duration")
val durationUs = (durationMs * 1000L).toFloat()
//val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt() ?: throw IllegalArgumentException("Bad video width")
//val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt() ?: throw IllegalArgumentException("Bad video height")
// log2 # 10s durations in the video ~= how many segments we have
val maxLod = ceil(log2((durationMs / 10_000).toFloat())).toInt().coerceIn(MIN_LOD, MAX_LOD)
for (l in 1..maxLod) {
val items = (1 shl (l - 1))
for (i in 0 until items) {
val idx = items - 1 + i // as sum(prev) = cur-1
// frame = 100 / 2^lod + i * 100 / 2^(lod-1) = duration % where lod is one indexed
val fraction = (1.0f.div((1 shl l).toFloat()) + i * 1.0f.div(items.toFloat()))
Log.i(TAG, "Generating preview for ${fraction * 100}%")
val frame = durationUs * fraction
val img = retriever.getFrameAtTime(
frame.toLong(),
MediaMetadataRetriever.OPTION_CLOSEST_SYNC
)
if (!scope.isActive) return
synchronized(images) {
images[idx] = img
loadedImages = maxOf(loadedImages,idx)
}
}
synchronized(images) {
loadedLod = maxOf(loadedLod, l)
}
}
}
}

View file

@ -151,7 +151,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
startPosition = 0L, startPosition = 0L,
subtitles = emptySet(), subtitles = emptySet(),
subtitle = null, subtitle = null,
autoPlay = false autoPlay = false,
preview = false
) )
true true
} ?: run { } ?: run {

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="@dimen/video_frame_width"
android:color="@android:color/white" />
<solid android:color="@android:color/black" />
</shape>

View file

@ -86,7 +86,6 @@
</FrameLayout> </FrameLayout>
<!-- atm this is useless, however it might be used for PIP one day? --> <!-- atm this is useless, however it might be used for PIP one day? -->
<ImageView <ImageView
android:visibility="gone"
android:id="@+id/player_fullscreen" android:id="@+id/player_fullscreen"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="30dp" android:layout_height="30dp"
@ -94,7 +93,9 @@
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24" android:src="@drawable/baseline_fullscreen_24"
android:visibility="gone"
app:tint="@color/white" /> app:tint="@color/white" />
<FrameLayout <FrameLayout
android:id="@+id/player_intro_play" android:id="@+id/player_intro_play"
android:layout_width="0dp" android:layout_width="0dp"
@ -108,8 +109,8 @@
android:clickable="false" android:clickable="false"
android:focusable="false" android:focusable="false"
android:focusableInTouchMode="false" android:focusableInTouchMode="false"
android:visibility="gone" android:importantForAccessibility="no"
android:importantForAccessibility="no" /> android:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -117,6 +118,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<FrameLayout <FrameLayout
android:id="@+id/player_top_holder" android:id="@+id/player_top_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -272,6 +274,7 @@
app:tint="@color/white" app:tint="@color/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/player_pause_play_holder_holder" android:id="@+id/player_pause_play_holder_holder"
android:layout_width="100dp" android:layout_width="100dp"
@ -298,6 +301,7 @@
app:tint="@color/white" app:tint="@color/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/player_ffwd_holder" android:id="@+id/player_ffwd_holder"
android:layout_width="0dp" android:layout_width="0dp"
@ -427,7 +431,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toEndOf="parent">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_bar" android:id="@+id/player_video_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -437,10 +441,10 @@
<TextView <TextView
android:id="@id/exo_position" android:id="@id/exo_position"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="30dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:gravity="end" android:gravity="end|center_vertical"
android:includeFontPadding="false" android:includeFontPadding="false"
android:minWidth="50dp" android:minWidth="50dp"
android:paddingLeft="4dp" android:paddingLeft="4dp"
@ -448,14 +452,46 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="normal" android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="15:30" /> tools:text="15:30" />
<!--app:buffered_color="@color/videoCache"-->
<androidx.media3.ui.DefaultTimeBar <FrameLayout
android:id="@+id/previewFrameLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/video_frame"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/exo_progress"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.25"
tools:visibility="visible">
<ImageView
android:id="@+id/previewImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/video_frame_width"
android:importantForAccessibility="no"
android:scaleType="centerCrop" />
</FrameLayout>
<com.github.rubensousa.previewseekbar.media3.PreviewTimeBar
android:id="@id/exo_progress" android:id="@id/exo_progress"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_weight="1" android:layout_weight="1"
app:bar_height="2dp" app:bar_height="2dp"
app:layout_constraintBottom_toBottomOf="@id/exo_position"
app:layout_constraintEnd_toStartOf="@id/exo_duration"
app:layout_constraintStart_toEndOf="@+id/exo_position"
app:played_color="?attr/colorPrimary" app:played_color="?attr/colorPrimary"
app:scrubber_color="?attr/colorPrimary" app:scrubber_color="?attr/colorPrimary"
@ -463,12 +499,12 @@
app:scrubber_enabled_size="24dp" app:scrubber_enabled_size="24dp"
app:unplayed_color="@color/videoProgress" /> app:unplayed_color="@color/videoProgress" />
<!-- exo_duration-->
<TextView <TextView
android:id="@id/exo_duration" android:id="@id/exo_duration"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="30dp"
android:layout_gravity="center" android:layout_gravity="center|center_vertical"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:minWidth="50dp" android:minWidth="50dp"
@ -477,9 +513,10 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="normal" android:textStyle="normal"
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="parent"
tools:text="23:20" /> tools:text="23:20" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -503,13 +503,12 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_bar" android:id="@+id/player_video_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layoutDirection="ltr" android:layoutDirection="ltr"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
android:id="@+id/player_pause_play" android:id="@+id/player_pause_play"
@ -526,14 +525,17 @@
android:tag="@string/tv_no_focus_tag" android:tag="@string/tv_no_focus_tag"
app:tint="@color/player_button_tv" app:tint="@color/player_button_tv"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView <TextView
android:id="@id/exo_position" android:id="@id/exo_position"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="30dp"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="end" android:layout_marginStart="20dp"
android:gravity="end|center_vertical"
android:includeFontPadding="false" android:includeFontPadding="false"
android:minWidth="50dp" android:minWidth="50dp"
android:paddingLeft="4dp" android:paddingLeft="4dp"
@ -541,29 +543,59 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="normal" android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/player_pause_play"
tools:text="15:30" /> tools:text="15:30" />
<!--app:buffered_color="@color/videoCache"-->
<androidx.media3.ui.DefaultTimeBar <FrameLayout
android:id="@+id/previewFrameLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/video_frame"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/exo_progress"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.25"
tools:visibility="visible">
<ImageView
android:id="@+id/previewImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/video_frame_width"
android:importantForAccessibility="no"
android:scaleType="centerCrop" />
</FrameLayout>
<com.github.rubensousa.previewseekbar.media3.PreviewTimeBar
android:id="@id/exo_progress" android:id="@id/exo_progress"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_gravity="center"
android:layout_weight="1" android:layout_weight="1"
android:focusable="false"
android:focusableInTouchMode="false"
app:bar_height="2dp" app:bar_height="2dp"
app:layout_constraintBottom_toBottomOf="@id/exo_position"
app:layout_constraintEnd_toStartOf="@id/exo_duration"
app:layout_constraintStart_toEndOf="@+id/exo_position"
app:played_color="?attr/colorPrimary" app:played_color="?attr/colorPrimary"
app:scrubber_color="?attr/colorPrimary" app:scrubber_color="?attr/colorPrimary"
app:scrubber_dragged_size="26dp" app:scrubber_dragged_size="26dp"
app:scrubber_enabled_size="24dp" app:scrubber_enabled_size="24dp"
app:unplayed_color="@color/videoProgress" /> app:unplayed_color="@color/videoProgress" />
<!-- exo_duration-->
<TextView <TextView
android:id="@id/exo_duration" android:id="@id/exo_duration"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="30dp"
android:layout_gravity="center" android:layout_gravity="center|center_vertical"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:minWidth="50dp" android:minWidth="50dp"
@ -572,9 +604,11 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="normal" android:textStyle="normal"
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="parent"
tools:text="23:20" /> tools:text="23:20" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -212,7 +212,6 @@
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_center_menu" android:id="@+id/player_center_menu"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -294,6 +293,7 @@
app:tint="@color/white" app:tint="@color/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/player_ffwd_holder" android:id="@+id/player_ffwd_holder"
android:layout_width="0dp" android:layout_width="0dp"
@ -420,20 +420,20 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toEndOf="parent">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_bar" android:id="@+id/player_video_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layoutDirection="ltr"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:id="@id/exo_position" android:id="@id/exo_position"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="30dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:gravity="end" android:gravity="end|center_vertical"
android:includeFontPadding="false" android:includeFontPadding="false"
android:minWidth="50dp" android:minWidth="50dp"
android:paddingLeft="4dp" android:paddingLeft="4dp"
@ -441,14 +441,46 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="normal" android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="15:30" /> tools:text="15:30" />
<!--app:buffered_color="@color/videoCache"-->
<androidx.media3.ui.DefaultTimeBar <FrameLayout
android:id="@+id/previewFrameLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/video_frame"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/exo_progress"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.25"
tools:visibility="visible">
<ImageView
android:id="@+id/previewImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/video_frame_width"
android:importantForAccessibility="no"
android:scaleType="centerCrop" />
</FrameLayout>
<com.github.rubensousa.previewseekbar.media3.PreviewTimeBar
android:id="@id/exo_progress" android:id="@id/exo_progress"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_weight="1" android:layout_weight="1"
app:bar_height="2dp" app:bar_height="2dp"
app:layout_constraintBottom_toBottomOf="@id/exo_position"
app:layout_constraintEnd_toStartOf="@id/exo_duration"
app:layout_constraintStart_toEndOf="@+id/exo_position"
app:played_color="?attr/colorPrimary" app:played_color="?attr/colorPrimary"
app:scrubber_color="?attr/colorPrimary" app:scrubber_color="?attr/colorPrimary"
@ -456,13 +488,13 @@
app:scrubber_enabled_size="24dp" app:scrubber_enabled_size="24dp"
app:unplayed_color="@color/videoProgress" /> app:unplayed_color="@color/videoProgress" />
<!-- exo_duration-->
<TextView <TextView
android:id="@id/exo_duration" android:id="@id/exo_duration"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="30dp"
android:layout_gravity="center" android:layout_gravity="center|center_vertical"
android:layout_marginEnd="20dp"
android:layout_marginEnd="30dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:minWidth="50dp" android:minWidth="50dp"
android:paddingLeft="4dp" android:paddingLeft="4dp"
@ -470,19 +502,23 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="normal" android:textStyle="normal"
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="@id/player_fullscreen"
tools:text="23:20" /> tools:text="23:20" />
</LinearLayout> <ImageView
android:id="@+id/player_fullscreen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@color/white" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/player_fullscreen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" />
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -19,5 +19,5 @@
<dimen name="navbar_width">62dp</dimen> <dimen name="navbar_width">62dp</dimen>
<dimen name="download_size">50dp</dimen> <dimen name="download_size">50dp</dimen>
<dimen name="video_frame_width">1dp</dimen>
</resources> </resources>