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
相关产品推荐
相关产品推荐

