录音播放组件

1,035 阅读2分钟

使用的技术栈 ( vue3.0+typeScript+Element Plus)

image.png

推荐封装成组件 在现有的项目中使用 。 vue2.x 可以需要修改 不支持ts可以将相关的类型判断去掉即可 需要不同的样式 需要不同的样式 可以自己定义

h5 audio 相关事件以及属性

    <audio
        ref="audio"
        class="audioStyle"
        :src="url"
        :preload="audioState.preload"
        @play="onPlay"
        @error="onError"
        @waiting="onWaiting"
        @pause="onPause"
        @timeupdate="onTimeupdate"
        @loadedmetadata="onLoadedmetadata"
    >
    </audio>
1. src 就是音频的地址
2. preload 属性规定是否在页面加载后载入音频 
        -  auto 当页面加载后载入整个音频
        -  meta 当页面加载后只载入元数据
        -  none 当页面加载后不载入音频
3. onPlay  播放方法
4. onPause 暂停方法
5. onTimeupdate 当timeupdate事件大概每秒一次,
                用来更新音频流的当前播放时间 此时也可以更新进度条
6. loadedmetadata 加载音频的时长

定义播放录音的样式以及相关方法使用

1. 通过 onLoadedmetadata 来加载音频的时长
const onLoadedmetadata = (res)=> {
    state.audioState.waiting = false
    state.audioState.maxTime = parseInt(res.target.duration)
}
2. 点击暂停或者播放按钮来调用 audio 的对应功能
const audio = ref(); // vue3.x的写法 这里不用管
const startPlayOrPause=()=> {
    return state.audioState.playing ? pausePlay() : startPlay()
}
// 开始播放
const startPlay =()=> {
    audio.value.play()
}
// 暂停
const pausePlay = ()=> {
    audio.value.pause()
}
3. 拖拽播放进度条实时更新播放时间
// 播放跳转
const changeCurrentTime = (index)=> {
    audio.value.currentTime = Math.floor((index) / 100 * state.audioState.maxTime))
}
4. 改变音量大小
// 音量改变通过 audio 的 volume属性
const changeVolume = (index = 0)=> {
    audio.value.volume = Number(index) / 100
    state.volume = index
}
5. 下载录音文件 
const downloadFile=()=>{
    let temurl=state.url.split('.').slice(-2)[0].split('/').slice(-1)[0]
    var laststr=temurl.lastIndexOf('_');
    var newStr=temurl.substring(0,laststr);
    let itemName=newStr
    download(state.url,itemName)
}
const download =(url, filename)=> {
    url=url+`?${Math.random()}`
    getBlob(url, function(blob:any) {
        saveAs(blob, filename);
    })
}
const getBlob=(url:String,cb:any)=> {
    var xhr:any = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'blob';
    xhr.onload = function() {
        if (xhr.status === 200) {
            cb(xhr.response);
        }
     };
     xhr.send();
}
const saveAs=(blob, filename)=> {
    var link:any = document.createElement('a');
    var body:any = document.querySelector('body');
    link.href = window.URL.createObjectURL(blob);
    link.download = filename;
    // fix Firefox
    link.style.display = 'none';
    body.appendChild(link);
    link.click();
    body.removeChild(link);
    window.URL.revokeObjectURL(link.href);
}
6. 快进 通过修改 audio 的playbackRate 属性
const changeSpeed =()=> {
    let index = state.speeds.indexOf(state.audioState.speed) + 1
    state.audioState.speed = state.speeds[index % state.speeds.length]
    audio.value.playbackRate = state.audioState.speed
}

完整代码

