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 实现音视频直播(附自定义播放器样式)的更多相关文章

  1. 基于 AVPlayer 自定义播放器

    如果我只是简单的播放一个视频,而不需要考虑播放器的界面.iOS9.0 之前使用 MPMoviePlayerController, 或者内部自带一个 view 的 MPMoviePlayerViewCo ...

  2. 12┃音视频直播系统之 WebRTC 实现1对1直播系统实战

    一.搭建 Web 服务器 前面我们已经实现过,但是没有详细说HTTPS服务 首先需要引入了 express 库,它的功能非常强大,用它来实现 Web 服务器非常方便 同时还需要引入 HTTPS 服务, ...

  3. 8┃音视频直播系统之 WebRTC 信令系统实现以及通讯核心并实现视频通话

    一.信令系统 信令系统主要用来进行信令的交换 在通信双方彼此连接.传输媒体数据之前,它们要通过信令服务器交换一些信息,如规范协商 若 A 与 B 要进行音视频通信,那么 A 要知道 B 已经上线了,同 ...

  4. JMeter扩展Java请求实现WebRTC本地音视频推流压测脚本

    WebRTC是Web Real-Time Communication缩写,指网页即时通讯,是一个支持Web浏览器进行实时语音或视频对话的API,实现了基于网页的视频会议,比如声网的Agora Web ...

  5. 使用html5中video自定义播放器必备知识点总结以及JS全屏API介绍

    一.video的js知识点: controls(控制器).autoplay(自动播放).loop(循环)==video默认的: 自定义播放器中一些JS中提供的方法和属性的记录: 1.play()控制视 ...

  6. 从零开始学 Web 之 HTML5(四)拖拽接口,Web存储,自定义播放器

    大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...

  7. 3┃音视频直播系统之浏览器中通过 WebRTC 直播视频实时录制回放下载

    一.录制分类 在音视频会议.在线教育等系统中,录制是一个特别重要的功能 录制一般分为服务端录制和客户端录制 服务端录制:优点是不用担心客户因自身电脑问题造成录制失败(如磁盘空间不足),也不会因录制时抢 ...

  8. 5┃音视频直播系统之 WebRTC 中的协议UDP、TCP、RTP、RTCP详解

    一.UDP/TCP 如果让你自己开发一套实时互动直播系统,在选择网络传输协议时,你会选择使用UDP协议还是TCP协议 假如使用 TCP 会怎样呢?在极端网络情况下,TCP 为了传输的可靠性,将会进行反 ...

  9. 10┃音视频直播系统之 WebRTC 中的数据统计和绘制统计图形

    一.数据统计 在视频直播中,还有一项比较重要,那就是数据监控 比如开发人员需要知道收了多少包.发了多少包.丢了多少包,以及每路流的流量是多少,才能评估出目前用户使用的音视频产品的服务质量是好还是坏 如 ...

随机推荐

  1. 怎样学好 java ?

    浅谈Java的学习之路--怎样学好JAVA ?Java - 近10年来计算机软件发展过程中的传奇,其在众多开发者心中的地位就如"屠龙刀"."倚天剑". Java ...

  2. 扫描仪扫描文件处理-A4分辨率

    转换公式:毫米转英寸,英寸乘以DPI(每英寸点数) 1英寸 = 2.54 厘米 = 25.4 毫米 例子(600dpi):mm: 210x297 = px: 4961(210/25.4*600)x70 ...

  3. 旋转子段 (思维stl)

    题目: 大概意思就是给你一个序列,你可以选择一段区间使它左右翻折一遍,然后呢,从1到n找一遍,看a[i]==i的数最多是多少. 其实刚才我已经把暴力思路说出来了,枚举每一个区间长度,枚举每一个左端点, ...

  4. 可变数据类型不能作为python函数的参数

    可变数据类型:列表.字典 不可变数据类型:整型.浮点型.字符串.元组 为什么可变数据类型不能作为python函数的参数?请看以下例子: def foo(a=[]): a.append(1) retur ...

  5. scrapy 采集数据存入excel

    # -*- coding: utf-8 -*- # Define your item pipelines here # # Don't forget to add your pipeline to t ...

  6. phpstorm10.0.3 下载与激活

    phpstorm10.0.3 百度网盘下载   提取码: kqvc 激活服务器: http://jetbrains.tencent.click/ (2016-09-19 可用) http://owo. ...

  7. django—路由相关

    django不同版本的路由配置 django 2之前,配置urlpatterns使用的是url方法 django 2之后,配置urlpatterns使用的是path方法 path与url的区别: ur ...

  8. 集合与map

  9. A. Arena of Greed 解析(思維)

    Codeforce 1425 A. Arena of Greed 解析(思維) 今天我們來看看CF1425A 題目連結 題目 略,請直接看原題. 前言 明明是難度1400的題目,但總感覺不是很好寫阿, ...

  10. Memcached 的惹祸,.NET 5.0 的背锅

    抱歉,拖到现在才写这篇为 .NET 5.0 洗白的博文(之前的博文),不好意思,又错了,不是洗白,是还 .NET 5.0 的清白. 抱歉,就在今天上午写这篇博客的过程中,由于一个bug被迫在访问高峰发 ...