基于wavesurfer,regions 封装的可视化音标标注控件

/*!* wavesurfer.js regions plugin 5.1.0 (2021-08-05)* https://wavesurfer-js.org* @license BSD-3-Clause* @author huinian*/import WaveSurfer from '@/assets/js/wavesurfer';
import Regions from '@/assets/js/wavesurfer.regions';type OptionsType = {el: string; // 播放器容器src: string; // 播放音频资源regions?: Array<RegionsType>; // 初始化区域数据isTightRegions?: boolean; // 连续的区域渲染regionUpdateCallBack?: any; // region 修改后的回调desabled?: boolean; // 如果是true,仅支持播放操作
}type RegionsType = {start: number; // 开始时间end: number; // 结束时间resize: boolean; // 是否允许调整大小drag?: boolean; // 是否允许移动loop?: boolean; // 是否循环播放minLength?: number; // 区域最小长度color?: string; // 背景色
}/*** region data 相关配置参数*/
const normalColor = 'rgba(255, 83, 83, 0.22)';
const activeColor = 'rgba(255, 83, 83, 0.3)';
class InitWavesurfer {wavesurfer: any; // 播放器实例options: OptionsType // 初始化实例参数duration: string; // 音频总时长regions: any = null; // 这里是所有的region实例static waveIsReady: boolean = false; // wave渲染完成/*** 下面这5行,全部和拖拽手柄相关*/static isMoveHandle: boolean = false; // 鼠标松开时重置targetElement: HTMLElement = null; // 拖拽手柄时的target,鼠标松开时清空prevElement: HTMLElement = null; // 拖拽手柄时的上一个region,鼠标松开时清空nextElement: HTMLElement = null; // 拖拽手柄时的下一个region,鼠标松开时清空currentRegion: any = null; // 被操作的region对象,点击region和拖动手柄时记录,但是拖动手柄后会被清除,而点击region时不会清空constructor (options: OptionsType) {if (new.target !== InitWavesurfer) {return Object.create(null);}this.options = options;this.options.regions.forEach(item => {if (options.desabled) {item.resize = false;}item.drag = false;item.minLength = 1;item.color = normalColor;})this.initAudio(); // 初始化音频实例,并加载音频/*** 将事件绑定的this指向改成构造函数,要不然remove时,this错误*/this.regionBarMouseMove = this.regionBarMouseMove.bind(this);this.regionClickEvent = this.regionClickEvent.bind(this);this.regionBarMouseDown = this.regionBarMouseDown.bind(this);this.regionBarMouseUp = this.regionBarMouseUp.bind(this);}/*** 初始化音频和regions插件*/initAudio () {this.wavesurfer = WaveSurfer.create({container: document.querySelector(this.options.el),backgroundColor: '#0b151c', // 容器的背景色waveColor: '#20BBB9', // 波形的填充颜色progressColor: '#03a2a0', // 光标后的波形颜色cursorColor: '#20BBB9', // 光标填充颜色height: 150, // 波形区域的高度barHeight: 1, // 波形条的高度,大于 1 的数字将增加波形条的高度partialRender: true, // PeakCache峰值缓存提高大波形的渲染速度normalize: true, // 如果true,则按最大峰值而不是 1.0 进行归一化plugins: [Regions.create({regions: this.options.regions,dragSelection: !this.options.desabled})]});this.load(this.options.src); // 加载音频this.listenerWave(this.wavesurfer); // 监听音频是否加载完成}/*** 加载音频* @param src*/load (src) {this.wavesurfer.load(src);}/*** wavesurfer 原生事件监听器* @param wavesurfer*/listenerWave (wavesurfer) {wavesurfer.on('ready', function () { // 音频加载完毕this.duration = wavesurfer.getDuration(); // 总时长this.bindRegionEvents(); // 音频加载完成后,绑定元素事件this.waveIsReady = true; //this.regions = this.wavesurfer.regions.list;this.handleCallback();}.bind(this));wavesurfer.on('error', function (e) {console.warn(e); // 音频加载失败});// 监听新建和修改的region 回调函数wavesurfer.on('region-update-end', function (region) {if (this.waveIsReady) {if (!this.currentRegion || this.currentRegion.id !== region.id) { // 如果是新增region.drag = false;this.bindRegionEvents(region);}this.handleCallback(region);}}.bind(this));// 监听移除region 回调函数wavesurfer.on('region-removed', function (region) {this.handleCallback(region);}.bind(this))}/*** region 事件绑定*/bindRegionEvents (region?) {this.addRegionEvent(region);this.addRegionBarEvent(region);}/*** 如果传入了region 是对单个region 添加事件绑定* @param region  事件绑定*/addRegionEvent (region?) {let nodeList: any = document.querySelectorAll('region');if (region) {nodeList = document.querySelectorAll('#' + region.id);}for (let i = 0; i < nodeList.length; i++) {nodeList[i].addEventListener('click', this.regionClickEvent);}}/*** 鼠标点击region的事件,如果是拖拽手柄的话,isMoveHandel是true* @param e*/regionClickEvent (e) {const regionNodeList = document.querySelectorAll('.wavesurfer-region');if (regionNodeList.length > 0) {regionNodeList.forEach(item => {this.wavesurfer.regions.util.style(item, {backgroundColor: normalColor})})}if (!InitWavesurfer.isMoveHandle && e.target.classList.contains('wavesurfer-region')) {e.stopPropagation();e.preventDefault();const currentRegionId = e.target.dataset.id;const regionsList = this.wavesurfer.regions.list;const currentRegion = regionsList[currentRegionId];if (this.wavesurfer.isPlaying()) {currentRegion.play();}currentRegion.style(e.target, {backgroundColor: activeColor})// 全局存储下,目前没什么卵用,就是记录下this.currentRegion = currentRegion;}}/*** 如果传入了 region 是对单个region下的 handle添加事件* @param region*/addRegionBarEvent (region?) {let nodeList: any = document.querySelectorAll('.wavesurfer-handle');if (region) {nodeList = document.getElementById(region.id).childNodes;}for (let i = 0; i < nodeList.length; i++) {nodeList[i].addEventListener('mousedown', this.regionBarMouseDown);nodeList[i].addEventListener('mouseup', this.regionBarMouseUp);}}/*** 拖动手柄mousedown事件* @param e*/regionBarMouseDown (e) {const currentRegion = this.getRegion(e.target.parentElement.dataset.id); // 获取手柄对应的region实例,后面一直取操作这个实例if (currentRegion && !InitWavesurfer.isMoveHandle) {currentRegion.style(e.target.parentElement, { zIndex: 99 });// 当前region 置顶,不然会导致mousemove事件失效this.targetElement = e.target;this.currentRegion = currentRegion; // 全局存储下this.addWaveMouseMove();}}/*** 拖动手柄mousemove事件* @param e*/regionBarMouseMove () {if (this.targetElement) {InitWavesurfer.isMoveHandle = true; // 增加一个标识,可以和click事件区分开来const currentRegion = this.currentRegion;const { prevElement, nextElement, prevRegionId, nextRegionId } = this.getPrevAndNextElement(); // 获取相邻的两个节点if (this.targetElement.classList.contains('wavesurfer-handle-start')) {if (prevElement) {const prevRegion = this.getRegion(prevRegionId);if (currentRegion.start <= prevRegion.end) {// 前一个region 需要跟着变化this.updateRegion(prevRegion, prevRegion.start, currentRegion.start);}}} else {if (nextElement) {const nextRegion = this.getRegion(nextRegionId);if (currentRegion.end >= nextRegion.start) {// 后一个region 需要跟着变化this.updateRegion(nextRegion, currentRegion.end, nextRegion.end);}}}}}/*** 拖动手柄鼠标mouseup事件* @param e*/regionBarMouseUp () {const currentRegion = this.currentRegion;if (this.targetElement) {const parentElement = this.targetElement.parentElementcurrentRegion.style(parentElement, { zIndex: 4 }); // 复原 region 的层级位置this.timeAlignment(currentRegion); // 拖拽可能存在误差,鼠标松开时 对齐下,将end大于 后一个region的start的强行修改this.removeWaveMouseMove();}this.resetCurrentHandle.apply(this); // 重置targetElement & currentRegionInitWavesurfer.isMoveHandle = false; // 延时重置 isMoveHandle的状态,给mouseup和click增加一个标识,为了以后扩展,目前没有实际用途}/*** 对齐拖拽造成的误差,拖拽的时候,有时候会重叠,结束的时候咱们去矫正它* @param currentRegion* @param parentElement*/timeAlignment (currentRegion) {const { prevElement, nextElement, prevRegionId, nextRegionId } = this.getPrevAndNextElement(); // 获取相邻的两个节点if (prevElement && prevElement.className === 'wavesurfer-region') { // 和前一个dom对齐const prevRegion = this.getRegion(prevRegionId);if (currentRegion.start < prevRegion.end) {prevRegion.update({start: prevRegion.start,end: currentRegion.start})}}if (nextElement && nextElement.className === 'wavesurfer-region') { // 和后一个dom对齐const nextRegion = this.getRegion(nextRegionId);if (currentRegion.end > nextRegion.start) {nextRegion.update({start: currentRegion.end,end: nextRegion.end})}}}/*** 添加wave mousemove事件*/addWaveMouseMove () {document.getElementById('waveform').addEventListener('mousemove', this.regionBarMouseMove);}/*** 移除 wave mousemove事件*/removeWaveMouseMove () {document.getElementById('waveform').removeEventListener('mousemove', this.regionBarMouseMove);}/*** 重置当前拖拽手柄数据*/resetCurrentHandle () {this.targetElement = null;this.currentRegion = null;this.prevElement = null;this.nextElement = null;}/*** 音频播放*/play () {if (this.wavesurfer.isPlaying()) { // 如果正在播放 则暂停this.wavesurfer.pause();} else {this.wavesurfer.play();// 如果要用下面的方法,需要在点击非region区域时,移除currentRegion,当前这个事件没有绑定// if (!this.currentRegion) {//   this.wavesurfer.play();// } else {//   this.currentRegion.play()// }}}/*** 音频暂停*/pause () {this.wavesurfer.pause();}/*** 根据regionElement的id,获取region实例* */getRegion (id) {return this.wavesurfer.regions.list[id];}/*** 更新region* region =  this.wavesurfer.regions.list[i]* */updateRegion (region, start: number = 0, end: number = 0) {window.requestAnimationFrame(() => region.update({start: start,end: end}));}/*** 移除一个region* @param id*/removeRegion (id?) {const regionId = id || this.currentRegion.id;this.removeRegionEvent(regionId);this.wavesurfer.regions.list[regionId].remove();}/*** 移除region绑定的事件* @param id*/removeRegionEvent (id?) {document.querySelectorAll('#' + id)[0].removeEventListener('click', this.regionClickEvent);const childrenElement = document.getElementById(id).childNodes;if (childrenElement.length > 0) {for (let i = 0; i < childrenElement.length; i++) {childrenElement[i].removeEventListener('mousedown', this.regionBarMouseDown);childrenElement[i].removeEventListener('mouseup', this.regionBarMouseUp);}}}/*** 获取被操作region相邻的region element*/getPrevAndNextElement (region?) {const currentRegion = region || this.currentRegionconst regions = this.wavesurfer.regions.list;const prevArr = new Map();const nextArr = new Map();for (const key in regions) {if (key !== currentRegion.id) {if (regions[key].start < currentRegion.start) {prevArr.set(key, currentRegion.start - regions[key].start)} else {nextArr.set(key, regions[key].end - currentRegion.end)}}}const prevRegionId = InitWavesurfer.sortMap(prevArr) ? InitWavesurfer.sortMap(prevArr)[0][0] : null;const nextRegionId = InitWavesurfer.sortMap(nextArr) ? InitWavesurfer.sortMap(nextArr)[0][0] : null;this.prevElement = document.getElementById(prevRegionId);this.nextElement = document.getElementById(nextRegionId);return {prevRegionId: prevRegionId,nextRegionId: nextRegionId,prevElement: document.getElementById(prevRegionId),nextElement: document.getElementById(nextRegionId)}}/*** sort map* return [[key,value],[key,value]]*/static sortMap (mapList) {if (mapList.size > 0) {const arrayObj = Array.from(mapList);arrayObj.sort((a, b) => { return a[1] - b[1] });return arrayObj} else {return null}}/*** 执行region 创建 修改 删除后的回调* @param region*/handleCallback (region = null) {if (typeof this.options.regionUpdateCallBack === 'function') {this.options.regionUpdateCallBack(this.regions, region);}}
}export default InitWavesurfer

