主要技术

MediaRecorder 录音

webrtc 获取麦克风

URL.createObjectURL 转换为url(实际生产中,通过后端转换blob为mp3网址)

实现elementui+vue

1.html

<div class="chat-record">
<audio ref="chataudio"></audio>
<transition-group tag="ul" class="msg-list" name="fade">
<li v-for="(item, index) in chunList" :key="index" class="msg" @click="onPlay(index)" @touchend.prevent="onPlay(index)" :style="`flex-direction:${item.sendUserName===userName?'row-reverse':'row'}`">
<div class="avatar">
<dt>{{item.sendUserName}}</dt>
<dd>{{item.sendTime}}</dd>
</div>
<div v-cloak class="audio" :style="{width: 20 * item.audioStream + 'px'}" :class="{wink: item.wink,rotate:item.sendUserName!==userName}">
<span>(</span>
<span>(</span>
<span>(</span>
</div>
<div class="duration">{{item.audioStream}}"</div>
</li>
</transition-group>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" class="submit-btn" @mousedown.native="onMousedown" @touchstart.native="onMousedown" @mouseup.native="onMouseup" @touchend.native="onMouseup">{{btnText}}</el-button>
</span>

touchstart.native为了移动端触感灵敏

2.判断浏览器是否有相关api

function getUserMedia(constrains, success, error) {
let promise
if (navigator.mediaDevices.getUserMedia) {
//最新标准API
promise = navigator.mediaDevices
.getUserMedia(constrains)
.then(success)
.catch(error)
} else if (navigator.webkitGetUserMedia) {
//webkit内核浏览器
promise = navigator
.webkitGetUserMedia(constrains)
.then(success)
.catch(error)
} else if (navigator.mozGetUserMedia) {
//Firefox浏览器
promise = navigator.mozGetUserMedia(constrains).then(success).catch(error)
} else if (navigator.getUserMedia) {
//旧版API
promise = navigator.getUserMedia(constrains).then(success).catch(error)
}
return promise
}
function canGetUserMediaUse() {
return !!(
navigator.mediaDevices.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia
)
}

3. new window.MediaRecorder(stream)

 if (canGetUserMediaUse()) {
getUserMedia(
{
video: false,
audio: true,
},
(stream) => {
this.recorder = new window.MediaRecorder(stream)
this.bindEvents()
},
(error) => {
console.log(error)
alert('出错,请确保已允许浏览器获取录音权限' + error)
}
)
} else {
alert('您的浏览器不兼容')
}
},

4.录音开始,暂停,保存,转换

 onMousedown() {
this.btnText = '松开结束'
this.onStart()
}, onMouseup() {
this.btnText = '按住说话'
this.onStop()
}, onStart() {
this.recorder.start()
}, onStop() {
this.recorder.stop()
},
//重点
bindEvents() {
this.recorder.ondataavailable = this.getRecordingData
this.recorder.onstop = this.saveRecordingData
}, getRecordingData(e) {
this.chunks.push(e.data)
},
saveRecordingData() {
let blob = new Blob(this.chunks, { type: 'audio/ogg; codecs=opus' })
let audioStream = URL.createObjectURL(blob)
//估算时长
let duration = parseInt(blob.size / 6600)
if (duration <= 0) {
alert('说话时间太短')
return
}
if (duration > 60) {
duration = 60
}
this.chunkList.push({audioStream: audioStream, duration: duration})
this.chunks = []
},

5.播放

onPlay(index) {
let item = this.chunList[index]
this.audio = this.$refs.chataudio
this.audio.src = item.audioStream
this.audio.play()
this.bindAudioEvent(index)
},

完整代码

这是props获取websoket数据,自己使用请把wsdata换成chunkList

点击查看代码
<template>
<el-dialog append-to-body class="box-dialog" custom-class="dark-dialog" :visible.sync="dialogVisible" width="500px" center>
<div class="chat-record">
<audio ref="chataudio"></audio>
<transition-group tag="ul" class="msg-list" name="fade">
<li v-for="(item, index) in wsdata" :key="index" class="msg" @click="onPlay(index)" @touchend.prevent="onPlay(index)" :style="`flex-direction:${item.sendUserName===userName?'row-reverse':'row'}`">
<div class="avatar">
<dt>{{item.sendUserName}}</dt>
<dd>{{item.sendTime}}</dd>
</div>
<div v-cloak class="audio" :style="{width: 20 * item.sendContentLength + 'px'}" :class="{wink: item.wink,rotate:item.sendUserName!==userName}">
<span>(</span>
<span>(</span>
<span>(</span>
</div>
<div class="duration">{{item.sendContentLength}}"</div>
</li>
</transition-group>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" class="submit-btn" @mousedown.native="onMousedown" @touchstart.native="onMousedown" @mouseup.native="onMouseup" @touchend.native="onMouseup">{{btnText}}</el-button>
</span>
</el-dialog>
</template> <script>
function getUserMedia(constrains, success, error) {
let promise
if (navigator.mediaDevices.getUserMedia) {
//最新标准API
promise = navigator.mediaDevices
.getUserMedia(constrains)
.then(success)
.catch(error)
} else if (navigator.webkitGetUserMedia) {
//webkit内核浏览器
promise = navigator
.webkitGetUserMedia(constrains)
.then(success)
.catch(error)
} else if (navigator.mozGetUserMedia) {
//Firefox浏览器
promise = navigator.mozGetUserMedia(constrains).then(success).catch(error)
} else if (navigator.getUserMedia) {
//旧版API
promise = navigator.getUserMedia(constrains).then(success).catch(error)
}
return promise
}
function canGetUserMediaUse() {
return !!(
navigator.mediaDevices.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia
)
}
export default {
data() {
return {
dialogVisible: false,
chunks: [],
audio: '',
chunkList: [],
btnText: '按住说话',
}
},
props: {
wsdata: {
type: Array,
default: () => {},
},
userName: {
type: String,
default: '',
},
},
mounted() {
this.requestAudioAccess()
},
methods: {
open(data) {
this.dialogVisible = true
},
close() {
this.dialogVisible = false
this.resetForm()
},
requestAudioAccess() {
if (canGetUserMediaUse()) {
getUserMedia(
{
video: false,
audio: true,
},
(stream) => {
this.recorder = new window.MediaRecorder(stream)
this.bindEvents()
},
(error) => {
console.log(error)
alert('出错,请确保已允许浏览器获取录音权限' + error)
}
)
} else {
alert('您的浏览器不兼容')
}
}, onMousedown() {
this.btnText = '松开结束'
this.onStart()
}, onMouseup() {
this.btnText = '按住说话'
this.onStop()
}, onStart() {
this.recorder.start()
}, onStop() {
this.recorder.stop()
}, onPlay(index) {
this.wsdata.forEach((item) => {
this.$set(item, 'wink', false)
})
let item = this.wsdata[index]
this.audio = this.$refs.chataudio
this.audio.src = item.sendContent
this.audio.play() this.bindAudioEvent(index)
}, bindAudioEvent(index) {
let item = this.wsdata[index] this.audio.onplaying = () => {
this.$set(item, 'wink', true)
} this.audio.onended = () => {
this.$set(item, 'wink', false)
}
}, bindEvents() {
this.recorder.ondataavailable = this.getRecordingData
this.recorder.onstop = this.saveRecordingData
}, getRecordingData(e) {
this.chunks.push(e.data)
}, saveRecordingData() {
let blob = new Blob(this.chunks, { type: 'audio/ogg; codecs=opus' })
let audioStream = URL.createObjectURL(blob)
//估算时长
let duration = parseInt(blob.size / 6600)
if (duration <= 0) {
alert('说话时间太短')
return
}
if (duration > 60) {
duration = 60
}
this.$emit('getvoice', {blob: blob, duration: duration})
this.chunks = []
},
},
}
</script>
<style lang="scss" scoped>
.box-dialog::v-deep .el-dialog__footer {
padding:0 0;
padding-bottom: 10px;
}
.box-dialog::v-deep .el-dialog__body {
padding-bottom:0;
//background-color: rgba(#181b40, 0.9);
}
.box-dialog::v-deep .el-dialog__header {
display: none;
}
.submit-btn {
width: 96%;
height: 45px;
position: relative;
background-color: #181b40;
&:active {
background-color: #03225c;
}
&:active:before {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
top: -2px;
content: '';
width: 0%;
height: 2px;
background-color: #7bed9f;
animation: loading 1s ease-in-out infinite backwards;
}
} .chat-record {
width: 100%;
height: 300px;
overflow-y: scroll;
color: #fff; }
.msg-list {
margin: 0;
padding: 0;
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.msg-list::-webkit-scrollbar {
display: none;
}
.msg-list .msg {
list-style: none;
padding: 0 8px;
margin: 10px 0;
overflow: hidden;
cursor: pointer;
}
.msg-list .msg {
display: flex;
flex-direction: row-reverse;
}
.rotate{
transform: rotate(180deg);
}
.msg-list .msg .avatar {
height: 34px;
line-height: 14px;
font-size: 12px;
background-size: 100%;
dt {
font-size: 14px;
}
}
.msg-list .msg .audio {
position: relative;
margin-right: 6px;
max-width: 116px;
min-width: 30px;
height: 24px;
line-height: 24px;
padding: 0 4px 0 10px;
border-radius: 2px;
color: #000;
text-align: right;
background-color: rgba(107, 197, 107, 0.85);
}
.msg-list .msg.eg {
cursor: default;
}
.msg-list .msg.eg .audio {
text-align: left;
}
.msg-list .msg .audio:before {
position: absolute;
right: -8px;
top: 8px;
content: '';
display: inline-block;
width: 0;
height: 0;
border-style: solid;
border-width: 4px;
border-color: transparent transparent transparent rgba(107, 197, 107, 0.85);
}
.msg-list .msg .audio span {
color: rgba(255, 255, 255, 0.8);
display: inline-block;
transform-origin: center;
}
.msg-list .msg .audio span:nth-child(1) {
font-weight: 400;
}
.msg-list .msg .audio span:nth-child(2) {
transform: scale(0.8);
font-weight: 500;
}
.msg-list .msg .audio span:nth-child(3) {
transform: scale(0.5);
font-weight: 700;
}
.msg-list .msg .audio.wink span {
animation: wink 1s ease infinite;
}
.msg-list .msg .duration {
margin: 3px 2px;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
@keyframes wink {
from {
color: rgba(255, 255, 255, 0.8);
}
to {
color: rgba(255, 255, 255, 0.1);
}
}
@keyframes loading {
from {
width: 0%;
}
to {
width: 100%;
}
}
</style>

仿微信语音聊天webrtc的更多相关文章

  1. Android 高仿微信语音聊天页面高斯模糊效果

    目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高 ...

  2. html5聊天案例|趣聊h5|仿微信界面聊天|红包|语音聊天|地图

    之前有开发过一个h5微直播项目,当时里面也用到过聊天模块部分,今天就在之前聊天部分的基础上重新抽离模块,开发了这个h5趣聊项目,功能效果比较类似微信聊天界面.采用html5+css3+Zepto+sw ...

  3. uniapp+nvue实现仿微信App聊天应用 —— 成功实现好友聊天+语音视频通话功能

    基于uniapp + nvue实现的uniapp仿微信App聊天应用 txim 实例项目,实现了以下功能. 1: 聊天会话管理 2: 好友列表 3: 文字.语音.视频.表情.位置等聊天消息收发 4: ...

  4. 【手把手教程】uniapp + vue 从0搭建仿微信App聊天应用:腾讯云TXIM即时通讯的最佳实践

    基于uniapp + vue 实现仿微信App聊天应用实践,实现以下功能 1: 用户登陆 2: 聊天会话管理 3: 文本/图片/视频/定位消息收发 4: 贴图表情消息收发 5: 一对一语音视频在线通话 ...

  5. html5的audio实现高仿微信语音播放效果

    效果图 前台大体呈现效果图如下: 点击就可以播放mp3格式的录音.点击另外一个录音,当前录音停止! 思路 关于播放动画,这个很简单,我们可以用css3的逐帧动画来实现.关于逐帧动画,我之前的文章也写过 ...

  6. h5移动端聊天室|仿微信界面聊天室|h5多人聊天室

    今年的FIFA世界杯甚是精彩,最近兴致高涨就利用HTML5开发了一个手机端仿微信界面聊天室,该h5聊天室采用750px全新伸缩flex布局,以及使用rem响应式配合fontsize.js,页面弹窗则是 ...

  7. Tauri-Vue3桌面端聊天室|tauri+vite3仿微信|tauri聊天程序EXE

    基于tauri+vue3.js+vite3跨桌面端仿微信聊天实例TauriVue3Chat. tauri-chat 运用最新tauri+vue3+vite3+element-plus+v3layer等 ...

  8. Android 高仿微信实时聊天 基于百度云推送

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天终于有幸利用百 ...

  9. Android 高仿微信即时聊天 百度云为基础的推

    转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天最终有幸利用百 ...

  10. uniapp+nvue开发之仿微信语音+视频通话功能 :实现一对一语音视频在线通话

    ​ 本篇文章是利用uni-app和nvue实现微信效果功能的第三篇了,今天我们基于uniapp + nvue实现的uniapp仿微信音视频通话插件实例项目,实现了以下功能: 1: 语音通话 2: 视频 ...

随机推荐

  1. umich cv-3-2

    UMICH CV Neural Network 既然谈到神经网络,我们肯定要讨论在神经网络中是如何进行梯度的计算以及参数的优化的 传统的方法就是我们手动计算梯度,但是随着神经网络层数的增加,这种方法显 ...

  2. 简述location规则优先级-实现域名跳转-不同语言-终端跳转-错误页面返回首页-腾讯公益首页

    1.简述location的常见规则优先级,并且逐个验证: = :精确匹配(必须全部相等) #精准匹配优先级最高 ~ :大小写敏感(正则表达式) #一般使用~*忽略大小写匹配 (正则表达式 有上下区分, ...

  3. docker本地仓库-registry

    Docker本地私有仓库实战: docker仓库主要用于存放docker镜像,docker仓库分为公有仓库和私有仓库,基于registry可以搭建本地私有仓库,使用私有仓库的优点如下: 节省网络带宽, ...

  4. linux其他命令(查找,软链接,打包和压缩,软件安装)笔记

    1,查找文件 *  是通配符,代表任意字符,0到多个. find 路径  -name  "*.txt"  : 查找在路径下所有以 .txt 结尾的文件. 2,软链接 (1)将桌面目 ...

  5. postgresql 标量子查询改写的各种姿势

    同事提供一条SQL,原执行时间需要 3.6S ,反馈比较慢需要优化一下,废话不说贴SQL: 原SQL: select ((select count(1) FROM AAAAAAAAA wf join ...

  6. 如何使用C#编写低代码应用插件

    本文由葡萄城技术团队发布.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 作为当今快速发展的技术之一,低代码平台为开发人员提供了更高效.更简便的工具和 ...

  7. 🔥🔥Java开发者的Python快速进修指南:迭代器(Iterator)与生成器

    这一篇内容可能相对较少,但是迭代器在Java中是有用处的.因此,我想介绍一下Python中迭代器的使用方法.除了写法简单之外,Python的迭代器还有一个最大的不同之处,就是无法直接判断是否还有下一个 ...

  8. 写代码不用"if"行不行,曾经的反 if 运动

    如果在IT行业的时间够长的话,可能还记得大约10几年前,设计模式风靡一时的时候,有过一段反 "if" 的运动. 所谓的反"if"运动,其实是夸大了"i ...

  9. 玩转开源 |Hugo 的使用实践

    Hugo 是一个能够以出色速度构建静态网页的工具,它为我们提供了极具灵活性的平台,可以塑造成符合个人需求的网页.在上一篇博文中已经介绍了 Hugo 的基本搭建步骤,那如何使用 Hugo 搭建符合自己需 ...

  10. 实例讲解Python 解析JSON实现主机管理

    本文分享自华为云社区<Python 解析JSON实现主机管理>,作者: LyShark. JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它以易 ...