当前位置: 首页 > news >正文

vue3实现实现手机/PC端录音:recorder-core

在这里插入图片描述

通过 recorder-core 这个插件实现录音

recorder-core插件使用

下方的js文件是安装后封装的一个js文件,在需要使用的地方直接引入这个文件:import record from “./recorderCore.js”;

//  文件名称:recorderCore.js// recorder-core插件使用方式:https://huaweicloud.csdn.net/6549fb3434bf9e25c799ca07.html?dp_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTI5NDc5OSwiZXhwIjoxNzUzOTM3ODg5LCJpYXQiOjE3NTMzMzMwODksInVzZXJuYW1lIjoic2lzdWliYW4ifQ.Y2_R3XsABjzRvhML0rdYMuGJhYrIDM-rrPob4RDJtro&spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Eactivity-6-131902136-blog-147322532.235%5Ev43%5Econtrol&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Eactivity-6-131902136-blog-147322532.235%5Ev43%5Econtrol&utm_relevant_index=13
// 例子 https://blog.csdn.net/weixin_47137972/article/details/147322532?ops_request_misc=%257B%2522request%255Fid%2522%253A%25226a538d344bc89fef66029e7d7a2a0b06%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=6a538d344bc89fef66029e7d7a2a0b06&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~sobaiduend~default-1-147322532-null-null.nonecase&utm_term=vue%E5%AE%9E%E7%8E%B0%E5%BD%95%E9%9F%B3%E5%8A%9F%E8%83%BD&spm=1018.2226.3001.4450
//必须引入的核心
import Recorder from 'recorder-core';//引入mp3格式支持文件;如果需要多个格式支持,把这些格式的编码引擎js文件放到后面统统引入进来即可
import 'recorder-core/src/engine/mp3';
import 'recorder-core/src/engine/mp3-engine';
//录制wav格式的用这一句就行
import 'recorder-core/src/engine/wav';const record = {RecordApp: null,recBlob: null,/**麦克风授权 */getPermission: (fn) => {const newRec = Recorder({type: 'wav',bitRate: 16,sampleRate: 16000, //阿里采样率16000onProcess: function (buffers, powerLevel, duration, bufferSampleRate) {// console.log(buffers);},});//打开录音,获得权限newRec.open(() => {record.RecordApp = newRec;fn({ status: 'success', data: '开启成功' });},(msg, isUserNotAllow) => {//用户拒绝了录音权限,或者浏览器不支持录音fn({ status: 'fail', data: msg });// console.log((isUserNotAllow ? 'UserNotAllow,' : '') + '无法录音:' + msg);});},/**开始录音 */startRecorder: () => {if (record.RecordApp && Recorder.IsOpen()) {record.RecordApp.start();}},/** 停止录音 */stopRecorder: (fn) => {try {if (!record) {// console.error('未打开录音');return;}record.RecordApp.stop((blob, duration) => {// console.log('录音成功', blob, '时长:' + duration + 'ms');if (blob) {record.recBlob = blob;const url = URL.createObjectURL(blob);const formData = new FormData();formData.append('audio', blob);fn({ loading: true }, url, blob);}/* eslint-enable */record.RecordApp.close();record.RecordApp = null;});} catch (err) {fn({ err: err });// console.error('结束录音出错:' + err);record.RecordApp.close();record.RecordApp = null;}},/**关闭录音,释放麦克风资源 */destroyRecorder: () => {if (record.RecordApp) {record.RecordApp.close();record.RecordApp = null;}},/**暂停 */pauseRecorder: () => {if (record.RecordApp) {record.RecordApp.pause();}},/**恢复继续录音 */resumeRecorder: () => {if (record.RecordApp) {record.RecordApp.resume();}},
};export default record;

接下来就是使用上方的js文件

import record from "./config/recorderCore.js";// 点击了开始录音按钮
const onclick_luyin = () => {record.getPermission(function (permiss) {if (permiss.status == "fail") {ElMessage.error(permiss.data); //这里直接写一个报错提示,我这用的是elementUI的} else {record.startRecorder(); //开始录音}});
};
// 录音停止按钮
const onclick_guanbi = () => {record.stopRecorder((res, url, blob) => {if (blob && blob.size) {console.log("文件大小:", blob.size);// 调用语音转文字的接口AudioToText(blob);}});
};
// 语音转文字
const AudioToText = async (file) => {const maxSize = 14 * 1024 * 1024; // 14MBif (file.size > maxSize) {ElMessage.error("语音文件的大小超过14MB,请重新录音");return;}const baseurl = `${DEEPSEEK_CONFIG.baseURL}/audio-to-text`;const apiKey = `${DEEPSEEK_CONFIG.apiKey}`;// 强制指定 MIME 为 audio/mp3const newFile = new File([file], "语音", { type: "audio/mp3" });const formData = new FormData();formData.append("file", newFile);try {const response = await fetch(baseurl, {method: "POST",headers: {Authorization: `Bearer ${apiKey}`,},body: formData,});const text = await response.text();try {const data = JSON.parse(text);if (response.ok) {queryKeys.value = data.text;} else {queryKeys.value = "";ElMessage.error(`语音转文字接口错误:${JSON.stringify(data)}`);}} catch {queryKeys.value = "";ElMessage.error(`语音转文字接口响应不是 JSON:${text}`);}} catch (error) {queryKeys.value = "";ElMessage.error(`语音转文字接口请求异常:${error.message}`);}
};

提供一个vue录音动画组件

<template><transition name="modal-fade"><div v-if="isOpen" class="modal-overlay" @click="handleOverlayClick"><!-- 科技感网格背景 --><div class="tech-grid"></div><!-- 扫描线效果 --><div class="scan-line" :class="{ active: props.isOpen }"></div><div class="modal-container"><!-- 装饰性光效 --><div class="glow-effect top"></div><div class="glow-effect bottom"></div><div class="audio-animation-container"><!-- 高科技风格波形容器 --><div class="wave-container"><divv-for="(bar, index) in bars":key="index"class="wave-bar":style="{height: bar.height,background: bar.gradient,boxShadow: bar.glow,transitionDelay: `${bar.delay}ms`,transform: `scaleX(${bar.scale})`}"></div><!-- 倒计时显示 - 现在位于波形中央 --><div class="countdown-display"><span class="countdown-number">{{ countdown }} / 60</span></div></div></div><!-- 状态指示器 --><div class="status-indicator"><div class="pulse-dot"></div><span style="color:#fff">可以开始讲话了,总共能讲60秒</span></div></div></div></transition>
</template><script setup>
import { ref, onBeforeUnmount, watch, onUnmounted } from "vue";const props = defineProps({isOpen: {type: Boolean,required: true,},// 科技感主色调primaryColor: {type: String,default: '#fff'},// 动画速度(毫秒)speed: {type: Number,default: 80},// 波形条数量barCount: {type: Number,default: 40, // 更多的波形条增强科技感validator: (value) => value >= 20 && value <= 40},// 最大高度比例maxHeightRatio: {type: Number,default: 85,validator: (value) => value >= 70 && value <= 100}
});const emit = defineEmits(["close"]);// 倒计时相关
const countdown = ref(0);
let countdownTimer = null;// 关闭模态框
const closeModal = () => {stopAnimation();stopCountdown();emit("close");
};// 点击遮罩关闭
const handleOverlayClick = () => {closeModal();
};// 生成科技感渐变色
const generateGradient = (index) => {// 基于位置生成微妙的色调变化const hueOffset = (index % 10) * 3;const baseColor = props.primaryColor;const lightColor = shadeColor(baseColor, 30);return `linear-gradient(180deg, ${lightColor} 0%, ${baseColor} 70%)`;
};// 调整颜色明暗度
const shadeColor = (color, percent) => {let R = parseInt(color.substring(1, 3), 16);let G = parseInt(color.substring(3, 5), 16);let B = parseInt(color.substring(5, 7), 16);R = parseInt(R * (100 + percent) / 100);G = parseInt(G * (100 + percent) / 100);B = parseInt(B * (100 + percent) / 100);R = (R < 255) ? R : 255;G = (G < 255) ? G : 255;B = (B < 255) ? B : 255;R = Math.round(R);G = Math.round(G);B = Math.round(B);const RR = ((R.toString(16).length === 1) ? "0" + R.toString(16) : R.toString(16));const GG = ((G.toString(16).length === 1) ? "0" + G.toString(16) : G.toString(16));const BB = ((B.toString(16).length === 1) ? "0" + B.toString(16) : B.toString(16));return `#${RR}${GG}${BB}`;
};// 波形数据数组
const bars = ref([]);
// 初始化波形数据
for (let i = 0; i < props.barCount; i++) {const color = generateGradient(i);const glowColor = shadeColor(props.primaryColor, 50);bars.value.push({height: '5%',gradient: color,glow: `0 0 8px ${glowColor}, 0 0 12px ${glowColor}33`,delay: calculateDelay(i, props.barCount),scale: 1});
}// 计算每个波形条的动画延迟,创建同步波动效果
function calculateDelay(index, total) {// 创造波浪式延迟模式,增强科技感return (index % 5) * 40;
}// 动画定时器
let animationTimer = null;
let pulseTimer = null;// 生成更有规律的波形高度,符合高科技感
const generateHeights = () => {const newBars = [...bars.value];const maxHeight = props.isOpen ? props.maxHeightRatio : 10;const minHeight = props.isOpen ? 5 : 3;// 创建更有规律的波形模式,类似音频频谱const time = Date.now() / 500;newBars.forEach((bar, index) => {// 使用正弦函数创建更流畅的波形const frequency = 0.5 + (index / newBars.length) * 2;const amplitude = props.isOpen ? 0.5 + Math.random() * 0.5 : 0.2;const baseHeight = (maxHeight - minHeight) * 0.5 + minHeight;const wave = Math.sin(time * frequency + (index * 0.3)) * amplitude;const height = Math.floor(baseHeight * (1 + wave));// 添加微妙的缩放效果const scale = props.isOpen ? 1 + (wave * 0.1) : 1;newBars[index] = {...bar,height: `${height}%`,scale: scale,gradient: generateGradient(index)};});bars.value = newBars;
};// 启动动画
const startAnimation = () => {if (animationTimer) clearInterval(animationTimer);if (pulseTimer) clearInterval(pulseTimer);generateHeights();animationTimer = setInterval(generateHeights, props.speed);// 启动脉冲效果pulseTimer = setInterval(() => {const glowElements = document.querySelectorAll('.glow-effect');glowElements.forEach(el => {el.classList.add('pulse');setTimeout(() => el.classList.remove('pulse'), 500);});}, 2000);
};// 停止动画并重置
const stopAnimation = () => {if (animationTimer) {clearInterval(animationTimer);animationTimer = null;}if (pulseTimer) {clearInterval(pulseTimer);pulseTimer = null;}// 平滑重置为低波形const newBars = [...bars.value].map(bar => ({...bar,height: '5%',scale: 1}));bars.value = newBars;
};// 启动倒计时
const startCountdown = () => {// 重置倒计时countdown.value = 0;// 清除现有定时器if (countdownTimer) {clearInterval(countdownTimer);}// 设置新定时器countdownTimer = setInterval(() => {countdown.value++;// 当倒计时达到60时关闭模态框if (countdown.value >= 60) {handleOverlayClick();}}, 1000);
};// 停止倒计时
const stopCountdown = () => {if (countdownTimer) {clearInterval(countdownTimer);countdownTimer = null;}
};// 监听isOpen状态变化,控制动画和倒计时
watch(() => props.isOpen,(newVal) => {if (newVal) {startAnimation();startCountdown();} else {stopCountdown();}},{ immediate: true }
);// 监听颜色变化
watch(() => props.primaryColor,(newVal) => {const updatedBars = [...bars.value].map((bar, index) => {const color = generateGradient(index);const glowColor = shadeColor(newVal, 50);return {...bar,gradient: color,glow: `0 0 8px ${glowColor}, 0 0 12px ${glowColor}33`};});bars.value = updatedBars;}
);// 组件卸载时清理
onBeforeUnmount(() => {stopAnimation();stopCountdown();
});onUnmounted(() => {if (animationTimer) clearInterval(animationTimer);if (pulseTimer) clearInterval(pulseTimer);if (countdownTimer) clearInterval(countdownTimer);
});
</script><style scoped>
/* 高科技风格配色方案 */
:root {--tech-blue: #00e5ff;--tech-dark: #0a1929;--tech-darker: #050f1a;--tech-light: #64ffda;--glow: 0 0 10px var(--tech-blue), 0 0 20px rgba(0, 229, 255, 0.3);--glow-strong: 0 0 15px var(--tech-blue), 0 0 30px rgba(0, 229, 255, 0.5);--transition-fast: all 0.1s ease-out;--transition-slow: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}.modal-overlay {position: fixed;left: 0;right: 0;bottom: 200px;display: flex;justify-content: center;align-items: center;z-index: 1000;height: 200px;overflow: hidden;
}/* 科技感网格背景 */
.tech-grid {position: absolute;top: 0;left: 0;right: 0;bottom: 0;background-image: linear-gradient(rgba(0, 229, 255, 0.1) 1px, transparent 1px),linear-gradient(90deg, rgba(0, 229, 255, 0.1) 1px, transparent 1px);background-size: 20px 20px;z-index: -1;animation: gridMove 8s linear infinite;
}/* 扫描线效果 */
.scan-line {position: absolute;top: -5%;left: 0;right: 0;height: 2px;background: linear-gradient(90deg, transparent, var(--tech-blue), transparent);opacity: 0.3;z-index: 1;transition: opacity 0.5s ease;
}.scan-line.active {opacity: 0.6;animation: scan 3s linear infinite;
}@keyframes scan {0% { top: -5%; }100% { top: 105%; }
}@keyframes gridMove {0% { background-position: 0 0; }100% { background-position: 20px 20px; }
}.modal-container {position: relative;background-color: var(--tech-dark);border: 1px solid rgba(0, 229, 255, 0.3);border-radius: 8px;backdrop-filter: blur(10px);box-shadow: var(--glow);width: 90%;max-width: 600px;padding: 20px;display: flex;flex-direction: column;align-items: center;overflow: hidden;
}/* 装饰性光效 */
.glow-effect {position: absolute;left: 0;right: 0;height: 2px;background: linear-gradient(90deg, transparent, var(--tech-blue), transparent);opacity: 0.6;transition: var(--transition-slow);
}.glow-effect.top { top: 0; }
.glow-effect.bottom { bottom: 0; }.glow-effect.pulse {opacity: 1;box-shadow: var(--glow-strong);
}.audio-animation-container {display: flex;justify-content: center;align-items: center;width: 100%;height: 140px;
}.wave-container {display: flex;align-items: center;justify-content: center;gap: 2px; /* 更紧密的波形条 */width: 100%;height: 100%;position: relative; /* 新增:为了让倒计时能绝对定位在波形内部 */
}.wave-bar {width: 3px; /* 更细的波形条 */border-radius: 1px;transition: var(--transition-fast);transform-origin: center bottom;
}/* 倒计时显示样式 - 现在位于波形中央 */
.countdown-display {position: absolute;display: flex;flex-direction: column;align-items: center;justify-content: center;color: var(--tech-blue);font-family: 'Courier New', monospace;z-index: 2; /* 确保在波形条上方显示 */pointer-events: none; /* 允许点击穿透到下方的波形 */
}.countdown-number {font-size: 24px;font-weight: 600;font-weight: bold;text-shadow: 0 0 8px var(--tech-blue), 0 0 12px rgba(0, 229, 255, 0.5);line-height: 1;
}.countdown-label {font-size: 10px;opacity: 0.8;margin-top: 2px;
}/* 状态指示器 */
.status-indicator {display: flex;align-items: center;gap: 8px;margin-top: 10px;
}.status-indicator span {color: var(--tech-blue);font-family: 'Courier New', monospace;font-size: 11px;letter-spacing: 1px;opacity: 0.8;
}.pulse-dot {width: 6px;height: 6px;border-radius: 50%;background-color: #ff3e3e;animation: pulse 1.5s infinite;
}@keyframes pulse {0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 62, 62, 0.7); }70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(255, 62, 62, 0); }100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 62, 62, 0); }
}/* 过渡动画 */
.modal-fade-enter-active,
.modal-fade-leave-active {transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}.modal-fade-enter-from {opacity: 0;transform: translateY(20px) scale(0.98);
}.modal-fade-leave-to {opacity: 0;transform: translateY(10px) scale(0.99);
}
</style>

组件使用:

<AudioWaveAnimation:isOpen="isRecording":primaryColor="getRandomShadow()"@close="onclick_guanbi"/>// js
import AudioWaveAnimation from "./component/AudioWaveAnimation.vue";// 组件的显示与隐藏开关
const isRecording = ref(false);
// 生成随机颜色
function getRandomShadow() {const hue = Math.floor(Math.random() * 360); // 色相(0-360)const saturation = Math.floor(Math.random() * 30) + 70; // 饱和度(70%-100%)const lightness = Math.floor(Math.random() * 20) + 60; // 明度(60%-80%)const alpha = 0.3 + Math.random() * 0.3; // 透明度(0.3-0.6)// 直接返回hsla颜色值return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
}
http://www.dtcms.com/a/338575.html

相关文章:

  • Apache IoTDB(4):深度解析时序数据库 IoTDB 在Kubernetes 集群中的部署与实践指南
  • Chrome原生工具网页长截图方法
  • 实现Johnson SU分布的参数计算和优化过程
  • STM32 vscode 环境, 官方插件
  • 进程通信:进程池的实现
  • JUC之CompletableFuture【上】
  • PythonDay31
  • 力扣(电话号码的字母组合)
  • 如何安全删除GitHub中的敏感文件?git-filter-repo操作全解析
  • STM32 定时器(主从模式实现 3路PWM相位差)
  • c#联合halcon的基础教程(案例:亮度计算、角度计算和缺陷检测)(含halcon代码)
  • 运维监控prometheus+grafana
  • 深入理解Java中的四类引用:强、软、弱、虚引用
  • 【科研绘图系列】R语言绘制多组火山图
  • 第六天~提取Arxml中CAN Node节点信息Creat_ECU
  • STL库——string(类模拟实现)
  • ETLCloud中的数据转化规则是什么意思?怎么执行
  • QT示例 基于Subdiv2D的Voronoi图实现鼠标点击屏幕碎裂掉落特效
  • Linux中Docker k8s介绍以及应用
  • C++ 默认参数深度解析【C++每日一学】
  • Flutter AlwaysScrollableScrollPhysics详解
  • LIA-X - 一张照片生成任意表情肖像动画视频 精准操控面部动作 支持50系显卡 一键整合包下载
  • RWA加密金融高峰论坛星链品牌全球发布 —— 稳定币与Web3的香港新篇章
  • C# winform FTP功能
  • openldap安装 -添加条目
  • 项目管理.管理理念学习
  • 跟踪不稳定目标:基于外观引导的运动建模实现无人机视频中的鲁棒多目标跟踪
  • 第10章 React应用测试
  • 一、快速掌握python中的序列、集合和字典的用法(数据容器)
  • RxJava 在 Android 即时通讯中的应用:封装、处理与控制