Vue + WebRTC 实现音视频直播(附自定义播放器样式)
1. 什么是WebRTC
1.1 WebRTC简介
WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的实时通信框架,提供了一系列页面可调用API。
参考定义:谷歌开放实时通信框架
在上一篇博客Vue +WebSocket + WaveSurferJS 实现H5聊天对话交互 中,已经涉及到WebRTC接口的使用,使用到了
getUserMedia
方法,用于通过浏览器获取设备麦克风,从而采集音频。
最近项目中的需求则是与服务端建立即时通信,实现低延迟音视频直播。
RTC的特征是(参考来源:https://www.zhihu.com/question/22301898)
- 复杂度较高
- 半可靠传输,对于特定情境(比如网络环境较差时)可以对音视频进行有损传输,降低延迟
- 音视频友好:可针对音视频做定制化优化
- 提供端对端优化方案。 对于传统连接模式,使用C/S架构,A=>服务端=>B,而WebRTC使用的是
peer-to-peer
模式,A=>B,一旦点和点之间的连接形成,它们之间的数据传输是不经过服务端的,大大降低了服务端的压力。 - 理论延迟较低,能应用在各种低延迟场景。
2. 业务描述
功能描述:
实现对摄像设备的管理列表,在设备列表点击查看视频时,弹出页面浮窗,进行摄像机摄像的视频和音频实时转播。
视频弹窗下方有自己实现的控制条,实现播放/暂停控制,能显示播放时间、切换分辨率、是否全屏等。
效果如图:
3. 代码实现
3.1 Html模板代码
<el-dialog ref="videoDialog" title="视频播放" :visible.sync="dialogShowed" :close-on-click-modal="false">
<div id="dialog-wrap">
<div id="video-wrap" v-if="isSuccess" v-loading="isLoading" element-loading-text="视频加载中" element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)" />
<div class="video-onloading" v-else v-loading="isLoading" element-loading-text="视频加载中" element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<span><i class="el-icon-error" v-if="!isLoading" />{{errorMessage}}</span>
</div>
<!-- 遮罩层 -->
<div class="cover" v-if="isSuccess">
<div class="controls">
<i class="el-icon-video-play" v-if="!isPlaying" @click="playOrPauseVideo" />
<i class="el-icon-video-pause" v-else @click="playOrPauseVideo" />
<div id="currentTime">播放时长:{{currentTime}}</div>
<div class="control-resolution">
分辨率:
<el-select v-model="selectResolution" @change="changeResolution">
<el-option v-for="item in resolutions" :key="item" :value="item">
{{item}}
</el-option>
</el-select>
</div>
<i class="el-icon-full-screen" @click="onClickFullScreen"></i>
</div>
</div>
</div>
</el-dialog>
使用了
Element-UI
框架提供的v-loading
指令,该指令根据isLoading
属性决定是否在区域内加载loading动画
若视频加载失败,则显示错误信息
预留标签,用于挂载`video和audio DOM元素
<div id="video-wrap" ></div>
注意该标签内最好不要再加其他元素,这样后续判断比较简单。
3.2 建立连接、接收音频
getVideo() {
let that = this;
that.isLoading = true;
that.pc = new RTCPeerConnection();
that.pc.addTransceiver("video");
that.pc.addTransceiver("audio");
that.pc.ontrack = function (event) {
var el = document.createElement(event.track.kind);
el.srcObject = event.streams[0];
el.autoplay = true;
document.getElementById("video-wrap").appendChild(el);
if (el.nodeName === "VIDEO") {
el.oncanplay = () => {
that.isLoading = false;
// 播放状态设置为true
that.isPlaying = true;
that.getVideoDuration();
};
} else if (el.nodeName === "AUDIO") {
el.oncanplay = () => {
};
}
};
that.pc
.createOffer()
.then((offer) => {
that.pc.setLocalDescription(offer);
let req = {
webrtc: offer,
};
console.log(offer);
return that.$api.device.getSignaling(
that.deviceData.id,
that.origin,
that.selectResolution,
req
);
})
.then((res) => {
if (res.code === 0) {
that.isSuccess = true;
that.pc.setRemoteDescription(res.body.webrtc);
that.connId = res.body.connId;
} else {
that.errorMessage = res.message || "视频加载错误";
}
})
.catch(alert);
}
参考https://www.jianshu.com/p/43957ee18f1a,查看
Peer Connection
建立连接的流程。
参考 https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection 查看RTCPeerConnection
支持的接口
createOffer()
方法: 主动与其他peer
建立P2P连接,把自己的SDP信息整理好,通过signaling server
转发给其他peer。
在上面的代码中,通过向后端发送POST请求,实现信令交换。
that.pc.addTransceiver("video");
that.pc.addTransceiver("audio");
指明同时接收音频和视频。
that.pc.ontrack = function(event){
}
该方法进行音视频的接收,使用接收到的数据创建video和audio元素。
只对pc状态进行监听无法监听到实际视频可以播放的状态,因此需要对video添加监听方法:
el.oncanplay = () => {
that.isLoading = false;
// 播放状态设置为true
that.isPlaying = true;
that.getVideoDuration();
};
在video可以播放时,才将loading状态取消,并开始获取video时长。
3.3 控制音视频的JS代码
获取视频播放时长方法:
getVideoDuration() {
var video = document.getElementsByTagName("video")[0];
// 如果没有获取到视频元素
if (!video) {
return;
}
let that = this;
video.addEventListener("timeupdate", () => {
that.currentTime = getTime(video.currentTime);
});
var getTime = function (time) {
let hour =
Math.floor(time / 3600) < 10
? "0" + Math.floor(time / 3600)
: Math.floor(time / 3600);
let min =
Math.floor((time % 3600) / 60) < 10
? "0" + Math.floor((time % 3600) / 60)
: Math.floor((time % 3600) / 60);
var sec =
Math.floor(time % 60) < 10
? "0" + Math.floor(time % 60)
: Math.floor(time % 60);
return hour + ":" + min + ":" + sec;
};
}
控制音频/视频同步暂停的方法:
playOrPauseVideo() {
var video = document.getElementsByTagName("video")[0];
var audio = document.getElementsByTagName("audio")[0];
if (this.isPlaying) {
video.pause();
audio.pause();
} else {
// audio
video.play();
audio.play();
}
this.isPlaying = !this.isPlaying;
}
全屏方法
onClickFullScreen() {
let dialogElement = document.getElementById("dialog-wrap");
dialogElement.webkitRequestFullScreen();
}
3.4 样式表
样式部分较为简单,值得注意的有以下几点:
- 隐藏原有视频控制条,便于对控制条进行自定义
video::-webkit-media-controls {
/* 去掉全屏时显示的自带控制条 */
display: none !important;
}
- 扩大hover热区,视频下半部分(高度为400px部分)悬浮显示控制条
(不设置为全部部分是因为如果设置为全部部分,则全屏状态无法隐藏控制条)
以下完整样式表(scss
):
$controlFontColor: rgb(136 141 150);
$backgroundColor: rgba(0, 0, 0, 0.8);
$height: 60px;
.el-dialog .el-dialog__body {
padding: 0 !important;
margin-bottom: 0 !important;
width: unset !important;
}
.video-onloading {
min-height: 500px;
background-color: $backgroundColor;
span {
width: 100%;
display: block;
line-height: 500px;
text-align: center;
color: $controlFontColor;
i {
margin-right: 5px;
}
i::before {
font-size: 17px;
}
}
}
.cover {
bottom: 0px;
height: 300px;
position: absolute;
width: 100%;
z-index: 2;
&:hover,
&:focus,
&:focus-within {
.controls {
display: flex;
}
}
}
.controls {
width: 100%;
height: $height;
line-height: $height;
font-size: 15px;
display: none;
z-index: 2;
background-color: $backgroundColor;
color: $controlFontColor;
position: absolute;
bottom: 0
justify-content: space-between;
& > [class^="el-icon-"] {
&::before {
font-size: 26px;
line-height: $height;
padding: 0 15px;
cursor: pointer;
}
}
.playStatus {
width: 64px;
height: $height;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#currentTime {
width: 140px;
height: $height;
text-align: center;
}
.control-resolution {
line-height: $height;
.el-input__inner {
background: $backgroundColor;
}
.el-input {
width: 95px;
}
input {
border: none;
font-size: 15px !important;
color: $controlFontColor;
&::-webkit-input-placeholder {
color: $controlFontColor;
}
}
}
#fullScreen {
width: 32px;
height: 32px;
position: relative;
top: 16px;
}
}
总结
本次的前端业务WebRTC
只做了浅显的了解和应用,只应用了接收流,还没有用到推流,WebRTC
还有更多用法,比如实现实时视频通话、语音通话等,也许以后的业务中会用到,所以以这篇博客做一个入门记录~
Vue + WebRTC 实现音视频直播(附自定义播放器样式)的更多相关文章
- 基于 AVPlayer 自定义播放器
如果我只是简单的播放一个视频,而不需要考虑播放器的界面.iOS9.0 之前使用 MPMoviePlayerController, 或者内部自带一个 view 的 MPMoviePlayerViewCo ...
- 12┃音视频直播系统之 WebRTC 实现1对1直播系统实战
一.搭建 Web 服务器 前面我们已经实现过,但是没有详细说HTTPS服务 首先需要引入了 express 库,它的功能非常强大,用它来实现 Web 服务器非常方便 同时还需要引入 HTTPS 服务, ...
- 8┃音视频直播系统之 WebRTC 信令系统实现以及通讯核心并实现视频通话
一.信令系统 信令系统主要用来进行信令的交换 在通信双方彼此连接.传输媒体数据之前,它们要通过信令服务器交换一些信息,如规范协商 若 A 与 B 要进行音视频通信,那么 A 要知道 B 已经上线了,同 ...
- JMeter扩展Java请求实现WebRTC本地音视频推流压测脚本
WebRTC是Web Real-Time Communication缩写,指网页即时通讯,是一个支持Web浏览器进行实时语音或视频对话的API,实现了基于网页的视频会议,比如声网的Agora Web ...
- 使用html5中video自定义播放器必备知识点总结以及JS全屏API介绍
一.video的js知识点: controls(控制器).autoplay(自动播放).loop(循环)==video默认的: 自定义播放器中一些JS中提供的方法和属性的记录: 1.play()控制视 ...
- 从零开始学 Web 之 HTML5(四)拖拽接口,Web存储,自定义播放器
大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...
- 3┃音视频直播系统之浏览器中通过 WebRTC 直播视频实时录制回放下载
一.录制分类 在音视频会议.在线教育等系统中,录制是一个特别重要的功能 录制一般分为服务端录制和客户端录制 服务端录制:优点是不用担心客户因自身电脑问题造成录制失败(如磁盘空间不足),也不会因录制时抢 ...
- 5┃音视频直播系统之 WebRTC 中的协议UDP、TCP、RTP、RTCP详解
一.UDP/TCP 如果让你自己开发一套实时互动直播系统,在选择网络传输协议时,你会选择使用UDP协议还是TCP协议 假如使用 TCP 会怎样呢?在极端网络情况下,TCP 为了传输的可靠性,将会进行反 ...
- 10┃音视频直播系统之 WebRTC 中的数据统计和绘制统计图形
一.数据统计 在视频直播中,还有一项比较重要,那就是数据监控 比如开发人员需要知道收了多少包.发了多少包.丢了多少包,以及每路流的流量是多少,才能评估出目前用户使用的音视频产品的服务质量是好还是坏 如 ...
随机推荐
- Informatic 内些坑
1. 工作流调用工作流(可实现无规则时间点自由调度) pmcmd startworkflow -sv 集成服务名称 -d 配置域名称 -u Administrator -p Administrato ...
- Pythonic【15个代码示例】
Python由于语言的简洁性,让我们以人类思考的方式来写代码,新手更容易上手,老鸟更爱不释手. 要写出 Pythonic(优雅的.地道的.整洁的)代码,还要平时多观察那些大牛代码,Github 上有很 ...
- socket php
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); socket_bind($socket,'0.0.0.0',6666); while(tru ...
- 第十九章 keepalived高可用
一.keepalived高可用 1.什么是高可用 一般是指2台机器启动着完全相同的业务系统,当有一台机器down机了,另外一台服务器就能快速的接管,对于访问的用户是无感知的. 2.高可用使用的工具 1 ...
- 【事件中心 Azure Event Hub】使用Logstash消费EventHub中的event时遇见的几种异常(TimeoutException, ReceiverDisconnectedException)
问题描述 使用EFK(Elasticsearch, Fluentd and Kibana)在收集日志的解决方案中, 可以先把日志发送到EventHub中,然后通过Logstash消费EventHub中 ...
- 《Kafka笔记》4、Kafka架构,与其他组件集成
目录 1 kafka架构进阶 1.1 Kafka底层数据的同步机制(面试常问) 1.1.1 高水位截断的同步方式可能带来数据丢失(Kafka 0.11版本前的问题) 1.1.2 解决高水位截断数据丢失 ...
- Java8新特性--Base64转换
1.简介 在Java8中,Base64编码已经成为Java类库的标准.Java 8 内置了 Base64 编码的编码器和解码器. Base64工具类提供了一套静态方法获取下面三种BASE64编解码器: ...
- Django (学习第二部 ORM 模型层)
Django对数据库的操作 Django的 ORM 简介 ORM操作 (增删改查) ORM操作数据库的增删改查 ORM创建表关系 ORM中常用字段及参数 数据库的查询优化 ORM中如何开启事务 ORM ...
- 基于gin的golang web开发:路由二
在基于gin的golang web开发:路由中我们介绍了Gin的路由和一些获取链接中参数的方法,本文继续介绍其他获取参数的方法. 文件上传 在web开发中文件上传是一个很常见的需求,下面我们来看一下基 ...
- 《Head First 设计模式》:剩下的模式
正文 一.桥接模式 1.定义 桥接模式通过将实现和抽象分离开来,放在两个不同的类层次中,从而使得它们可以独立改变. 要点: 当一个类存在两个独立变化的维度,而且都需要进行扩展时,可以将其中一个维度抽象 ...