vue3 实现音频转文字组件

使用recorder-core第三方插件实现音频转纯文本的功能。

工具类文件
recoder.ts

import Recorder from 'recorder-core'
import 'recorder-core/src/engine/wav'
import 'recorder-core/src/extensions/lib.fft.js'
import 'recorder-core/src/extensions/frequency.histogram.view'
interface RecorderConfig {onProcess?: Promise<any> | Function[keyname: string]: any
}
interface FrequencyHistogramViewConfig {[keyname: string]: any
}
let recorderInstance: any = null
export const RecorderContructor = Recorder
export const createRecorder = (config?: RecorderConfig) => {if (recorderInstance) {return recorderInstance}recorderInstance = Recorder({type: 'wav', // 录音格式,可以换成wav等其他格式sampleRate: 16000, // 录音的采样率,越大细节越丰富越细腻bitRate: 16, // 录音的比特率,越大音质越好...(config || {})// onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) => {//   // 录音实时回调,大约1秒调用12次本回调//   // 可实时绘制波形,实时上传(发送)数据//   if (this.wave) {//     this.wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate)//   }// }})return recorderInstance
}
export const destoryRecorder = () => {if (recorderInstance) {recorderInstance.close()recorderInstance = nullRecorder.Destroy()}
}
export const createRecorderWithWaveView = (el: HTMLElement, config?: FrequencyHistogramViewConfig) => {return Recorder.FrequencyHistogramView({elem: el,lineCount: 30,position: 0,minHeight: 1,fallDuration: 400,stripeEnable: false,mirrorEnable: true,linear: [0, '#fff', 1, '#fff'],...(config || {})})
}

组件文案
AudioInput.vue

<template><div v-if="visibleModal" class="custom-Modal-container"><Teleport to="body"><div class="modal_box" ref="modalRef"><div class="modal_mask" @click.stop="closeModal"></div><div class="modal_content"><div class="audio_box"><div class="audio_header"><span class="audio_title_text"><span v-if="audioStatus == 'input'">收音中</span><span v-else-if="audioStatus == 'transform' || audioStatus == 'end'">识别中</span><span v-else-if="audioStatus == 'unknown'">停止收音</span>...</span><svg-icon class="close_icon" iconFileName="关闭" /></div><div class="audio_content"><div class="input_content_box"><div class="input_content"><span v-if="audioStatus == 'input'">请说,我在聆听…</span><span v-else-if="audioStatus == 'transform' || audioStatus == 'end'">{{ audioContentText }}</span><span class="unknow_tip_text" v-else-if="audioStatus == 'unknown'">未能识别,请点击图标重试</span></div><div v-if="audioStatus == 'input'" class="input_tip_text">您可以说出您需要搜索的内容关键词</div><div v-if="audioStatus == 'unknown'" class="input_tip_text">说出您需要搜索的内容关键词</div></div><div class="audio_icon_box" :class="audioStatus"><i v-if="audioStatus == 'unknown'" class="img_box input_audio" @click="reStartRecorderHandle"></i><i v-if="audioStatus == 'end'" class="img_box input_audio" @click="confirmSearchHandle"></i><i v-if="audioStatus == 'input'" class="img_box input_audio" @click="finishRecorderHandle"></i><i v-if="audioStatus == 'transform'" class="img_box input_audio" @click="closeModal"></i></div><div ref="recorderWaveRef" class=""></div></div></div></div></div></Teleport></div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { v4 as uuidv4 } from 'uuid'
// 语音输入工具
import { createRecorder, createRecorderWithWaveView, destoryRecorder } from './recorder'// api
import { getVoiceToText } from '@/services/common'// type interface
type AudioInputStatus = 'ready' | 'input' | 'transform' | 'end' | 'unknown'const visibleModal = defineModel<boolean>()
const emit = defineEmits(['close', 'complete'])const audioStatus = ref<AudioInputStatus>('ready')
const modalRef = ref<any>(null)
const audioContentBlobData = ref<string>('')
const audioContentText = ref<string>('')
// recorder
const recorderIntance = ref<any>(null)
const recorderWaveInstance = ref<any>(null)
const recorderWaveRef = ref<any>(null)
const isLoadingRecorder = ref<boolean>(false)/** *************** method ************** **/
const initRecorder = () => {recorderIntance.value = createRecorder({onProcess: (buffers: any[], powerLevel: any, bufferDuration, bufferSampleRate: any, newBufferIdx, asyncEnd) => {// 录音实时回调,大约1秒调用12次本回调// 可实时绘制波形,实时上传(发送)数据if (recorderWaveInstance.value) {recorderWaveInstance.value.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate)}}})
}
// 开始录音
const startRecorder = async () => {audioStatus.value = 'input'audioContentBlobData.value = ''audioContentText.value = ''isLoadingRecorder.value = trueawait new Promise((resolve, reject) => {recorderIntance.value.open(async () => {console.log('录音已打开')resolve(true)},(msg: string, isUserNotAllow: boolean) => {console.error('打开录音出错:' + msg, 'isUserNotAllow: ', isUserNotAllow)reject(false)})})try {if (recorderWaveRef.value) {// 创建音频可视化图形绘制对象recorderWaveInstance.value = createRecorderWithWaveView(recorderWaveRef.value)}} catch (err) {console.error('音频可视化图形绘制出错', err)}try {console.log('尝试录音打开')isLoadingRecorder.value = falseawait recorderIntance.value.start()console.log('录音已打开')} catch {console.error('打开录音出错')audioStatus.value = 'unknown'} finally {isLoadingRecorder.value = false}
}
// 结束录音
const stopRecorderHandle = async () => {audioStatus.value = 'transform'try {console.log('尝试终止录音')const { blob, duration } = await new Promise((resolve, reject) => {recorderIntance.value.stop((blob: any, duration: any) => {resolve({ blob, duration })},err => {console.error('终止录音出错:' + err)recorderIntance.value.close()reject({ error: true, msg: err })})})// 简单利用URL生成本地文件地址,此地址只能本地使用,比如赋值给audio.src进行播放,赋值给a.href然后a.click()进行下载(a需提供download="xxx.mp3"属性)// this.localUrl = URL.createObjectURL(blob)// console.log('录音成功blob', blob)// console.log('localUrl', this.localUrl)console.log('时长:' + duration + 'ms')await recorderIntance.value.close()audioContentBlobData.value = blob} catch {audioStatus.value = 'input'}
}// 重置输入
const reStartRecorderHandle = async () => {if (isLoadingRecorder.value) returnisLoadingRecorder.value = falseawait stopRecorderHandle().catch(err => err)await startRecorder()
}
// 完成录音
const finishRecorderHandle = async () => {if (isLoadingRecorder.value) returnisLoadingRecorder.value = truetry {await stopRecorderHandle()// 获取语音转文本并返回文案await fetchVoiceToText()audioStatus.value = 'end'} catch {audioStatus.value = 'unknown'} finally {isLoadingRecorder.value = false}
}
// 把录音转成文本
const fetchVoiceToText = async () => {const voice_data = audioContentBlobData.valueconst formData = new FormData()formData.append('voice_data', voice_data)formData.append('seq', 0)formData.append('end', 1)formData.append('voice_id', uuidv4())formData.append('voice_format', 12)const { code, data } = await getVoiceToText(formData)if (code === 200) {console.log(data)const { text } = dataaudioContentText.value = text}
}
const confirmSearchHandle = () => {const text = audioContentText.valueemit('complete', text)audioStatus.value = 'ready'visibleModal.value = falseaudioContentBlobData.value = ''audioContentText.value = ''
}
const closeModal = async () => {await stopRecorderHandle()audioStatus.value = 'ready'visibleModal.value = falseaudioContentBlobData.value = ''audioContentText.value = ''emit('close')
}
/** ***************** watch ************** **/
watch(visibleModal, async val => {if (val) {await startRecorder()}
})
/** *******************    life cycle ******************* **/
onMounted(() => {initRecorder()
})
onUnmounted(() => {recorderIntance.value = nulldestoryRecorder()
})
/**  ************** component expose *********** **/
defineExpose({closeModal,visibleModal
})
</script>
<style lang="scss" scoped>
.modal_box {position: fixed;width: 100%;left: 0;top: 0;right: 0;bottom: 0;z-index: 2000;
}
.modal_mask {position: absolute;left: 0;bottom: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.5);
}
.modal_content {position: absolute;bottom: 32px;z-index: 1;left: 0;right: 0;padding: 0 12px;box-sizing: border-box;* {box-sizing: border-box;}.audio_box {border-radius: 10px;position: relative;width: auto;background-image: linear-gradient(91deg, #7d79ff 9%, #43e1ff 93%);.audio_header {height: 44px;border-radius: 10px 10px 0 0;color: #ffffff;font-size: 14px;font-weight: bold;display: flex;align-items: center;justify-content: space-between;padding-left: 22px;padding-right: 18px;}.close_icon {width: 12px;height: 12px;}}.audio_content {height: 248px;padding: 24px;border-radius: 10px;background-color: #ffffff;}.input_content_box {height: 64px;text-align: center;overflow-y: auto;}.input_content {text-align: center;font-size: 16px;color: $color-text;font-weight: bold;line-height: 22px;}.unknow_tip_text {color: $color-danger;}.input_tip_text {margin-top: 10px;font-size: 12px;line-height: 17px;color: $color-text-light-3;text-align: center;}.audio_icon_box {display: flex;align-items: center;justify-content: center;.img_box {width: 138px;height: 138px;display: block;&.input_audio {background: url('@/assets/images/audio_input_icon.png') no-repeat center center;}}}
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/487134.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Windows11系统局域网共享文件数据】

【Windows11系统局域网共享文件数据】 1. 引言1. 规划网络2. 获取必要的硬件3. 设置网络4. 配置网络设备5. 测试网络连接6. 安全性和维护7. 扩展和优化 2. 准备工作2.1: 启用网络发现和文件共享2.2: 设置共享文件夹 3. 访问共享文件夹4. 小贴士5. 总结 1. 引言 随着家庭和小型办…

记录ubuntu22.04重启以后无法获取IP地址的问题处理方案

现象描述&#xff1a;我的虚拟机网络设置为桥接模式&#xff0c;输入ifconfig只显示127.0.0.1&#xff0c;不能连上外网。&#xff0c;且无法上网&#xff0c;用ifconfig只有如下显示&#xff1a; 1、sudo -i切换为root用户 2、输入dhclient -v 再输入ifconfig就可以看到多了…

guava 整合springboot 自定义注解实现接口鉴权调用保护

文章目录 一、简要概述二、实现过程1. pom引入依赖2. 自定义注解3. 定义切面4. 定义权限检查逻辑 三、注解使用四、运行结果五、源码放送 一、简要概述 Guava Cache是一个全内存的本地缓存实现&#xff0c;它提供了线程安全的实现机制。我们借助expireAfterWrite过期时间设置和…

MQTT消息服务器mosquitto介绍及说明

Mosquitto是一个开源的消息代理软件&#xff0c;支持MQTT协议&#xff08;消息队列遥测传输协议&#xff09;。MQTT是一种轻量级的发布/订阅消息传输协议&#xff0c;专为低带宽、不可靠网络环境下的物联网设备通信而设计。以下是关于Mosquitto服务器的一些介绍和说明&#xff…

React 组件中 State 的定义、使用及正确更新方式

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;React篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来React篇专栏内容React 组件中 State 的定义、使用及正确更新方式 前言 在 React 应用开发中&#xff0c;state …

DLL注入(AppInit_DLLs)

DLL注入(AppInit_DLLs) 一&#xff1a;概述 利用注册表进行dll注入&#xff0c;Windows操作系统的注册表默认是提供了AppInit_DLLs和LoadAppInit_DLLs两个注册表项的。打开我们的注册表编辑器&#xff0c;将要注入的DLL的路径字符串写入到AppInit_DLLs项目&#xff0c;然后将…

Spring Boot + Spring AI快速体验

Spring AI快速体验 1 什么是Spring AI主要功能 2 快速开始2.1 版本说明2.2 配置文件2.3 pom依赖2.3.1 spring maven仓库2.3.2 核心依赖 2.4 定义ChatClient2.5 启动类2.6 测试 3 参考链接 1 什么是Spring AI Spring AI是Spring的一个子项目&#xff0c;是Spring专门面向于AI的…

算法基础学习Day5(双指针、动态窗口)

文章目录 1.题目2.题目解答1.四数之和题目及题目解析算法学习代码提交 2.长度最小的子数组题目及题目解析滑动窗口的算法学习方法一&#xff1a;单向双指针(暴力解法)方法二&#xff1a;同向双指针(滑动窗口) 代码提交 1.题目 18. 四数之和 - 力扣&#xff08;LeetCode&#x…

通义千问sft-甄嬛对话

流程步骤 https://www.datawhale.cn/activity/110/21/76?rankingPage1 按照上面的流程&#xff0c;准备好数据之后就可以直接对7b的模型进行指令微调了&#xff0c;整个流程不是很复杂&#xff0c;操作起来比较方便。但是发布服务等了较长时间&#xff0c;以为出了bug 结果展…

1-6 ESP32控制LED灯

1.0 LED简介 LED是英文 "Light Emitting Diode" 的缩写&#xff0c;中文翻译为发光二极管。它是一种能够将电能转化为光能的电子元件。LED是一种半导体器件&#xff0c;在通电时会发出可见光。和传统的白炽灯泡或荧光灯相比&#xff0c;LED具有诸多优点&#xff1a;高…

前端成长之路:HTML(1)

每个网页都会有一个基本的结构标签&#xff08;也称为骨架标签&#xff09;&#xff0c;页面内容也是在这些基本标签上书写。 基本结构标签&#xff08;骨架标签&#xff09; <html></html>标签是HTML标签&#xff0c;是页面中最大的标签&#xff0c;被称为根标签…

细说敏捷:敏捷四会之回顾会

在前面的分享中&#xff0c;我们已经梳理了计划会、每日站会和复盘会的召开要点&#xff0c;本篇我们再对Scrum敏捷四大仪式中的最后一个会议仪式 - 迭代回顾会 进行探讨 回顾会的目的和作用 回顾会因为和复盘会一般都放在迭代的最后一天&#xff0c;而且通常安排是相邻在一起…

重生之我在异世界学智力题(1)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言智力题题目&#xff1a;《奇怪的时钟…

【模型对比】ChatGPT vs Kimi vs 文心一言那个更好用?数据详细解析,找出最适合你的AI辅助工具!

在这个人工智能迅猛发展的时代&#xff0c;AI聊天助手已经深入我们的工作与生活。你是否曾在选择使用ChatGPT、Kimi或是百度的文心一言时感到一头雾水&#xff1f;每款AI都有其独特的魅力与优势&#xff0c;那么&#xff0c;究竟哪一款AI聊天助手最适合你呢&#xff1f;本文将带…

【时时三省】(C语言基础)结构体内存对齐练习题

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 练习一 这个输出结果是8 练习二 这个输出结果是16 练习三 这个输出结果是32 上面的输出结果都是根据结构体对齐规则来计算的

【python】UTF-8编码

# -*- coding: utf-8 -*-import sys reload(sys) # This reloads the system default encoding setup sys.setdefaultencoding(utf-8) # Set the default encoding to utf-8 print(sys.getdefaultencoding())写在最后&#xff1a;若本文章对您有帮助&#xff0c;请点个赞啦 ٩…

MySQL 性能优化详解

MySQL 性能优化详解 硬件升级系统配置优化调整buffer_pool数据预热降低日志的磁盘落盘 表结构设计优化SQL语句及索引优化SQL优化实战案例 MySQL性能优化我们可以从以下四个维度考虑&#xff1a;硬件升级、系统配置、表结构设计、SQL语句和索引。 从成本上来说&#xff1a;硬件升…

PCB设计规范

过孔设计 过孔盖油工艺&#xff08;也成为连塞带印&#xff09;&#xff1a;常规工艺、免费工艺&#xff0c;无特殊情况也建议使用此工艺。过孔大小建议直径在0.3mm-0.5mm之间。最省钱&#xff0c;效果最好。 非金属化槽孔 PCB制造商在加工非金属化槽孔时通常采用锣刀加工。最…

MVC基础——市场管理系统(二)

文章目录 项目地址三、Produtcts的CRUD3.1 Products列表的展示页面(Read)3.1.1 给Product的Model里添加Category的属性3.1.2 View视图里展示Product List3.2 增加Product数据(Add)3.2.1 创建ViewModel用来组合多个Model3.2.2 在_ViewImposts里引入ViewModels3.2.3 添加Add的…

vivado中,generate output product 和Create HDL wrapper的作用

generate output product 以zynq的ip核举例&#xff0c;没有generate output product之前&#xff0c;在ip source 什么也看不到。 但是同样的一个ip核&#xff0c;generate output product之后&#xff0c;会生成综合&#xff0c;布线和仿真文件&#xff0c;约束文件等等。 …