实现功能有:音词同步,倍速播放,拖拽播放,快进\退 ,重播,显示总 时长,关闭页面时关闭声音等功能
package.json 引入 "wavesurfer.js": "^7.7.14",
父页面引入自己封的 MyWaveSurfer.vue
<!-- waveSrc 录音地址参数 -->
<WaveSurfer :waveSrc="model['fsFilepath']">
</WaveSurfer>
<script lang="ts" setup>
import WaveSurfer from "@/views/recordCdr/MyWaveSurfer.vue";
MyWaveSurfer.vue
<template>
<a-card>
<Spin :tip="null" :spinning="loadLoading">
<div :class="`wave-surfer`" id="waveform" >
</div>
<!-- <div id="wave-timeline" ref="wave-timeline" style="height: 0px;"> -->
<!--时间轴 -->
</div>
</Spin>
</a-card>
<a-card style="height: 62px">
<div style="float: left;" v-if="playMs">
<div style=";float: left">播放: </div>
<Icon icon="ant-design:play-circle-outlined" title="播放" class="playIcon" style="font-size: 32px;color: #2e9aff" @click="playMusic()"> </Icon>
</div>
<div style="float: left;" v-if="!playMs">
<div style="float: left">暂停: </div>
<Icon icon="ant-design:pause-circle-outlined" title="暂停" style="font-size: 30px;color: #2e9aff" @click="downMusic()"></Icon>
</div>
<div style="float: left;margin-left: 5%;"> 倍速:
<a-select style="width: 90px;" :disabled="false" v-model:value="speedValue" @change="speedClick">
<a-select-option value="1">
1.0px
</a-select-option>
<a-select-option v-for="item in options" :key="item.value" :value="item.value">
{{ item.text }}
</a-select-option>
</a-select>
</div>
<div style="float: left;margin-left: 5%;">
<Icon icon="ant-design:notification-filled" title="声音" style="float: left;font-size: 29px;;color: #2e9aff" @click="downMusic()"></Icon>
<a-slider style="float: left;width: 200px" v-model:value="voiceValue" @afterChange="setVolume" />
</div>
<div style="float: left;margin-left: 5%;">
<div style="float: left;margin-top: 2px">快退/快进 : </div>
<Icon icon="ant-design:fast-backward-filled" title="快退" style="float: left;font-size: 29px;;color: #2e9aff" @click="rew()"></Icon>
<div style="float: left;margin-top: 2px"> -- </div>
<Icon icon="ant-design:fast-forward-filled" title="快进" style="float: left;font-size: 29px;;color: #2e9aff" @click="speed()"></Icon>
</div>
<div style="float: left;margin-left: 5%;">
<div style="float: left;margin-top: 2px">重播 : </div>
<Icon icon="ant-design:reload-outlined" title="重播" style="float: left;font-size: 25px;color: #2e9aff" @click="replay()"></Icon>
</div>
<div style="float: left;margin-left: 5%;">
总时长: <span style="color:#2e9aff">{{totalTime}}</span> 秒
</div>
</a-card>
<div style="width:100%">
<a-card style="width: 49%;float: left;height: 500px;overflow-y: auto">
<div id="speech_feature" ref="myDiv">
<div v-for="(line, index) in lyrics" class="myTextClass">
<p v-if="line.user_type == 'agent'" :id="Math.round(line.begin_time/100)" class="blueColor" style="text-align:left;">
<Icon icon="ant-design:customer-service-filled" title="座席" style="float: left;font-size: 22px;color: #707fe3" ></Icon>
:{{ line.res }}
</p>
<p v-else style="text-align:right" :id="Math.round(line.begin_time/100)" class="blueColor">
{{line.res}} :
<Icon icon="ant-design:message-filled" title="客户" style="float: right;font-size: 22px;color: #707fe3" ></Icon>
</p>
</div>
</div>
</a-card>
<a-card style="width: 49%;float: left;height: 500px">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="Tab 1">Content of Tab Pane 1
</a-tab-pane>
<a-tab-pane key="2" tab="Tab 2" force-render>Content of Tab Pane 2
</a-tab-pane>
<a-tab-pane key="3" tab="Tab 3">
Content of Tab Pane 3
</a-tab-pane>
</a-tabs>
</a-card>
</div>
</template>
<script lang="ts">
import { Spin } from 'ant-design-vue';
import {defineComponent, toRefs, ref, onMounted,onUnmounted, nextTick,} from 'vue';
import { getUploadFileAccessHttpUrl } from '/@/utils/common/compUtils';
import Icon from '/@/components/Icon';
import WaveSurfer from '@/ext_node_modules/wavesurfer.js/dist/wavesurfer'
/*import CursorPlugin from '@/ext_node_modules/wavesurfer.js/dist/plugin/wavesurfer.cursor.js'
import Timeline from '@/ext_node_modules/wavesurfer.js/dist/plugin/wavesurfer.timeline.js'
import Regions from '@/ext_node_modules/wavesurfer.js/dist/plugin/wavesurfer.regions.js'*/
import _default from "ant-design-vue/es/vc-slick/inner-slider";
import DictItemList from "@/views/system/dict/components/DictItemList.vue";
export default defineComponent({
name: 'WaveSurfer',
components: {DictItemList, Icon, Spin },
props: {
waveSrc: {
type: String,
// default: '../../../src/assets/images/test.mp3',
},
index: {
type: Number,
},
},
setup(props) {
const myDiv = ref(null);
const activeKey = ref('1');
let totalTime = ref<number>(0);
const voiceValue = ref<number>(30);
const playMs = ref(true);
const { waveSrc } = toRefs(props);
const icon = ref('icon-bofang');
let wavesurfer = [];
const loadLoading = ref(false);
const speedValue = ref(1);
const options = ref([
{ value: 1.0, text: '1.0X' },
{ value: 1.25, text: '1.25X' },
{ value: 1.5, text: '1.5X' },
{ value: 1.75, text: '1.75X' },
{ value: 2.0, text: '2.0X' },
])
const lyrics = ref([
{
"res": ["有什么可以帮您?"],
"end_time": 7700,
"begin_time": 5900,
"words_info": [],
"sn": "96541708621716291667",
"corpus_no": "7371416581381329804",
"user_type": "agent"
}, {
"res": ["我客户想把那个会员号变更一下可以吗?"],
"end_time": 17700,
"begin_time": 8340,
"words_info": [],
"sn": "92604005381716291667",
"corpus_no": "7371416580911366314",
"user_type": "cust"
}, {
"res": ["嗯,可以的,还是需要给顾客自己近先联系我们。"],
"end_time": 18860,
"begin_time": 17000,
"words_info": [],
"sn": "92604005381716291667",
"corpus_no": "7371416580911366314",
"user_type": "agent"
}, {
"res": ["如果变更成别的号码,这个等级还在吗?"],
"end_time": 19860,
"begin_time":18860 ,
"words_info": [],
"sn": "381723455751716291667",
"corpus_no": "7371416581432964228",
"user_type": "cust"
}, {
"res": ["您您放心,会保留他的一个等级和这个积分的。"],
"end_time": 28020,
"begin_time": 26060,
"words_info": [],
"sn": "381723455751716291667",
"corpus_no": "7371416581432964228",
"user_type": "agent"
}, {
"res": ["好累,好让我让他打电话过来哈,"],
"end_time": 42280,
"begin_time": 33880,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "cust"
}, {
"res": ["请问下,可以帮您的吗?"],
"end_time": 42280,
"begin_time": 36880,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "agent"
}, {
"res": ["啊没有了,谢谢啊,我让他打过来,"],
"end_time": 40280,
"begin_time": 39880,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "cust"
}, {
"res": ["祝您生活愉快,再见,"],
"end_time": 40280,
"begin_time": 40000,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "agent"
}, {
"res": ["好的"],
"end_time": 40280,
"begin_time": 40080,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "cust"
}, {
"res": ["祝您生活愉快,再见,"],
"end_time": 40280,
"begin_time": 40500,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "agent"
}, {
"res": ["好的"],
"end_time": 40280,
"begin_time": 40800,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "cust"
}, {
"res": ["祝您生活愉快,再见,"],
"end_time": 40280,
"begin_time": 41000,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "agent"
}, {
"res": ["好的"],
"end_time": 40280,
"begin_time": 41200,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "cust"
}, {
"res": ["祝您生活愉快,再见,"],
"end_time": 40280,
"begin_time": 41400,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "agent"
}, {
"res": ["。。。。,"],
"end_time": 40280,
"begin_time": 41600,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "cust"
}, {
"res": ["祝您生活愉快,再见,"],
"end_time": 40280,
"begin_time": 41800,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "agent"
}, {
"res": ["。。。。。"],
"end_time": 40280,
"begin_time": 41900,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "cust"
}, {
"res": ["祝您生活愉快,再见,"],
"end_time": 40280,
"begin_time": 42200,
"words_info": [],
"sn": "912755075221716291667",
"corpus_no": "7371416581565561738",
"user_type": "agent"
}
])
//点击倍速播放
function speedClick() {
console.info(speedValue.value)
// console.log("fieldValue===============",fieldValue);
playMusic();
}
//生成wavesurfer
function render( selector, url) {
loadLoading.value = true;
var domEl = document.createElement('div');
document.querySelector(selector).appendChild(domEl);
wavesurfer = WaveSurfer.create({
container: domEl,
waveColor: 'rgb(57 171 132)',
progressColor: '#e0820e',
cursorColor: 'rgb(57 171 132)',
abnormalvolume:{"1":"violet"},
/* plugins: [
CursorPlugin.create({
showTime: true,
opacity: 0.2,
customShowTimeStyle: {
'background-color': '#000',
color: '#fff',
padding: '2px',
'font-size': '20px'
}
}),
Timeline.create({
container: '#wave-timeline'
}),
Regions.create()
]*/
});
// url="../../../src/assets/images/test.mp3";
wavesurfer.load(url);
//获取当前播放进度的时间点
wavesurfer.on('audioprocess', (time) => {
//console.log("======time======",time)
// 根据当前音频播放进度 time 来同步显示歌词Math.round(line.begin_time/100)
const currentLine =Math.round(time*10) ; // 假设每行歌词对应的时间间隔为 10 秒
//console.log("======currentLine======",currentLine)
var mytextId=document.getElementById(currentLine);
changeBackground(time);
if(mytextId!=undefined){
mytextId.className= "redColor";
setTimeout(function(){
mytextId.scrollIntoView({ behavior: "smooth", block: 'center' }); //0.1秒滑动到指定位置*
},100);
}
});
//拖拽进度条播放
wavesurfer.on('seek', function () {
playMusic();
});
wavesurfer.on('error', function (e) {
console.warn(e);
});
//加载后事件
wavesurfer.on('ready', function () {
loadLoading.value = false;
totalTime.value = wavesurfer.getDuration();
console.log('音频总时长:', totalTime, '秒');
});
return wavesurfer;
}
//组件加载时加载录音
onMounted(() => {
setTimeout(()=>{
if(waveSrc.value!=undefined){
const fileAccessHttpUrl= getUploadFileAccessHttpUrl(waveSrc.value)
nextTick(() => {
render( '.wave-surfer' , fileAccessHttpUrl);
});
}
},100)
});
//页面关闭时,组件关闭, 关闭录音
onUnmounted(()=>{
console.info( '组件关闭时 关闭录音====')
wavesurfer.stop() ; //停止
wavesurfer.destroy();//销毁
})
//设置音量大小
function setVolume(val) {
wavesurfer.setVolume(val / 100);
//console.log(val)
}
//根据播放进度改编已播放的字体样式
function changeBackground(nowTime) {
nowTime=Math.round(nowTime*10)
const elements = document.querySelectorAll('.myTextClass > *');
elements.forEach(element => {
if(element.id>nowTime){
element.className="blueColor";
}else{
element.className="redColor";
}
});
}
//暂停
function downMusic() {
playMs.value = true;
icon.value = 'icon-bofang';
wavesurfer.playPause();
}
//播放
function playMusic() {
playMs.value = false;
icon.value = 'icon-bofang';
wavesurfer.setPlaybackRate(speedValue.value)
wavesurfer.play();
}
// 回退
function rew() {
wavesurfer.skip(-3);
goPlay();
}
// 快进
function speed() {
wavesurfer.skip(3);
goPlay();
}
function goPlay() {
let start = wavesurfer.getCurrentTime();
wavesurfer.play(start);
}
// 重载
function replay() {
wavesurfer.stop();
wavesurfer.clearRegions();
wavesurfer.play(0);
}
return { playMusic, icon, loadLoading,playMs,speedClick,speedValue,options,downMusic,totalTime,
setVolume,voiceValue,lyrics,changeBackground,myDiv,speed,rew ,replay,activeKey};
},
});
</script>
<style lang="less">
.playIcon {
line-height: 36px;
margin-right: 8px;
cursor: pointer;
}
.waveform {
}
.redColor {
color: #d96f00;;
}
.blueColor {
color: #292a2f;
}
</style>