首页 前端知识 录音动效css及功能

录音动效css及功能

2025-02-25 13:02:50 前端知识 前端哥 715 425 我要收藏

<template>

    <div :class="longPress == '1' ? 'record-layer' : 'record-layer1'">

        <div :class="longPress == '1' ? 'record-box' : 'record-box1'">

            <div class="record-btn-layer">

                <button

                    class="record-btn"

                    :class="longPress == '1' ? 'record-btn-1' : 'record-btn-2'"

                    :style="

                        VoiceTitle != '松开手指,取消发送' && longPress != '1'

                            ? 'background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);'

                            : 'background-color: rgba(0, 0, 0, .5);color:white'

                    "

                    @longtap="longpressBtn"

                    @touchend="touchendBtn()"

                    @touchstart="longpressBtn"

                >

                    <image

                        src="https://penghuahai.obs.cn-south-1.myhuaweicloud.com:443/penghuahai/blind/1659237233452.png"

                    />

                    <text style="user-select: none">{{ VoiceText }}</text>

                </button>

            </div>

            <!-- 语音音阶动画 -->

            <div

                :class="

                    VoiceTitle != '松开手指,取消发送'

                        ? 'prompt-layer prompt-layer-1'

                        : 'prompt-layer1 prompt-layer-1'

                "

                v-if="longPress == '2'"

            >

                <div class="prompt-loader">

                    <canvas

                        id="recordCanvas"

                        ref="record"

                        style="width: 130px; height: 50px"

                    ></canvas>

                </div>

                <text class="span">{{ VoiceTitle }}</text>

            </div>

        </div>

    </div>

</template>

<script>

var init; // 录制时长计时器

var timer; // 播放 录制倒计时

import Recorder from "js-audio-recorder";

import axios from "axios";

const baseApi = ref(import.meta.env.VITE_APP_API_BASEURL);