vue组件

<template><div class="wavesurfer"><div id="waveform"></div><div class="audioController"><span class="time">0.253 【0.2-0.32】</span><div class="controller"><Icon @click="skipBackward" type="md-skip-backward" /><Icon v-if="!isPlaying" @click="play" type="md-play" /><Icon v-if="isPlaying" @click="play" type="md-pause" /><Icon @click="skipForward" type="md-skip-forward" /></div><div class="attribute"><a>循环</a><a>语速</a></div></div></div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Emit } from 'vue-property-decorator';
import InitWavesurfer from '@/assets/js/wavesurfer.regions.main';@Component
export default class MyAudio extends Vue {@Prop({type: String,default: require('@/assets/91167.mp3')})audioSrc: string; // 音频初始化地址@Prop({type: Boolean,default: false})desabled?: boolean; // 是否仅支持播放@Prop({type: Array,default: () => [{start: 10,end: 50,drag: false,minLength: 1,color: 'hsla(200, 50%, 70%, 0.4)'}, {start: 60,end: 100,drag: false,minLength: 1,color: 'hsla(200, 50%, 70%, 0.4)'}]})regions?: Array<any>; // regions 信息ws: any = null; // 音频实例private get isPlaying () {if (this.ws) {return this.ws.wavesurfer.isPlaying()} else {return false}} // 音频播放状态private mounted () {this.ws = this.initWave();}/*** 初始化音频*/private initWave () {return new InitWavesurfer({el: '#waveform',src: this.audioSrc,desabled: this.desabled,regions: this.regions,regionUpdateCallBack: this.regionUpdateCallBack});}/*** 播放/暂停*/private play () {this.ws.play()}/*** 快退*/private skipBackward () {this.ws.wavesurfer.skipBackward(2)}/*** 快进*/private skipForward () {this.ws.wavesurfer.skipForward(2)}/*** 通过region id移除当前region*/public removeRegion (id) {this.ws.removeRegion(id)}/*** region 拖拽/新增/删除后的回调*/@Emit('regionUpdateCallBack')public regionUpdateCallBack (regions, region) {//}/*** 销毁音频实例*/private beforeDestroy () {this.ws.wavesurfer.destroy();}
}
</script>
<style lang='less' scoped>
.wavesurfer {background: #0B151C;border-radius: 2px;.audioController{display: flex;align-items: center;justify-content: space-between;height: 40px;line-height:40px;color: #20BBB9;padding: 0 5px 0 15px;border-top:1px solid #242d33;.time{font-size: 12px;}.controller{color:#ddd;i{margin:0 10px;cursor: pointer;}}.attribute{a{background: #30383e;color: #20BBB9;border-radius: 11px;line-height: 22px;padding:0 13px;display: inline-block;margin-right: 10px;}}}
}
</style>

