原理:基于websocket+mediaRecorder+mediaSource将录制的极短直播视频片段通过websocket实时推送变相完成推流。

1.直播推流端

<template><el-container class="palyer-main"><el-header height="35px"><span style="float: left">直播设置</span><span>xxx直播间(已播 :03:35:12)</span><span style="float: right">聊天室</span></el-header><el-container><el-aside width="250px" class="palyer-main-left"><div class="live-item"><el-select v-model="videoSourceVal" placeholder="摄像头选择" style="width: 130px"><el-optionv-for="(item,index) in videoSourceOption":key="index":label="item.label":value="item.value"/></el-select><el-switchv-model="videoActive"active-text="开"inactive-text="关"@change="videoActiveChange"/></div><div class="live-item"><el-select v-model="audioInputVal" placeholder="麦克风选择" style="width: 130px"><el-optionv-for="(item,index) in audioInputOption":key="index":label="item.label":value="item.value"/></el-select><el-switchv-model="audioInputActive"active-text="开"inactive-text="关"@change="audioInputActiveChange"/></div><div class="live-item"><i class="el-icon-video-camera" /><span>屏幕录制</span><el-switchv-model="screenRecoderActive"class="live-item-switch"active-text="开"inactive-text="关"@change="screenRecoderChange"/></div><el-button v-if="liveBtn" type="success" size="medium" icon="el-icon-mic" class="live-btn" @click="startRecord">开始直播</el-button><el-button v-else type="success" size="medium" icon="el-icon-mic" class="live-btn" @click="stopRecord">结束直播</el-button></el-aside><el-main class="palyer-main-video"><el-main :style="{height:tableHeight + 'px'}"><video ref="canvsVideo" autoplay controls width="557px" height="412px" /></el-main><el-footer height="120px" /></el-main><el-aside width="290px" class="palyer-main-right"><div style="padding: 15px 25px;height: 82px;"><el-avatar size="large" :src="circleUrl" class="chat-room-user" /><div class="chat-room-info"><div>陈晓二</div><div><i class="el-icon-view" title="当前在线人数" />&nbsp;<span>19</span></div></div></div><el-tabs v-model="activeName" stretch @tab-click="handleClick"><el-tab-pane label="聊天" name="first"><chatroom /></el-tab-pane><el-tab-pane label="在线(26)" name="second"><userlist /></el-tab-pane><el-tab-pane label="问答" name="third"><answers /></el-tab-pane></el-tabs></el-aside></el-container><video ref="localVideo" autoplay style="display: none" /><video ref="screenVideo" autoplay style="display: none" /><canvas ref="canvs" style="display: none" /></el-container>
</template>
<script>
import { mapGetters } from 'vuex'
import chatroom from '../module/chatroom'
import answers from '../module/answers'
import userlist from '../module/userlist'export default {name: 'LivePalyer',components: {chatroom, answers, userlist},data() {return {SCREEN_WIDTH: 1024,SCREEN_HEIGHT: 640,CAMERA_VIDEO_WIDTH: 200,CAMERA_VIDEO_HEIGHT: 150,screenHeight: 0,videoActive: false,videoSourceVal: '',videoSourceOption: [],audioInputActive: false,audioInputVal: '',audioInputOption: [],audioOutActive: false,audioOutputVal: '',audioOutputOption: [],screenRecoderActive: false,circleUrl: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',activeName: 'first',liveBtn: true,roomid: '1111',fileHeader: []}},computed: {...mapGetters(['sysHospital','userId','username']),tableHeight: function() {return window.screen.height - 352}},created() {if (this.$socket.disconnected) {this.$socket.connect()}this.getDevices()this.getUserMedia()},mounted() {this.canvs = this.$refs.canvsthis.canvs.width = this.SCREEN_WIDTHthis.canvs.height = this.SCREEN_HEIGHTthis.canvsVideo = this.$refs.canvsVideothis.localVideo = this.$refs.localVideothis.screenVideo = this.$refs.screenVideothis._context2d = this.canvs.getContext('2d')setTimeout(this._animationFrameHandler.bind(this), 30)},methods: {RecordLoop() {if (this.mediaRecorder.state !== 'inactive') {this.mediaRecorder.stop()}if (this.mediaRecorder.state !== 'recording') {this.mediaRecorder.start()}setTimeout(this.RecordLoop, 2500)},// 拿到媒体流startRecord() {this.liveBtn = falseconsole.log(`start Record`)const _this = this// 1. Create a `MediaSource`this.mediaSource = new MediaSource()// 2. Create an object URL from the `MediaSource`var url = URL.createObjectURL(this.mediaSource)// 3. Set the video's `src` to the object URLthis.canvsVideo.src = urlthis.sourceBuffer = nullthis.mediaSource.addEventListener('sourceopen', async function() {URL.revokeObjectURL(_this.localVideo.src)_this.sourceBuffer = _this.mediaSource.addSourceBuffer('video/webm; codecs=opus,vp9')_this.sourceBuffer.mode = 'sequence'// 不然需要添加时间戳_this.sourceBuffer.addEventListener('updateend', function() {})})this.RecordLoop()},stopRecord() {this.liveBtn = truethis.canvsVideo.srcObject = nullthis.mediaRecorder.stop()URL.revokeObjectURL(this.canvsVideo.src)},async getUserMedia() {const _this = thisthis._stream = new MediaStream()if (this.videoActive) {// 视频if (navigator.mediaDevices.getUserMedia) {// 最新标准APIthis.cameraStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false })} else if (navigator.webkitGetUserMedia) {// webkit内核浏览器this.cameraStream = await navigator.webkitGetUserMedia({ video: true, audio: false })} else if (navigator.mozGetUserMedia) {// Firefox浏览器this.cameraStream = await navigator.mozGetUserMedia({ video: true, audio: false })} else if (navigator.getUserMedia) {// 旧版APIthis.cameraStream = await navigator.getUserMedia({ video: true, audio: false })}this.localVideo.srcObject = this.cameraStream}if (this.screenRecoderActive) {this.captureStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false })this.screenVideo.srcObject = this.captureStream}this._audioStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true })if (this.audioInputActive) {this._audioStream.getAudioTracks().forEach(value => this._stream.addTrack(value))}this._audioStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true })this._audioStream.getAudioTracks().forEach(value => this._stream.addTrack(value))const playerCanvasStream = this.canvs.captureStream()playerCanvasStream.getTracks().forEach(t => this._stream.addTrack(t))const options = {mimeType: 'video/webm; codecs=opus,vp9'}if (!MediaRecorder.isTypeSupported(options.mimeType)) {this.$message.error(`${options.mimeType} is not supported!`)return}this.mediaRecorder = new MediaRecorder(this._stream, options)// 当录制的数据可用时this.mediaRecorder.ondataavailable = async function(e) {if (e.data.size > 0) {var reader = new FileReader()reader.readAsArrayBuffer(e.data)reader.onloadend = function(e) {_this.sourceBuffer.appendBuffer(reader.result)// if (_this.joinRoomState === 'joinedRoom') {_this.$socket.emit('sendLiveMessage', _this.roomid, reader.result)// }}}}},getDevices() {if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {this.$message.error('不支持获取设备信息!')} else {navigator.mediaDevices.enumerateDevices().then(this.showDevice).catch((err) => {console.log(err.name + ':' + err.message)})}},showDevice(deviceInfos) {const _this = thisdeviceInfos.forEach(function(deviceinfo) {const option = {label: deviceinfo.label,value: deviceinfo.deviceId}if (deviceinfo.kind === 'audioinput') {_this.audioInputOption.push(option)} else if (deviceinfo.kind === 'audiooutput') {_this.audioOutputOption.push(option)} else if (deviceinfo.kind === 'videoinput') {_this.videoSourceOption.push(option)}})},async recordScreenStream() {if (!this.screenRecoderActive) {return}this.captureStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false })this.screenVideo.srcObject = this.captureStream},async recordCameraStream() {if (this.videoActive) {// 视频if (navigator.mediaDevices.getUserMedia) {// 最新标准APIthis.cameraStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false })} else if (navigator.webkitGetUserMedia) {// webkit内核浏览器this.cameraStream = await navigator.webkitGetUserMedia({ video: true, audio: false })} else if (navigator.mozGetUserMedia) {// Firefox浏览器this.cameraStream = await navigator.mozGetUserMedia({ video: true, audio: false })} else if (navigator.getUserMedia) {// 旧版APIthis.cameraStream = await navigator.getUserMedia({ video: true, audio: false })}this.localVideo.srcObject = this.cameraStream}},async recordAudioStream() {if (this.audioInputActive) {this._audioStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true })this._audioStream.getAudioTracks().forEach(value => this._stream.addTrack(value))}},handleClick(tab, event) {console.log(tab, event)},_animationFrameHandler() {if (this.screenVideo && this.screenRecoderActive) {this._context2d.drawImage(this.screenVideo, 0, 0, this.SCREEN_WIDTH, this.SCREEN_HEIGHT)}if (this.localVideo && this.videoActive) {if (this.screenRecoderActive) {this._context2d.drawImage(this.localVideo,this.SCREEN_WIDTH - this.CAMERA_VIDEO_WIDTH,this.SCREEN_HEIGHT - this.CAMERA_VIDEO_HEIGHT,this.CAMERA_VIDEO_WIDTH,this.CAMERA_VIDEO_HEIGHT)} else {this._context2d.drawImage(this.localVideo, 0, 0, this.SCREEN_WIDTH, this.SCREEN_HEIGHT)}}setTimeout(this._animationFrameHandler.bind(this), 30)},screenRecoderChange(callback) {this.screenRecoderActive = callbackif (this.screenRecoderActive) {this.recordScreenStream()}},videoActiveChange(callback) {this.videoActive = callbackif (this.videoActive) {this.recordCameraStream()}},audioInputActiveChange(callback) {this.audioInputActive = callbackif (this.audioInputActive) {this.recordAudioStream()}}// audioOutActiveChange() {//   this.audioOutActive = !this.audioInputActive// }},sockets: {/*** socket自带3个事件connect,disconnect,reconnect* **/connect: function() {// 与socket.io连接后回调console.log('socket connected')this.$socket.emit('joinRoom', this.roomid)},disconnect: function() {console.log('socket disconnect')},reconnect: function() {console.log('socket reconnect')},joinedRoom: function(data) {// 加入成功后不能再次连接,可以离开this.connectFlag = truethis.leaveFlag = false// 改变状态this.joinRoomState = 'joinedRoom'console.log('joinedRoom')},otherJoin: function(data) {console.log('otherJoin' + data.room)if (this.joinRoomState === 'joinedRoom') {const msg = `${data.username}加入会议室(${data.room}),当前有${data.numUsers}人`this.$message.success(msg)}},roomFull: function(data) {this.joinRoomState = 'leavedRoom'// 可以再次连接this.connectFlag = falsethis.leaveFlag = truethis.$message.error('房间人数已满,请稍后重试!')this.$socket.disconnect()console.log('leavedRoom')},leaveRoomed: function(data) {this.connectFlag = falsethis.leaveFlag = truethis.joinRoomState = 'leavedRoom'this.$socket.disconnect()console.log('leavedRoom')},sayBye: function(data) {this.connectFlag = falsethis.leaveFlag = truethis.joinRoomState = 'leavedRoom'this.$socket.disconnect()console.log('leavedRoom')},receivedMessage: function(msg) {}}
}
</script>
<style scoped>
.palyer-main{padding: 10px 10px;
}
.palyer-main-left{border: 1px solid #e5e4e4;margin-right: 10px;border-radius: 10px;
}
.palyer-main-right{border: 1px solid #e5e4e4;margin-left: 10px;border-radius: 10px;
}
.palyer-main-video{padding: 10px;border: 1px solid #e5e4e4;
}
.el-header {padding: 0 126px 0 95px;color: #333;text-align: center;line-height: 35px;
}
.el-footer {color: #333;text-align: center;
}.el-aside {background: #fff;color: #333;text-align: center;margin-bottom: 0;padding: 8px 5px;
}
.live-item{padding-top: 10px;padding-bottom: 10px;border-bottom: 1px solid #eee
}
.live-item-switch{float: right;padding-top: 10px;padding-right: 8px;
}
.live-btn{position: absolute;left: 0;bottom: 0;margin-left: 15px;padding: 10px 80px;
}
.chat-room-info{float: left;padding-left: 10px;color: #aea9a9;
}
.chat-room-user{position: relative;float: left;
}
.el-avatar--large{width: 60px;height: 60px;
}
.el-main {padding: 0;color: #333;text-align: center;
}body > .el-container {margin-bottom: 40px;
}.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {}.el-container:nth-child(7) .el-aside {}
</style>

2.直播播放端

<template><el-container class="palyer-main"><el-header height="35px"><span style="padding-right: 200px">xxx直播间(已播 :03:35:12)</span><span style="float: right">聊天室</span></el-header><el-container><el-main class="palyer-main-video" style="margin: 0"><el-main :style="{height:tableHeight + 'px'}"><video ref="localVideo" autoplay controls width="725px" height="460px" /></el-main><el-footer height="120px" /></el-main><el-aside width="352px" class="palyer-main-right"><div style="padding: 15px 25px;height: 82px;"><el-avatar size="large" :src="circleUrl" class="chat-room-user" /><div class="chat-room-info"><div>陈晓二</div><div><i class="el-icon-view" title="当前在线人数" />&nbsp;<span>19</span></div></div></div><el-tabs v-model="activeName" stretch @tab-click="handleClick"><el-tab-pane label="聊天" name="first"><chatroom /></el-tab-pane><el-tab-pane label="在线(26)" name="second"><userlist /></el-tab-pane><el-tab-pane label="问答" name="third"><answers /></el-tab-pane></el-tabs></el-aside></el-container></el-container>
</template>
<script>
import { mapGetters } from 'vuex'
import chatroom from '../module/chatroom'
import answers from '../module/answers'
import userlist from '../module/userlist'export default {name: 'VideoPlayer',components: {chatroom, answers, userlist},data() {return {screenHeight: 0,circleUrl: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',activeName: 'first',roomid: '1111',palysState: false,arrayOfBlobs: []}},computed: {...mapGetters(['sysHospital','userId','username']),tableHeight: function() {return window.screen.height - 302}},created() {this.getUserMedia()if (this.$socket.disconnected) {this.$socket.connect()}},mounted() {this.localVideo = this.$refs.localVideo},methods: {async getUserMedia() {await navigator.mediaDevices.enumerateDevicesthis.mediaSource = new MediaSource()const url = URL.createObjectURL(this.mediaSource)this.localVideo.src = urlthis.sourceBuffer = nullconst _this = thisthis.mediaSource.addEventListener('sourceopen', function() {URL.revokeObjectURL(_this.localVideo.src)_this.sourceBuffer = _this.mediaSource.addSourceBuffer('video/webm; codecs=opus,vp9')_this.sourceBuffer.mode = 'sequence'// 不然需要添加时间戳_this.sourceBuffer.addEventListener('updateend', function() {})})},handleClick(tab, event) {console.log(tab, event)}},sockets: {/*** socket自带3个事件connect,disconnect,reconnect* **/connect: function() {// 与socket.io连接后回调console.log('socket connected')this.$socket.emit('joinRoom', this.roomid)},disconnect: function() {console.log('socket disconnect')},reconnect: function() {console.log('socket reconnect')},joinedRoom: function(data) {// 加入成功后不能再次连接,可以离开this.connectFlag = truethis.leaveFlag = false// 改变状态this.joinRoomState = 'joinedRoom'},otherJoin: function(data) {if (this.joinRoomState === 'joinedRoom') {const msg = `${data.username}加入会议室(${data.room}),当前有${data.numUsers}人`this.$message.success(msg)}},roomFull: function(data) {this.joinRoomState = 'leavedRoom'// 可以再次连接this.connectFlag = falsethis.leaveFlag = truethis.$message.error('房间人数已满,请稍后重试!')this.$socket.disconnect()},leaveRoomed: function(data) {this.connectFlag = falsethis.leaveFlag = truethis.joinRoomState = 'leavedRoom'this.$socket.disconnect()},sayBye: function(data) {this.connectFlag = falsethis.leaveFlag = truethis.joinRoomState = 'leavedRoom'this.$socket.disconnect()},receivedMessage: function(msg) {if (this.sourceBuffer && this.sourceBuffer.updating === false) {this.sourceBuffer.appendBuffer(msg[1])}if (this.localVideo.buffered.length && this.localVideo.buffered.end(0) - this.localVideo.buffered.start(0) > 30) {this.sourceBuffer.remove(0, this.localVideo.buffered.end(0) - 30)}}}
}
</script>
<style scoped>
.palyer-main{padding: 10px 10px;
}
.palyer-main-left{border: 1px solid #e5e4e4;margin-right: 10px;border-radius: 10px;
}
.palyer-main-right{border: 1px solid #e5e4e4;margin-left: 10px;border-radius: 10px;
}
.palyer-main-video{padding: 10px;border: 1px solid #e5e4e4;
}
.el-header {padding: 0 126px 0 95px;color: #333;text-align: center;line-height: 35px;
}
.el-footer {color: #333;text-align: center;
}.el-aside {background: #fff;color: #333;text-align: center;margin-bottom: 0;padding: 8px 5px;
}
.live-item{padding-top: 10px;padding-bottom: 10px;border-bottom: 1px solid #eee
}
.live-item-switch{float: right;padding-top: 10px;padding-right: 8px;
}
.live-btn{position: absolute;left: 0;bottom: 0;margin-left: 15px;padding: 10px 80px;
}
.chat-room-info{float: left;padding-left: 10px;color: #aea9a9;
}
.chat-room-user{position: relative;float: left;
}
.el-avatar--large{width: 60px;height: 60px;
}
.el-main {padding: 0;color: #333;text-align: center;
}body > .el-container {margin-bottom: 40px;
}.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {}.el-container:nth-child(7) .el-aside {}
</style>

基于vue实现网页直播推流(不能落地,仅作记录)相关推荐

  1. 魔坊APP项目-26-直播、docker安装OSSRS流媒体直播服务器、基于APICloud的acLive直播推流模块实现RTMP直播推流、直播流管理

    一.docker安装OSSRS流媒体直播服务器 在外界开发中, 如果要实现直播功能.常用的方式有: 1. 通过第三方接口来实现.可以申请阿里云,腾讯云,网易云,七牛云的直播接口,根据文档,下载集成SD ...

  2. 基于Vue的网页版录音并播放

    最近项目中需要实现一个效果,需要在网页上录制音频,并上传给后台,后续还需要做语音识别处理. 下面的表格罗列了我的前端项目中所使用的框架以及插件(本项目基于Vue): 插件名称 资源地址 Element ...

  3. vue连线 插件_基于vue的网页标尺辅助线工具(vue-ruler-tool)

    原文首发于我的博客,欢迎点击查看获得更好的阅读体验~ 标尺辅助线.gif vue-ruler-tool 最近在网上找到一个网页制作辅助工具-jQuery标尺参考线插件,觉得在现在的一个项目中能用的上, ...

  4. Vue中如何进行屏幕录制与直播推流

    Vue中如何进行屏幕录制与直播推流 屏幕录制和直播推流是现代Web应用中常用的功能,例如在线教育.视频会议和游戏直播等.Vue作为一种流行的JavaScript框架,提供了一些工具和库,可以方便地实现 ...

  5. 基于Vue实现的网页音乐播放器

    基于Vue现实网页音乐播放器 该音乐播放器是由父组件和子组件相结合通过axios获取音乐的相关属性,将其显示在界面,通过点击实现相应的操作.链接为https://download.csdn.net/d ...

  6. 开源直播推流sdk_基于WebRTC的互动直播实践

    互动直播已经逐渐成为直播的主要形式.映客直播资深音视频工程师叶峰峰在LiveVideoStackCon 2018大会的演讲中详细介绍了INKE自研连麦整体设计思路.如何基于WebRTC搭建互动直播SD ...

  7. 基于GPUImage的多滤镜rtmp直播推流

    之前做过开源videocore的推流改进:1)加入了美颜滤镜; 2) 加入了librtmp替换原来过于简单的rtmpclient: 后来听朋友说,在videocore上面进行opengl修改,加入新的 ...

  8. C++编程FFMpeg实时美颜直播推流实战-基于ffmpeg,qt5,opencv视频课程-夏曹俊-专题视频课程...

    C++编程FFMpeg实时美颜直播推流实战-基于ffmpeg,qt5,opencv视频课程-11788人已学习 课程介绍         C++编程FFMpeg实时美颜直播推流实战视频培训教程,本课程 ...

  9. EasyGBS摄像机网页直播之问题解决:海康设备通过TCP接入到EasyGBS, 设备不推流问题解析

    EasyGBS诞生背景分析 近年来,国内视频监控应用发展迅猛,系统接入规模不断扩大,涌现了大量平台提供商,平台提供商的接入协议各不相同,终端制造商需要给每款终端维护提供各种不同平台的软件版本,造成了极 ...

  10. 熹微~~~基于Vue开发的昏暗风格的响应式网页!

    熹微网页介绍 1.项目简介 熹微(dim-star),名字来源于晋代田园派诗人陶渊明的<归去来兮辞>: 问征夫以前路,恨晨光之熹微, 因为整个界面的风格是较为暗淡的,页面中的组件又总是给人 ...

最新文章

  1. 基于Python查找图像中最常见的颜色
  2. 6.3文件传输协议FTP
  3. signature=a8a3e788013f73439051c7287d7f5f0b,来用百度密语吧!!!
  4. 永洪Desktop自由表格间计算使用教程
  5. 3.1.6 OS之分页存储(页号、页偏移量等)
  6. linux 卸载kde,Ubuntu KDE终端系统安装与卸载
  7. cobalt strick 4.0 系列教程(6)Payload Artifact 和反病毒规避
  8. 想打ACM?想刷题?来这些online judge!
  9. Centos 5.3 Nginx+php+mysql配置 独立的 Subversion (SVN)服务器
  10. 浙大慕课c语言答案,程序设计入门——C语言
  11. 高德地图上线武汉千家商超信息 可预约团购、查营业时间和电话
  12. Linux系统实现ICMP ping功能,并计算时延
  13. mysql begin rollback_事务控制语句,begin,rollback,savepoint,隐式提交的SQL语句
  14. LeetCode 64.最小路径和(动态规划)
  15. Image Pyramids
  16. 【CGAL】提取中心线
  17. 记账小程序 微信小程序 源码 uniapp vue3
  18. 为程序员提供一杯免费咖啡
  19. 数字信号处理学习笔记[1] 离散信号 奇异信号 抽样定理
  20. 由祖冲之圆周率洞悉——古中国数学家的计算力真是惊人

热门文章

  1. 1.1微信支付之现金红包 - Java 开发
  2. 【PC页面设计项目】宠物物流页面设计(源码+图示)
  3. MaixII-Dock(v831)学习笔记——PWM
  4. 【Codeforces Round #695 (Div. 2) B】Hills And Valleys
  5. unity5(一)unity5新特性 unity下载与安装
  6. 基于 Netty 重构 RPC 框架
  7. linux下root权限管理账号
  8. WPF界面工具Telerik UI for WPF入门级教程 - 设置一个主题(二)
  9. prettier工具格式化
  10. 聚币网API[Python2版]