export default {

    data() {

        return {

            longPress: "1", // 1显示 按住 说话 2显示 说话中

            delShow: false, // 删除提示框显示隐藏

            time: 0, //录音时长

            duration: 60000, //录音最大值ms 60000/1分钟

            tempFilePath: "", //音频路径

            startPoint: {}, //记录长按录音开始点信息,用于后面计算滑动距离。

            sendLock: true, //发送锁,当为true时上锁,false时解锁发送

            VoiceTitle: "松手结束录音",

            // recorderManager: uni.getRecorderManager(),

            VoiceText: "按住 说话",

            types: "",

            recorder: null,

            playTime: 0,

            timer: null,

            src: null,

            voiceShowFlag: false,

            drawRecordId: null,

            drawPlayId: null,

            isVoiceOpen: false,

        };

    },

    props: [],

    created: function () {

        let that = this;

        // 检测并请求访问麦克风

        navigator.mediaDevices

            .getUserMedia({ audio: true })

            .then(function (stream) {

                that.isVoiceOpen = true;

            })

            .catch(function (error) {

                that.isVoiceOpen = false;

            });

    },

    methods: {

        // 长按录音事件

        longpressBtn(e) {

            console.log(this.isVoiceOpen);

            if (this.isVoiceOpen) {

                this.startPoint = e.touches[0]; //记录长按时开始点信息,后面用于计算上划取消时手指滑动的距离。

                this.longPress = "2";

                this.VoiceText = "语音输入中... ...";

                // 监听音频开始事件

                this.sendLock = false; //长按时是不上锁的。

                this.handleStart();

            } else {

                this.$message({

                    message: "请先允许该网页使用麦克风",

                    type: "info",

                });

            }

        },

        // 长按松开录音事件

        touchendBtn() {

            this.longPress = "1";

            this.VoiceText = "按住 说话";

            this.VoiceTitle = "松手结束录音";

            if (this.isVoiceOpen) {

                this.handleStop();

                this.uploadRecord();

            }

        },

        // // 删除录音

        // handleTouchMove(e) {

        //     //touchmove时触发

        //     var moveLenght =

        //         e.touches[e.touches.length - 1].clientY -

        //         this.startPoint.clientY; //移动距离

        //     if (Math.abs(moveLenght) > 70) {

        //         this.VoiceTitle = "松开手指,取消发送";

        //         this.VoiceText = "松开手指,取消发送";

        //         this.delBtn();

        //         this.handleDestroy();

        //         this.sendLock = true; //触发了上滑取消发送,上锁

        //     } else {

        //         this.VoiceTitle = "松手结束录音";

        //         this.VoiceText = "松手结束录音";

        //         this.handleStop();

        //         this.uploadRecord();

        //         this.sendLock = false; //上划距离不足,依然可以发送,不上锁

        //     }

        // },

        delBtn() {

            this.delShow = false;

            this.time = 0;

            this.tempFilePath = "";

            // this.VoiceTitle = '松手结束录音'

        },

        // 开始录音

        handleStart() {

            this.recorder = new Recorder();

            Recorder.getPermission().then(

                () => {

                    console.log("开始录音");

                    this.recorder.start(); // 开始录音

                    this.drawRecord();

                },

                (error) => {

                    this.$message({

                        message: "请先允许该网页使用麦克风",

                        type: "info",

                    });

                    console.log(`${error.name} : ${error.message}`);

                }

            );

        },

        handlePause() {

            console.log("暂停录音");

            this.recorder.pause(); // 暂停录音

        },

        handleResume() {

            console.log("恢复录音");

            this.recorder.resume(); // 恢复录音

        },

        handleStop() {

            console.log("停止录音");

            this.recorder.stop(); // 停止录音

            this.drawRecordId && cancelAnimationFrame(this.drawRecordId);

            this.drawRecordId = null;

        },

        handlePlay() {

            console.log("播放录音");

            console.log(this.recorder);

            this.recorder.play(); // 播放录音

            // 播放时长

            this.timer = setInterval(() => {

                try {

                    this.playTime = this.recorder.getPlayTime();

                } catch (error) {

                    this.timer = null;

                }

            }, 100);

        },

        handlePausePlay() {

            console.log("暂停播放");

            this.recorder.pausePlay(); // 暂停播放

            // 播放时长

            this.playTime = this.recorder.getPlayTime();

            this.time = null;

        },

        handleResumePlay() {

            console.log("恢复播放");

            this.recorder.resumePlay(); // 恢复播放

            // 播放时长

            this.timer = setInterval(() => {

                try {

                    this.playTime = this.recorder.getPlayTime();

                } catch (error) {

                    this.timer = null;

                }

            }, 100);

        },

        handleStopPlay() {

            console.log("停止播放");

            this.recorder.stopPlay(); // 停止播放

            // 播放时长

            this.playTime = this.recorder.getPlayTime();

            this.timer = null;

        },

        handleDestroy() {

            console.log("销毁实例");

            this.recorder.destroy(); // 毁实例

            this.timer = null;

        },

        uploadRecord() {

            if (this.recorder == null || this.recorder.duration === 0) {

                this.$message({

                    message: "请先录音",

                    type: "error",

                });

                return false;

            }

            this.recorder.pause(); // 暂停录音

            this.timer = null;

            console.log("上传录音"); // 上传录音

            const formData = new FormData();

            const blob = this.recorder.getWAVBlob(); // 获取wav格式音频数据

            // 此处获取到blob对象后需要设置fileName满足当前项目上传需求,其它项目可直接传把blob作为file塞入formData

            const newbolb = new Blob([blob], { type: "audio/wav" });

            const fileOfBlob = new File(

                [newbolb],

                new Date().getTime() + ".wav"

            );

            console.log(fileOfBlob, "-------------");

            formData.append("file", fileOfBlob);

            const url = window.URL.createObjectURL(fileOfBlob);

            this.src = url;

            this.$emit("post-ready");

            axios({

                method: "post",

                url: `${baseApi.value}/qa/asr`,

                data: formData,

                headers: {

                    "Content-Type": "multipart/form-data",

                },

            })

                .then((res) => {

                    this.$emit("post-success", res.data.data);

                })

                .catch((err) => {

                    data.value.Loading = false;

                    proxy.$message.warning(err.msg);

                });

        },

        // 录音波浪图

        drawRecord() {

            // 用requestAnimationFrame稳定60fps绘制

            this.drawRecordId = requestAnimationFrame(this.drawRecord);

            this.drawWave({

                canvas: this.$refs.record,

                dataArray: this.recorder.getRecordAnalyseData(),

            });

        },

        // 播放波浪图

        drawPlay() {

            // 用requestAnimationFrame稳定60fps绘制

            this.drawPlayId = requestAnimationFrame(this.drawPlay);

            this.drawWave({

                canvas: this.$refs.play,

                dataArray: this.recorder.getPlayAnalyseData(),

            });

        },

        // 绘制波形图

        drawWave({ canvas, dataArray }) {

            const ctx = canvas.getContext("2d");

            const bufferLength = dataArray.length;

            // 填充背景色

            ctx.fillStyle = "#95eb6c";

            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // 设定波形绘制颜色

            ctx.lineWidth = 4;

            ctx.strokeStyle = "#000";

            ctx.beginPath();

            var sliceWidth = (canvas.width * 1.0) / bufferLength, // 一个点占多少位置,共有bufferLength个点要绘制

                x = 0; // 绘制点的x轴位置

            for (var i = 0; i < bufferLength; i++) {

                var v = dataArray[i] / 128.0;

                var y = (v * canvas.height) / 2;

                if (i === 0) {

                    // 第一个点

                    ctx.moveTo(x, y);

                } else {

                    // 剩余的点

                    ctx.lineTo(x, y);

                }

                // 依次平移,绘制所有点

                x += sliceWidth;

            }

            ctx.lineTo(canvas.width, canvas.height / 2);

            ctx.stroke();

        },

    },

};

</script>

<style lang="scss">

/* 语音录制开始--------------------------------------------------------------------- */

.record-layer {

    width: 65%;

    // padding: 300px 0;

    box-sizing: border-box;

    z-index: 10;

}

.record-layer1 {

    width: 100vw;

    // padding: 300px 0;

    box-sizing: border-box;

    height: 100vh;

    position: fixed;

    background-color: rgb(0 0 0 / 60%);

    // padding: 0 4vw;

    z-index: 10;

    bottom: 0;

    transform: translateX(-11px);

}

