vue2(H5录音)

<template>
    <div class="upload_container">
        <div class="igmBox">
            <div class="img_box" v-for="(item, index) in data" :key="item.url">
                <img :src="getFileUrl(item)" @click="handleclicksc(index)">
                <div class="delete_btn"> <van-icon name="cross" class="" @click="afterDelete(index)" /> </div>
            </div>
        </div>

        <van-uploader v-bind="uploadParameter" :after-read="afterRead" :before-read="beforeRead" @oversize="onOversize"
            upload-icon="plus" :preview-full-image="false" v-show="isUploadShow" ref="uploadRef">
            <template #default>
                <div></div>
            </template>
        </van-uploader>

        <div class="uploadBox">
            <div @click="$refs.uploadRef.chooseFile()" class="chooseFile"><i
                    class="iconfont icon-zengjia"></i><span>图片/视频</span></div>
            <div v-longpress="handleAudio" class="chooseFile" @touchend="touchendBtn"><i
                    class="iconfont icon-zengjia"></i><span>语音输入</span></div>
            <!-- 语音音阶动画 -->
            <div class="prompt-layer prompt-layer-1" v-if="isLongPress">
                <div class="prompt-loader">
                    <div class="em" v-for="(item, index) in 15" :key="index"></div>
                </div>
                <!-- <div class="p">{{ '剩余:' + count + 's' }}</div> -->
                <div class="span">松手结束录音</div>
            </div>
        </div>

        <PreviewImgOrVideo :previewShow="previewShow" @close="$event => previewShow = $event" :data="preViewData"
            v-if="previewShow">
        </PreviewImgOrVideo>
    </div>
