mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
preview seekbar
This commit is contained in:
parent
bb8cbb5167
commit
bd05a67f26
12 changed files with 413 additions and 57 deletions
|
@ -258,6 +258,8 @@ dependencies {
|
|||
|
||||
// color palette for images -> colors
|
||||
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) {
|
||||
|
|
|
@ -18,10 +18,9 @@ import android.widget.ProgressBar
|
|||
import android.widget.Toast
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.session.MediaSession
|
||||
|
@ -30,6 +29,10 @@ import androidx.media3.ui.DefaultTimeBar
|
|||
import androidx.media3.ui.PlayerView
|
||||
import androidx.media3.ui.SubtitleView
|
||||
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.setKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.canEnterPipMode
|
||||
|
@ -454,6 +457,41 @@ abstract class AbstractPlayerFragment(
|
|||
)
|
||||
|
||||
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)
|
||||
subStyle = SubtitlesFragment.getCurrentSavedStyle()
|
||||
player.initSubtitles(subView, subtitleHolder, subStyle)
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
@ -88,7 +89,9 @@ class CS3IPlayer : IPlayer {
|
|||
private var exoPlayer: ExoPlayer? = null
|
||||
set(value) {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -96,6 +99,8 @@ class CS3IPlayer : IPlayer {
|
|||
var simpleCacheSize = 0L
|
||||
var videoBufferMs = 0L
|
||||
|
||||
private val imageGenerator = PreviewGenerator()
|
||||
|
||||
private val seekActionTime = 30000L
|
||||
|
||||
private var ignoreSSL: Boolean = true
|
||||
|
@ -182,6 +187,14 @@ class CS3IPlayer : IPlayer {
|
|||
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(
|
||||
context: Context,
|
||||
sameEpisode: Boolean,
|
||||
|
@ -190,7 +203,8 @@ class CS3IPlayer : IPlayer {
|
|||
startPosition: Long?,
|
||||
subtitles: Set<SubtitleData>,
|
||||
subtitle: SubtitleData?,
|
||||
autoPlay: Boolean?
|
||||
autoPlay: Boolean?,
|
||||
preview : Boolean,
|
||||
) {
|
||||
Log.i(TAG, "loadPlayer")
|
||||
if (sameEpisode) {
|
||||
|
@ -210,9 +224,27 @@ class CS3IPlayer : IPlayer {
|
|||
// release the current exoplayer and cache
|
||||
releasePlayer()
|
||||
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)
|
||||
} else if (data != null) {
|
||||
if (preview) {
|
||||
imageGenerator.load(sameEpisode, context, data.uri)
|
||||
} else {
|
||||
imageGenerator.clear(sameEpisode)
|
||||
}
|
||||
loadOfflinePlayer(context, data)
|
||||
} else {
|
||||
throw IllegalArgumentException("Requires link or uri")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,6 +526,7 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
override fun release() {
|
||||
imageGenerator.release()
|
||||
releasePlayer()
|
||||
}
|
||||
|
||||
|
@ -871,8 +904,20 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
|
||||
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
|
||||
CSPlayerEvent.NextEpisode -> event(EpisodeSeekEvent(offset = 1, source = source))
|
||||
CSPlayerEvent.PrevEpisode -> event(EpisodeSeekEvent(offset = -1, source = source))
|
||||
CSPlayerEvent.NextEpisode -> event(
|
||||
EpisodeSeekEvent(
|
||||
offset = 1,
|
||||
source = source
|
||||
)
|
||||
)
|
||||
|
||||
CSPlayerEvent.PrevEpisode -> event(
|
||||
EpisodeSeekEvent(
|
||||
offset = -1,
|
||||
source = source
|
||||
)
|
||||
)
|
||||
|
||||
CSPlayerEvent.SkipCurrentChapter -> {
|
||||
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
|
||||
getCurrentTimestamp()?.let { lastTimeStamp ->
|
||||
|
|
|
@ -180,6 +180,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
(if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(
|
||||
currentSubs, settings = true, downloads = true
|
||||
),
|
||||
preview = isFullScreenPlayer
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.lagradost.cloudstream3.ui.player
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Rational
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||
import com.lagradost.cloudstream3.utils.EpisodeSkip
|
||||
|
@ -246,11 +247,15 @@ interface IPlayer {
|
|||
startPosition: Long? = null,
|
||||
subtitles: Set<SubtitleData>,
|
||||
subtitle: SubtitleData?,
|
||||
autoPlay: Boolean? = true
|
||||
autoPlay: Boolean? = true,
|
||||
preview : Boolean = true,
|
||||
)
|
||||
|
||||
fun reloadPlayer(context: Context)
|
||||
|
||||
fun getPreview(fraction : Float) : Bitmap?
|
||||
fun hasPreview() : Boolean
|
||||
|
||||
fun setActiveSubtitles(subtitles: Set<SubtitleData>)
|
||||
fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean // returns true if the player requires a reload, null for nothing
|
||||
fun getCurrentPreferredSubtitle(): SubtitleData?
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -151,7 +151,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
startPosition = 0L,
|
||||
subtitles = emptySet(),
|
||||
subtitle = null,
|
||||
autoPlay = false
|
||||
autoPlay = false,
|
||||
preview = false
|
||||
)
|
||||
true
|
||||
} ?: run {
|
||||
|
|
10
app/src/main/res/drawable/video_frame.xml
Normal file
10
app/src/main/res/drawable/video_frame.xml
Normal 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>
|
|
@ -86,7 +86,6 @@
|
|||
</FrameLayout>
|
||||
<!-- atm this is useless, however it might be used for PIP one day? -->
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:id="@+id/player_fullscreen"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
|
@ -94,7 +93,9 @@
|
|||
android:layout_marginEnd="20dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/baseline_fullscreen_24"
|
||||
android:visibility="gone"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/player_intro_play"
|
||||
android:layout_width="0dp"
|
||||
|
@ -108,8 +109,8 @@
|
|||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:visibility="gone"
|
||||
android:importantForAccessibility="no" />
|
||||
android:importantForAccessibility="no"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
@ -117,6 +118,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/player_top_holder"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -272,6 +274,7 @@
|
|||
app:tint="@color/white"
|
||||
tools:ignore="ContentDescription" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/player_pause_play_holder_holder"
|
||||
android:layout_width="100dp"
|
||||
|
@ -298,6 +301,7 @@
|
|||
app:tint="@color/white"
|
||||
tools:ignore="ContentDescription" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/player_ffwd_holder"
|
||||
android:layout_width="0dp"
|
||||
|
@ -427,7 +431,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/player_video_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -437,10 +441,10 @@
|
|||
<TextView
|
||||
android:id="@id/exo_position"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="20dp"
|
||||
android:gravity="end"
|
||||
android:gravity="end|center_vertical"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
android:paddingLeft="4dp"
|
||||
|
@ -448,14 +452,46 @@
|
|||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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:layout_width="0dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_weight="1"
|
||||
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:scrubber_color="?attr/colorPrimary"
|
||||
|
@ -463,12 +499,12 @@
|
|||
app:scrubber_enabled_size="24dp"
|
||||
app:unplayed_color="@color/videoProgress" />
|
||||
|
||||
<!-- exo_duration-->
|
||||
<TextView
|
||||
android:id="@id/exo_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center|center_vertical"
|
||||
|
||||
android:layout_marginEnd="20dp"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
|
@ -477,9 +513,10 @@
|
|||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="23:20" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -503,13 +503,12 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/player_video_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="ltr"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/player_pause_play"
|
||||
|
||||
|
@ -526,14 +525,17 @@
|
|||
|
||||
android:tag="@string/tv_no_focus_tag"
|
||||
app:tint="@color/player_button_tv"
|
||||
tools:ignore="ContentDescription" />
|
||||
tools:ignore="ContentDescription"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/exo_position"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="end"
|
||||
android:layout_marginStart="20dp"
|
||||
android:gravity="end|center_vertical"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
android:paddingLeft="4dp"
|
||||
|
@ -541,29 +543,59 @@
|
|||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/player_pause_play"
|
||||
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:layout_width="0dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
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:scrubber_color="?attr/colorPrimary"
|
||||
app:scrubber_dragged_size="26dp"
|
||||
app:scrubber_enabled_size="24dp"
|
||||
app:unplayed_color="@color/videoProgress" />
|
||||
|
||||
<!-- exo_duration-->
|
||||
<TextView
|
||||
android:id="@id/exo_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center|center_vertical"
|
||||
|
||||
android:layout_marginEnd="20dp"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
|
@ -572,9 +604,11 @@
|
|||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="23:20" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -212,7 +212,6 @@
|
|||
tools:visibility="visible" />
|
||||
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/player_center_menu"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -294,6 +293,7 @@
|
|||
app:tint="@color/white"
|
||||
tools:ignore="ContentDescription" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/player_ffwd_holder"
|
||||
android:layout_width="0dp"
|
||||
|
@ -420,20 +420,20 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/player_video_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layoutDirection="ltr"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@id/exo_position"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="20dp"
|
||||
android:gravity="end"
|
||||
android:gravity="end|center_vertical"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
android:paddingLeft="4dp"
|
||||
|
@ -441,14 +441,46 @@
|
|||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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:layout_width="0dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_weight="1"
|
||||
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:scrubber_color="?attr/colorPrimary"
|
||||
|
@ -456,13 +488,13 @@
|
|||
app:scrubber_enabled_size="24dp"
|
||||
app:unplayed_color="@color/videoProgress" />
|
||||
|
||||
<!-- exo_duration-->
|
||||
<TextView
|
||||
android:id="@id/exo_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center|center_vertical"
|
||||
|
||||
android:layout_marginEnd="30dp"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
android:paddingLeft="4dp"
|
||||
|
@ -470,19 +502,23 @@
|
|||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="@id/player_fullscreen"
|
||||
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
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -19,5 +19,5 @@
|
|||
<dimen name="navbar_width">62dp</dimen>
|
||||
|
||||
<dimen name="download_size">50dp</dimen>
|
||||
|
||||
<dimen name="video_frame_width">1dp</dimen>
|
||||
</resources>
|
Loading…
Reference in a new issue