You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中如何加载本地.vtt文件实现VideoView拖动缩略图显示

实现Android VideoView拖动进度条显示VTT缩略图

你的VTT文件是雪碧图式的缩略图索引,每一条目对应雪碧图中的一个切片区域。要实现拖动进度条显示对应缩略图,需要完成VTT解析、进度监听、图片裁剪、悬浮显示这几个核心步骤,具体实现如下:

1. 定义数据结构与解析VTT文件

首先创建一个数据类存储单条缩略图的核心信息:

data class ThumbnailInfo(
    val startTimeMs: Long,
    val endTimeMs: Long,
    val imagePath: String,
    val x: Int,
    val y: Int,
    val width: Int,
    val height: Int
)

然后编写解析本地VTT文件的方法,将时间字符串转换为毫秒值,提取雪碧图的切片参数:

fun parseVttFile(vttFilePath: String): List<ThumbnailInfo> {
    val thumbnailList = mutableListOf<ThumbnailInfo>()
    val file = File(vttFilePath)
    if (!file.exists()) return thumbnailList

    file.bufferedReader().useLines { lines ->
        val iterator = lines.iterator()
        // 跳过首行WEBVTT标识
        if (iterator.hasNext()) iterator.next()

        var currentStartTime: Long? = null
        while (iterator.hasNext()) {
            val line = iterator.next().trim()
            if (line.isEmpty()) continue

            // 解析时间区间行
            if (line.contains("-->")) {
                val timeParts = line.split("-->").map { it.trim() }
                currentStartTime = convertTimeToMs(timeParts[0])
                val endTime = convertTimeToMs(timeParts[1])
                // 读取下一行的图片路径与xywh参数
                if (iterator.hasNext()) {
                    val imageLine = iterator.next().trim()
                    val imagePath = imageLine.split("#")[0]
                    val xywhParam = imageLine.split("#")[1].removePrefix("xywh=").split(",")
                    thumbnailList.add(
                        ThumbnailInfo(
                            startTimeMs = currentStartTime,
                            endTimeMs = endTime,
                            imagePath = imagePath,
                            x = xywhParam[0].toInt(),
                            y = xywhParam[1].toInt(),
                            width = xywhParam[2].toInt(),
                            height = xywhParam[3].toInt()
                        )
                    )
                }
            }
        }
    }
    return thumbnailList
}

// 将VTT时间格式(00:00:00.000)转换为毫秒
private fun convertTimeToMs(timeStr: String): Long {
    val parts = timeStr.split(":")
    val hours = parts[0].toLong()
    val minutes = parts[1].toLong()
    val secondsAndMillis = parts[2].split(".")
    val seconds = secondsAndMillis[0].toLong()
    val millis = secondsAndMillis[1].toLong()
    return hours * 3600000 + minutes * 60000 + seconds * 1000 + millis
}

2. 监听SeekBar拖动事件

VideoView默认使用MediaController,需获取其中的SeekBar并添加拖动监听:

val videoView = findViewById<VideoView>(R.id.videoView)
val mediaController = MediaController(this)
videoView.setMediaController(mediaController)
val thumbnailList = parseVttFile("/sdcard/your_path/thumbnails.vtt")
val thumbnailView = findViewById<ImageView>(R.id.thumbnailView)

// 延迟获取SeekBar,确保MediaController完成初始化
videoView.post {
    val seekBar = mediaController.findViewById<SeekBar>(androidx.media.R.id.seekbar)
    seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
            if (fromUser) {
                // 计算当前拖动的进度(毫秒)
                val currentProgressMs = (progress * videoView.duration / 100).toLong()
                // 匹配对应时间区间的缩略图
                val targetThumbnail = thumbnailList.firstOrNull {
                    currentProgressMs in it.startTimeMs..it.endTimeMs
                }
                targetThumbnail?.let { showThumbnail(it, seekBar!!) }
            }
        }

        override fun onStartTrackingTouch(seekBar: SeekBar?) {
            thumbnailView.visibility = View.VISIBLE
        }

        override fun onStopTrackingTouch(seekBar: SeekBar?) {
            thumbnailView.visibility = View.GONE
        }
    })
}

3. 裁剪并显示雪碧图切片

使用BitmapRegionDecoder高效裁剪雪碧图的指定区域,避免加载整张大图浪费内存:

private fun showThumbnail(info: ThumbnailInfo, seekBar: SeekBar) {
    // 替换为你的雪碧图本地绝对路径
    val localImagePath = "${Environment.getExternalStorageDirectory().absolutePath}/${info.imagePath}"
    val file = File(localImagePath)
    if (!file.exists()) return

    try {
        val inputStream = FileInputStream(file)
        val decoder = BitmapRegionDecoder.newInstance(inputStream, false)
        val region = Rect(info.x, info.y, info.x + info.width, info.y + info.height)
        val bitmap = decoder.decodeRegion(region, BitmapFactory.Options())
        thumbnailView.setImageBitmap(bitmap)
        // 调整悬浮缩略图的位置,对齐SeekBar滑块
        adjustThumbnailPosition(seekBar)
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

// 调整悬浮View位置,使其显示在SeekBar滑块上方
private fun adjustThumbnailPosition(seekBar: SeekBar) {
    val location = IntArray(2)
    seekBar.getLocationOnScreen(location)
    val seekBarX = location[0]
    val seekBarY = location[1]
    // 计算滑块的X坐标
    val progressRatio = seekBar.progress.toFloat() / seekBar.max.toFloat()
    val thumbX = seekBarX + (seekBar.width * progressRatio).toInt() - thumbnailView.width / 2
    // 悬浮在SeekBar上方,留出间距
    val thumbY = seekBarY - thumbnailView.height - 20
    thumbnailView.x = thumbX.toFloat()
    thumbnailView.y = thumbY.toFloat()
}

4. 布局配置

在布局文件中添加用于显示缩略图的悬浮ImageView,默认隐藏:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/thumbnailView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:background="#FFFFFF"
        android:padding="4dp" />
</RelativeLayout>

注意事项

  • 确保已申请外部存储读取权限(Android 10及以上需适配分区存储逻辑)
  • 替换代码中的VTT文件路径和雪碧图路径为你的实际本地路径
  • 可添加Bitmap缓存逻辑,避免重复裁剪雪碧图提升性能

内容的提问来源于stack exchange,提问作者Hardik K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 13:55:29