注意:需要提前引入wavesurfer、wavesurfer.regions 两个文件

基于wavesurfer,regions 封装的可视化音标标注控件相关推荐

  1. LightningChart数据可视化工具图形控件教程51-BarSeries3D

    BarSeries3D 演示示例: Horizontal bars; Bars, grouping; Bars, manhattan BarSeries3D 可以以3D视图实现柱状形数据可视化. 柱状 ...

  2. LightningChart数据可视化工具图形控件教程48-SurfaceGridSeries3D(曲线网格系列3D)_续

    LightningChart数据可视化工具图形控件教程48 SurfaceGridSeries3D(曲线网格系列3D)_续 轮廓线 轮廓线可以快速表明高度数据,而不用采取调色板填充来填充曲面.轮廓线可 ...

  3. 【Silverlight】Bing Maps开发应用与技巧二:自定义图钉标注控件和动态ToolPanel

    在Bing Maps Silverlight Control中以及为我们提供了地图图钉控件Pushpin,我曾经在< 使用图钉层(Pushpin layer)及地图图层(MapLayer)> ...

  4. 基于OpenCV+WinForm开发的图形图像渲染控件

    基于OpenCV+WinForm开发的图形图像渲染控件 WinForm版图形图像渲染控件 图像居中渲染 图像放大 图像缩小 绘制图形 点 线 矩形 圆形 旋转矩形 多边形 资源连接 WinForm版图 ...

  5. RAD Studio 10.4.1新的基于Chromium的Microsoft Edge浏览器的TEdgeBrowser控件用法

    目录 RAD Studio 10.4.1新的基于Chromium的Microsoft Edge浏览器的TEdgeBrowser控件用法 一.TEdgeBrowser安装部署说明 1.1.TEdgeBr ...

  6. .Net Core使用视图组件(ViewComponent)封装表单文本框控件

    实例程序的界面效果如下图所示: 在表单中的搜索条件有姓名,学号,成绩.他们在一行中按照水平三等分排列. 在cshtml中用html实现上述表单效果的的代码如下: 1 <form class=&q ...

  7. 基于web的工作流设计器(多比图形控件)

    多比图形控件是一款基于Web的矢量图形控件, 类似于网页上的Visio控件,是目前国内外最佳的基于web的工作流设计器.工作流流程监视器解决方案. 可广泛应用于包括:电力.军工.煤炭.化工.科研.能源 ...

  8. 分享基于silverlight的一个大文件上传控件

    虽然codeplex已经有一些多文件,带进度条的上传控件,但是觉得都不是很好用,所以基于上面的控件重新设计了一个上传控件,更好的交互,属性绑定和管理文件. 1. 客户端使用: <mycontro ...

  9. 基于qt中QCalendarWidget的双日历时间范围选择控件(自定义)

    控件预览: 控件基于QT设计,单击日历设置时间范围起点,再次单击日历设置时间范围终点: 当起止时间为同一天时,所选日期右上角显示"单"字样: 控件设计说明: 控件基于QT中QDia ...

  10. 基于Authorware课件的具有导航功能的ActiveX控件的设计和实现

    随着计算机的普及,Authorware已经步入了校园.企业和部队.由于Authorware有多媒体制作的优势,以及制作简单,Authorware受到学校教师.企业和部队培训人员的高度关注,每年都有很多 ...

