# webrtc 基础

# 一、获取媒体流

navigator.mediaDevices 上有四个方法

  1. getUserMedia: 获取用户摄像头的媒体流,媒体流包括视频流和音频流。
  2. getDiaplayMedia: 获取用户的屏幕。
  3. enumerateDevices: 返回用户设备。
  4. getSupportedConstraints: 返回一个对象,表示可以约束的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function(){
//是一个promise,返回一个媒体流对象。
let stream = await navigator.mediaDevices.getUserMedia({video:true,audio:true}) //获取用户摄像头以及麦克风的流,另外画面是镜像
let stream = await navigator.mediaDevices.getDisplayMedia({video:true,audio:false}) //获取用户桌面的流
video.srcObject = stream //将获取到了媒体流放在video的SrcObject上。不过如果是获取用户桌面的媒体流放在video上播放会出现画面重叠,目前还不知道如何解决,我看别人做的也是这样。
let list = await navigator.mediaDevices.enumerateDevices() //返回一个数组储存着用户输入输出设备
list.forEach((arr)=>{
let {deviceId,label,kind,groupId} = arr
deviceId : 设备的id
label : 设备的名称
kind : 设备种类 audioinput audiooutput videoinput
groupId : 如果设备是同一个,他们的groupId就是同一个
})
//另外,如果网页不在本地运行且不是https,deviceId,label和groupId则为空。
}

# 二、媒体流和轨道

# 一、媒体流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
stream{
属性:
active //如果获取到用户的媒体流是活跃的则为true,否则为false
id //每条媒体流有一个专属的id值,用于区别不同的媒体流
方法:
addTrack(track) //给这个媒体流增加一个轨
removeTrack(track) //移除该媒体流下的某一条轨道
clone(id) //克隆某一个id值下的轨道并返回这个轨道同时新的轨道有新的id值
getTrackById(id) //返回指定id的轨道
getTracks() //返回这个视频流中的所有轨道,用数组储存。
getVideoTracks() //返回所有视频轨,返回顺序不固定,每次调用的时候顺序有变。
getAudioTracks() //返回所有音频轨,返回顺序不固定,每次调用的时候顺序有变。
事件处理:
onaddtrack() //当添加完轨道后触发此事件
onremovetrack() //当删除某一条轨道后触发此事件
}

# 二、轨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
track{
属性:
id //每一个轨都有一个专属的id
kind //轨的种类,分为audio和video
enabled //表示该轨是否可用,可以手动设置。值为布尔值
label //设备名称
muted //是否静音,值为布尔值
readyState //live设备正常连接,ended没有更多数据,而且不会提供更多的数据
方法:
getConstraints() //返回该轨道的约束,这个与获取媒体流时传的参数有关
applyConstraints() //给该轨道新的约束
getSettings() //返回该轨道所有约束包括自己添加的和浏览器默认的
getCapabilities() //返回一个对象,表示该轨道可调节的属性值以及范围,可以通过这个获取到约束的范围从而给该轨道设置范围。
clone() //返回一个track的克隆,同时产生一个新的id
stop() //stop后,readyState的状态变为ended
}

# 三、约束属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
约束属性是指音频与视频的属性,一共有以下这些,太长了之后有时间再一个一个写吧
aspectRatio
autoGainControl
brightness
channelCount
colorTemperature
contrast
deviceId
displaySurface
echoCancellation
exposureCompensation
exposureMode
exposureTime
facingMode
focusDistance
focusMode
frameRate
groupId
height
iso
latency
noiseSuppression
pan
pointsOfInterest
resizeMode
sampleRate
sampleSize
saturation
sharpness
suppressLocalAudioPlayback
tilt
torch
whiteBalanceMode
width
zoom

相关资料

mediaDevices 开启你本地视频之旅行

MediaDevices MDN

功能、约束和设置 MDN

# 三、video 标签相关属性

1
2
3
4
5
6
7
属性:
autoplay : 在声明该属性后,视频会尽快播放。另外给该值设置false没有用,如果不想自动播放要移除该属性
controls : 给视频增加一个控制面板
controlslist : 当有controls才生效,可以选择将控件移除,可选值nodownload,nofullscreen,noremoteplayback
crossorigin : 是否使用cors来获取视频
loop : 重复播放
poster : 下载时展示的封面,,如果没指定默认按第一帧显示

相关资料

video: 视频嵌入元素 MDN

# 四、视频的录制以及下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let recoder = new MediaRecorder(options) //options是一个配置对象
options{
mimeType : 选择录制时的mime格式,分为视频格式和编码格式
state : inactive(闲置中),recording(录制中),paused(暂停)
stream : 需要录制的媒体流
videoBitsPerSecond : 视频的编码比率
audioBitsPerSecond : 音频的编码比率
}
方法:
isTypeSupported() : 用来判断用户的浏览器是否支持某个视频格式
pause() : 录制暂停
requestData() : 从最开始或者上一个requestData开始到当前接受到的,储存为blob。
resume() : 继续录制之前被暂停的录制
start(timeslice) : 开始录制,timeslice每隔timeslice毫秒记录视频,储存格式为blob
stop() : 录制停止,无法继续录制,同时触发dataavailable事件,返回一个blob
事件:
ondataavailable : 当调用stop或者requestData或者设置了start的参数,时间到了后触发,该事件的data属性会有录制视频的blob数据。
onpause : pause后触发
onresume : resume后触发
onstart : start后触发
onstop : stop后触发

# 五、实战

