当前位置:首页 > 每日看点 > 正文内容

各位大神,vue怎么加流动字幕?

卡卷网7个月前 (05-07)每日看点201
他们朝我扔泥巴,我拿泥巴种荷花;他们朝我扔巴巴,我用巴巴敲代码,哦哦哦哦哦...

需求描述

  • 有一个MP3音频文件,在播放的时候,需要展示对应的字幕给到用户
  • 即为需要做到视频和音频同步的效果
  • 如下效果图
  • 演示地址:
ashuai.work:8890/19

各位大神,vue怎么加流动字幕?  第1张

字幕文件的种类

常见的字幕文件,有三种

1. SRT格式(SubRip Subtitle)

最常见的字幕格式,包含了字幕文本、显示时间(开始和结束时间),文件结构简单、易于创建,如下简单示例:

1 00:00:01,000 --> 00:00:04,000 你好这个世界 2 00:00:05,000 --> 00:00:08,000 这个世界你好

2. VVT格式(WebVTT,Web Video Text Tracks)

HTML网页专属,前端最常用,支持HTML5视频元素。与SRT类似,但具有更多的功能,如HTML标签、文本样式和位置。如下简单示例:

WEBVTT 00:00:00.100 --> 00:00:02.175 不必说碧绿的菜畦 00:00:02.125 --> 00:00:03.850 光滑的石井栏

3. ASS格式(Advanced SubStation Alpha)

比较复杂的字幕文件,支持更多的样式和特效,如字体、颜色、位置等,常用于高质量的视频或动画字幕。用的少,如下示例:

[Script Info] Title: Example Subtitle Original Script: John Doe ScriptType: v4.00+ [Events] Dialogue: 0,0:00:01.00,0:00:05.00,Default,,0,0,0,,Hello, how are you?

就前端而言,VVT用的最多,因此本篇文章,我们以VVT来讲解

首先来一份字幕文件

字幕文件如何获取

  • 这里笔者推荐一些在线网站
  • 可以直接把纯人声音频或者视频转出一个字幕文件
  • 比如这个熊猫字幕:在线字幕自动生成工具_字幕制作_语音转字幕-熊猫字幕 (pdsub.com)
另外,可能部分道友会遇到想要把文本转语音的同时,再生成对应的字幕,后续笔者也会出一篇TTS文章,敬请期待...

示例VVT字幕

WEBVTT 00:00:00.100 --> 00:00:02.175 不必说碧绿的菜畦 00:00:02.125 --> 00:00:03.850 光滑的石井栏 00:00:03.850 --> 00:00:05.713 高大的皂荚树 00:00:05.713 --> 00:00:07.287 紫红的桑葚 00:00:07.287 --> 00:00:10.350 也不必说鸣蝉在树叶里长吟 00:00:10.350 --> 00:00:13.062 肥胖的黄蜂伏在菜花上 00:00:13.062 --> 00:00:18.488 轻捷的叫天子云雀忽然从草间直窜向云霄里去了 00:00:18.488 --> 00:00:21.000 单是周围的短短的泥墙根一带 00:00:21.000 --> 00:00:22.738 就有无限趣味 00:00:22.738 --> 00:00:24.438 油蛉在这里低唱 00:00:24.438 --> 00:00:26.613 蟋蟀们在这里弹琴 00:00:26.613 --> 00:00:28.337 翻开断砖来 00:00:28.337 --> 00:00:30.113 有时会遇见蜈蚣 00:00:30.113 --> 00:00:31.488 还有斑蝥 00:00:31.488 --> 00:00:33.950 倘若用手指按住它的脊梁 00:00:33.950 --> 00:00:35.625 便会的一声 00:00:35.625 --> 00:00:38.175 从后窍喷出一阵烟雾

一、audio标签形式之读取并加工展示字幕

1. 读取字幕

  • 这里把字幕文件,放在public文件夹下
  • 再使用fetch去得到对应字幕文件内容