<template>
    <div class="audio_player">
        <audio
            ref="audio"
            class="audioStyle"
            :src="url"
            :preload="audioState.preload"
            @play="onPlay"
            @error="onError"
            @waiting="onWaiting"
            @pause="onPause"
            @timeupdate="onTimeupdate"
            @loadedmetadata="onLoadedmetadata"
        >
        </audio>
        <!-- 功能配置菜单 功能关闭 -->
        <div class="playevent">
            <!-- 播放暂停 -->
            <el-image
                class="hoverClass"
                @click="startPlayOrPause"
                style="width: 30px; height: 30px"
                :src="playStatusurlArr[audioState.playing]"
                fit="scale-down"
                >
             </el-image>
             <div class="split_style"></div>
             <!-- 快进可以配置 功能关闭 -->
             <el-button 
                v-if="false" 
                type="text" 
                @click="changeSpeed">{{ '快进: x' + audioState.speed }}
             </el-button>
             <el-button 
                    type="text" 
                    style="color:#909399;">{{ realFormatSecond(audioState.currentTime)}}
             </el-button>
            <div class="content_solid">
                <el-slider
                    class="slider"
                    v-model="sliderTime"
                    :format-tooltip="formatProcessToolTip"
                    @change="changeCurrentTime" >
                </el-slider>
             </div>
            <el-button type="text" style="color:#909399;">
                {{realFormatSecond(audioState.maxTime)}}
            </el-button>
            <div class="split_style"></div>
            <!-- 静音功能可以配置 功能关闭 -->
            <el-button
                v-if="false" type="text" 
                @click="startMutedOrNot">{{audioState.muted ? '放音' : '静音'}}
            </el-button>
            <div class="voice_solid">
                <el-image
                    @click="moreMiniVoice"
                    class="hoverClass"
                    style="width: 30px; height: 30px; margin-right:8px;"
                    :src="playStatusurlArr['sound']"
                    fit="scale-down"
                    >
                </el-image>
                <el-slider
                    class="sliderVoice"
                    v-model="volume"
                    :format-tooltip="formatVolumeToolTip"
                    @change="changeVolume" >
                </el-slider>
                <el-image
                    @click="moreMaxVoice"
                    class="hoverClass"
                    style="width: 30px; height: 30px"
                    :src="playStatusurlArr['moresound']"
                    fit="scale-down"
                >
                </el-image>

            </div>
            <div class="split_style" style="margin-right: 12px;"></div>
            <!-- 下载 -->
            <el-image
                class="hoverClass"
                @click="downloadFile"
                style="width: 30px; height: 30px"
                :src="playStatusurlArr['download']"
                fit="scale-down"
            >
            </el-image>
        </div>
    </div>