</template>
<script>
import { mapGetters } from "vuex";
import videoIcon from '../images/video_icon.png';
import audioIcon from '../images/audio_icon.png';
import { getNewFileUrl } from "@/utils/download";
import { filesExperts } from '@/api/appyjzj/newIndex';
import PreviewImgOrVideo from './previewImgOrVideo.vue';
export default {
    directives: {
        longpress: {
            bind: function (el, binding) {

                // 定义长按时间阈值
                let pressTimer = null
                let longPressActive = false

                // 创建计时器(这会在长按后启动)
                let start = (e) => {
                    if (e.type === 'click' && e.button !== 0) {
                        return
                    }
                    if (pressTimer === null) {
                        pressTimer = setTimeout(() => {
                            handler()
                        }, 500)
                    }
                }

                // 取消计时器(长按结束时会运行)
                let cancel = (e) => {
                    if (pressTimer !== null) {
                        clearTimeout(pressTimer)
                        pressTimer = null
                    }
                }

                // 长按要运行的函数
                const handler = (e) => {
                    binding.value(e)
                }

                // 添加事件监听器
                el.addEventListener("mousedown", start)
                el.addEventListener("touchstart", start)

                // 取消计时器
                el.addEventListener("click", cancel)
                el.addEventListener("mouseout", cancel)
                el.addEventListener("touchend", cancel)
                el.addEventListener("touchcancel", cancel)
            }
        }

    },
    components: {
        PreviewImgOrVideo
    },
    props: {
        data: {
            type: Array,
            default: () => []
        },
        uploadParameter: {
            type: Object,
            default: () => { }
        }
    },
    model: {
        prop: 'data',
        event: 'change'
    },
    data() {
        return {
            previewShow: false,
            preViewData: {},
            loading: false,
            mediaRecorder: null,
            isLongPress: false,
            recordedBlob: false
        };
    },
    computed: {
        ...mapGetters(["userInfo"]),
        isUploadShow() {
            return this.uploadParameter['max-count'] && this.uploadParameter['max-count'] > this.data.length
        }
    },
    methods: {
        getNewFileUrl,
        isVideo(filename) {
            const videoExtensions = ['mp4', 'mov', 'avi', 'mkv', 'MOV'];
            const extension = filename.split('.').pop();
            return videoExtensions.includes(extension);
        },
        isAudio(filename) {
            const audioExtensions = ['mp3', 'wav', 'wma', 'ogg', 'aac', 'flac'];
            const extension = filename.split('.').pop();
            return audioExtensions.includes(extension);
        },
        getFileUrl(file) {
            if (this.isVideo(file.name)) {
                return videoIcon;
            } else if (this.isAudio(file.name)) {
                return audioIcon;
            } else {
                return this.getNewFileUrl(file.url);
            }
        },
        // 上传附件
        async afterRead(file) {
            this.loading = true
            file.status = "uploading";
            file.message = "上传中...";
            const formData = new FormData();
            formData.append("singleFile", file.file);
            try {
                const { data, code } = await filesExperts(formData);
                if (code === 2000) {
                    this.data.push({
                        name: data.fileName,
                        uid: data.id,
                        url: data.location
                    });
                    file.status = "done";
                    file.message = "上传成功";
                    console.log('this.from', this.formData)
                }
            } catch (err) {
                console.log('err', err)
                file.status = "failed";
                file.message = "上传失败";
            } finally {
                this.loading = false
            }
        },
        beforeRead(file) {
            if (this.loading) return false
            if (!file.type.startsWith('image/') && !file.type.startsWith('video/')) {
                this.$toast('请上传图片或视频文件');
                return false;
            }
            return true;
        },
        //手动点击删除,修改包含所有信息的文件列表,通过watch根据该列表动态修改图片文件列表
        afterDelete(index) {
            this.data.splice(index, 1)
        },
        onOversize(file) {
            this.$toast('超过大小');
        },
        //取消掉组件自带的点击预览功能,自己添加(系统自带预览点击视频时会先视频的播放图片)
        handleclicksc(index) {
            let file = this.data[index]
            const name = file.name;
            const fileExtension = name.split(".").pop().toLowerCase();

            const isImage = ["jpg", "jpeg", "png", "gif", "bmp"].includes(fileExtension);
            const isVideo = ["mp4", "mov", "avi", "mkv", 'MOV'].includes(fileExtension);
            if (isVideo) {
                this.preViewData = {
                    url: getNewFileUrl(file.url),
                    urlType: 'video'
                }
            }
            if (isImage) {
                this.preViewData = {
                    url: getNewFileUrl(file.url),
                    urlType: 'image'
                }
            }
            this.previewShow = true
        },
        async handleAudio() {
            this.isLongPress = true // 开始长按
            // 开始录音 
             if (this.isLongPress) {
                 console.log('navigator', navigator)
                 console.log('navigator.mediaDevices', navigator.mediaDevices)
                 navigator.mediaDevices.getUserMedia({ audio: true })
                    .then(stream => {
                         this.mediaRecorder = new MediaRecorder(stream);

                         this.mediaRecorder.addEventListener('dataavailable', event => {
                            console.log('event', event.data)
                             this.recordedBlob = event.data;
                        });

                         this.mediaRecorder.start();
                     })
                    .catch(error => {
                         // 用户拒绝访问麦克风
                   });
             }
           

        },
        async touchendBtn() {
            this.isLongPress = false // 结束长按
            this.mediaRecorder.stop();
            this.mediaRecorder.addEventListener('stop', async () => {
                // dataavailable 会在这里被触发
                try {
                    const formData = new FormData();
                    formData.append("singleFile", this.recordedBlob, `${this.userInfo.profile.name}的录音.mp3`);
                    const { data, code } = await filesExperts(formData);
                    if (code === 2000) {
                        this.data.push({
                            name: data.fileName,
                            uid: data.id,
                            url: data.location
                        });
                    }
                } catch (err) {
                    console.log('err', err)
                } finally {
                    this.loading = false
                    this.mediaRecorder = null;

                }
            })

        }
    }
}
</script>
<style scoped lang="scss">
.upload_container {
    margin-top: 7px;

    .igmBox {
        display: grid;
        grid-template-columns: repeat(auto-fill, 80px);
        grid-gap: 10px;
    }

    width: 100%;

}

