Exoplayer 搜索栏预览
Posted
技术标签:
【中文标题】Exoplayer 搜索栏预览【英文标题】:Exoplayer seekbar preview 【发布时间】:2020-09-05 07:48:26 【问题描述】:我正在尝试在我的 exoplayer 上向我的搜索栏添加预览,就像在 youtube 或 plex 中一样(见下图)
我找到了这个library,但它还不是最新的。
我已经有了每帧的图像,但我不知道如何将它们集成到我的 Exoplayer
中,我正在寻找应该从哪里开始的教程或解释,因为我有点迷路了。
我在浏览 exoplayer 文档时发现了 Timebar.onScrubListener
。我猜我将使用这 3 个侦听器来获取擦洗的位置并显示相应的图像。
【问题讨论】:
【参考方案1】:更新:library 在 2020 年 5 月是最新的,因此您可以直接使用它。
我会在下面为那些不想使用该库的人留下代码。
在搜索并调整它以适应我的需求后,我通过查看 previewSeekBar
的工作方式找到了一种方法,最终我使用了相同的东西,所以它是:
我的精灵由10列6行组成,每个方块代表1秒
滑行变换
private const val MAX_LINES = 6
private const val MAX_COLUMNS = 10
private const val THUMBNAILS_EACH = 1000 // milliseconds
private const val ONE_MINUTE = 60000 // one minute in millisecond
class GlideThumbnailTransformation(position: Long) : BitmapTransformation()
private val x: Int
private val y: Int
init
// Remainder of position on one minute because we just need to know which square of the current miniature
val square = position.rem(ONE_MINUTE).toInt() / THUMBNAILS_EACH
y = square / MAX_COLUMNS
x = square % MAX_COLUMNS
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap
val width = toTransform.width / MAX_COLUMNS
val height = toTransform.height / MAX_LINES
return Bitmap.createBitmap(toTransform, x * width, y * height, width, height)
override fun updateDiskCacheKey(messageDigest: MessageDigest)
val data: ByteArray = ByteBuffer.allocate(8).putInt(x).putInt(y).array()
messageDigest.update(data)
override fun hashCode(): Int
return (x.toString() + y.toString()).hashCode()
override fun equals(other: Any?): Boolean
if (other !is GlideThumbnailTransformation)
return false
return other.x == x && other.y == y
活动
val thumbnailUrl = "https://bitdash-a.akamaihd.net/content/MI201109210084_1/thumbnails/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.jpg"
exo_progress.addListener(object : TimeBar.OnScrubListener
override fun onScrubMove(timeBar: TimeBar, position: Long)
previewFrameLayout.visibility = View.VISIBLE
val targetX = updatePreviewX(position.toInt(), exoPlayer.duration.toInt())
previewFrameLayout.x = targetX.toFloat()
GlideApp.with(scrubbingPreview)
.load(thumbnailUrl)
.override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL)
.transform(GlideThumbnailTransformation(position))
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(scrubbingPreview)
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean)
previewFrameLayout.visibility = View.INVISIBLE
override fun onScrubStart(timeBar: TimeBar, position: Long)
)
private fun updatePreviewX(progress: Int, max: Int): Int
if (max == 0) return 0
val parent = previewFrameLayout.parent as ViewGroup
val layoutParams = previewFrameLayout.layoutParams as MarginLayoutParams
val offset = progress.toFloat() / max
val minimumX: Int = previewFrameLayout.left
val maximumX = (parent.width - parent.paddingRight - layoutParams.rightMargin)
// We remove the padding of the scrubbing, if you have a custom size juste use dimen to calculate this
val previewPaddingRadius: Int = dpToPx(resources.displayMetrics, DefaultTimeBar.DEFAULT_SCRUBBER_DRAGGED_SIZE_DP).div(2)
val previewLeftX = (exo_progress as View).left.toFloat()
val previewRightX = (exo_progress as View).right.toFloat()
val previewSeekBarStartX: Float = previewLeftX + previewPaddingRadius
val previewSeekBarEndX: Float = previewRightX - previewPaddingRadius
val currentX = (previewSeekBarStartX + (previewSeekBarEndX - previewSeekBarStartX) * offset)
val startX: Float = currentX - previewFrameLayout.width / 2f
val endX: Float = startX + previewFrameLayout.width
// Clamp the moves
return if (startX >= minimumX && endX <= maximumX)
startX.toInt()
else if (startX < minimumX)
minimumX
else
maximumX - previewFrameLayout.width
private fun dpToPx(displayMetrics: DisplayMetrics, dps: Int): Int
return (dps * displayMetrics.density).toInt()
XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:background="#CC000000"
android:orientation="vertical">
<LinearLayout
android:layout_
android:layout_
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal"
android:id="@+id/controlsLayout"
app:layout_constraintBottom_toBottomOf="parent">
<ImageButton android:id="@id/exo_prev"
style="@style/ExoMediaButton.Previous"/>
<ImageButton android:id="@id/exo_rew"
style="@style/ExoMediaButton.Rewind"/>
<ImageButton android:id="@id/exo_repeat_toggle"
style="@style/ExoMediaButton"/>
<ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"/>
<ImageButton android:id="@id/exo_ffwd"
style="@style/ExoMediaButton.FastForward"/>
<ImageButton android:id="@id/exo_next"
style="@style/ExoMediaButton.Next"/>
</LinearLayout>
<TextView android:id="@id/exo_position"
android:layout_
android:layout_
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"
app:layout_constraintBottom_toTopOf="@id/controlsLayout"
app:layout_constraintStart_toStartOf="parent"/>
<FrameLayout
android:id="@+id/previewFrameLayout"
android:layout_
android:layout_
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/video_frame"
android:padding="2dp"
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/scrubbingPreview"
android:layout_
android:layout_
android:scaleType="fitXY" />
</FrameLayout>
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_
android:layout_weight="1"
android:layout_
app:layout_constraintBottom_toBottomOf="@id/exo_position"
app:layout_constraintEnd_toStartOf="@id/exo_duration"
app:layout_constraintStart_toEndOf="@+id/exo_position"
app:layout_constraintTop_toTopOf="@+id/exo_position"/>
<TextView android:id="@id/exo_duration"
android:layout_
android:layout_
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
drawable/video_frame
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:
android:color="@android:color/white" />
<solid android:color="@android:color/black" />
</shape>
可能会有一些改进,请随时发表评论
【讨论】:
你能解释一下这个[MOSAIQUE_URL],val thumbnailUrl = "$MOSAIQUE_URL$position.div(60000).jpg" 这是一个错误,我会从代码中删除它,thumbnailUrl
类似于bitdash-a.akamaihd.net/content/MI201109210084_1/thumbnails/…,感谢.transform(GlideThumbnailTransformation(position))
我不会计算并显示好的框架
如何生成缩略图?
我不知道如何生成这样的缩略图
如何创建这种类型的图片链接 (bitdash-a.akamaihd.net/content/MI201109210084_1/thumbnails/...)。因为在这张图片中,您有一些与您的视频相关的屏幕截图。以上是关于Exoplayer 搜索栏预览的主要内容,如果未能解决你的问题,请参考以下文章
如何用recyclerview和viewpager2实现exoplayer?
使用 ExoPlayer 的 Android MediaPlayer 持久缓存