仿微信语音聊天webrtc

主要技术
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的更多相关文章
- Android 高仿微信语音聊天页面高斯模糊效果
目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高 ...
- html5聊天案例|趣聊h5|仿微信界面聊天|红包|语音聊天|地图
之前有开发过一个h5微直播项目,当时里面也用到过聊天模块部分,今天就在之前聊天部分的基础上重新抽离模块,开发了这个h5趣聊项目,功能效果比较类似微信聊天界面.采用html5+css3+Zepto+sw ...
- uniapp+nvue实现仿微信App聊天应用 —— 成功实现好友聊天+语音视频通话功能
基于uniapp + nvue实现的uniapp仿微信App聊天应用 txim 实例项目,实现了以下功能. 1: 聊天会话管理 2: 好友列表 3: 文字.语音.视频.表情.位置等聊天消息收发 4: ...
- 【手把手教程】uniapp + vue 从0搭建仿微信App聊天应用:腾讯云TXIM即时通讯的最佳实践
基于uniapp + vue 实现仿微信App聊天应用实践,实现以下功能 1: 用户登陆 2: 聊天会话管理 3: 文本/图片/视频/定位消息收发 4: 贴图表情消息收发 5: 一对一语音视频在线通话 ...
- html5的audio实现高仿微信语音播放效果
效果图 前台大体呈现效果图如下: 点击就可以播放mp3格式的录音.点击另外一个录音,当前录音停止! 思路 关于播放动画,这个很简单,我们可以用css3的逐帧动画来实现.关于逐帧动画,我之前的文章也写过 ...
- h5移动端聊天室|仿微信界面聊天室|h5多人聊天室
今年的FIFA世界杯甚是精彩,最近兴致高涨就利用HTML5开发了一个手机端仿微信界面聊天室,该h5聊天室采用750px全新伸缩flex布局,以及使用rem响应式配合fontsize.js,页面弹窗则是 ...
- Tauri-Vue3桌面端聊天室|tauri+vite3仿微信|tauri聊天程序EXE
基于tauri+vue3.js+vite3跨桌面端仿微信聊天实例TauriVue3Chat. tauri-chat 运用最新tauri+vue3+vite3+element-plus+v3layer等 ...
- Android 高仿微信实时聊天 基于百度云推送
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天终于有幸利用百 ...
- Android 高仿微信即时聊天 百度云为基础的推
转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天最终有幸利用百 ...
- uniapp+nvue开发之仿微信语音+视频通话功能 :实现一对一语音视频在线通话
本篇文章是利用uni-app和nvue实现微信效果功能的第三篇了,今天我们基于uniapp + nvue实现的uniapp仿微信音视频通话插件实例项目,实现了以下功能: 1: 语音通话 2: 视频 ...
随机推荐
- Termius for macOS or Windows Download ssh
mac电脑 苹果系统 window 系统 链接 服务器,访问 ecs ,ssh链接 工具 下载地址:http://pgyd.online/?website/53.html Termius for ...
- 【Unity3D】Shader Graph节点
1 前言 Shader Graph 16.0.3 中有 208 个 Node(节点),本文梳理了 Shader Graph 中大部分 Node 的释义,官方介绍详见→Node-Library. ...
- 每天5分钟复习OpenStack(五)CPU虚拟化
KVM 虚拟化之CPU 虚拟化存在是为了更高效的利用物理机的资源,而虚拟机技术主要是针对三大组件,分别是CPU虚拟化.存储虚拟化.网络虚拟化.下面我们分别介绍下三大组件的常用知识. CPU 虚拟化 1 ...
- 产品代码都给你看了,可别再说不会DDD(九):领域事件
这是一个讲解DDD落地的文章系列,作者是<实现领域驱动设计>的译者滕云.本文章系列以一个真实的并已成功上线的软件项目--码如云(https://www.mryqr.com)为例,系统性地讲 ...
- 【Qt6】列表模型——几个便捷的列表类型
前面一些文章,老周简单介绍了在Qt 中使用列表模型的方法.很明显,使用 Item Model 在许多时候还是挺麻烦的--要先建模型,再放数据,最后才构建视图.为了简化这些骚操作,Qt 提供了几个便捷类 ...
- mysql常用函数详解
1. Mysql内置函数分类及使用范围 数学函数: 这类函数只要用于处理数字.这类函数包括绝对值函数.正弦函数.余弦函数.获取随机数函数等. 字符串函数:这类函数主要用于处理字符串.其中包括字符串连接 ...
- 让 keil MDK 支持C99
打开options fot target-> C/C++ 在 Misc Controls 中添加 --c99.
- JavaScript高级程序设计笔记12 BOM
BOM BOM的核心--window对象 窗口和弹窗 location对象--页面信息 navigator对象--浏览器信息 history对象--浏览器历史记录 BOM是使用JavaScript开发 ...
- (Good topic)圆圈中最后剩下的数字(leetcode 3.30每日打卡)
著名的约瑟夫问题: 0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆圈,从数字0开 ...
- 【python】Tkinter学习
定义 python自带的可编辑的GUI界面,是一个图像窗口. Tkinter是使用python进行窗口视窗设计的模块. 标签-按钮 2.1.Lable&Button标签和按钮 定义window ...