webrtc
参考
offer sdp
SDP
(会话描述协议 Session Description Protocol)是描述点对点连接的标准,SDP
包含音视频的编解码器、源地址和时序信息
使用 createOffer()
创建 offer sdp
JSON |
---|
| sdp:
v=0
o=- 7093805778388360368 2 IN IP4 127.0.0.1
s=-
t=0 0
a=extmap-allow-mixed
a=msid-semantic: WMS
type:
offer
|
v
SDP
版本号:0
表示使用 SDP
的第一个版本
o
- 用户名字段:
-
表示没有指定用户名
- 会话的唯一标识符(Session ID):
7093805778388360368
- 会话版本号:
2
,每当会话信息改变时,这个版本号会增加
- 网络类型:
IN
,表示 Internet
- 地址类型:
IP4
表示 IPv4
- 本机
IP
地址:127.0.0.1
s
t
a
- 允许
RTP
扩展的混合使用:extmap-allow-mixed
- 标识符语义:
msid-semantic: WMS
,WMS
通常是 WebRTC
中使用的标识符
answer sdp
ice candidate
signaling
将所有消息无差别转发给所有人
Go |
---|
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 | package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
type Message struct {
Id string `json:"id"`
Type string `json:"type"`
Sdp string `json:"sdp"`
Ice string `json:"ice"`
}
var cs = make(map[*websocket.Conn]bool) // 1. 连接的集合
func handleConnection(w http.ResponseWriter, r *http.Request) {
upgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("升级为 websocket 失败:", err)
return
}
cs[conn] = true
defer conn.Close() // 连接关闭时删除连接
log.Println("连接成功:", conn.LocalAddr())
for {
var msg Message
err := conn.ReadJSON(&msg)
if err != nil {
log.Println("读取消息失败:", err)
return
}
log.Println("收到消息:", msg.Type)
for c := range cs {
if c != conn {
c.WriteJSON(msg)
}
}
}
}
func main() {
http.HandleFunc("/", handleConnection)
http.ListenAndServeTLS(":3002", "/etc/letsencrypt/live/xn--e6q212bhn0c.xn--6qq986b3xl/fullchain.pem", "/etc/letsencrypt/live/xn--e6q212bhn0c.xn--6qq986b3xl/privkey.pem", nil)
}
|
1c1s
JavaScript |
---|
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 | const signaling = new WebSocket('wss://xn--e6q212bhn0c.xn--6qq986b3xl:3002') // 信令服务器
let PC = null // 连接
signaling.onopen = () => {
console.log('信令服务器:成功连接!')
navigator.mediaDevices.getUserMedia({ video: true, audio: true}).then(stream => { // 1. 获取流
document.getElementById('local_H').srcObject = stream // 2. 将流绑定到标签
PC = new RTCPeerConnection() // 3. 创建连接
PC.onicecandidate = (e) => { // 4. 发送 ice
if (e.candidate) { // 一定,一定,一定要在 new 完 RTCPeerConnection 后马上设置 onicecandidate
signaling.send(JSON.stringify({
type: 'ice',
ice: JSON.stringify(e.candidate)
}))
}
console.log('发送 ice candidate', e.candidate)
}
stream.getTracks().forEach((track) => PC.addTrack(track, stream)) // 5. 将流添加到连接
PC.createOffer().then(offer => { // 6. 创建 offer sdp
PC.setLocalDescription(offer) // 7. 设置本地 sdp
signaling.send(JSON.stringify(offer)) // 8. 发送 offer sdp
console.log('发送 offer sdp', offer)
})})
}
signaling.onmessage = async (event) => { // 9. 接收 answer sdp
const msg = JSON.parse(event.data)
if (msg.type === 'answer') {
console.log('收到 answer sdp', msg)
PC.setRemoteDescription(msg) // 10. 设置远程 sdp
}
}
signaling.onclose = () => {console.log('信令服务器:关闭连接!')}
signaling.onerror = (error) => {console.log('信令服务器:错误连接!', error)}
|
JavaScript |
---|
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 | const signaling = new WebSocket('wss://xn--e6q212bhn0c.xn--6qq986b3xl:3002') // 信令服务器
let PC = null // 连接
signaling.onmessage = async (event) => {
const msg = JSON.parse(event.data)
if (msg.type === 'offer') { // 1. 收到 offer sdp
console.log('收到 offer sdp', msg)
PC = new RTCPeerConnection() // 2. 创建连接
PC.ontrack = (e) => { // 3. 监听连接上的流
document.getElementById('remote_H').srcObject = e.streams[0]
}
PC.setRemoteDescription(msg) // 4. 设置远程 sdp
const answer = await PC.createAnswer() // 5. 创建 answer sdp
PC.setLocalDescription(answer) // 6. 设置本地 sdp
signaling.send(JSON.stringify(answer)) // 7. 发送 answer
console.log('发送 answer sdp', answer)
} else if (msg.type === 'ice') {
console.log('收到 ice candidate', JSON.parse(msg.ice))
PC.addIceCandidate(JSON.parse(msg.ice)) // 8. 添加 ice
}
}
signaling.onopen = () => {console.log('信令服务器:成功连接!')}
signaling.onclose = () => {console.log('信令服务器:关闭连接!')}
signaling.onerror = (error) => {console.log('信令服务器:错误连接!', error)}
|
nc1s
JavaScript |
---|
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 | const signaling = new WebSocket('wss://xn--e6q212bhn0c.xn--6qq986b3xl:3002') // 信令服务器
let PC = {} // 连接
let STREAM = null // 流
let kill = {}
setInterval(() => {
let dddd = {}
for (let id in PC) {
if (PC[id].iceConnectionState === 'connected' || PC[id].iceConnectionState === 'completed') {
if(kill[id] !== 1) console.log('正常连接', id, kill[id])
kill[id] = 1
}else{
console.log('异常连接', id, kill[id])
kill[id]++
if (kill[id] > 3) {
dddd[id] = 1
}
}
}
for (let id in dddd) {
console.log('断开连接', id)
// 删除连接
PC[id].getSenders().map(sender => PC[id].removeTrack(sender))
PC[id].onicecandidate = null
PC[id].oniceconnectionstatechange = null;
PC[id].ontrack = null
PC[id].close()
PC[id] = null
delete kill[id]
delete PC[id]
}
}, 2000)
signaling.onopen = () => {
console.log('信令服务器:成功连接!')
navigator.mediaDevices.getDisplayMedia({ video: true, audio: true}).then(stream => {// 1. 获取流
document.getElementById('local_H').srcObject = stream // 2. 将流绑定到标签
STREAM = stream // 3. 保存流
})
}
signaling.onmessage = async (event) => {
const msg = JSON.parse(event.data)
if(msg.type === 'join') {
console.log('收到 join', msg.id)
PC[msg.id] = new RTCPeerConnection() // 4. 创建连接
PC[msg.id].onicecandidate = (e) => { // 5. 发送 ice
if (e.candidate) { // 一定,一定,一定要在 new 完 RTCPeerConnection 后马上设置 onicecandidate
signaling.send(JSON.stringify({
type: 'ice',
id: msg.id,
ice: JSON.stringify(e.candidate)
}))
}
console.log('发送 ice candidate 到', msg.id, e.candidate)
}
STREAM.getTracks().forEach((track) => PC[msg.id].addTrack(track, STREAM)) // 6. 将流添加到连接
PC[msg.id].createOffer().then(offer => { // 7. 创建 offer sdp
PC[msg.id].setLocalDescription(offer) // 8. 设置本地 sdp
signaling.send(JSON.stringify({
type: 'offer',
id: msg.id,
sdp: offer.sdp
})) // 9. 发送 offer sdp
console.log('发送 offer sdp 到', msg.id, offer)
})
} else if (msg.type === 'answer') { // 10. 接收 answer sdp
console.log('收到 answer sdp 来自', msg.id, msg)
PC[msg.id].setRemoteDescription(msg) // 11. 设置远程 sdp
}
}
signaling.onclose = () => {console.log('信令服务器:关闭连接!')}
signaling.onerror = (error) => {console.log('信令服务器:错误连接!', error)}
|
JavaScript |
---|
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 | const signaling = new WebSocket('wss://xn--e6q212bhn0c.xn--6qq986b3xl:3002') // 信令服务器
let PC = null // 连接
let ID = null // ID
signaling.onmessage = async (event) => {
const msg = JSON.parse(event.data)
if (msg.id !== ID) return
if (msg.type === 'offer') { // 1. 收到 offer sdp
console.log('收到 offer sdp', msg)
PC = new RTCPeerConnection() // 2. 创建连接
PC.ontrack = (e) => { // 3. 监听连接上的流
document.getElementById('remote_H').srcObject = e.streams[0]
}
PC.setRemoteDescription({ // 4. 设置远程 sdp
type: msg.type,
sdp: msg.sdp
})
const answer = await PC.createAnswer() // 5. 创建 answer sdp
PC.setLocalDescription(answer) // 6. 设置本地 sdp
signaling.send(JSON.stringify({ // 7. 发送 answer sdp
id: msg.id,
type: answer.type,
sdp: answer.sdp
}))
console.log('发送 answer sdp', answer)
} else if (msg.type === 'ice') {
console.log('收到 ice candidate', JSON.parse(msg.ice))
PC.addIceCandidate(JSON.parse(msg.ice)) // 8. 添加 ice
}
}
signaling.onopen = () => {
console.log('信令服务器:成功连接!')
// 创建唯一 ID
ID = Date.now().toString(36) + Math.random().toString(36).slice(2)
// 发送 请求 到信令服务器
signaling.send(JSON.stringify({type: 'join', id: ID}))
}
signaling.onclose = () => {console.log('信令服务器:关闭连接!')}
signaling.onerror = (error) => {console.log('信令服务器:错误连接!', error)}
|
1cs1cs
JavaScript |
---|
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 | const signaling = new WebSocket('wss://xn--e6q212bhn0c.xn--6qq986b3xl:3002') // 信令服务器
let local_PC = null // 本地连接
let remote_PC = null // 远程连接
const config = {
iceServers: [
{urls: 'stun:daixll.tpddns.cn:3478'},
{urls: 'turn:daixll.tpddns.cn:3478', username: 'admin', credential: '123456'}
]
}
signaling.onopen = () => {
console.log('信令服务器:成功连接!')
navigator.mediaDevices.getUserMedia({ video: true, audio: true}).then(stream => { // 1. 获取流
document.getElementById('local_H').srcObject = stream // 2. 将流绑定到标签
//local_PC = new RTCPeerConnection() // 3. 创建连接
local_PC = new RTCPeerConnection({iceServers: [{urls: 'stun:xn--e6q212bhn0c.xn--6qq986b3xl:3478'},{urls: 'turn:xn--e6q212bhn0c.xn--6qq986b3xl:3478',username: 'admin',credential: '123456'}]})
document.getElementById('call_H').onclick = () => {
local_PC.onicecandidate = (e) => { // 4. 发送 ice
if (e.candidate) { // 一定,一定,一定要在 new 完 RTCPeerConnection 后马上设置 onicecandidate
signaling.send(JSON.stringify({
type: 'ice',
ice: JSON.stringify(e.candidate)
}))
}
console.log('local_PC 发送 ice candidate', e.candidate)
}
stream.getTracks().forEach((track) => local_PC.addTrack(track, stream)) // 5. 将流添加到连接
local_PC.createOffer().then(offer => { // 6. 创建 offer sdp
local_PC.setLocalDescription(offer) // 7. 设置本地 sdp
signaling.send(JSON.stringify(offer)) // 8. 发送 offer sdp
console.log('local_PC 发送 offer sdp', offer)
})
}
})
}
signaling.onmessage = async (event) => {
const msg = JSON.parse(event.data)
if (msg.type === 'offer') { // 1. 收到 offer sdp
console.log('remote_PC 收到 offer sdp', msg)
//remote_PC = new RTCPeerConnection() // 2. 创建连接
remote_PC = new RTCPeerConnection({iceServers: [{urls: 'stun:xn--e6q212bhn0c.xn--6qq986b3xl:3478'},{urls: 'turn:xn--e6q212bhn0c.xn--6qq986b3xl:3478',username: 'admin',credential: '123456'}]})
remote_PC.ontrack = (e) => { // 3. 监听连接上的流
document.getElementById('remote_H').srcObject = e.streams[0]
}
remote_PC.setRemoteDescription(msg) // 4. 设置远程 sdp
const answer = await remote_PC.createAnswer() // 5. 创建 answer sdp
remote_PC.setLocalDescription(answer) // 6. 设置本地 sdp
signaling.send(JSON.stringify(answer)) // 7. 发送 answer
console.log('remote_PC 发送 answer sdp', answer)
} else if (msg.type === 'ice') {
console.log('remote_PC 收到 ice candidate', JSON.parse(msg.ice))
remote_PC.addIceCandidate(JSON.parse(msg.ice)) // 8. 添加 ice
} else if (msg.type === 'answer') {
console.log('local_PC 收到 answer sdp', msg)
local_PC.setRemoteDescription(msg) // 10. 设置远程 sdp
}
}
signaling.onclose = () => {console.log('信令服务器:关闭连接!')}
signaling.onerror = (error) => {console.log('信令服务器:错误连接!', error)}
|