【问题标题】:Exoplayer seekbar previewExoplayer 搜索栏预览
【发布时间】:2020-09-05 07:48:26
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: android exoplayer2.x


    【解决方案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_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>
    

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

    【讨论】:

    • 你能解释一下这个[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/...)。因为在这张图片中,您有一些与您的视频相关的屏幕截图。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-17
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-06
    • 2023-03-05
    相关资源
    最近更新 更多