Exoplayer 搜索栏预览

Exoplayer seekbar preview

我正在尝试像在 youtube 或 plex 中一样在我的 exoplayer 上的搜索栏中添加预览(见下图)

我找到了这个 library 但它还不是最新的。

我已经有了每帧图像,但我不知道如何将它们整合到我的 Exoplayer 中,我正在寻找我应该从哪里开始的教程或解释,因为我有点迷路了。

我在浏览 exoplayer 文档时发现 Timebar.onScrubListener。我猜我会使用这 3 个侦听器来获取擦洗的位置并显示相应的图像。

更新: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
    }
}

Activity


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_width="match_parent"
    android:layout_height="wrap_content"
    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_width="match_parent"
        android:layout_height="wrap_content"
        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_width="wrap_content"
        android:layout_height="wrap_content"
        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_width="0dp"
        android:layout_height="0dp"
        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_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY" />

    </FrameLayout>

    <com.google.android.exoplayer2.ui.DefaultTimeBar
        android:id="@id/exo_progress"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="26dp"
        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_width="wrap_content"
        android:layout_height="wrap_content"
        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:width="2dp"
        android:color="@android:color/white" />

    <solid android:color="@android:color/black" />
</shape>

可能会有一些改进,请随时发表评论