最新文章

  1. python3.6安装-Linux安装python3.6
  2. BZOJ-1034-[ZJOI2008]泡泡堂BNB(贪心)
  3. 建站用什么cms_我想做独立站,我应该用什么建站工具?
  4. 2017.3.19 约数个数和 失败总结
  5. java文件删除失败
  6. 【QT】简单易学的QT安装教程
  7. js生成二维码并下载、批量生成二维码和压缩下载
  8. 蓝鸽智慧计算机教室云服务平台操作说明,智慧教室操作说明.PDF
  9. MATLAB智能算法
  10. 【PTA-python】第4章-15 换硬币 (20 分)
  11. 图解splay / splay模板 / p3369
  12. linux与pe到移动硬盘,PE下找不到移动硬盘?不用怕
  13. 【解决思路】Spring Boot:Error creating bean with name ‘xxxDao‘
  14. 干货|什么是字节码?字节码扩展名是什么?
  15. 官媒痛批“精神鸦片”,曾拿百万年终奖的腾讯游戏员工要失业了吗?
  16. 无法解析域名“cn.archive.ubuntu.com”。
  17. 孙海平:已有退休念头
  18. 台式机前面的插孔没有声音,而且也找不到realtek高清晰音频管理系统
  19. vue 头像上传裁剪功能
  20. echarts系列-----2 (多个Y轴)

热门文章

  1. linux怎样安装麒麟双系统,手把手教您win10系统装麒麟系统双系统的解决办法
  2. 如何在51单片机实现日程提醒(生日闹钟)
  3. Mysql 8.0.27 免安装配置教程(windows)
  4. 什么是公司Offer里的RSU?
  5. Fiddler高级用法—FiddlerScript抓取app网页json数据并保存
  6. 电影《五十度灰》主题曲《Love me like you do》——尽你所能爱我Ellie Goulding(埃利·高登)——【英文小清新歌曲推荐】
  7. Coldfusion的基础知识
  8. JAVA自学-day16-List的子类、泛型、增强for循环、静态导入、可变参数
  9. c++可视化性能测试
  10. 在Unity3D中制作VR全景视频、图片