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 搜索栏预览的主要内容,如果未能解决你的问题,请参考以下文章

ExoPlayer 无法与摄像机 IP 地址建立连接

如何用recyclerview和viewpager2实现exoplayer?

在 ExoPlayer 中检测暂停/恢复

使用 ExoPlayer 的 Android MediaPlayer 持久缓存

Exoplayer Recyclerview 第二次播放错误的视频

如何将元数据从 ExoPlayer 发送到蓝牙?