onMounted(() => { getVvtData(); }); const getVvtData = async () => { // 获取当前字幕文件的路径 const vvtUrl = new URL("/subtitles/1.vvt", import.meta.url).href; // 使用fetch请求,此路径下的字幕文件 const response = await fetch(vvtUrl); // 状态判断 if (!response.ok) throw new Error("网络错误或文件不存在"); // 拿到字幕数据转成的文本 const vvtData = await response.text(); // 使用正则将字幕文件加工成JSON格式 subtitles.value = parseVvtData(vvtData); };

字幕文本不能直接使用,所以我们需要将其转成对象形式,才方便使用

2. 解析并加工成对象形式

// 解析字幕文件并将其转换为 JSON const parseVvtData = (data) => { const subtitlePattern = /(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})\s*([\s\S]+?)(?=\n\d{2}:\d{2}:\d{2}\.\d{3}|$)/g; let matches; const parsedSubtitles = []; while ((matches = subtitlePattern.exec(data)) !== null) { const start = convertTimeToSeconds(matches[1]); const end = convertTimeToSeconds(matches[2]); const text = matches[3].trim(); parsedSubtitles.push({ start, end, text, }); } return parsedSubtitles; }; // 将字幕时间从字符串"00:00:05.000" 格式转换为秒数数字 const convertTimeToSeconds = (timeStr) => { const [hours, minutes, seconds] = timeStr.split(":"); const [sec, ms] = seconds.split("."); return ( parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(sec) + parseInt(ms) / 1000 ); };

加工完毕以后,能得到这样的字幕数组对象,如下:

各位大神,vue怎么加流动字幕?  第2张

  • 即为,数组中每一项,都是一条字幕对象
  • 字幕对象记录了字幕开始时间,字幕结束时间,以及在开始结束时间之间,需要呈现的字幕文字
  • 这样的话,我们就可以在对应时间节点,展示对应字幕即可

3. 音频播放的时候,根据时间,找到对应的字幕展示即可

当音频播放的时候,audio标签,自带的timeupdate事件,可以拿到当前播放的时间是什么时间节点

<audio ref="myAudioRef" @timeupdate="timeupdate" controls :src="mp3"></audio> // 展示字幕的div <div v-if="currentSubtitle">{{ currentSubtitle.text }}</div> const timeupdate = (e) => { // 当前音频播放的时间 currentTime.value = e.target.currentTime; updateSubtitle(currentTime.value); }; // 根据当前时间戳更新显示的字幕 const updateSubtitle = (curTime) => { // 根据播放的时间,找到当前播放的是哪一项 const subtitle = subtitles.value.find( // 当前时间,大于字幕开始,小于字幕结束 (sub) => curTime >= sub.start && curTime <= sub.end ); // 找到对应字幕项 currentSubtitle.value = subtitle || null; };

4. 完整代码(单行字幕播放)

至于多行字幕,就是循环不断往后拼接即可,这里不赘述

<template> <div class="boxA"> <h3>音频播放字幕同步出现——只显示单条</h3> <audio ref="myAudioRef" @timeupdate="timeupdate" controls :src="mp3"></audio> <div v-if="currentSubtitle">{{ currentSubtitle.text }}</div> </div> </template> <script setup> import { ref, onMounted } from "vue"; import mp3 from "./1.mp3"; const myAudioRef = ref(); const currentTime = ref(); const subtitles = ref(); // 所有字幕数据 const currentSubtitle = ref(); // 当前显示的字幕项 onMounted(() => { getVvtData(); }); const getVvtData = async () => { // 获取当前字幕文件的路径 const vvtUrl = new URL("/subtitles/1.vvt", import.meta.url).href; // 使用fetch请求,此路径下的字幕文件 const response = await fetch(vvtUrl); // 状态判断 if (!response.ok) throw new Error("网络错误或文件不存在"); // 拿到字幕数据转成的文本 const vvtData = await response.text(); // 使用正则将字幕文件加工成JSON格式 subtitles.value = parseVvtData(vvtData); }; // 解析字幕文件并将其转换为 JSON const parseVvtData = (data) => { const subtitlePattern = /(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})\s*([\s\S]+?)(?=\n\d{2}:\d{2}:\d{2}\.\d{3}|$)/g; let matches; const parsedSubtitles = []; while ((matches = subtitlePattern.exec(data)) !== null) { const start = convertTimeToSeconds(matches[1]); const end = convertTimeToSeconds(matches[2]); const text = matches[3].trim(); parsedSubtitles.push({ start, end, text, }); } return parsedSubtitles; }; // 将字幕时间从字符串"00:00:05.000" 格式转换为秒数数字 const convertTimeToSeconds = (timeStr) => { const [hours, minutes, seconds] = timeStr.split(":"); const [sec, ms] = seconds.split("."); return ( parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(sec) + parseInt(ms) / 1000 ); }; const timeupdate = (e) => { currentTime.value = e.target.currentTime; updateSubtitle(currentTime.value); }; // 根据当前时间戳更新显示的字幕 const updateSubtitle = (curTime) => { // 根据播放的时间,找到当前播放的是哪一项 const subtitle = subtitles.value.find( // 当前时间,大于字幕开始,小于字幕结束 (sub) => curTime >= sub.start && curTime <= sub.end ); currentSubtitle.value = subtitle || null; }; </script> <style lang="less" scoped> .boxA { height: 160px; } </style>

某些情况下,我们不能使用audio标签来播放音频,这个时候,就需要使用另外一种方式:window.AudioContext 去实例化一个音频播放器,q去对应播放音频,如下

二、AudioContext之读取并加工展示字幕

1. 读取字幕并加工字幕

  • 原理很简单,和上述的读取字幕一样,这里不赘述
  • 也是把public文件夹中字幕文件读取并解析
  • 最后得到字幕数组对象
  • 在AudioContext播放音频的时候,使用一个定时器,或者requestAnimationFrame之类的
  • 不断查找当前时间对应的字幕数据,直接展示到页面上

2. 当点击按钮时,播放音频且用定时器,查找字幕数组中的对应文件

如下html结构

<template> <div class="boxA"> <button @click="play">播放音频</button> <!-- 循环出字幕内容 --> <div v-if="displayedSubtitles.length"> <p v-for="(subtitle, index) in displayedSubtitles" :key="index">{{ subtitle }}</p> </div> </div> </template>

注意,play方法的音频和字幕文件的处理使用

const subtitles = ref(); // 所有字幕数据 const displayedSubtitles = ref([]); // 当前显示的所有字幕项 // 创建 AudioContext 实例 const audioContext = new (window.AudioContext || window.webkitAudioContext)(); // 用于播放音频 const currentTime = ref(0); // 当前播放时间 const play = async () => { try { // 获取音频文件并转换为 ArrayBuffer const response = await fetch(mp3); const arrayBuffer = await response.arrayBuffer(); // 解码音频数据 const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); // 创建音频源 const audioSource = audioContext.createBufferSource(); audioSource.buffer = audioBuffer; // 连接音频源到输出(扬声器) audioSource.connect(audioContext.destination); // 播放音频 audioSource.start(); // 设置一个定时器,模拟 timeupdate 事件 const intervalId = setInterval(() => { if (audioContext.state === "running") { currentTime.value = audioContext.currentTime; console.log("currentTime.value", currentTime.value.toFixed(3)); updateSubtitle(currentTime.value); } // 停止定时器,当音频播放结束时 if (audioContext.currentTime >= audioBuffer.duration) { clearInterval(intervalId); } }, 100); // 每100ms更新一次 } catch (error) { console.error("音频播放失败:", error); } }; // 根据当前时间戳更新显示的字幕 const updateSubtitle = (curTime) => { // 找到当前时间点应该显示的字幕 const newSubtitles = subtitles.value.filter( (sub) => curTime >= sub.start && curTime <= sub.end ); // 找到了,就将其添加到displayedSubtitles数组中 if (newSubtitles.length > 0) { // 但是因为timeupdate触发频繁,所以追加前,要看看这条字幕是否存在过 newSubtitles.forEach((subtitle) => { // 不存在,才去往里面追加 if (!displayedSubtitles.value.includes(subtitle.text)) { displayedSubtitles.value.push(subtitle.text); } }); } };

3. 完整代码

在笔者的github上:

github.com/shuirongshui

扫描二维码推送至手机访问。

版权声明:本文由卡卷网发布,如需转载请注明出处。

本文链接:https://www.kajuan.net/ttnews/2025/05/12926.html

分享给朋友:

相关文章

请问什么软件支持看电视直播?

请问什么软件支持看电视直播?

2024年9月6日最新更新:本文列举的所有看电视直播、或流媒体影视资源点播的软件,我这两天又重新梳理成最新版本,最大程度保证可用性。获取方式见文末图片,或见评论。后续我也将持续更新,文章是旧的软件也会是新的,保证你拿到的都是最新可用版本。作...

感觉手机配置都差不多,为什么有的手机能卖2k-3k,而有的手机却能卖到6k-8k?

感觉手机配置都差不多,为什么有的手机能卖2k-3k,而有的手机却能卖到6k-8k?

与所有的商品一样,手机的价格,也是由它的成本所决定的。虽然看起来3000元的手机和6000的手机配置差不多,甚至处理器都可能是同一个,但在很多大家容易忽略的地方,决定了两者价格的不同:例如手机的外观,塑料的机身,与素皮机身和玻璃机身就完全不...

大量刷短视频,会让大脑变笨拙吗?

会。我曾经是一名高三学生,亲身实践过。当时集中突破语文,每天都在刷语文卷。然后有一天想躺一下刷手机,结果短视频刷完了,我再去看哪些文章,只觉头晕眼花,难以理解文字。不过好在这种情况是短时间的,过了一天我的能力又恢复了。在我看来,长期刷短视频...

被网络诈骗了,钱还能追得回来吗?

我刷单被骗,当时不知道怎么办在百度上找个律师说他们能追回,还说不用报警,我傻的就信了,支付了费用签合同,我还是不信他们,报警了,报警没几天警官就连系我追回一笔钱让我注意银行卡到账通知,又过几天我第二笔钱到账了,律师说是他们追回的,要我支付后...

你们发文章的插图都是从哪来的?在网上搜出来的图能用吗?

你们发文章的插图都是从哪来的?在网上搜出来的图能用吗?

写文章发帖子,里面的插图尽量自己画,自己编辑,避免引起版权或者所有权纠纷。我写过6本书,在知乎上也写了4000篇帖子和文章,其中的插图都是自己绘制的,照片绝大多数是自己拍摄的,摘自技术样本的图则必须加以说明。自己制图,看似麻烦,但积少成多,...

国外有没有像国内闲鱼这样的二手平台?

国外有没有像国内闲鱼这样的二手平台?

在当今全球市场,二手电商正迅速崛起,成为全球消费者购物的热门选择。很多国外的消费者,特别是那些注重可持续和环保的人群,包括富裕的消费者都经常购买二手商品。对电商卖家来说,进军二手平台是个很好的创收机会。尤其是在美国,美国二手物品交易平台eB...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。