.img_box {
    width: 80px;
    height: 80px;
    position: relative;

    img {
        width: 100%;
        height: 100%;
    }

    .delete_btn {
        position: absolute;
        top: 0;
        right: 0;
        width: 14px;
        height: 14px;
        background-color: rgba(0, 0, 0, 0.7);
        border-radius: 0 0 0 12px;

        .van-icon-cross {
            position: absolute;
            top: -2px;
            right: -2px;
            color: #fff;
            font-size: 16px;
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
        }
    }
}

.uploadBox {
    position: relative;
    all: unset;
    display: flex;
    justify-content: flex-end;

    .chooseFile {
        margin-left: 16px;

        i {
            margin-right: 4px;
            font-size: 16px;
            color: #1890FF;
        }

        span {
            font-size: 13px;
            font-family: PingFangSC-Regular, PingFang SC;
            color: #1890FF;
        }
    }

    /* 提示小弹窗 */
    .prompt-layer {
        border-radius: 8px;
        background: #f6f6f6;
        padding: 8px 16px;
        box-sizing: border-box;
        position: absolute;
        left: 50%;
        top: 50%;
        z-index: 999;
        transform: translate(-50%, -50%);
        // box-shadow: 5px rgba(0, 0, 0, .5);
    }

    // .prompt-layer::after {
    //     content: '';
    //     display: block;
    //     border: 6px solid rgba(0, 0, 0, 0);
    //     border-top-color: rgba(255, 211, 0, 1);
    //     position: absolute;
    //     bottom: -10px;
    //     left: 50%;
    //     transform: translateX(-50%);
    // }

    .prompt-layer-1 {
        font-size: 12px;
        width: 128px;
        text-align: center;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }

    .prompt-layer-1 .p {
        color: #000000;
    }

    .prompt-layer-1 .span {
        color: rgba(0, 0, 0, .6);
    }
}

/* 语音音阶------------- */
.prompt-loader {
    width: 96px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}

.prompt-loader .em {
    display: block;
    background: #333333;
    width: 1px;
    height: 10%;
    margin-right: 2.5px;
    float: left;
}

.prompt-loader .em:last-child {
    margin-right: 0px;
}

.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: rgba(0, 0, 0, 1);
    font-size: 12px;
}

/deep/.van-uploader {
    width: 100%;

    .van-uploader__wrapper,
    .van-uploader__input-wrapper {
        width: 100%;
    }
}
</style>

uniApp是一个基于Vue.js的跨平台框架,它允许开发者构建一次编写,多端运行的应用,包括H5网页应用、小程序和Android/iOS原生应用。在uniApp中,如果想要在H5应用中实现录音功能,可以借助uni-app提供的`uni.mediaSession` API。 以下是基本步骤: 1. **权限申请**: 首先需要在项目的manifest.json文件中添加对录音权限的请求,例如: ```json "permissions": { "media": { "audio": { "record": true } } } ``` 2. **初始化录音**: 使用`uni.getUserMedia`获取麦克风访问权,然后创建一个AudioContext实例进行录音操作: ```javascript async function startRecord() { try { const audioCtx = uni.createAudioContext(); const options = { mediaType: 'audio', bufferSize: 4096 }; const stream = await uni.getUserMedia(options); // 创建MediaRecorder实例并开始录制 const recorder = new MediaRecorder(stream); const recordingBlob = await new Promise((resolve) => { recorder.ondataavailable = (event) => resolve(event.data); recorder.start(); }); } catch (error) { console.error('Recording error:', error); } } ``` 3. **停止录音并保存**: 当需要结束录音时,调用`recorder.stop()`,处理生成的blob,可以选择存储到本地或者上传至服务器。 需要注意的是,在实际应用中,可能需要处理暂停、继续录音功能,并处理录音文件的管理和用户界面显示。另外,由于H5的兼容性问题,某些浏览器可能会限制录音功能,因此在开发前需要做好兼容性测试。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值