完成获取用户摄像头,麦克风或者桌面的媒体流并展示,同时录制,支持暂停,继续,停止录制。同时将录制的视频展示并下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
HTML部分
<div>
<video autoplay controls playsinline id="video" style="width: 800px;height: 600px;"></video>
<video autoplay controls playsinline id="recordVideo" style="width: 600px;height: 400px;"></video>
</div>
<div>
<button id="user">摄像头录制</button>
摄像头选择:
<select id="selectVideo" style="width: 100px;"></select>
麦克风选择:
<select id="selectAudio" style="width: 100px;"></select>
<button id="desktop">桌面共享</button>
<button id="start">视频录制</button>
<button id="pause">视频录制暂停</button>
<button id="stop">视频录制停止</button>
<button id="resume">继续录制</button>
<button id="show">展示录制视频</button>
<button id="download">下载录制的视频</button>
选择录制视频的格式:
<select id="select" style="width: 120px;"> </select>
</div>
JS部分
<script>
let video = document.querySelector('#video')
let recordVideo = document.querySelector('#recordVideo')
let user = document.querySelector('#user')
let desktop = document.querySelector('#desktop')
let select = document.querySelector('#select')
let selectVideo = document.querySelector('#selectVideo')
let selectAudio = document.querySelector('#selectAudio')
let start = document.querySelector('#start')
let pause = document.querySelector('#pause')
let stop = document.querySelector('#stop')
let show = document.querySelector('#show')
let resume = document.querySelector('#resume')
let download = document.querySelector('#download')
let types = ['webm','mp4','ogg','mov','avi','wmv','flv','mkv','ts','x=matroska']
let codecs = ['vp9','vp9.0','vp8','vp8.0','avc1','av1','h265','h264']
let mimeType = 'video/webm;codecs=vp8'
let stream,blobList=[],recordStream
//获取用户摄像头权限
user.addEventListener('click',async()=>{
stream = await navigator.mediaDevices.getUserMedia({
video :{
width:800,
height:600
},
audio:true
})
video.srcObject = stream
})
//获取用户桌面共享权限
desktop.addEventListener('click',async()=>{
stream = await navigator.mediaDevices.getDisplayMedia({
video:{
width:800,
height:600
},
audio:false
})
video.srcObject = stream
})
//开始录制
start.addEventListener('click',()=>{
if(recordStream) return //如果已经在开始录制就返回,防止重复录制
recordStream = new MediaRecorder(stream,{mimeType})
recordStream.start(1000)
recordStream.ondataavailable = (e)=>{
blobList.push(e.data) //e.data为blob,将blob用数组储存方便后面展示以及下载
}
})
select.addEventListener('click',support())
selectVideo.addEventListener('click',selectDevice('video'))
selectAudio.addEventListener('click',selectDevice('audio'))
pause.addEventListener('click',()=>{if(recordStream) recordStream.pause()})
stop.addEventListener('click',()=>{if(recordStream) recordStream.stop()})
show.addEventListener('click',()=>{
if(blobList.length>0){
let blob = new Blob(blobList)
let url = URL.createObjectURL(blob)
recordVideo.src = url
}
})
resume.addEventListener('click',()=>{if(recordStream) recordStream.resume()})
download.addEventListener('click',downloadVideo)
function support(){
let click //闭包,事件委托。第一次点击时获取用户浏览器支持的录制格式,之后获取用户选择的录制格式。
return async function(e){
if(click){
mimeType = e.target.value
}else{
click = true
let list = await getSupportList()
list.forEach((type)=>{
let option = document.createElement('option')
option.text = type
option.value = type
select.add(option)
})
}
}
}
function getSupportList(){
let list = []
types.forEach((type)=>{
codecs.forEach(async(code)=>{
let videoType = `video/${type};codecs=${code}`
let res = await MediaRecorder.isTypeSupported(videoType)
if(res) list.push(videoType)
})
})
return list
}
function selectDevice(type){
let video,audio
return async function(e){
if(type=='video'&&!video||type=='audio'&&!audio){
if(type=='video') video = true
if(type=='audio') audio = true
let deviceList = await navigator.mediaDevices.enumerateDevices()
deviceList.forEach((device)=>{
let {deviceId,label,kind,groupId} = device
if(type=='video'&&kind=='videoinput'){
let option = document.createElement('option')
option.text = label
option.value = deviceId
selectVideo.add(option)
}else if(type=='audio'&&kind=='audioinput'){
let option = document.createElement('option')
option.text = label
option.value = deviceId
selectAudio.add(option)
}
})
}else{
if(e.target.value){
switchCamera(e.target.value)
}
}
}
}
//切换摄像头前先停止录制,将录制的媒体流的所有轨暂停同时将媒体流设置为空
function stopCamera(){
if(stream){
let trackList = stream.getTracks()
trackList.forEach((track)=>{
track.stop()
})
video.srcObject = null
stream = null
}
}
async function switchCamera(deviceId){
stopCamera()
stream = navigator.mediaDevices.getUserMedia({
video:{
width:800,
height:600
},
audio:false,
deviceId
})
video.srcObject = stream
}
function downloadVideo(){
let blob = new Blob(blobList,{ type: 'video/mp4' })
let url = URL.createObjectURL(blob)
let a = document.createElement('a')
a.href = url
a.download = '1' //给a链接一个download即可下载
a.click() //点击a下载
URL.revokeObjectURL(url) //URL.createObjectURL生成的url是储存在内存中,即时清空防止内存泄露
}
</script>

相关资料

[WebRTC 从实战到未来!迎接风口,前端必学的技术🔥 - 掘金 (juejin.cn)](https://juejin.cn/post/7151932832041058340