</template>
<script lang="ts">
import { onMounted, reactive, toRefs, ref } from 'vue';
export default {
    name: 'audioPlayer',
    props: {
        theUrl: {
            type: String,
            required: true,
        },
        theSpeeds: {
            type: Array,
            default () {
                return [1, 1.5, 2]
            }
        },
        theControlList: {
            type: String,
            default: ''
        }
    },
setup(props:any, ctx:any) {
    const state=reactive({
        url: props.theUrl,
        audioState: {
            currentTime: 0,
            maxTime: 0,
            playing: false,
            muted: false,
            speed: 1,
            waiting: true,
            preload: 'auto'
        },
        sliderTime: 0,
        volume: 50,
        speeds: props.theSpeeds,
        /**
        * 控制功能数据
        */
        controlList: {
            // 可以配置传入参数是显示具体功能
        },
        // 此处需要配置自己的图片效果
        playStatusurlArr:{
            false:'',
            true:'',
            'download':'',
            'sound':'',
            'moresound':'',
        }
    })
    setControlList()
    // 当音频开始播放
    const onPlay = (res:any)=> {
        state.audioState.playing = true
        if(!state.controlList.onlyOnePlaying){
            return
        }
        let target = res.target
        let audios = document.getElementsByTagName('audio');
        [...audios].forEach((item) => {
            if(item !== target){
                item.pause()
            }
        })
    }
    // 当发生错误, 就出现loading状态
    const onError = ()=> {
        state.audioState.waiting = true
    }

    // 当音频开始等待
    const onWaiting = (res:any) => {
        console.log(res)
    }

    // 当音频暂停

    const onPause = ()=> {

    state.audioState.playing = false

    }
    // 当timeupdate事件大概每秒一次,用来更新音频流的当前播放时间
    const onTimeupdate =(res:any)=> {
        state.audioState.currentTime = res.target.currentTime
        state.sliderTime = Math.floor(Number(state.audioState.currentTime) / Number(state.audioState.maxTime) * 100)
    }
    const onLoadedmetadata = (res:any)=> {
        state.audioState.waiting = false
        state.audioState.maxTime = parseInt(res.target.duration)
    }

    /**
    * 以下是按钮触发事件
    */

    const audio = ref();
    const startPlayOrPause=()=> {
        return state.audioState.playing ? pausePlay() : startPlay()
    }

    // 开始播放
    const startPlay =()=> {
        audio.value.play()
    }
    // 暂停
    const pausePlay = ()=> {
        audio.value.pause()
    }

    const changeSpeed =()=> {
        let index = state.speeds.indexOf(state.audioState.speed) + 1
        state.audioState.speed = state.speeds[index % state.speeds.length]
        audio.value.playbackRate = state.audioState.speed
    }

    const realFormatSecond=(second:any)=> {
        var secondType = typeof second
        if (secondType === 'number' || secondType === 'string') {
            second = parseInt(second)
            var hours = Math.floor(second / 3600)
            second = second - hours * 3600
            var mimute = Math.floor(second / 60)
            second = second - mimute * 60
            return hours + ':' + ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)
        } else {
            return '0:00:00'
        }
    }

    // 进度条toolTip
    const formatProcessToolTip =(index = 0)=> {
        index =Math.floor(Number(state.audioState.maxTime) / 100 * index)
        return '进度条 : ' + realFormatSecond(index)
    }
    // 播放跳转
    const changeCurrentTime = (index:any)=> {
        audio.value.currentTime = Math.floor(Number(index) / 100 * Number( state.audioState.maxTime))
    }
    const startMutedOrNot =()=> {
        audio.value.muted = !audio.value.muted
        audio.value.muted = audio.value.muted
    }
    // 音量条toolTip
    const formatVolumeToolTip=(index:any)=> {
        return '音量条 : ' + index
    }
    // 音量改变
    const changeVolume = (index:any = 0)=> {
        audio.value.volume = Number(index) / 100
        state.volume = index
    }
    const moreMiniVoice =()=>{
        audio.value.volume = 0
        state.volume = 0
    }
    const moreMaxVoice =()=>{
        audio.value.volume = 1
        state.volume = 100
    }
    // 下载文件
    const downloadFile=()=>{
        let temurl=state.url.split('.').slice(-2)[0].split('/').slice(-1)[0]
        var laststr=temurl.lastIndexOf('_');
        var newStr=temurl.substring(0,laststr);
        let itemName=newStr
        download(state.url,itemName)
    }
    const download =(url:any, filename:String)=> {
        url=url+`?${Math.random()}`
        getBlob(url, function(blob:any) {
            saveAs(blob, filename);
        })
    }
    const getBlob=(url:String,cb:any)=> {
        var xhr:any = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = 'blob';
        xhr.onload = function() {
            if (xhr.status === 200) {
                cb(xhr.response);
            }
        };
        xhr.send();
    }
    const saveAs=(blob:any, filename:any)=> {
        var link:any = document.createElement('a');
        var body:any = document.querySelector('body');
        link.href = window.URL.createObjectURL(blob);
        link.download = filename;
        // fix Firefox
        link.style.display = 'none';
        body.appendChild(link);
        link.click();
        body.removeChild(link);
        window.URL.revokeObjectURL(link.href);
    }
    onMounted(() => {
        // 点击的时候自动播放
        // startPlayOrPause()
    })
    return {
        ...toRefs(state),
        onPlay,
        onError,
        onWaiting,
        onPause,
        onTimeupdate,
        onLoadedmetadata,
        startPlayOrPause,
        audio,
        changeSpeed,
        realFormatSecond,
        formatProcessToolTip,
        changeCurrentTime,
        startMutedOrNot,
        formatVolumeToolTip,
        changeVolume,
        setControlList,
        downloadFile,
        download,
        getBlob,
        saveAs,
        moreMiniVoice,
        moreMaxVoice,
    }
 }
}

</script>
<style scope>
.audio_player {
    display: inline-block;
    padding:4px 16px 4px 16px;
    border-radius: 4px;
}
.audioStyle {
    display: none;
}
.slider {
    display: inline-block;
    width: 190px;
    margin: 0 15px ;
    margin-top: 4px;
}
.sliderVoice {
    display: inline-block;
    width: 100px;
    margin-right:16px;
}
.download {
    color: #409EFF;
    margin-left: 15px;
}
.playevent {
    display: flex;
    align-items: center;
    justify-content: center;
}
.split_style {
    border-left:1px solid #DCDFE6;
    height:38px;
    margin:0 16px;
    width: 1px;
}
.hoverClass:hover {
    cursor: pointer;
}
</style>
<style>
.audio_player .el-button {
    display: inline-block;
    line-height: 1;
    min-height: 24px;
    padding:0;
}
.audio_player .el-dialog__header {
    padding: 0;
    border-bottom:none;
}
.content_solid .el-slider__button {
    display: inline-block;
    width: 10px;
    height: 10px;
    vertical-align: middle;
    border: 1px solid #025BAC;
    border-radius: 50%;
    box-sizing: border-box;
    box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.3);
    transition: .1s;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    background:#025BAC;
}
.voice_solid .el-slider__button {
    width: 16px;
    height: 16px;
    background: #FFFFFF;
    border: 1px solid #fff;
    box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.3);
}
.voice_solid {
    display:flex;
    align-items:center;
}
</style>

以上是全部组件里面的代码 建议全部copy 进行测试以及修改

定义组件

image.png

使用组件

image.png

组件传参

image.png