.record-box {

    width: 100%;

    position: relative;

}

.record-box1 {

    width: 100%;

    position: relative;

    bottom: -83vh;

    height: 17vh;

}

.record-btn-layer {

    width: 100%;

}

.record-btn-layer button::after {

    border: none;

    transition: all 0.1s;

}

.record-btn-layer button {

    font-size: 14px;

    line-height: 37px;

    width: 100%;

    height: 37px;

    border-radius: 8px;

    text-align: center;

    background: #ffd300;

    transition: all 0.1s;

    border: none;

    outline: none;

}

.record-btn-layer button image {

    width: 16px;

    height: 16px;

    margin-right: 4px;

    vertical-align: middle;

    transition: all 0.3s;

}

.record-btn-layer .record-btn-1 {

    background-image: linear-gradient(to right, #43e97b 0%, #38f9d7 100%);

    color: #000 !important;

}

.record-btn-layer .record-btn-2 {

    border-radius: 168rpx 168rpx 0 0;

    height: 17vh;

    line-height: 17vh;

    transition: all 0.3s;

}

/* 提示小弹窗 */

.prompt-layer {

    border-radius: 15px;

    background: #95eb6c;

    padding: 8px 16px;

    box-sizing: border-box;

    position: absolute;

    left: 50%;

    height: 11vh;

    transform: translateX(-50%);

    transition: all 0.3s;

}

// .prompt-layer::after {

//     content: "";

//     display: block;

//     border: 12px solid rgb(0 0 0 / 0%);

//     border-radius: 10rpx;

//     border-top-color: #95eb6c;

//     position: absolute;

//     bottom: -46rpx;

//     left: 50%;

//     transform: translateX(-50%);

//     transition: all 0.3s;

// }

//取消动画

.prompt-layer1 {

    border-radius: 15px;

    background: #95eb6c;

    padding: 8px 16px;

    box-sizing: border-box;

    position: absolute;

    left: 50%;

    height: 11vh;

    transform: translateX(-50%);

    transition: all 0.3s;

}

.prompt-layer-1 {

    font-size: 12px;

    width: 200px;

    text-align: center;

    display: flex;

    flex-direction: column;

    align-items: center;

    justify-content: center;

    top: -300px;

}

.prompt-layer-1 .p {

    color: #000;

}

.prompt-layer-1 .span {

    color: rgb(0 0 0 / 60%);

}

.prompt-loader .em {

}

/* 语音音阶------------- */

.prompt-loader {

    width: 125px;

    height: 48px;

    display: flex;

    align-items: center;

    justify-content: space-between;

    margin-bottom: 6px;

}

.prompt-loader .em {

    display: block;

    background: #333;

    width: 1px;

    height: 10%;

    margin-right: 2.5px;

    float: left;

}

.prompt-loader .em:last-child {

    margin-right: 0;

}

.prompt-loader .em:nth-child(1) {

    animation: load 2.5s 1.4s infinite linear;

}

.prompt-loader .em:nth-child(2) {

    animation: load 2.5s 1.2s infinite linear;

}

.prompt-loader .em:nth-child(3) {

    animation: load 2.5s 1s infinite linear;

}

.prompt-loader .em:nth-child(4) {

    animation: load 2.5s 0.8s infinite linear;

}

.prompt-loader .em:nth-child(5) {

    animation: load 2.5s 0.6s infinite linear;

}

.prompt-loader .em:nth-child(6) {

    animation: load 2.5s 0.4s infinite linear;

}

.prompt-loader .em:nth-child(7) {

    animation: load 2.5s 0.2s infinite linear;

}

.prompt-loader .em:nth-child(8) {

    animation: load 2.5s 0s infinite linear;

}

.prompt-loader .em:nth-child(9) {

    animation: load 2.5s 0.2s infinite linear;

}

.prompt-loader .em:nth-child(10) {

    animation: load 2.5s 0.4s infinite linear;

}

.prompt-loader .em:nth-child(11) {

    animation: load 2.5s 0.6s infinite linear;

}

.prompt-loader .em:nth-child(12) {

    animation: load 2.5s 0.8s infinite linear;

}

.prompt-loader .em:nth-child(13) {

    animation: load 2.5s 1s infinite linear;

}

.prompt-loader .em:nth-child(14) {

    animation: load 2.5s 1.2s infinite linear;

}

.prompt-loader .em:nth-child(15) {

    animation: load 2.5s 1.4s infinite linear;

}

@keyframes load {

    0% {

        height: 10%;

    }

    50% {

        height: 100%;

    }

    100% {

        height: 10%;

    }

}

/* 语音音阶-------------------- */

.prompt-layer-2 {

    top: -40px;

}

.prompt-layer-2 .text {

    color: rgb(0 0 0 / 100%);

    font-size: 12px;

}

/* 语音录制结束---------------------------------------------------------------- */

</style>

转载请注明出处或者链接地址:https://www.qianduange.cn//article/21314.html
标签
评论
发布的文章

python调用ollama库详解

2025-02-25 13:02:30

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!