WebRTC_IM
WebRTC_IM copied to clipboard
纯 go 实现的分布式IM即时通讯系统。一对一呼叫、邀请呼叫、音视频通话、多人通话,陌生人交友、在线教学、在线医疗、腾讯会议,Zoom会议,钉钉课堂等多人音视频交互类场景.
-
IMè§å±è天系ç»éç¨PHP+ golang + Swoole + Redis + Mysql + Comet + WebRtc
æ¼ç¤ºï¼
-
Androidä¸è½½ï¼https://wwa.lanzouy.com/ii7TS00fxfva
-
IOSä¸è½½ï¼https://wwa.lanzouy.com/iG8cL00fxm7i
-
H5ï¼https://im.52webrtc.top/
ææ¯ç¾¤ï¼
微信ï¼BCFind5 ã请å¤æ³¨å¥½ä¿¡æ¯ã
ææ¡£å°åï¼https://www.52webrtc.top
å客å°åï¼https://blog.csdn.net/u012115197/article/details/106916635
Giteeï¼https://gitee.com/baoyalive/baoyalive.git
åä¸åä½ ï¼UI设计ï¼å®å¶å¼åï¼ç³»ç»éæï¼ä»£çæ¨å¹¿çï¼
åºäºBç«å¼æºGoIMæ¶ææ¹æ¡ï¼
GoIM
ä¸ä¸ªæ¯æé群çimåå®æ¶æ¨éæå¡ã
ç¹æ§
- è½»é级
- é«æ§è½
- 纯Golangå®ç°
- æ¯æå个ãå¤ä¸ªãåæ¿é´ä»¥å广ææ¶æ¯æ¨é
- æ¯æå个Keyå¤ä¸ªè®¢é è ï¼å¯éå¶è®¢é è æ大人æ°ï¼
- å¿è·³æ¯æï¼åºç¨å¿è·³åtcpãkeepaliveï¼
- æ¯æå®å ¨éªè¯ï¼æªææç¨æ·ä¸è½è®¢é ï¼
- å¤åè®®æ¯æï¼websocketï¼tcpï¼
- å¯ææçæ¶æï¼jobãlogic模åå¯å¨ææ éæ©å±ï¼
- åºäºKafkaåå¼æ¥æ¶æ¯æ¨é
å®è£
ä¸ãå®è£ ä¾èµ
$ yum -y install java-1.7.0-openjdk
äºãå®è£ Kafkaæ¶æ¯éåæå¡
kafkaå¨å®ç½å·²ç»æè¿°çé常详ç»ï¼å¨è¿éå°±ä¸è¿å¤è¯´æï¼å®è£ ãå¯å¨è¯·æ¥çè¿é.
ä¸ãæ建golangç¯å¢
1.ä¸è½½æºç (æ ¹æ®èªå·±çç³»ç»ä¸è½½å¯¹åºçå®è£ å )
$ cd /data/programfiles
$ wget -c --no-check-certificate https://storage.googleapis.com/golang/go1.5.2.linux-amd64.tar.gz
$ tar -xvf go1.5.2.linux-amd64.tar.gz -C /usr/local
2.é ç½®GOç¯å¢åé (è¿éæå å¨/etc/profile.d/golang.sh)
$ vi /etc/profile.d/golang.sh
# å°ä»¥ä¸ç¯å¢åéæ·»å å°profileæåé¢
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
export GOPATH=/data/apps/go
$ source /etc/profile
åãé¨ç½²goim
1.ä¸è½½goimåä¾èµå
$ yum install hg
$ go get -u github.com/Terry-Mao/goim
$ mv $GOPATH/src/github.com/Terry-Mao/goim $GOPATH/src/goim
$ cd $GOPATH/src/goim
$ go get ./...
2.å®è£ routerãlogicãcometãjob模å(é ç½®æ件请ä¾æ®å®é æºå¨ç¯å¢é ç½®)
$ cd $GOPATH/src/goim/router
$ go install
$ cp router-example.conf $GOPATH/bin/router.conf
$ cp router-log.xml $GOPATH/bin/
$ cd ../logic/
$ go install
$ cp logic-example.conf $GOPATH/bin/logic.conf
$ cp logic-log.xml $GOPATH/bin/
$ cd ../comet/
$ go install
$ cp comet-example.conf $GOPATH/bin/comet.conf
$ cp comet-log.xml $GOPATH/bin/
$ cd ../logic/job/
$ go install
$ cp job-example.conf $GOPATH/bin/job.conf
$ cp job-log.xml $GOPATH/bin/
å°æ¤ææçç¯å¢é½æ建å®æï¼
äºãå¯å¨goim
$ cd /$GOPATH/bin
$ nohup $GOPATH/bin/router -c $GOPATH/bin/router.conf 2>&1 > /data/logs/goim/panic-router.log &
$ nohup $GOPATH/bin/logic -c $GOPATH/bin/logic.conf 2>&1 > /data/logs/goim/panic-logic.log &
$ nohup $GOPATH/bin/comet -c $GOPATH/bin/comet.conf 2>&1 > /data/logs/goim/panic-comet.log &
$ nohup $GOPATH/bin/job -c $GOPATH/bin/job.conf 2>&1 > /data/logs/goim/panic-job.log &
å¦æå¯å¨å¤±è´¥ï¼é»è®¤é ç½®å¯éè¿æ¥çpanic-xxx.logæ¥å¿æ件æ¥ææ¥å个模åé®é¢.
å ãæµè¯
Benchmark Server
CPU | Memory | OS | Instance |
---|---|---|---|
Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz | DDR3 32GB | Debian GNU/Linux 8 | 1 |
Benchmark Case
- Online: 1,000,000
- Duration: 15min
- Push Speed: 40/s (broadcast room)
- Push Message: {"test":1}
- Received calc mode: 1s per times, total 30 times
Benchmark Resource
- CPU: 2000%~2300%
- Memory: 14GB
- GC Pause: 504ms
- Network: Incoming(450MBit/s), Outgoing(4.39GBit/s)
Benchmark Result
- Received: 35,900,000/s
æ¨éåè®®å¯æ¥çpush httpåè®®ææ¡£
é ç½®
TODO
ä¾å
ææææå¦è·èµ·goimï¼https://github.com/DOUBLE-Baller/momo/tree/master/IM
Websocket: Websocket Client Demo
Android: Android
iOS: iOS
ææ¡£
push httpåè®®ææ¡£æ¨éæ¥å£
é群
comet
comet å±äºæ¥å ¥å±ï¼é常容ææ©å±ï¼ç´æ¥å¼å¯å¤ä¸ªcometèç¹ï¼ä¿®æ¹é ç½®æ件ä¸çbaseèç¹ä¸çserver.idä¿®æ¹æä¸åå¼ï¼æ³¨æä¸å®è¦ä¿è¯ä¸åçcometè¿ç¨å¼å¯ä¸ï¼ï¼å端æ¥å ¥å¯ä»¥ä½¿ç¨LVS æè DNSæ¥è½¬å
logic
logic å±äºæ ç¶æçé»è¾å±ï¼å¯ä»¥éæå¢å èç¹ï¼ä½¿ç¨nginx upstreamæ¥æ©å±httpæ¥å£ï¼å é¨rpcé¨åï¼å¯ä»¥ä½¿ç¨LVSåå±è½¬å
kafka
kafka å¯ä»¥ä½¿ç¨å¤brokerï¼æè å¤partitionæ¥æ©å±éå
router
router å±äºæç¶æèç¹ï¼logicå¯ä»¥ä½¿ç¨ä¸è´æ§hashé ç½®èç¹ï¼å¢å å¤ä¸ªrouterèç¹ï¼ç®åè¿ä¸æ¯æå¨ææ©å®¹ï¼ï¼æåé¢ä¼°å¥½å¨çº¿åååæ åµ
job
job æ ¹æ®kafkaçpartitionæ¥æ©å±å¤jobå·¥ä½æ¹å¼ï¼å ·ä½å¯ä»¥åèä¸kafkaçpartitionè´è½½
PHPæ¶ææ¹æ¡:
使ç¨PHP+Swooleå®ç°çç½é¡µå³æ¶èå¤©å·¥å ·ï¼
- å ¨å¼æ¥éé»å¡Serverï¼å¯ä»¥åæ¶æ¯ææ°ç¾ä¸TCPè¿æ¥å¨çº¿
- åºäºwebsocket+flash_websocketæ¯ææææµè§å¨/客æ·ç«¯/移å¨ç«¯
- æ¯æåè/群è/ç»èçåè½
- æ¯ææ°¸ä¹ ä¿åè天记å½ï¼ä½¿ç¨MySQLåå¨
- åºäºServer PUSHçå³æ¶å 容æ´æ°ï¼ç»å½/ç»åº/ç¶æåæ´/æ¶æ¯çä¼å 容å³æ¶æ´æ°
- ç¨æ·å表åå¨çº¿ä¿¡æ¯ä½¿ç¨Redisåå¨
- æ¯æåéè¿æ¥/å¾ç/è¯é³/è§é¢/æ件
- æ¯æWeb端ç´æ¥ç®¡çææå¨çº¿ç¨æ·å群ç»
åç»å¾ å¼ååè½æï¼è§å±çè¨ï¼è¿ç¨æ¼ç¤ºï¼è¿ç¨æ¡é¢,è§å±ç¾¤èç
ææ°ççæ¬å·²ç»å¯ä»¥åçæ¯æIEç³»åæµè§å¨äºï¼åºäºHttpé¿è¿æ¥
|å®è£ |
swooleæ©å±
pecl install swoole
swooleæ¡æ¶
composer install
è¿è¡
å°webroot
ç®å½é
ç½®å°Nginx/Apacheçèæ主æºç®å½ä¸ï¼ä½¿webroot/
å¯è®¿é®ã
详ç»é¨ç½²è¯´æ
1. å®è£ composer(phpä¾èµå å·¥å ·)
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
注æï¼å¦ææªå°php解éå¨ç¨åºè®¾ç½®ä¸ºç¯å¢åéPATHä¸ï¼éè¦è®¾ç½®ãå 为composeræ件第ä¸è¡ä¸º#!/usr/bin/env phpï¼å¹¶ä¸è½ä¿®æ¹ã æ´å 详ç»ç对composer说æï¼http://blog.csdn.net/zzulp/article/details/18981029
2. composer install
åæ¢å°PHPWebIM项ç®ç®å½ï¼æ§è¡æ令composer installï¼å¦å¾æ ¢å
composer install --prefer-dist
3. Ningxé ç½®
- è¿éæªä½¿ç¨swoole_frameworkæä¾çWeb AppServer
- Apache请åç §Nginxé ç½®ï¼èªè¡ä¿®æ¹å®ç°
- è¿é使ç¨äº
im.swoole.com
ä½ä¸ºååï¼éè¦é ç½®hostæè æ¹æä½ çåå
server {
listen 80;
server_name im.swoole.com;
index index.html index.php;
location / {
root /path/to/webim/webroot;
proxy_set_header X-Real-IP $remote_addr;
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php;
}
}
location ~ .*\.(php|php5)?$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
}
**注æï¼httpsä¸å¿
é¡»éåwss So-æ两ç§æ¹æ¡ 1.éç¨nginx åå代ç4431ç«¯å£ swoole ç端å£å4431è¿è¡é讯ã2.swoole 确认æ¯å¦å¯ç¨äºopensslï¼æ¯å¦å¨ç¼è¯æ¶å å
¥äº--enable-opensslçæ¯æ,ç¶åå¨set è¯ä¹¦è·¯å¾å³å¯ã两ç§æ¹æ¡éæ©å
¶ä¸å°±å¥½ï¼ä¸è¿ç¬¬ä¸ç§æ¹æ¡æ个æ½å¨ç¥åå°±æ¯ä½ éè¿åå代çæ¿ä¸å°çå®çIPå°åäº,è¿ç¹å¼å¾æ³¨æï¼Nginxæåæ³æ¿å°çå®çipï¼ä¸æå¯ä»¥ç§èæï¼å
wssçå太å¤äºå°±ä¸ä¸ä¸è¯´äºã**
4. ä¿®æ¹é
ç½®
- é
ç½®
configs/db.php
ä¸æ°æ®åºä¿¡æ¯ï¼å°è天记å½åå¨å°MySQLä¸ - é
ç½®
configs/redis.php
ä¸çRedisæå¡å¨ä¿¡æ¯ï¼å°ç¨æ·å表åä¿¡æ¯åå°Redisä¸
表ç»æ
CREATE TABLE `webim_history` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`addtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`name` varchar(64) COLLATE utf8mb4_bin NOT NULL,
`avatar` varchar(255) COLLATE utf8mb4_bin NOT NULL,
`type` varchar(12) COLLATE utf8mb4_bin NOT NULL,
`msg` text COLLATE utf8mb4_bin NOT NULL,
`send_ip` varchar(20) COLLATE utf8mb4_bin,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
- ä¿®æ¹
configs/webim.php
ä¸çé项ï¼è®¾ç½®æå¡å¨çURLå端å£
$config['server'] = array(
//çå¬çHOST
'host' => '0.0.0.0',
//çå¬ç端å£
'port' => '9503',
//WebSocketçURLå°åï¼ä¾æµè§å¨ä½¿ç¨ç
'url' => 'ws://im.xxx.com:9503',
//ç¨äºCometè·¨åï¼å¿
须设置为web页é¢çURL
//æ¯å¦ä½ çç½ç«éæ页é¢æ¾å¨ http://im.xxx.com:8888/main.html
//è¿éå°±æ¯ http://im.xxx.com:8888
'origin' => 'http://im.xxx.com:8888',
);
- server.host server.port 项为WebIMæå¡å¨å³WebSocketæå¡å¨çIPä¸ç«¯å£ï¼å ¶ä»éæ©é¡¹æ ¹æ®å ·ä½æ åµä¿®æ¹
- server.url对åºçå°±æ¯æå¡å¨IPæåå以åwebsocketæå¡ç端å£ï¼è¿ä¸ªå°±æ¯æä¾ç»æµè§å¨çWebSocketå°å
- server.origin为Cometè·¨å设置ï¼å¿ 须修æ¹originæå¯ä»¥æ¯æIEçä¸æ¯æWebSocketçæµè§å¨
5. å¯å¨WebSocketæå¡å¨
php server.php start
IEæµè§å¨ä¸æ¯æWebSocketï¼éè¦ä½¿ç¨FlashWebSocket模æï¼è¯·ä¿®æ¹flash_policy.phpä¸å¯¹åºç端å£ï¼ç¶åå¯å¨flash_policy.phpã
php webim/flash_policy.php
6. ç»å®hostä¸è®¿é®è天çªå£ï¼å¯éï¼
å¦æURLç´æ¥ä½¿ç¨IP:PORTï¼è¿éä¸éè¦è®¾ç½®ã
vi /etc/hosts
å¢å
127.0.0.1
ç¨æµè§å¨æå¼ï¼https://XXX.com
å¿«éäºè§£é¡¹ç®æ¶æ
1.ç®å½ç»æ
+ webim
|- server.php //WebSocketåè®®æå¡å¨
|+ swoole.ini // WebSocketåè®®å®ç°é
ç½®
|+ configs //é
ç½®æ件ç®å½
|+ webroot
|+ static
|- config.js // WebSocketé
ç½®
|+ log // swooleæ¥å¿åWebIMæ¥å¿
|+ src // WebIM ç±»æ件å¨åç®å½
|+ Store
|- File.php // é»è®¤ç¨å
åtmpfsæ件系ç»(linux /dev/shm)åæ¾å¤©çæ°æ®ï¼å¦æä¸æ¯linux请æå¨ä¿®æ¹$shm_dir
|- Redis.php // å°è天æ°æ®åæ¾å°Redis
|- Server.php // 继æ¿å®ç°WebSocketçç±»ï¼å®ææäºä¸å¡åè½
|+ vendor // ä¾èµå
ç®å½
2.Socket Serverä¸Socket Clientéä¿¡æ°æ®æ ¼å¼
å¦ï¼ç»å½
Clientåéæ°æ®
{"cmd":"login","name":"xdy","avatar":"http://tp3.sinaimg.cn/1586005914/50/5649388281/1"}
Serverååºç»å½
{"cmd":"login", "fd": "31", "name":"xdy","avatar":"http://tp3.sinaimg.cn/1586005914/50/5649388281/1"}
å¯ä»¥çå°cmdå±æ§ï¼clientä¸serveråéæ¶æ°æ®é½ææå®ï¼ä¸»è¦æ¯ç¨äºclientæè serverçåè°å¤çå½æ°ã
3.éè¦çæ¸ çå ç§åè®®æè æå¡çå ³ç³»
httpåè®®ï¼è¶ ææ¬ä¼ è¾åè®®ãåå·¥éä¿¡ï¼çç客æ·ç«¯è¯·æ±ä¹åååºã
WebSocketåè®®ï¼æ¯HTML5ä¸ç§æ°çåè®®ï¼å®æ¯å®ç°äºæµè§å¨ä¸æå¡å¨å ¨åå·¥éä¿¡ãæå¡å¨ç«¯å£ä¸å®¢æ·ç«¯é½å¯ä»¥æ¨ææ°æ®ã
Webæå¡å¨ï¼æ¤é¡¹ç®ä¸å¯ä»¥ç¨åºäºSwooleçApp Serverå å½Webæå¡å¨ï¼ä¹å¯ä»¥ç¨ä¼ ç»çnginx/apacheä½ä¸ºwebæå¡å¨
Socketæå¡å¨ï¼æ¤é¡¹ç®ä¸æµè§å¨çWebSocket客æ·ç«¯è¿æ¥çæå¡å¨ï¼swoole_frameworkä¸æå®ç°WebSocketåè®®PHPçæ¬çæå¡å¨ã
WebSocket Clientï¼å®ç°html5çæµè§å¨é½æ¯æWebSocket对象ï¼å¦ä¸æ¯ææ¤é¡¹ç®ä¸ææä¾flashçæ¬çå®ç°ã
WebRtcç¯Â é³è§é¢é¨å ï¼éç¹é¾ç¹é¨åï¼
èæ¯ä»ç»ï¼
æ¬æå 容æ¶åå°äºWebRTCæ¶åçå议讲解ãç¸å ³æå¡å¨çæ建ãWebRTCæ ¸å¿APIå¦ä¹ ï¼æåå å«ä¸ä¸ªWenRTCé³è§é¢éè¯çå°å®ä¾å¼åæç¨å®è·µï¼å«å®æ´ä»£ç ï¼ãæµè¯è¿æé½å¸å ãæé½âæ¦æ±ãæé½âå京ãæé½âæ²é³ï¼åºæ¬é½æåäºãäºè§£æ´å¤...
:::
âä¸ãåè®®
1.1 P2Péä¿¡åçä¸å®ç°
1.1.1 åºæ¬æ¯è¯
é²ç«å¢ï¼Firewallï¼ï¼ é²ç«å¢ä¸»è¦éå¶å ç½åå ¬ç½çé讯ï¼é常丢å¼æªç»è®¸å¯çæ°æ®å ãé²ç«å¢ä¼æ£æµ(ä½æ¯ä¸ä¿®æ¹)è¯å¾è¿å ¥å ç½æ°æ®å çIPå°ååTCP/UDP端å£ä¿¡æ¯ã ç½ç»å°å转æ¢åè®®ï¼NATï¼ï¼ ç¨æ¥ç»ä½ çï¼ç§ç½ï¼è®¾å¤æ å°ä¸ä¸ªå ¬ç½çIPå°åçåè®®ãä¸è¬æ åµä¸ï¼è·¯ç±å¨çWANå£æä¸ä¸ªå ¬ç½IPï¼ææè¿æ¥è¿ä¸ªè·¯ç±å¨LANå£ç设å¤ä¼åé ä¸ä¸ªç§æç½æ®µçIPå°åï¼ä¾å¦192.168.1.3ï¼ãç§ç½è®¾å¤çIP被æ å°æè·¯ç±å¨çå ¬ç½IPåå¯ä¸ç端å£ï¼éè¿è¿ç§æ¹å¼ä¸éè¦ä¸ºæ¯ä¸ä¸ªç§ç½è®¾å¤åé ä¸åçå ¬ç½IPï¼ä½æ¯ä¾ç¶è½è¢«å¤ç½è®¾å¤åç°ãNATä¸æ¢æ£æ¥è¿å ¥æ°æ®å ç头é¨ï¼èä¸å¯¹å ¶è¿è¡ä¿®æ¹ï¼ä»èå®ç°åä¸å ç½ä¸ä¸å主æºå ±ç¨æ´å°çå ¬ç½IPï¼é常æ¯ä¸ä¸ªï¼ã åºæ¬NATï¼Basic NATï¼ï¼ åºæ¬NATä¼å°å ç½ä¸»æºçIPå°åæ å°ä¸ºä¸ä¸ªå ¬ç½IPï¼ä¸æ¹åå ¶TCP/UDP端å£å·ãåºæ¬NATé常åªæå¨å½NATæå ¬ç½IPæ± çæ¶åææç¨ã ç½ç»å°å-端å£è½¬æ¢å¨ï¼NAPTï¼ï¼ å°ç®å为æ¢æ常è§çå³ä¸ºNAPTï¼å ¶æ£æµå¹¶ä¿®æ¹åºå ¥æ°æ®å çIPå°åå端å£å·ï¼ä»èå 许å¤ä¸ªå ç½ä¸»æºåæ¶å ±äº«ä¸ä¸ªå ¬ç½IPå°åã é¥å½¢NATï¼Cone NATï¼ï¼ å¨å»ºç«äºä¸å¯¹ï¼å ¬ç½IPï¼å ¬ç½ç«¯å£ï¼åï¼å ç½IPï¼å ç½ç«¯å£ï¼äºå ç»çç»å®ä¹åï¼Cone NATä¼éç¨è¿ç»ç»å®ç¨äºæ¥ä¸æ¥è¯¥åºç¨ç¨åºçææä¼è¯ï¼åä¸å ç½IPå端å£ï¼ï¼åªè¦è¿æä¸ä¸ªä¼è¯è¿æ¯æ¿æ´»çã ä¾å¦ï¼å设客æ·ç«¯A建ç«äºä¸¤ä¸ªè¿ç»ç对å¤ä¼è¯ï¼ä»ç¸åçå é¨ç«¯ç¹ï¼10.0.0.1:1234ï¼å°ä¸¤ä¸ªä¸åçå¤é¨æå¡ç«¯S1åS2ãCone NATåªä¸ºä¸¤ä¸ªä¼è¯æ å°äºä¸ä¸ªå ¬ç½ç«¯ç¹ï¼155.99.25.11:62000ï¼ï¼ ç¡®ä¿å®¢æ·ç«¯ç«¯å£çâ身份âå¨å°å转æ¢çæ¶åä¿æä¸åãç±äºåºæ¬NATåé²ç«å¢é½ä¸æ¹åæ°æ®å ç端å£å·ï¼å æ¤è¿äºç±»åçä¸é´ä»¶ä¹å¯ä»¥çä½æ¯éåçCone NATã
Server S1 Server S2
18.181.0.31:1235 138.76.29.7:1235
| |
| |
+----------------------+----------------------+
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 155.99.25.11:62000 v | v 155.99.25.11:62000 v
|
Cone NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 10.0.0.1:1234 v | v 10.0.0.1:1234 v
|
Client A
10.0.0.1:1234
1.1.2 UDPææ´(UDP hole punching)
P2Péä¿¡ææ¯ä¸è¢«å¹¿æ³éç¨çææ¯âUDPææ´âãUDPææ´ææ¯ä¾èµäºé常é²ç«å¢åcone NATå 许æ£å½çP2Påºç¨ç¨åºå¨ä¸é´ä»¶ä¸ææ´ä¸ä¸å¯¹æ¹å»ºç«ç´æ¥é¾æ¥çç¹æ§ã å¨å¦ä¹ UDPææ´ä¹åï¼æ们å äºè§£ä¸ä¸å¦å¤ä¸¤ç§P2Péä¿¡ææ¯ã ï¼1ï¼ä¸ç»§ï¼Relayingï¼ ä¸ç»§æ¯æå¯é ä½æçæä½çä¸ç§P2Péä¿¡ææ¯ï¼å®çåçæ¯éè¿ä¸å°æå¡å¨æ¥ä¸ç»§è½¬åä¸å客æ·ç«¯çæ°æ®ã
Server S
|
|
+----------------------+----------------------+
| |
NAT A NAT B
| |
| |
Client A Client B
ä»ä¹ææå¢ï¼å°±æ¯æåä½ å¼è§é¢ï¼æåä½ çè§é¢æ°æ®ä¼ç´æ¥è¢«æä»¬å ±åè¿æ¥ä¸çä¸å°æå¡å¨æ¥æ¶ï¼è¿å°æå¡å¨ä¼å°ä½ æçè§é¢æ°æ®åå«è½¬åååºç»æåä½ ç客æ·ç«¯ãè¿æ ·æå¡å¨ååå°±å¾å¤§ï¼å¸¦å®½éæ±ä¹é常大ï¼å½ä» ä» åªæ两个客æ·ç«¯è¿æ¥æå¡å¨å¼è§é¢çè¯ï¼æå¡å¨ç带宽就è³å°æ¯å®¢æ·ç«¯å¸¦å®½ç两åï¼CPUæ¶èåæ ·ä¹æ¯ãé£ä¹å½åæ¶è§é¢éè¯ç人å¾å¤äºï¼é£ä¹æå¡å¨çååé¾ä»¥æ³è±¡ã æ以ä¸ç»§æ¯ä¸ç§æçå¾ä½çP2Péä¿¡ææ¯ã ï¼2ï¼éåè¿æ¥ï¼Connection reversalï¼ è¿ç§è¿æ¥åªæå¨ä¸¤ä¸ªé信端ç¹ä¸æä¸ä¸ªä¸åå¨ä¸é´ä»¶çæ¶åææã ä¾å¦ï¼å®¢æ·ç«¯Aå¨NATä¹åè客æ·ç«¯Bæ¥æå ¨å±IPå°åï¼å¦ä¸å¾ï¼
Server S
18.181.0.31:1235
|
|
+----------------------+----------------------+
| |
NAT A |
155.99.25.11:62000 |
| |
| |
Client A Client B
10.0.0.1:1234 138.76.29.7:1234ã
客æ·ç«¯Aå
ç½å°å为10.0.0.1ï¼ä¸åºç¨ç¨åºæ£å¨ä½¿ç¨TCP端å£1234ãAåæå¡å¨S建ç«äºä¸ä¸ªè¿æ¥ï¼æå¡å¨çIPå°å为18.181.0.31ï¼çå¬1235端å£ãNAT Aç»å®¢æ·ç«¯Aåé
äºTCP端å£62000ï¼å°å为NATçå
¬ç½IPå°å155.99.25.11ï¼ ä½ä¸ºå®¢æ·ç«¯A对å¤å½åä¼è¯ç临æ¶IPå端å£ãå æ¤S认为客æ·ç«¯Aå°±æ¯155.99.25.11:62000ãèBç±äºæå
¬ç½å°åï¼æ以对Sæ¥è¯´Bå°±æ¯138.76.29.7:1234ã
å½å®¢æ·ç«¯Bæ³è¦åèµ·ä¸ä¸ªå¯¹å®¢æ·ç«¯AçP2Pé¾æ¥æ¶ï¼è¦ä¹é¾æ¥Açå¤ç½å°å155.99.25.11:62000ï¼è¦ä¹é¾æ¥Açå ç½å°å10.0.0.1:1234ï¼ç¶è两ç§æ¹å¼é¾æ¥é½ä¼å¤±è´¥ã é¾æ¥10.0.0.1:1234失败èªä¸ç¨è¯´ï¼ä¸ºä»ä¹é¾æ¥155.99.25.11:62000ä¹ä¼å¤±è´¥å¢ï¼æ¥èªBçTCP SYNæ¡æ请æ±å°è¾¾NAT Açæ¶åä¼è¢«æç»ï¼å 为对NAT Aæ¥è¯´åªæå¤åºçé¾æ¥ææ¯å 许çã å¨ç´æ¥é¾æ¥A失败ä¹åï¼Bå¯ä»¥éè¿SåAä¸ç»§ä¸ä¸ªé¾æ¥è¯·æ±ï¼ä»èä»Aæ¹åâéåâå°å»ºç«èµ·A-Bä¹é´çç¹å¯¹ç¹é¾æ¥ã ç°å¨å¾å¤P2Pç³»ç»é½å®ç°äºè¿ç§ææ¯ï¼ä½æ¯è¿ç§ææ¯æå±éæ§ï¼åªæå½å ¶ä¸ä¸æ¾å®¢æ·ç«¯æå ¬ç½IPçæ¶åæè½å»ºç«èµ·è¿æ¥ã为ä»ä¹ç°å¨å¾å¤P2Pç³»ç»é½å®ç°äºéåè¿æ¥ææ¯ï¼å 为æ们æ¥ä¸æ¥è¦è®²çUDPææ´ææ¯ï¼ä¸»è¦æ¯ä¾èµè¿ç§ææ¯ã
UDPææ´æ£æå¼å§ï¼
ç°å¨æå¤çç½è·¯è¿æ¥æ åµæ¯åæ¹é½æ¯å¨å ç½ä¸ï¼é½éè¦éè¿NATè¿è¡å°å转æ¢ï¼æ以ä¸é¢çéåè¿æ¥ä¸éç¨ï¼ä½æ¯å¯ä»¥å©ç¨éåè¿æ¥ææ¯è¿è¡æ¹é ã å设客æ·ç«¯Aå客æ·ç«¯Bçå°åé½æ¯å ç½å°åï¼ä¸å¨ä¸åçNATåé¢ãAãBä¸è¿è¡çP2Påºç¨ç¨åºåæå¡å¨Sé½ä½¿ç¨äºUDP端å£1234ï¼AåBåå«åå§åäº ä¸ServerçUDPéä¿¡ï¼å°åæ å°å¦å¾æ示:
Server S
18.181.0.31:1234
|
|
+----------------------+----------------------+
| |
NAT A NAT B
155.99.25.11:62000 138.76.29.7:31000
| |
| |
Client A Client B
10.0.0.1:1234 10.1.1.3:1234
ç°å¨å设客æ·ç«¯Aæç®ä¸å®¢æ·ç«¯Bç´æ¥å»ºç«ä¸ä¸ªUDPéä¿¡ä¼è¯ãå¦æAç´æ¥ç»Bçå ¬ç½å°å138.76.29.7:31000åéUDPæ°æ®ï¼NAT Bå°å¾å¯è½ä¼æ è§è¿å ¥ç æ°æ®ï¼é¤éæ¯Full Cone NATï¼ï¼å 为æºå°åå端å£ä¸Sä¸å¹é ï¼èæååªä¸S建ç«è¿ä¼è¯ãBå¾Aç´æ¥åä¿¡æ¯ä¹ç±»ä¼¼ã å设Aå¼å§ç»Bçå ¬ç½å°ååéUDPæ°æ®çåæ¶ï¼ç»æå¡å¨Såéä¸ä¸ªä¸ç»§è¯·æ±ï¼è¦æ±Bå¼å§ç»Açå ¬ç½å°ååéUDPä¿¡æ¯ã Aå¾Bçè¾åºä¿¡æ¯ä¼å¯¼è´NAT Aæå¼ ä¸ä¸ªAçå ç½å°åä¸ä¸Bçå¤ç½å°åä¹é´çæ°é讯ä¼è¯ï¼Bå¾A亦ç¶ãä¸æ¦æ°çUDPä¼è¯å¨ä¸¤ä¸ªæ¹åé½æå¼ä¹åï¼å®¢æ·ç«¯Aå客æ·ç«¯Bå°±è½ç´æ¥éè®¯ï¼ èæ é¡»åéè¿å¼å¯¼æå¡å¨Säºã UDPææ´ææ¯æ许å¤æç¨çæ§è´¨ãä¸æ¦ä¸ä¸ªçP2Pé¾æ¥å»ºç«ï¼é¾æ¥çåæ¹é½è½åè¿æ¥ä½ä¸ºâå¼å¯¼æå¡å¨âæ¥å¸®å©å ¶ä»ä¸é´ä»¶åç客æ·ç«¯è¿è¡ææ´ï¼ æ大åå°äºæå¡å¨çè´è½½ãåºç¨ç¨åºä¸éè¦ç¥éä¸é´ä»¶å ·ä½æ¯ä»ä¹ï¼å¦ææçè¯ï¼ï¼å 为以ä¸çè¿ç¨å¨æ²¡æä¸é´ä»¶æè æå¤ä¸ªä¸é´ä»¶çæ åµä¸ ä¹ä¸æ ·è½å»ºç«éä¿¡é¾è·¯ã è¿æä¸äºç¹æ®æ åµï¼å½éä¿¡åæ¹é½å¨åä¸å±åç½ï¼ä¹å°±æ¯ä¸¤ä¸ªå®¢æ·ç«¯é½å¨ä¸ä¸ªå ç½ä¸å¢ï¼æ¯ä¸æ¯å¯ä»¥éä½NAT转æ¢ï¼ç´æ¥å¨å ç½ä¸è¿æ¥å¢ï¼æ¤å¤è¿æï¼å½ä¸äºå¤§åä¼ä¸ï¼å ç½ä¸æå¤çº§NAT转æ¢å¢ï¼è¿éå·²ä¸åæ¬æç讨论ä¸äºï¼è¯¦ç»å¯ä»¥ç以ä¸åèæç« è¯¦ç»äºè§£ï¼
åèæç« ï¼https://www.52webrtc.top
å¦å°è¿éï¼æ ¹æ®ä¸é¢çåçæ¯å¯ä»¥å®ç°èªå·±çä¸å¥ç¨åºåéä¿¡è§åï¼ä½å¾å¤æ¶åæ¯éè¦å¯¹æ¥ç¬¬ä¸æ¹çåè®®ï¼å¾å¾è¿ä¸ªéé æ¯æ¯è¾éº»ç¦çãå æ¤å°±äº§çäºæ ååçéç¨è§åï¼STUNãTURNãICEï¼ï¼ä¸é¢çå ä¸ªç« èå°é个ä»ç»è¿äºåè®®ã
1.2 STUNåè®®
STUNï¼STUN/RFC3489(åºå¼)ï¼STUN/RFC5389ï¼æ¯P2Pæ ååéä¿¡è§åï¼åè®®ï¼ä¹ä¸ã
1.2.1 ç®ä»
NATçä¼è¯ç©¿è¶åè½Session Traversal Utilities for NAT (STUN) (缩ç¥è¯çæåä¸ä¸ªåæ¯æ¯NATçé¦åæ¯)æ¯ä¸ä¸ªå 许ä½äº
NATåç客æ·ç«¯æ¾åºèªå·±çå ¬ç½å°åï¼å¤æåºè·¯ç±å¨é»æ¢ç´è¿çéå¶æ¹æ³çåè®®ã STUNæ¯ä¸ä¸ªC/Sæ¶æçåè®®ï¼æ¯æ两ç§ä¼ è¾ç±»åãä¸ç§æ¯è¯·æ±/ååºï¼request/respondï¼ç±»åï¼ç±å®¢æ·ç«¯ç»æå¡å¨åé请æ±ï¼å¹¶çå¾ æå¡å¨è¿åååºï¼å¦ä¸ç§æ¯æ示类åï¼indication transactionï¼ï¼ç±æå¡å¨æè 客æ·ç«¯ åéæ示ï¼å¦ä¸æ¹ä¸äº§çååºã对äºè¯·æ±/ååºç±»åï¼å 许客æ·ç«¯å°ååºå产çååºç请æ±è¿æ¥èµ·æ¥ï¼ 对äºæ示类åï¼é常å¨debugæ¶ä½¿ç¨ãæ们主è¦äºè§£è¯·æ±/ååºç±»åã
1.2.2 éä¿¡è¿ç¨
客æ·ç«¯éè¿ç»å ¬ç½çSTUNæå¡å¨åé请æ±è·å¾èªå·±çå ¬ç½å°åä¿¡æ¯ï¼ä»¥åæ¯å¦è½å¤è¢«ï¼ç©¿è¿è·¯ç±å¨ï¼è®¿é®ã
- 客æ·ç«¯Aåæå¡å¨äº§çä¸ä¸ªRequestï¼STUNååï¼ä½ è½åè¯ææçipæ¯å¤å°åï¼
- æå¡å¨æ¥æ¶Requestï¼æ£æ¥æ¥ææ¯å¦åæ³ï¼å¹¶çæSuccessååºæErrorååºï¼Aå°æåï¼ä½ çipæ¯208.141.55.130:3255ï¼
1.3 TURNåè®®
TURNï¼TURN/RFC5766ï¼æ¯P2Pæ ååéä¿¡è§åï¼åè®®ï¼ä¹ä¸ï¼æ¯å¯¹STUNçè¡¥å ã
1.3.1 ç®ä»
TURNçå ¨ç§°ä¸ºTraversal Using Relays around NAT (TURN) ï¼æ¯STUN/RFC5389çä¸ä¸ªæå±ï¼ä¸»è¦æ·»å äºRelayåè½ãåé¢ä»ç»çSTUNåè®®å¤ççæ¯å¸é¢ä¸å¤§å¤æ°çCone NATï¼ä½è¿æå°éç设å¤ä½¿ç¨çSymmetric NATãå æ¤ä¼ ç»çææ´æ¹æ³ä¸éç¨ï¼ä¸ºäºä¿è¯è¿ä¸é¨å设å¤è½å¤å»ºç«éä¿¡ï¼æ们ä¸å¾ä¸éè¿ä¸ç»§ï¼Relayingï¼çæ¹æ³è¿è¡è¿æ¥ï¼è¿æ¶å°±éè¦å ¬ç½çæå¡å¨ä½ä¸ºä¸ä¸ªä¸ç»§ï¼ 对æ¥å¾çæ°æ®è¿è¡è½¬åãè¿ä¸ªè½¬åçå议就被å®ä¹ä¸ºTURNãè¿ç§æ åµä¼å¢å æå¡å¨è´æ ï¼æ以è¿æ¯æåçæ åµçé信解å³æ¹æ¡ã TURNæå¡å¨ä¸å®¢æ·ç«¯ä¹é´çè¿æ¥é½æ¯åºäºUDPçï¼ä½æ¯æå¡å¨å客æ·ç«¯ä¹é´å¯ä»¥éè¿å ¶ä»åç§è¿æ¥æ¥ä¼ è¾STUNæ¥æ, æ¯å¦TCP/UDP/TLS-over-TCPã客æ·ç«¯ä¹é´éè¿ä¸ç»§ä¼ è¾æ°æ®æ¶åï¼å¦æç¨äºTCPï¼ä¹ä¼å¨æå¡ç«¯è½¬æ¢ä¸ºUDPï¼å æ¤å»ºè®®å®¢æ·ç«¯ä½¿ç¨ UDPæ¥è¿è¡ä¼ è¾ãè³äºä¸ºä»ä¹è¦æ¯æTCPï¼é£æ¯å 为ä¸é¨åé²ç«å¢ä¼å®å ¨é»æ¡UDPæ°æ®ï¼è对äºä¸æ¬¡æ¡æçTCPæ°æ®åä¸åé离ã
1.3.2 éä¿¡è¿ç¨
客æ·ç«¯AåSTUNæå¡å¨åé请æ±è·åèªå·±çå ¬ç½å°åï¼STUNæå¡å¨å¯ä»¥è·åå°å®¢æ·ç«¯Açå°åï¼ä½åç°å®¢æ·ç«¯Aç使ç¨çSymmetric NATï¼å æ¤STUNæå¡å¨åè¯å®¢æ·ç«¯Aï¼æä¸è½å¸®å©ä½ å客æ·ç«¯B建ç«è¿æ¥ï¼ä½ 们ä¹é´å¯ä»¥éè¿TURNè¿è¡è¿æ¥ãå æ¤å®¢æ·ç«¯Aå客æ·ç«¯Båæ¶å»è¿æ¥TURNæå¡å¨ï¼éè¿TURNæå¡å¨è¿è¡ä¸ç»§è¿æ¥ã
- 客æ·ç«¯AåSTUNæå¡å¨äº§çä¸ä¸ªRequestï¼STUNååï¼ä½ è½åè¯ææçipæ¯å¤å°åï¼
- STUNæå¡å¨ååºï¼Aå°æåï¼ä½ çipæ¯208.141.55.130:3255ï¼å¯æ¯ä½ çipå«äººä¸è½åä½ è¿æ¥å¦ï¼ä½ éè¦å»æ¾ä½ TURN大伯ï¼ä»æ¯ä¸é¨è´è´£å¸®ä½ è¿æ¥ï¼
- 客æ·ç«¯AåTURNæå¡å¨å起请æ±ï¼TURN大伯ï¼STUNååå«ææ¥æ¾ä½ ï¼
- TURNæå¡å¨ååºï¼Aå°ä¾å¿ï¼æç¥éäºï¼ä½æ¯ç°å¨è¿æ²¡æå ¶ä»å°æåæ¾ä½ å¦ï¼ä½ å¯ä»¥å¨è¿éè¿éä¸éï¼æ¯10åéè¦ç»ææ¥åä¸ä¸ä½ è¿å¨è¿éè¿å¦ï¼ä¸æå ¶ä»å°æåæ¥æ¾ä½ æå°±éç¥ä½ ãï¼
1.4 ICEåè®®
TURNï¼ICE/RFC5245ï¼æ¯P2Pæ ååéä¿¡è§åï¼åè®®ï¼ä¹ä¸ï¼æä¾äºå®æ´çNATä¼ è¾è§£å³æ¹æ¡ã STUNãTURNé½æ¯å·¥å ·ç±»åè®®ï¼åªæä¾ç©¿éNATçåè½ãä¸TURNæ¬èº«å°±æ¯è¢«è®¾è®¡ä¸ºICE/RFC5245çä¸é¨å
1.4.1 ç®ä»
ICEçå ¨ç§°ä¸ºInteractive Connectivity Establishment (ICE)ï¼å³äº¤äºå¼è¿æ¥å»ºç«ãå¨å®é çç½ç»å½ä¸ï¼æå¾å¤åå è½å¯¼è´ç®åçä»A端å°B端ç´è¿ä¸è½å¦æ¿å®æãè¿éè¦ç»è¿é»æ¢å»ºç«è¿æ¥çé²ç«å¢ï¼ç»ä½ ç设å¤åé ä¸ä¸ªå¯ä¸å¯è§çå°åï¼é常æ åµä¸æ们ç大é¨å设å¤æ²¡æä¸ä¸ªåºå®çå ¬ç½å°åï¼ï¼å¦æè·¯ç±å¨ä¸å 许主æºç´è¿ï¼è¿å¾éè¿ä¸å°æå¡å¨è½¬åæ°æ®ãICEéè¿ä½¿ç¨STUNãTURNãNATãSDPææ¯å®æä¸è¿°å·¥ä½ã(å¼ç¨èªï¼https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols) ICEæ¯ä¸ä¸ªç¨äºå¨Offer/Answer模å¼ä¸çNATä¼ è¾åè®®ï¼ä¸»è¦ç¨äºUDPä¸å¤åªä½ä¼è¯ç建ç«ï¼å ¶ä½¿ç¨äºSTUNå议以åTURN åè®®ï¼åæ¶ä¹è½è¢«å ¶ä»å®ç°äºOffer/Answer模åççå ¶ä»ç¨åºæ使ç¨ï¼æ¯å¦SIP(Session Initiation Protocol)ã ç½ç»ç¼ç¨çICEï¼Internate Communications Engineï¼ï¼æ¯ä¸ç§ç¨äºåå¸å¼ç¨åºè®¾è®¡çç½ç»éä¿¡ä¸é´ä»¶ï¼æ¬ææ并éæ¤ICE 交äºå¼è¿æ¥ICEï¼Interactive Connectivity Establishmentï¼ï¼æ¯ä¸ä¸ªå è®¸ä½ çæµè§å¨å对端æµè§å¨å»ºç«è¿æ¥çåè®®æ¡æ¶ã
1.4.2 SDPä¼è¯æè¿°
ICEä¿¡æ¯çæè¿°æ ¼å¼é常éç¨æ åçSDPï¼å ¶å ¨ç§°ä¸ºSession Description Protocol (SDP) ï¼å³ä¼è¯æè¿°åè®®ãSDPä¸æ¯ä¸ä¸ªçæ£çåè®®ï¼èæ¯ä¸ç§æ°æ®æ ¼å¼ï¼ç¨äºæè¿°å¨è®¾å¤ä¹é´å ±äº«åªä½çè¿æ¥ãå¯ä»¥è¢«å ¶ä»ä¼ è¾åè®®ç¨æ¥äº¤æ¢å¿ è¦çä¿¡æ¯ï¼å¦SIPåRTSPçã SDPæ ¼å¼ï¼ SDPç±ä¸è¡æå¤è¡UTF-8ææ¬ç»æï¼æ¯è¡ä»¥ä¸ä¸ªå符çç±»åå¼å¤´ï¼åè·çå·ï¼â =âï¼ï¼ç¶åæ¯å å«å¼ææè¿°çç»æåææ¬ï¼å ¶æ ¼å¼åå³äºç±»åã SDPä¼è¯æè¿°å å«äºå¤è¡å¦ä¸ç±»åçææ¬:
<type>=<value>
以ç»å®åæ¯å¼å¤´çææ¬è¡é常称为âåæ¯è¡âãä¾å¦ï¼æä¾åªä½æè¿°çè¡çç±»å为â mâï¼å æ¤è¿äºè¡ç§°ä¸ºâ mè¡âã
m=audio 49170 RTP/AVP 0
ä¼è¯æè¿°
v= (protocol version)
o= (originator and session identifier)
s= (session name)
i=* (session information)
u=* (URI of description)
e=* (email address)
p=* (phone number)
c=* (connection information -- not required if included in
all media)
b=* (zero or more bandwidth information lines)
One or more time descriptions ("t=" and "r=" lines; see below)
z=* (time zone adjustments)
k=* (encryption key)
a=* (zero or more session attribute lines)
Zero or more media descriptions
æ¶é´ä¿¡æ¯æè¿°:
t= (time the session is active)
r=* (zero or more repeat times)
å¤åªä½ä¿¡æ¯æè¿°(å¦ææçè¯):
m= (media name and transport address)
i=* (media title)
c=* (connection information -- optional if included at
session level)
b=* (zero or more bandwidth information lines)
k=* (encryption key)
a=* (zero or more media attribute lines)
ææå ç´ çtypeé½ä¸ºå°åï¼å¹¶ä¸ä¸æä¾æå±.ä½æ¯æ们å¯ä»¥ç¨a(attribute)å段æ¥æä¾é¢å¤çä¿¡æ¯ãä¸ä¸ªSDPæè¿°çä¾åå¦ä¸ï¼
v=0
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
s=SDP Seminar
i=A Seminar on the session description protocol
u=http://www.example.com/seminars/sdp.pdf
[email protected] (Jane Doe)
c=IN IP4 224.2.17.12/127
t=2873397496 2873404696
a=recvonly
m=audio 49170 RTP/AVP 0
m=video 51372 RTP/AVP 99
a=rtpmap:99 h263-1998/90000
å ·ä½å段çtype/valueæè¿°åæ ¼å¼å¯ä»¥åèRFC4566ã
1.4.3 Offer/Answer模å
SDPç¨æ¥æè¿°å¤æ主干ç½ç»çä¼è¯ä¿¡æ¯ï¼ä½æ¯å¹¶æ²¡æå
·ä½ç交äºæä½ç»èæ¯å¦ä½å®ç°çï¼å æ¤RFC3264 å®ä¹äºä¸ç§åºäºSDPçOffer/Answer模åã
å¨è¯¥æ¨¡åä¸ï¼ä¼è¯åä¸è
çå
¶ä¸ä¸æ¹çæä¸ä¸ªSDPæ¥æææofferï¼ å
¶ä¸å
å«äºä¸ç»offerå¸æ使ç¨çå¤åªä½æµåç¼è§£ç æ¹æ³ï¼ä»¥åofferç¨æ¥æ¥æ¶æ¹æ°æ®çIPå°åå端å£ä¿¡æ¯ã
offerä¼ è¾å°ä¼è¯çå¦ä¸ç«¯(称为answer)ï¼ç±è¿ä¸ç«¯çæä¸ä¸ªanswerï¼å³ç¨æ¥ååºå¯¹åºofferçSDPæ¥æã
answerä¸å
å«ä¸åoffer对åºçå¤åªä½æµï¼å¹¶ææ该æµæ¯å¦å¯ä»¥æ¥åã
1.4.4 ICEå·¥ä½æµç¨
ä¸ä¸ªå ¸åçICEå·¥ä½ç¯å¢å¦ä¸ï¼æ两个端ç¹AåBï¼é½è¿è¡å¨åèªçNATä¹å(ä»ä»¬èªå·±ä¹è®¸å¹¶ä¸ç¥é)ï¼NATçç±»ååæ§è´¨ä¹æ¯æªç¥çãLåRéè¿äº¤æ¢SDPä¿¡æ¯å¨å½¼æ¤ä¹é´å»ºç«å¤åªä½ä¼è¯ï¼é常交æ¢éè¿ä¸ä¸ªSIPæå¡å¨å®æï¼
+-----------+
| SIP |
+-------+ | Srvr | +-------+
| STUN | | | | STUN |
| Srvr | +-----------+ | Srvr |
| | / \ | |
+-------+ / \ +-------+
/<- Signaling ->\
/ \
+--------+ +--------+
| NAT | | NAT |
+--------+ +--------+
/ \
/ \
/ \
+-------+ +-------+
| Agent | | Agent |
| A | | B |
| | | |
+-------+ +-------+
ICEçåºæ¬æè·¯æ¯ï¼æ¯ä¸ªç»ç«¯é½æä¸ç³»åä¼ è¾å°å(å æ¬ä¼ è¾åè®®ï¼IPå°åå端å£)çåéï¼å¯ä»¥ç¨æ¥åå ¶ä»ç«¯ç¹è¿è¡éä¿¡ãå ¶ä¸å¯è½å æ¬ï¼
- ç´æ¥åç½ç»æ¥å£èç³»çä¼ è¾å°å(host address)
- ç»è¿NAT转æ¢çä¼ è¾å°å,å³åå°å°å(server reflective address)
- TURNæå¡å¨åé çä¸ç»§å°å(relay address)
éè¿ä¹åçå¦ä¹ ï¼æ们å¯ä»¥äºè§£å°æ¯ä¸ªç»ç«¯çæ åµæ¯æ¯è¾å¤æçï¼æçç»ç«¯å¯è½åæ¶è¿çwifiåç½çº¿ï¼æå¤ä¸ªå ç½å°åï¼ï¼æææ¯ä¸ªç»ç«¯æå¤ç§å¯ä»¥è¿æ¥çæ¹æ¡ã
è·åå°è¿ä¸ç³»åä¼ è¾å°ååï¼ä¼ä»¥ä¸å®ä¼å 级å°å°åæåºãæç §ä¼å 级åå ¶ä»ç»ç«¯çä¼ è¾å°åè¿è¡ç»åæ£æµè¿æ¥å¯ç¨æ§ï¼è¿æ¥æ§æ£æ¥ï¼Connectivity Checksï¼ã 两端è¿æ¥æ§æ£æ¥ï¼æ¯ä¸ä¸ª4次æ¡æè¿ç¨:
A B
- -
STUN request -> \ A's
<- STUN response / check
<- STUN request \ B's
STUN response -> / check
è¿æ¥æ§æ£æ¥è¯¦ç»è¿ç¨ï¼
- 为ä¸ç»§åéå°åçæ许å¯(Permissions)ï¼
- ä»æ¬å°åéå¾è¿ç«¯åéåéBinding Requestï¼å¨Binding请æ±ä¸é常éè¦å
å«ä¸äºç¹æ®çå±æ§ï¼ä»¥å¨ICEè¿è¡è¿æ¥æ§æ£æ¥çæ¶åæä¾å¿
è¦ä¿¡æ¯ï¼
- PRIORITY å USE-CANDIDATEï¼ä¼å 级ååé
- ICE-CONTROLLEDåICE-CONTROLLINGï¼æ è¯æ¬ç«¯æ¯åæ§æ¹è¿æ¯ä¸»æ§æ¹ï¼offerçææ¹ï¼ã
- çæCredentialï¼STUNçæ身份éªè¯
- å¤çResponseï¼å½æ¶å°Binding Responseæ¶ï¼ç»ç«¯ä¼å°å
¶ä¸Binding Requestç¸èç³»ï¼é常çæäºå¡IDãéåå°ä¼å°æ¤äºå¡IDä¸åéå°å对è¿è¡ç»å®ã
- æåååºï¼è¦åæ¶æ»¡è¶³ä¸ä¸ªæ¡ä»¶ï¼STUNä¼ è¾äº§çä¸ä¸ªSuccess Responseï¼responseçæºIPå端å£çäºBinding Requestçç®çIPå端å£ï¼responseçç®çIPå端å£çäºBinding RequestçæºIPå端å£ï¼
- 失败ååºï¼487é误ï¼å¹¶å°æ£æµå°åç¶æ设置为Waiting
以ä¸ä» 对åè®®ä½äºç®åçä»ç»ï¼å ·ä½æå¡å¨ç¨åºå®ç°å¯åèï¼https://github.com/evilpan/TurnServer
1.5 ç»å ¸WebRTCè¿æ¥å»ºç«æµç¨
éè¿åé¢çåè®®äºè§£å¦ä¹ ï¼ç¸ä¿¡å¤§å®¶å·²ç»å¯¹WebRTCçåºå±è¿æ¥æµç¨æäºä¸ä¸ªæ¨¡ç³çææï¼è¿éæå¼ å¾å±ç°äºå
·ä½çè¿æ¥æµç¨ã
å¼ç¨èªï¼https://aggresss.blog.csdn.net/article/details/106832965
äºãæå¡å¨æ建
2.1 STUN/TURNæå¡å¨ãå¯è·³è¿ã
ç½ä¸æå ¬ç¨çstunæå¡å¨ï¼æ¬èå¯ç´æ¥è·³è¿ã STUNæå¡å¨å·²æç°æ项ç®ï¼https://github.com/coturn/coturn 以ä¸æ¯å¨ubuntuä¸çå®è£ åé ç½®ï¼
2.1.1 å®è£ coturn
å¯ä»¥å égithubä¸çæºç ç¼è¯å®è£ ï¼å¨ubuntuéæç´æ¥çå®è£ å
apt-get -y update
apt-get -y install coturn
å®è£ å®æ¯åï¼å å ³écoturnæå¡ï¼
systemctl stop coturn
â
2.1.2 é ç½®coturn
(1) å 许turnserver é¦å éè¦å 许turnserverï¼æå¼/etc/default/coturnæ件ï¼å°æ³¨éå»æï¼
vim /etc/default/coturn
åæ¶æ³¨éåå¦ä¸ï¼
TURNSERVER_ENABLED=1
(2) è·åipåSSL é¦ééè¦è·åä¸ä¸èªå·±çå ç½ip以åç½å¡:
ifconfig
çæSSLè¯ä¹¦:
apt install openssl
openssl req -x509 -newkey rsa:2048 -keyout /etc/turn_server_pkey.pem -out /etc/turn_server_cert.pem -days 99999 -nodes
(3) é ç½® æ¥ä¸æ¥æ£å¼æ¹é ç½®æ件/etc/turnserver.confï¼æ¹ä¹åå å°åæ件å¤ä»½ä¸ä¸ªï¼
mv /etc/turnserver.conf /etc/turnserver.conf.bat
ç¶åæ°å»ºé ç½®æ件ï¼
vim /etc/turnserver.conf
ç¶åå¤å¶ä»¥ä¸é ç½®ï¼
server-name=turn.webrtc.zzboy.cn
realm=turn.webrtc.zzboy.cn
fingerprint
relay-device=eth0 #ä¸åifconfigæ¥å°çç½å¡å称ä¸è´
listening-ip=192.168.0.186 #å
ç½IP
listening-port=3478
tls-listening-port=5349
relay-ip=192.168.0.186
external-ip=121.36.105.109 #å
¬ç½IP
relay-threads=50
lt-cred-mech
no-cli
verbose
cert=/etc/turn_server_cert.pem
pkey=/etc/turn_server_pkey.pem
#pidfile=/var/run/turnserver.pid
min-port=49152
max-port=65535
user=jun:123456 #ç¨æ·åå¯ç ï¼å建IceServeræ¶ç¨
2.1.3 æµè¯
å·¥å ·ï¼Trickle ICE ç¹å»æå¼ä¸é¢çå·¥å ·
2.2 Nodejsæ建信令æå¡å¨(Signal Server)
信令æå¡å¨æç´æ¥ä½¿ç¨çä¸ä¸ªå¼æºé¡¹ç®ï¼https://github.com/qdgx/WebRtcRoomServer å ¶å®ä¿¡ä»¤æå¡å¨å·²ç»æ¶åå°å®æäºï¼è¿éå°±ä¸è®²å ·ä½å®ç°ï¼è¿éåªå é¨ç½²ã å纯å°çï¼ä¿¡ä»¤æå¡å¨å ¶å®å¯ä»¥ç®ä½æ¯ä¸ä¸ªå端项ç®ï¼æ们è¿éé¨ç½²ä¹åªæ¯å¯¹è¯¥é¡¹ç®è¿è¡æå¡å¨é¨ç½²ãè¿éæ使ç¨çè¿ä¸ªå¼æºé¡¹ç®æ¯ä½¿ç¨node.jså¼åçï¼å æ¤é¨ç½²æ¥éª¤ånode.jsé¨ç½²æ¥éª¤ç¸å·®æ å¼ã 以ä¸æ¯æå¨ubuntuä¸çå®è£ åé ç½®ï¼
2.2.1 å®è£ nodeç¯å¢
(1) æ´æ°ç¯å¢ï¼å®è£ curlãgit
apt-get update
apt-get install -y curl git
(2) å®è£ node.js å å»å®ç½https://nodejs.org/ï¼æ¥çææ°ç¨³å®é¿ææ¯æçï¼åç°ææ°ç¨³å®çæ¯14.15.3 LTSï¼node.jsçæ¯ä¸ªå¤§çæ¬å·é½æç¸å¯¹åºçæºï¼æ¯å¦è¿éç14.15.3çæ¬çæºæ¯ https://deb.nodesource.com/setup_14.x æ以å¨ç»ç«¯æ§è¡ï¼
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
ç¶åå®è£ node.js
apt-get install nodejs
node -v å npm -v æ¥çnodeånpmæ¯å¦å®è£ æå
2.2.2 å é项ç®ï¼å®è£ ä¾èµ
è¿å ¥ç¨æ·ç®å½ï¼å é项ç®ï¼
cd ~/ && git clone https://github.com/qdgx/WebRtcRoomServer.git
å®è£ ä¾èµï¼
cd ~/WebRtcRoomServer
npm i
å¯å¨æå¡ï¼
node app.js
å¨æµè§å¨æå¼ä»¥ä¸å°åï¼æµè¯ä¸ä¸æ¯å¦è®¿é®ï¼
https://ä½ æå¡å¨å¤ç½å°å:8443
åªè¦æµè§å¨æ示该页é¢åå¨é£é©ï¼å³è¡¨ç¤ºé¡¹ç®å·²çæï¼ç¹å»é«çº§ï¼éæ©æ¥åé£é©ç»§ç»è®¿é®å³å¯ãï¼ä¸ºä»ä¹æ示é£é©ï¼å 为è¿ä¸ªé¡¹ç®çè¯ä¹¦æ¯èªç¾åè¯ä¹¦ï¼
å¦ææ æ³è®¿é®ï¼è¯·æ£æ¥æå¡å¨å®å
¨ç»æ¯å¦æå¼äºTCPåUDPåè®®ç8443端å£ï¼æäºæå¡å¨å¼ç«¯å£éè¦å¨æå¡å¨ä¸é£é
ç½®å®å
¨ç»ï¼æ¯å¦é¿éäºECSåå为äºã
2.2.3 pm2管çnodeæå¡
ç´æ¥ç¨node app.jsè¿è¡é¡¹ç®ï¼å¨å ³éç»ç«¯åï¼node项ç®ä¹ä¼éä¹è¢«å ³éï¼å æ¤éè¦ä½¿ç¨é¢å¤çå·¥å ·æ¥ä¿ænodeæå¡ä¸ç´å¼å¯ã å®è£ pm2ï¼
npm install pm2@latest -g
å¯å¨æå¡ï¼
pm2 start app.js --name signal-server --watch
- nameï¼ç»åºç¨å½åï¼å¯ä»¥ä¸ç®¡
- watchï¼ç¸å½äºçæ´æ°ï¼åºç¨æ件æ´æ°åä¼éå¯åºç¨
æå ³pm2ç使ç¨ï¼å¯ä»¥ç¾åº¦æ¥è¯¢ä¸ä¸ï¼ä¹å¯ä»¥åèæ¬äººä¹ååçä¸ç¯æç« ï¼https://www.zzboy.cn/Learning/f360ef90efef
ä¸ãAPIå¦ä¹
以ä¸ä¸»è¦ä»ç»ä¸ä¸ç« èå®æå¼ä¸éè¦å°ç常ç¨æ¥å£ï¼å®æ´çæ¥å£å¦ä¹ å¯æ¥ç对åºå®æ¹ææ¡£ã
3.1 socket.io
å®æ¹ææ¡£ï¼https://socket.io/docs/v3/ ä¸æw3choolï¼https://www.w3cschool.cn/socket/ Socketæ¯ä¸ç§å ¨åå·¥éä¿¡,å½å®¢æ·ç«¯åæå¡ç«¯å»ºç«èµ·è¿æ¥åï¼å¦æä¸ä¸»å¨æå¼ï¼åæ¹å¯ä»¥ä¸ç´äºç¸åéæ¶æ¯ï¼éåäºåæ¹é¢ç¹éä¿¡çåºæ¯ï¼ä¹æ¯æ¯ææå¡ç«¯ä¸»å¨æ¨éçä¸ç§éä¿¡æ¹å¼ãWebSocketæ¯Html5æ¨åºçå端å¯ä»¥ç´æ¥ä½¿ç¨çAPIï¼ä¸è¿ç®å项ç®ä¸ç¨çè¿æ¯socket.ioæ¯è¾å¤ãsocket.ioå¨æµè§å¨ç¯å¢ä¸å°è£ äºWebSocket, å¯ä»¥ç»å¼åè 带æ¥æ´å¥½çä½éªï¼å¨åè½ä¸ä¹æ´å®åã socket.io主è¦ä½¿ç¨ä¸¤ä¸ªæ¹æ³ï¼
- emit(description: string, data: anyï¼çå¬äºä»¶ï¼descriptionæ¯æ è¯ï¼dataæ¯éè¦åéçæ°æ®ã
- on(description: string, callback: functionï¼çå¬äºä»¶ï¼description表示çå¬çæ è¯ï¼callbackæ¯çå°äºä»¶åå¤çæ¹æ³ï¼åæ°æ¯emitåéçæ°æ®ã
éä¿è¯´ï¼ä¸ä¸ªå°±æ¯åéï¼ä¸ä¸ªæ¯æ¥æ¶ãåéæ¹æ³éè¦æå®è°(description)æ¥æ¥æ¶ï¼æ¥æ¶æ¹æ³æ¾å°å¯¹åºdescriptionæ¥æ¶ã
3.1.1 æå¡å¨ç«¯
(1) å®è£
npm install socket.io
(2) åå§å
const httpServer = require("http").createServer(); // å建httpæå¡
// 使ç¨socket.ioçå¬httpæå¡
const socketIO = require("socket.io");
const io = socketIO.listen(httpServer);
// ä¹å¯ä»¥ä½¿ç¨å¦ä¸æ¹å¼
const io = require("socket.io")(httpServer, {
// optionsé
置项
});
é 置项ï¼æ¯åå§é ç½®socket.ioçä¸äºåæ°ï¼æ们使ç¨é»è®¤çæ¥å£ï¼å¦éè¦é ç½®ï¼å¯ä»¥çææ¡£äºè§£å ·ä½é 置项ï¼https://socket.io/docs/v3/server-api/#new-Server-httpServer-options æ ¹æ®WebRTCå®å ¨çç¥ï¼æ们éè¦ä½¿ç¨httpsï¼å æ¤ï¼æ¯è¾å®æ´çåå§å代ç 为ï¼
const fs = require('fs');
const server = require('https').createServer({
key: fs.readFileSync('/tmp/key.pem'),
cert: fs.readFileSync('/tmp/cert.pem')
});
const options = { /* ... */ };
const io = require('socket.io')(server, options);
io.on('connection', socket => { /* ... */ });
server.listen(3000);
(3) æ¹æ³ io.on(âconnectionâ, fn) ï¼çå¬å®¢æ·ç«¯è¿æ¥ ä»ä¸é¢åå§å代ç ä¸é¾çåºï¼socket.io第ä¸ä¸ªæ¹æ³åºè¯¥io.on('connection', fn)ã connectionæ¯ä¿çdescriptionï¼å½æ客æ·ç«¯è¿æ¥ä¸å½åæå¡å¨æ¶ï¼å°±ä¼è§¦åã æ们éè¦å¨å ¶åè°ä¸å¤çç¸å ³ä¸å¡ï¼
io.on('connection', socket => {
// çå¬æå¼è¿æ¥
socket.on('disconnect', reason => console.log(reason)) // socketæå¼çå¬ï¼disconnectä¹æ¯ä¿çå段
// å
¶ä»ä¸å¡çå¬
socket.on('join', data => console.log(`欢è¿${data.name}è¿å
¥ç´æé´`));
});
socket.on(âdisconnectâ, fn) ï¼çå¬å®¢æ·ç«¯æå¼è¿æ¥
socket.on('disconnect', reason => {
console.log(reason); // æå¼åå æå¾å¤ï¼å¯è½æ¯ç¨æ·ä¸»å¨æå¼ï¼ä¹å¯è½æ¯æµè§å¨ç´æ¥å
³éç
})
socket.emit() : åéä¿¡æ¯
3.1.2 客æ·ç«¯
3.2 é³è§é¢ç¸å ³API
3.2.1 navigator.mediaDevices
æµè§å¨APIï¼å¯ä»¥éè¿è¯¥æµè§å¨APIè·åç¨æ·åªä½è®¾å¤ï¼é常åªä¼ç¨å°ä¸ä¸ªæ¹æ³ï¼getUserMedia(options)ï¼è°ç¨è¯¥æ¹æ³æ¶ï¼æµè§å¨ä¼å¼¹åºè¯·æ±é³é¢æè§é¢çæéï¼ç¨æ·åæææè¿åï¼å³å¯è·åå°é³è§é¢æµã
navigator.mediaDevices.getUserMedia(options)
.then(function(stream) {
/* use the stream */
})
.catch(function(err) {
/* handle the error */
});
éè¦æ³¨æï¼navigatorçmediaDeviceså±æ§éè¦å¨httpsç¯å¢ä¸æä¼æï¼è¿æ¯æµè§å¨çéå¶ã options: é 置项 ä¸è¬å¯ç´æ¥è®¾ç½®ä¸ºï¼{ audio: true, video: true }ï¼è¡¨ç¤ºä¸ºè·åé³é¢åè§é¢ã
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
.then(function(stream) {
/* use the stream */
})
.catch(function(err) {
/* handle the error */
});
è§é¢æ¹é¢ï¼ä¹å¯ä»¥åç¡®å®ä¹è§é¢ç»é¢ç宽é«ï¼
navigator.mediaDevices.getUserMedia({
audio: true,
video: { width: 1280, height: 720 } // å½å®ä¹å®½é«æ¯ï¼è§é¢ç®æ¯trueï¼è¯·æ±è§é¢æé
})
.then(function(stream) {
/* use the stream */
})
.catch(function(err) {
/* handle the error */
});
å ¶ä»æ´å¤é ç½®å¯åèï¼https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
3.2.2 video
(1) videoæ ç¾
<video src="path/to/movie.mp4" controls="controls">
æ¨çæµè§å¨ä¸æ¯æ video æ ç¾ã
</video>
å±æ§ï¼
- autoplay: å¦æåºç°è¯¥å±æ§ï¼åè§é¢å¨å°±ç»ªå马ä¸ææ¾
- controlsï¼å¦æåºç°è¯¥å±æ§ï¼ååç¨æ·æ¾ç¤ºæ§ä»¶ï¼æ¯å¦ææ¾æé®
- loopï¼å¦æåºç°è¯¥å±æ§ï¼åå½åªä»æ件å®æææ¾åå次å¼å§ææ¾
- mutedï¼è§å®è§é¢çé³é¢è¾åºåºè¯¥è¢«éé³
- posterï¼è§å®è§é¢ä¸è½½æ¶æ¾ç¤ºçå¾åï¼æè å¨ç¨æ·ç¹å»ææ¾æé®åæ¾ç¤ºçå¾å
- preloadï¼å¦æåºç°è¯¥å±æ§ï¼åè§é¢å¨é¡µé¢å è½½æ¶è¿è¡å è½½ï¼å¹¶é¢å¤ææ¾ãå¦æä½¿ç¨ âautoplayâï¼å忽ç¥è¯¥å±æ§
- srcï¼è¦ææ¾çè§é¢ç URL
- widthï¼è®¾ç½®è§é¢ææ¾å¨ç宽度ï¼åä½px
- heightï¼è®¾ç½®è§é¢ææ¾å¨çé«åº¦ï¼åä½px
æ们å¨è¿è¡é³è§é¢éè¯æ¶ï¼é常 **æ¬å°è§é¢ï¼ææ¹è§é¢ï¼**åºå¦ä¸ï¼
<video id="local" muted autoplay>
æ¨çæµè§å¨ä¸æ¯æ video æ ç¾ã
</video>
æ¬å°è§é¢éé³ææ¾ï¼å 为æ们æ éæ们èªå·±ååºç声é³ï¼å 为æ们å°æ¶åè§é¢èµæºæ¯ä»è®¾å¤ç´æ¥å®æ¶è·åè§é¢æµï¼å æ¤æ é设置srcï¼å¹¶ä¸è®¾ç½®autoplayï¼å¯ä»¥è®©æ们è·åå°è§é¢æµç´æ¥ææ¾ã **è¿ç¨è§é¢ï¼å¯¹æ¹è§é¢ï¼**åºå¦ä¸ï¼
<video id="remote" poster="xxx" autoplay>
æ¨çæµè§å¨ä¸æ¯æ video æ ç¾ã
</video>
è¿ç¨è§é¢åæ ·è®¾ç½®autoplayå±æ§ï¼è®©æ¥æ¶å°çè§é¢æµç´æ¥ææ¾ãå¦å¤å¯è®¾ç½®ä¸ä¸ªposterå±æ§ï¼å¯ä»¥å¨å¼å«è¿ç¨ä¸æè 被å¼å«æ¶ï¼è®©é¡µé¢æ¾ç¤ºå¼å«ä¸æè æ¯æ¾ç¤ºå¯¹æ¹å¤´åèåçï¼ä¸ç¶é¡µé¢å ¨é»ä¼æ¾å¾å¾å°´å°¬ã (2) video对象 使ç¨é³è§é¢éè¯ï¼æ们æ§å¶é³è§é¢çææ¾åºæ¬éè¿jså®ç°çï¼å°±è¿åé¢ä»ç»çvideoæ ç¾ä¸è¬é½æ¯éè¿jså建ãvideo对象æå¾å¤å±æ§ï¼æè¿éåªç®åä»ç»é¨åå±æ§ï¼è½åºæ¬æ»¡è¶³WebRTCé³è§é¢éè¯ã æ们è¦å®ç°é³è§é¢å®æ¶é讯ï¼ä¼ éçæ°æ®æ¯é³è§é¢æµï¼é³è§é¢æµæä¹è®©videoææ¾åºæ¥å¢ï¼ççä¸é¢ä»£ç ï¼
/**
* è§é¢æµç»å®å°videoèç¹å±ç¤º
* @param {dom} video videoèç¹
* @param {obj} stream è§é¢æµ
*/
const pushStreamToVideo = (video, stream) => {
video.srcObject = stream;
}
// è·åvideoèç¹
const domLocalVideo = $('#local');
// è°ç¨æå头
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
.then(stream => {
pushStreamToVideo(domLocalVideo[0], stream); // å®æ¶æ¾ç¤º
})
.catch(err => {
alert(`getUserMedia() error: ${err.name}`)
});
ä¸é¾çåºï¼video对象æ个srcObjectçå±æ§ï¼åå§æ¶è¯¥å±æ§å¼æ¯nullï¼å°æ们è·åå°é³è§é¢æµç´æ¥èµå¼ç»è¯¥å±æ§ï¼æ们çvideoæ ç¾å°±å¯ä»¥å®æ¶ææ¾äºãä¸é¢è¿ä¸ªä¾åæ¯è°ç¨æ¬å°æå头并å±ç¤ºå°ä¸ä¸ªid=localçvideoæ ç¾ä¸ï¼éè¦å¨httpsä¸å°±å¯ä»¥æ£å¸¸è¿è¡äºã æ们å¦ä½å ³éè§é¢å¢ï¼ æ¹æ³ä¸ï¼ç®åç²æ´ï¼å ³é页é¢æè å ³éæµè§å¨ãï¼ä½ ä¼è®©ç¨æ·è¿ä¹å¹²ä¹ï¼ï¼ æ¹æ³äºï¼ä½¿ç¨MediaStream.getTracks()ï¼è·åå°ææåªä½æµè½¨éï¼æ¯æ¡è½¨éè°ç¨ä¸ä¸ªæ¹æ³stop()ï¼å°±å¯ä»¥å ³éå½åæµï¼æå头ä¹ä¼åæ¢å½å¶ã
/**
* å
³éæå头
* @param {dom} video videoèç¹
*/
const closeCamera = video => {
video.srcObject.getTracks()[0].stop(); // audio
video.srcObject.getTracks()[1].stop(); // video
}
é³é¢æ¯ç¬¬ä¸æ¡è½¨éï¼è§é¢æ¯ç¬¬äºæ¡è½¨éï¼ä¸¤ä¸ªåæ¶å ³éå³å¯ã
3.3 WebRTC
å®æ¹ææ¡£ï¼ä¸æ¨èï¼ï¼https://www.w3.org/TR/webrtc/#peer-to-peer-connections å®æ¹ææ¡£ä¸æç¿»è¯ï¼ä¸æ¨èï¼ï¼https://github.com/RTC-Developer/WebRTC-Documentation-in-Chinese/tree/master/resource MDN Web Docsï¼æ¨èï¼ï¼https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API
3.3.1 RTCPeerConnection
https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ RTCPeerConnectionæ¯æµè§å¨ä¹é´ç¹å¯¹ç¹è¿æ¥çæ ¸å¿APIï¼ç¨äºå¤ç对çä½ä¹é´æµæ°æ®ç稳å®åææéä¿¡ï¼
const pc = new RTCPeerConnection(serverConfig);
serverConfigå å«iceServersåæ°ï¼å®å å«æå ³STUNåTURNæå¡å¨ç¸å ³ä¿¡æ¯æ°ç»ï¼å¨æ¥æ¾ICEçæ¶ååé使ç¨ãå¯ä»¥å¨ç½ä¸æ¾ä¸äºå ¬å ±çSTUNæå¡å¨ï¼ä¹å¯ä»¥ä½¿ç¨åé¢ç« èæ们èªå·±éè¿coturnæ建çSTUNæå¡å¨ã
const serverConfig = {
iceServers: [
{
urls: 'stun:stun.xten.com'
},
{
urls: 'stun:ä½ çæå¡å¨ip:3478', // è§2.1æå¡å¨æ建
username: 'ç¨æ·å',
credential: 'å¯ç '
}
]
}
(1) onicecandidate = eventHandler ä½ç¨ï¼çå¬RTCPeerConnectionå®ä¾ä¸åçicecandidateäºä»¶ï¼è¯¥å½æ°ä¼è¿åICEååç»æï¼æ们éè¦å°ç»æåéç»ä¿¡ä»¤æå¡å¨ï¼äº¤ç±ä¿¡ä»¤æå¡å¨è½¬åç»å¯¹æ¹ã
pc.onicecandidate = event => {
if (event.candidate) {
sendCandidateToRemotePeer(event.candidate);
} else {
/* there are no more candidates coming during this negotiation */
}
};
**(2) ontrack = eventHandler ** ä½ç¨ï¼çå¬RTCPeerConnectionå®ä¾ä¸æ¥æ¶å°è¿ç¨çæ°æ®æµï¼è¯¥å½æ°å¯è·åå°å¯¹ç«¯çåªä½æµã
pc.ontrack = event => {
document.getElementById("received_video").srcObject = event.streams[0];
};
(3) addTrack(track, streamâ¦) ä½ç¨ï¼è®¾ç½®è½¨éï¼è¯¥è½¨éå°ä¼å¨è¿ååä¼ è¾å°å¯¹ç«¯ã
async openCall(pc) {
const gumStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
for (const track of gumStream.getTracks()) {
pc.addTrack(track);
}
}
MDNä¸å»ºè®®ä½¿ç¨addStream() (3) removeTrack(sender) ä½ç¨ï¼å é¤è½¨éï¼å é¤å·²æ·»å ç轨éï¼ç¨äºææçæ¶å
var pc, sender;
navigator.getUserMedia({video: true}, function(stream) {
pc = new RTCPeerConnection();
var track = stream.getVideoTracks()[0];
sender = pc.addTrack(track, stream);
});
document.getElementById("closeButton").addEventListener("click", function(event) {
pc.removeTrack(sender);
pc.close();
}, false);
ä¸å»ºè®®çï¼onremovestream
(5) setLocalDescription()/setRemoteDescription()
setLocalDescription(sessionDescription)ï¼
设置æ¬å°offerï¼å°èªå·±çæè¿°ä¿¡æ¯å å
¥å°PeerConnectionä¸ï¼åæ°ç±»åï¼RTCSessionDescriptionï¼è§ä¸ä¸å°è 3.2.2 RTCSessionDescriptionï¼
setRemoteDescription(sessionDescription)ï¼
设置è¿ç«¯çanswerï¼å°å¯¹æ¹çæè¿°ä¿¡æ¯å å
¥å°PeerConnectionä¸ï¼åæ°ç±»åï¼RTCSessionDescriptionï¼è§ä¸ä¸å°è 3.2.2 RTCSessionDescriptionï¼
éä¿è¯´ï¼Alice为äºåBob建ç«åä½å
³ç³»(è¿æ¥)ï¼Aliceæææ好äºä¸ä»½ååï¼å¹¶ç¾åäºï¼æè¿éå
ä¿çæ«æçï¼çº¸è´¨ååéè¿å¿«é(SDP)ç»ä½ äºï¼ä½ éè¿å¿«é(SDP)æ¿å°åååï¼å
ç¾å确认ï¼è¿æ¶å纸质ååä¸é½ææ们åæ¹çç¾åäºï¼ä½æè¿è¾¹è¿æ²¡æä½ çç¾åãä½ ä¿åä¸ä¸æ«æçï¼ç¶åéè¿å¿«éæ纸质åç»æååæ¥ï¼ææ¿å°å¿«éåï¼æä¹ä¿åä¸ä¸æ«æçãè¿æ ·ï¼ä½ æåæ¾é½æåæ¹ç¾åçæ«æçååãååå¼å§çæï¼
(6) createOffer()/createAnswer()
createOffer([options])ï¼
å建ä¸ä¸ªofferï¼è¡¨ç¤ºææ¹ç请æ±ãé常å¨WebRTCéä¿¡ä¸ï¼æ们ä¼è¯·æ±å¯¹æ¹æ¥æ¶æ们çé³é¢åè§é¢æ°æ®ã
const offerOptions = {
offerToReceiveAudio: true, // 请æ±æ¥æ¶é³é¢
offerToReceiveVideo: true, // 请æ±æ¥æ¶è§é¢
},
pc.createOffer(offerOptions)
.then(offer => onCreateOfferSuccess(offer.sdp))
.catch(error => onCreateOfferError());
createAnswer([options])ï¼ å建ä¸ä¸ªanswerï¼ååºå¯¹æ¹offerãanswerä¹æ¯æofferä½ç¨çï¼å¨ååºçæ¶åï¼è¡¨ç¤ºçåºä½ ï¼å¹¶åä½ è¯·æ±ã æ个æ¯æ¹ï¼AåB表ç½ï¼è¯·æ±BåAç女æåãå¦æBæ¥åäºï¼è¡¨ç¤ºBæäºA女æåãåæ¶ï¼è¿ä¹æå¦å¤ä¸å±å«ä¹ï¼è¡¨ç¤ºBæ请æ±ï¼è¯·Aåæçç·æåã
const answerOptions = {
offerToReceiveAudio: true, // 请æ±æ¥æ¶é³é¢
offerToReceiveVideo: true, // 请æ±æ¥æ¶è§é¢
},
pc.createAnswer(answerOptions)
.then(answer => onCreateAnswerSuccess(answer.sdp))
.catch(error => onCreateAnswerError());
3.3.2 RTCSessionDescription
ç¨äºçæOffer/Answerååè¿ç¨ä¸SDPåè®®çç¸å ³æè¿°ã
new RTCSessionDescription(rtcDescription)
rtcDescriptionåªæ两个å±æ§ï¼typeï¼sdp
- typeåªè½è®¾ç½®ï¼âanswerâï¼âofferâï¼âpranswerâï¼ârollbackâï¼
- sdpæ¯æ åçSDPä¼è¯æè¿°ï¼å¯ç±createOffer/createAnswerçæï¼
3.3.3 RTCIceCandidate
https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate https://blog.51cto.com/zhangjunhd/25481 ç¨äºå»ºç«ICEè¿æ¥ãé常æ们ä¸ä¼æå¨å»å®ä¾åä¸ä¸ªRTCIceCandidate对象ï¼å¨åé¢3.3.1 RTCPeerConnectionä¸çonicecandidateäºä»¶åè°å°±æ¯ä¸ä¸ªRTCIceCandidate对象ï¼æ们åªéè¦äºè§£å ¶ä¸å 个å±æ§å³å¯ã
- candidate: ç¨äºè¿æ¥æ§æ£æµç对象
- sdpMid: candidateçåªä½æµçè¯å«æ ç¾
- sdpMLineIndex: candidateçåªä½æµçç¸å ³èçSDPæè¿°ç´¢å¼å·
- address: æ¬æºIPå°å
- relatedAddress: ä¸ç»§IP
- port: æ¬æºç«¯å£
- relatedPort: ä¸ç»§ç«¯å£
- component: åéåè®®ï¼åªæ两ç§æ åµï¼RTP(Real-Time Transport Protocol)ï¼ RTCP(Real-Time Transport Control Protocol)
- foundation: æ¥èªäºSTUNæå¡å¨çå¯ä¸æ è¯ç¬¦
- priority: ä¼å 级
- tcpType: å¦æ使ç¨çTCPåè®®ï¼è¿ä¸ªå±æ§å表示TCPçç¶æ
- type: RTCIceCandidateTypeç±»å
- usernameFragment: ice-ufragç段ï¼ç¨äºçæice-pwdï¼åä¸ICEè¿ç¨çè¿æ¥é½å°ä½¿ç¨çæ¯åä¸ä¸ªç段ã
åãå®æå¼å
åé¢åºæ¬ä¸å·²ç»å举äºå¤§é¨ååºç¡ç¥è¯ï¼ç°å¨å¼å§è¿ç¨èµ·æ¥ã æ¬ç« å®æå¼åï¼æ¯å¼åä¸ä¸ª webå®æ¶é³è§é¢è天室 ï¼è¾å ¥ç¸åæ¿é´å·ï¼å³å¯å å ¥è天室ï¼è¿è¡è§é¢è天ã 主è¦æ两个项ç®ï¼å端çé¢(页é¢+WebRTC+socket.io)ï¼å端信令æå¡å¨æ§å¶è½¬å(Express+socket.io)ã æ´ä¸ªé¡¹ç®å®æ´ä»£ç ï¼WebRTC-demo
4.1 ç¯å¢åå¤
- anywhere: npm i -g anywhere
4.2 信令æå¡å¨
å 为信令æå¡å¨ä»£ç ç»ææ¯è¾ç®åï¼å±ä»¬å å¼å信令æå¡å¨ãè§å¯1.5 ç»å ¸WebRTCè¿æ¥å»ºç«æµç¨ï¼ä¸é¾åç°ï¼ä¿¡ä»¤æå¡å¨ä¸»è¦éè¦å®ç°ï¼è½¬åofferã转åanswerã转åcandidateçä¸å¤§æ ¸å¿åè½ãæ¤å¤ï¼æ们å¼åè天室ï¼è¿éè¦ï¼å建è天室ãéåºè天室çåè½ã â
4.2.1 æ建项ç®
ï¼1ï¼å建ä¸ä¸ªæ件夹signal-serverï¼å¨ç®å½ä¸å建两个æä»¶ï¼ package.json
{
"name": "signal-server",
"version": "1.0.0",
"author": "Patrick Jun",
"description": "A webRTC signal server",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.17.1",
"express-session": "^1.17.1",
"socket.io": "^2.3.0"
}
}
app.js
const https = require('https'); // httpsæå¡
const fs = require('fs'); // fs
const socketIO = require('socket.io');
//读åå¯é¥åç¾åè¯ä¹¦
const options = {
key: fs.readFileSync('keys/server_key.pem'),
cert: fs.readFileSync('keys/server_crt.pem'),
}
// æ建httpsæå¡å¨
const apps = https.createServer(options);
const SSL_PORT = 8443;
apps.listen(SSL_PORT);
// æ建signal server
const io = socketIO.listen(apps);
// socketçå¬è¿æ¥
io.sockets.on('connection', (socket) => {
console.log('è¿æ¥å»ºç«');
// ä¹åææä¸å¡å¤çï¼åå¨è¿éé¢
});
ï¼2ï¼å建è¯ä¹¦ å¨é¡¹ç®æ件夹ä¸ï¼å建ä¸ä¸ªæ件夹keysï¼ç¶åå¼å§çæèªç¾åè¯ä¹¦ï¼ linuxç¯å¢ä¸ï¼
openssl req -x509 -newkey rsa:2048 -keyout ./keys/server_key.pem -out ./keys/server_crt.pem -days 99999 -nodes
windowsä¸ï¼åè https://letsencrypt.org/zh-cn/docs/certificates-for-localhost/ ä¿®æ¹app.jsï¼å°ç§é¥åç¾åè¯ä¹¦çè·¯å¾æ¹ä¸ºä½ çµèä¸çç»å¯¹è·¯å¾ï¼ä¾å¦ï¼
//读åå¯é¥åç¾åè¯ä¹¦
const options = {
key: fs.readFileSync('D://signal-server/keys/server_key.pem'),
cert: fs.readFileSync('D://signal-server/keys/server_crt.pem'),
}
ï¼3ï¼è¿è¡ å¨é¡¹ç®æ ¹ç®å½ä¸ï¼å®è£ ä¾èµï¼
npm i
ç¶åï¼å¯å¨ï¼
node app.js
æå¼æµè§å¨ï¼è®¿é®ï¼https://localhost:8443 访é®æ¶ï¼æµè§å¨ä¼æ示ä¸å®å ¨ç访é®ï¼è¿ä¸ªæ¶åï¼ç´æ¥æ²é®çï¼thisisunsafe å³å¯ç»§ç»è®¿é®ãå½çå°æµè§å¨å°åæ 继ç»ä¸ç´å¨è¯·æ±ä¸ï¼é£ä¹å°±è¡¨ç¤ºé¡¹ç®æåè¿è¡ã
4.2.2 æ¿é´åè½
æ¿é´åè½ä¸»è¦å æ¬ï¼å建/å å ¥æ¿é´ãéåºæ¿é´ã ä¸å¡å¤çï¼é½æ¾å¨è¿æ¥æååçåè°å½æ°éã ï¼1ï¼å建æ¿é´
// socketçå¬è¿æ¥
io.sockets.on('connection', (socket) => {
console.log('è¿æ¥å»ºç«');
// å建/å å
¥æ¿é´
socket.on('createAndJoinRoom', (message) => {
const { room } = message;
console.log('Received createAndJoinRoomï¼' + room);
// å¤æroomæ¯å¦åå¨
const clientsInRoom = io.sockets.adapter.rooms[room];
const numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
console.log('Room ' + room + ' now has ' + numClients + ' client(s)');
if (numClients === 0) {
// room ä¸åå¨ ä¸åå¨åå建ï¼socket.joinï¼
// å å
¥å¹¶å建æ¿é´
socket.join(room);
console.log('Client ID ' + socket.id + ' created room ' + room);
// åéæ¶æ¯è³å®¢æ·ç«¯ [id,room,peers]
const data = {
id: socket.id, //socket id
room: room, // æ¿é´å·
peers: [], // å
¶ä»è¿æ¥
};
socket.emit('created', data);
} else {
// room åå¨
// å å
¥æ¿é´ä¸
socket.join(room);
console.log('Client ID ' + socket.id + ' joined room ' + room);
// joinedåç¥æ¿é´éçå
¶ä»å®¢æ·ç«¯ [id,room]
io.sockets.in(room).emit('joined', {
id: socket.id, //socket id
room: room, // æ¿é´å·
});
// åéæ¶æ¯è³å®¢æ·ç«¯ [id,room,peers]
const data = {
id: socket.id, //socket id
room: room, // æ¿é´å·
peers: [], // å
¶ä»è¿æ¥
};
// æ¥è¯¢å
¶ä»è¿æ¥
const otherSocketIds = Object.keys(clientsInRoom.sockets);
for (let i = 0; i < otherSocketIds.length; i++) {
if (otherSocketIds[i] !== socket.id) {
data.peers.push({
id: otherSocketIds[i],
});
}
}
socket.emit('created', data);
}
});
});
ï¼2ï¼éåºæ¿é´ å¨å å ¥æ¿é´çå¬åé¢ï¼ç»§ç»æ·»å ï¼
// éåºæ¿é´ï¼è½¬åexitæ¶æ¯è³roomå
¶ä»å®¢æ·ç«¯ [from,room]
socket.on('exit', (message) => {
console.log('Received exit: ' + message.from + ' message: ' + JSON.stringify(message));
const { room } = message;
// å
³é该è¿æ¥
socket.leave(room);
// 转åexitæ¶æ¯è³roomå
¶ä»å®¢æ·ç«¯
const clientsInRoom = io.sockets.adapter.rooms[room];
if (clientsInRoom) {
const otherSocketIds = Object.keys(clientsInRoom.sockets);
for (let i = 0; i < otherSocketIds.length; i++) {
const otherSocket = io.sockets.connected[otherSocketIds[i]];
otherSocket.emit('exit', message);
}
}
});
è¿æä¸ç§æ åµï¼å½socketè¿æ¥å¼å¸¸æå¼æ¶ï¼ä¹éè¦éåºæ¿é´ï¼
// socketå
³é
socket.on('disconnect', function(reason){
const socketId = socket.id;
console.log('disconnect: ' + socketId + ' reason:' + reason );
const message = {
from: socketId,
room: '',
};
socket.broadcast.emit('exit', message);
});
4.2.3 转ååè½
转ååè½æï¼è½¬åofferã转åanswerã转åcandidate ï¼1ï¼è½¬åoffer
// 转åofferæ¶æ¯è³roomå
¶ä»å®¢æ·ç«¯ [from,to,room,sdp]
socket.on('offer', (message) => {
// const room = Object.keys(socket.rooms)[1];
console.log('æ¶å°offer: from ' + message.from + ' room:' + message.room + ' to ' + message.to);
// æ ¹æ®idæ¾å°å¯¹åºè¿æ¥
const otherClient = io.sockets.connected[message.to];
if (!otherClient) {
return;
}
// 转åofferæ¶æ¯è³å
¶ä»å®¢æ·ç«¯
otherClient.emit('offer', message);
});
ï¼2ï¼è½¬åanswer
// 转åansweræ¶æ¯è³roomå
¶ä»å®¢æ·ç«¯ [from,to,room,sdp]
socket.on('answer', (message) => {
// const room = Object.keys(socket.rooms)[1];
console.log('æ¶å°answer: from ' + message.from + ' room:' + message.room + ' to ' + message.to);
// æ ¹æ®idæ¾å°å¯¹åºè¿æ¥
const otherClient = io.sockets.connected[message.to];
if (!otherClient) {
return;
}
// 转åansweræ¶æ¯è³å
¶ä»å®¢æ·ç«¯
otherClient.emit('answer', message);
});
ï¼3ï¼è½¬åcandidate
// 转åcandidateæ¶æ¯è³roomå
¶ä»å®¢æ·ç«¯ [from,to,room,candidate[sdpMid,sdpMLineIndex,sdp]]
socket.on('candidate', (message) => {
console.log('æ¶å°candidate: from ' + message.from + ' room:' + room + ' to ' + message.to);
// æ ¹æ®idæ¾å°å¯¹åºè¿æ¥
const otherClient = io.sockets.connected[message.to];
if (!otherClient) {
return;
}
// 转åcandidateæ¶æ¯è³å
¶ä»å®¢æ·ç«¯
otherClient.emit('candidate', message);
});
4.3 å端
å端å¯ä»¥å为ä¸å¤§åè½ï¼é³è§é¢è®¾å¤æ§å¶åé³è§é¢æ¾ç¤ºæ§å¶ãOffer/Answeræ²éãICEè¿æ¥ã
4.3.1 æ建项ç®
ï¼1ï¼å建ä¸ä¸ªæ件夹webrtc-clientï¼å¨ç®å½ä¸å建ä¸ä¸ªindex.htmlæ件ï¼å建ä¸ä¸ªç®å½`js
|- webrtc-client/
|- js/
|- index.html
ï¼2ï¼å¨jsç®å½ä¸å建å 个æ件ï¼å¹¶å¨ä»ç½ä¸ä¸è½½socket.io.jsåjquery.min.jsæ件
|- webrtc-client/
|- js/
|- config.js
|- sdk.js
|- main.js
|- socket.io.js // èªè¡ä»ç½ä¸ä¸è½½
|- jquery.min.js // èªè¡ä»ç½ä¸ä¸è½½
|- index.html
ï¼3ï¼ä»£ç index.html
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
<title>WebRtcè§é¢éè¯demo</title>
<style>
video {
background-color: bisque;
}
</style>
<script src="js/jquery.min.js"></script>
<script src="js/socket.io.js"></script>
<script src="js/config.js"></script>
<script src="js/sdk.js"></script>
</head>
<body>
<input type="text" id="room" value="1" placeholder="è¾å
¥æ¿é´å·" />
<button id="connect">è¿æ¥</button>
<button id="logout">ææ</button>
<br/>
<h3>æ¬å°è§é¢</h3>
<video id="localVideo" style='width:200px;height:200px;' autoplay muted></video>
<br/>
<h3>è¿ç¨è§é¢</h3>
<div id='remoteDiv'></div>
<script src="js/main.js"></script>
</body>
</html>
config.js
// WebRTCé
ç½®æ件
const THSConfig = {
// 信令æå¡å¨
signalServer: 'wss://localhost:8443',
// Offer/Answer模å请æ±é
ç½®
offerOptions: {
offerToReceiveAudio: true, // 请æ±æ¥æ¶é³é¢
offerToReceiveVideo: true, // 请æ±æ¥æ¶è§é¢
},
// ICEæå¡å¨
iceServers: {
iceServers: [
{ urls: 'stun:stun.xten.com' }, // Safriå
¼å®¹ï¼url -> urls
]
}
}
4.3.2 å ¼å®¹é¢å¤ç
å 为é¨åweb APIå¨ä¸åæµè§å¨æä¸åçå称æè å±æ§ï¼å æ¤éè¦å¤çå ¼å®¹ï¼ä»¥ä¸æ¯å ¼å®¹ä»£ç ï¼é¢å å®ä¹ä¸ä¸ã ç¼è¾sdk.jsï¼
// å
¼å®¹å¤ç
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
const SessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
const GET_USER_MEDIA = navigator.getUserMedia ? "getUserMedia" :
navigator.mozGetUserMedia ? "mozGetUserMedia" :
navigator.webkitGetUserMedia ? "webkitGetUserMedia" : "getUserMedia";
const v = document.createElement("video");
const SRC_OBJECT = 'srcObject' in v ? "srcObject" :
'mozSrcObject' in v ? "mozSrcObject" :
'webkitSrcObject' in v ? "webkitSrcObject" : "srcObject";
4.3.3 é³è§é¢æ§å¶
é³è§é¢æ§å¶ä¸»è¦åæå¼å ³éæå头ï¼è§é¢æµç»å®å°videoæ ç¾ï¼å ¶å®è¿ä¸èåé¢3.2 é³è§é¢ç¸å ³APIå·²ç»å¦ä¹ è¿äºï¼è¿éç´æ¥ç»åºä»£ç ã æ¥çç¼è¾sdk.js
/**
* å¯å¨æå头
*/
const openCamera = () => {
return navigator.mediaDevices[GET_USER_MEDIA]({
audio: true,
video: true
});
}
/**
* å
³éæå头
* @param {dom} video videoèç¹
*/
const closeCamera = video => {
video[SRC_OBJECT].getTracks()[0].stop(); // audio
video[SRC_OBJECT].getTracks()[1].stop(); // video
}
/**
* è§é¢æµç»å®å°videoèç¹å±ç¤º
* @param {dom} video videoèç¹
* @param {obj} stream è§é¢æµ
*/
const pushStreamToVideo = (video, stream) => {
console.log('è§é¢æµç»å®å°videoèç¹å±ç¤º', video, stream)
video[SRC_OBJECT] = stream;
}
ç¼è¾main.jsï¼
/**
* domè·å
*/
const btnConnect = $('#connect'); // è¿æ¥dom
const btnLogout = $('#logout'); // æædom
const domLocalVideo = $('#localVideo'); // æ¬å°è§é¢dom
/**
* è¿æ¥
*/
btnConnect.click(() => {
//å¯å¨æå头
if (localStream == null) {
openCamera().then(stream => {
pushStreamToVideo(domLocalVideo[0], stream);
}).catch(e => alert(`getUserMedia() error: ${e.name}`));
}
});
/**
* ææ
*/
btnLogout.click(() => {
closeCamera(domLocalVideo[0]);
})
æµè¯ä¸ä¸æå头åè½ï¼å 为å¼å¯æå头éè¦ä½¿ç¨httpsæå¡ï¼å æ¤å¨å端项ç®æ ¹ç®å½æå¼æ§å¶å°å½ä»¤ï¼è¿è¡ï¼
anywhere 5000
ç¶åæµè§å¨æå¼å½ä»¤è¡æ示éç端å£å·ä¸º5001çé£ä¸ªhttpsåè®®çå°åï¼ä¾å¦ï¼https://192.168.1.4:5001/ è¿æ¶åï¼å¯è½ä¹ä¼æ示æ¨çè¿æ¥ä¸æ¯ç§å¯è¿æ¥ï¼ç¹å»é«çº§ï¼æä¸é¢ç»§ç»åå¾ã ç¹å»è¿æ¥æé®ï¼å 许访é®æå头ï¼çæå头æ¯å¦æ£å¸¸æå¼ï¼é¡µé¢è§é¢æ¯å¦åºç°ï¼ç¶åç¹å»æå¼ï¼çæå头æ¯å¦å ³éãç»é¢æ¯å¦æ¶å¤±ã
4.3.4 Offer/Answer模å
ä»è¿èå¼å§ï¼å°±æ£å¼æ¶åå°WebRTCç¸å ³APIäºï¼ä¸é¢å åå ä¸ªå ¨å±åéï¼ç¨äºä¿åä¸äºå ¬ç¨æ°æ®ï¼ ç¼è¾sdk.js
// socketè¿æ¥
const socket = io(THSConfig.signalServer);
// æ¬å°socket id
let socketId;
// æ¿é´ id
let roomId;
// 对RTCPeerConnectionè¿æ¥è¿è¡ç¼å
let rtcPeerConnects = {};
// æ¬å°stream
let localStream = null;
ï¼1ï¼å å ¥æ¿é´ å¨å¼å§Offer/Answer模ååï¼æä»¬å¿ é¡»å¾è³å°æ两个客æ·ç«¯æè¡ãå æ¤ï¼æ们å åä¸ä¸ï¼æä¹æ§å¶æ¿é´ã å±ä»¬å æ´çä¸ä¸æè·¯ï¼æ们å 让ç²å建ä¸ä¸ªæ¿é´ï¼ç¶åï¼è¿ä¸ªæ¿é´éåªæç²ä¸ä¸ªäººï¼æ æ³è¿è¡Offer/Answerãè¿æ¶åä¹å¨è¿å ¥æ¿é´æ¶ï¼å¯ä»¥è·åä¸ä¸æ¿é´ç人æ°ï¼å¦ææ¿é´æ人ï¼é£ä¹ä¹å°±ç»æ¿é´éçæ¯ä¸ä¸ªäººåéOffer请æ±ãæ¿é´éçç²çå¬å°äºåè¿æ¥ä¹çOfferåï¼ç»ä¹åå¤Answerãè¿æ ·å°±å»ºç«èµ·äºOffer/Answer模åã ç¼è¾sdk.js
/**
* è¿æ¥ï¼ç»signal server åéå建æè
å å
¥æ¿é´çæ¶æ¯ï¼
* @param {string} roomid æ¿é´å·
*/
const connect = roomid => {
console.log('å建æè
å å
¥æ¿é´', roomid)
socket.emit('createAndJoinRoom', {
room: roomid
});
}
/**
* çå¬signal serverå建æ¿é´æè
å å
¥æ¿é´æåçæ¶æ¯ï¼signal serverä¼å¤ææ¿é´éæ¯å¦æ人
*/
socket.on('created', async data => {
// data: [id,room,peers]
console.log('created: ', data);
// ä¿åsignal serverç»æåé
çsocketId
socketId = data.id;
// ä¿åå建æ¿é´æè
å å
¥æ¿é´çroom id
roomId = data.room;
// å¦ædata.peers = []ï¼è¯´ææ¿é´é没æ人ï¼æ¯å建æ¿é´ï¼ä»¥ä¸æ¥éª¤åä¸ä¼æ§è¡
// å¦ædata.peers != []ï¼è¯´ææ¿é´éæ人ï¼æ¯å å
¥æ¿é´ï¼ç»è¿åçæ¯ä¸ä¸ªpeersï¼å建WebRtcPeerConnection并åéofferæ¶æ¯
for (let i = 0; i < data.peers.length; i++) {
let otherSocketId = data.peers[i].id;
// å建WebRtcPeerConnection // 注æï¼è¿ä¸ªå½æ°æ¯ä¸ä¸ä¸ªæ¥éª¤åçã
let pc = getWebRTCConnect(otherSocketId);
// å建offer
const offer = await pc.createOffer(THSConfig.offerOptions);
// åéoffer
onCreateOfferSuccess(pc, otherSocketId, offer);
}
})
/**
* offerå建æååè°
* @param {*} pc
* @param {*} otherSocketId
* @param {*} offer
*/
function onCreateOfferSuccess(pc, otherSocketId, offer) {
console.log('createOffer: success ' + ' id:' + otherSocketId + ' offer: ', offer);
// 设置æ¬å°setLocalDescription å°èªå·±çæè¿°ä¿¡æ¯å å
¥å°PeerConnectionä¸
pc.setLocalDescription(offer);
// æ建offer
const message = {
from: socketId,
to: otherSocketId,
room: roomId,
sdp: offer.sdp
};
console.log('åéofferæ¶æ¯', message)
// åéofferæ¶æ¯
socket.emit('offer', message);
}
åé¢ï¼å¯ä»¥ç®æ¯æOfferååºå»äºï¼å¯ä»¥å顾4.2.3 转ååè½ï¼ä¿¡ä»¤æå¡å¨æ¶å°Offeråï¼ä¼å°å ¶è½¬åç»æ¿é´éçæ¯ä¸ä¸ªç¨æ·ï¼ç¶åï¼æ们就éè¦åä¸ä¸ªçå¬ï¼å½ä¿¡ä»¤æå¡å¨è½¬åè¿æ¥Offeråï¼æ们åºè¯¥è¿è¡Answerï¼ ç»§ç»ç¼è¾sdk.js
/**
* çå¬signal server转åè¿æ¥çofferæ¶æ¯ï¼å°å¯¹æ¹çæè¿°ä¿¡æ¯å å
¥å°PeerConnectionä¸ï¼ç¶åæ建answer
*/
socket.on('offer', data => {
// data: [from,to,room,sdp]
console.log('æ¶å°offer: ', data);
// è·åRTCPeerConnection
const pc = getWebRTCConnect(data.from);
console.log('getWebRTCConnect: ', pc);
// æ建RTCSessionDescriptionåæ°
const rtcDescription = {
type: 'offer',
sdp: data.sdp
};
console.log('offer设置è¿ç«¯setRemoteDescription')
// 设置è¿ç«¯setRemoteDescription
pc.setRemoteDescription(new SessionDescription(rtcDescription));
console.log('setRemoteDescription: ', rtcDescription);
// createAnswer
pc.createAnswer(THSConfig.offerOptions)
.then(offer => onCreateAnswerSuccess(pc, data.from, offer))
.catch(error => onCreateAnswerError(error));
})
/**
* answerå建æååè°
* @param {*} pc
* @param {*} otherSocketId
* @param {*} offer
*/
function onCreateAnswerSuccess(pc, otherSocketId, offer) {
console.log('createAnswer: success ' + ' id:' + otherSocketId + ' offer: ', offer);
// 设置æ¬å°setLocalDescriptionï¼å°å¯¹æ¹çæè¿°ä¿¡æ¯å å
¥å°PeerConnectionä¸
pc.setLocalDescription(offer);
// æ建answerä¿¡æ¯
const message = {
from: socketId,
to: otherSocketId,
room: roomId,
sdp: offer.sdp
};
console.log('åéansweræ¶æ¯', message)
// åéansweræ¶æ¯
socket.emit('answer', message);
}
/**
* answerå建失败åè°
* @param {*} error
*/
function onCreateAnswerError(error) {
console.log('createAnswer: fail error ' + error);
}
ç°å¨ï¼æ们æAnswerä¿¡æ¯åå¤åºå»äºï¼éè¿ä¿¡ä»¤æå¡å¨ä¼è½¬åæå®çç¨æ·ï¼åååæ¥offerçç¨æ·ï¼ï¼ç¶åæ们è¿è¦æ·»å ä¸ä¸ªçå¬Answerçä¿¡æ¯ï¼ 继ç»ç¼è¾sdk.js
/**
* çå¬signal server转åè¿æ¥çansweræ¶æ¯ï¼å°å¯¹æ¹çæè¿°ä¿¡æ¯å å
¥å°PeerConnectionä¸
*/
socket.on('answer', data => {
// data: [from,to,room,sdp]
console.log('æ¶å°answer: ', data);
// è·åRTCPeerConnection
const pc = getWebRTCConnect(data.from);
// æ建RTCSessionDescriptionåæ°
const rtcDescription = {
type: 'answer',
sdp: data.sdp
};
console.log('answer设置è¿ç«¯setRemoteDescription')
console.log('setRemoteDescription: ', rtcDescription);
//设置è¿ç«¯setRemoteDescription
pc.setRemoteDescription(new SessionDescription(rtcDescription));
})
ï¼2ï¼è·åRTCPeerConnectionã移é¤RTCPeerConnection æ¥ä¸ä¸æ¥éª¤ï¼å ¶ä¸æ¶åå°ä¸ä¸ªgetWebRTCConnectçæ¹æ³ï¼è¿èå°±åå¦ä½å®ç°å®ï¼ä»¥åæ¬å°å¦ä½ç®¡çä¸ä»äººçè¿æ¥ã 继ç»ç¼è¾sdk.js
// 对RTCPeerConnectionè¿æ¥è¿è¡ç¼å
let rtcPeerConnects = {}; // è¿æ¯å¼å§å设置çå
¨å±åé
/**
* è·åRTCPeerConnection
* @param {string} otherSocketId 对æ¹socketId
*/
function getWebRTCConnect(otherSocketId) {
if (!otherSocketId) return;
// æ¥è¯¢å
¨å±ä¸æ¯å¦å·²ç»ä¿åäºè¿æ¥
let pc = rtcPeerConnects[otherSocketId];
console.log('建ç«è¿æ¥ï¼', otherSocketId, pc)
if (typeof (pc) === 'undefined') { // å¦æ没æä¿åï¼å°±å建RTCPeerConnection
// æ建RTCPeerConnection
pc = new PeerConnection(THSConfig.iceServers); // PeerConnectionæ¯4.3.2å®ä¹çå
¼å®¹å¤ç
// 设置è·åicecandidateä¿¡æ¯åè° æ¤å¤å¯ææ¶å¿½ç¥ï¼å°å¨4.3.5讲解
pc.onicecandidate = e => onIceCandidate(pc, otherSocketId, e);
// 设置è·å对端streamæ°æ®åè°-trackæ¹å¼ æ¤å¤å¯ææ¶å¿½ç¥ï¼å°å¨4.3.5讲解
pc.ontrack = e => {
console.log('ææ¥å°æ°æ®æµäºï¼ï¼', pc, otherSocketId, e)
onTrack(pc, otherSocketId, e);
}
// 设置è·å对端streamæ°æ®åè° æ¤å¤å¯ææ¶å¿½ç¥ï¼å°å¨4.3.5讲解
pc.onremovestream = e => onRemoveStream(pc, otherSocketId, e);
// peer设置æ¬å°æµ æ¤å¤å¯ææ¶å¿½ç¥ï¼å°å¨4.3.5讲解
if (localStream != null) {
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
}
// ç¼åpeerè¿æ¥
rtcPeerConnects[otherSocketId] = pc;
}
return pc;
}
/**
* 移é¤RTCPeerConnectionè¿æ¥ç¼å
* @param {string} otherSocketId 对æ¹socketId
*/
function removeRtcConnect(otherSocketId) {
delete rtcPeerConnects[otherSocketId];
}
4.3.5 ICEè¿æ¥/æ¥æ¶é³è§é¢æµ
Offer/Answer模å让两个客æ·ç«¯äºç¸å»ºç«äºç¾è®¢äºååï¼å»ºç«äºä¿¡ä»»çåä½ä¼ä¼´å ³ç³»ï¼æ¥ä¸æ¥å¯ä»¥å¼å§è¿è¡äº¤æäºï¼ä¼ è¾é³è§é¢æ°æ®ï¼ãå¨äº¤æåï¼æ们è¦äºç¸ç¥é对æ¹çå®ç交æå°ååé¶è¡è´¦å·ï¼å 许主æºç´è¿çå°åï¼è¯¦ç»å¯å顾1.4ICEåè®®ï¼ï¼æç»ä½ åè´§ï¼ä½ ç»ææé±ã é常ï¼å¨ç¬¬ä¸æ¥ä¹çOfferååºåï¼ä¹å®¢æ·ç«¯å°±å¼å§éè¿ICEè·åèªå·±çå°åï¼éè¿ICEåè®®å¯ä»¥äºè§£ï¼è¿ä¸ªå°åå¯è½æ¯èªå·±çIPå°åï¼ï¼åªè¦çç²æ¹åæï¼è®¾ç½®è¿ç¨æè¿°å®æï¼è¿æ¶åå¯è½è¿æªåå¤Answerï¼ï¼ç²æ¹å°±å¯ä»¥æ¥æ¶å°ä¹å®¢æ·ç«¯çé³è§é¢æµäºãåçï¼ç²æ¹åå¤çAnswerä¹åï¼åªè¦ä¹å®¢æ·ç«¯åæï¼ä¹å®¢æ·ç«¯ä¹å°±è½æ¶å°ç²æ¹çé³è§é¢æµäºãè³æ¤ï¼åæ¹é½æ¶å°å¯¹æ¹çè§é¢æµäºï¼è§é¢éè¯å»ºç«ã å顾ä¸ä¸å°è 4.3.4 (2) è·åRTCPeerConnectionä¸çä¸æ®µä»£ç ï¼
// æ建RTCPeerConnection
pc = new PeerConnection(THSConfig.iceServers); // PeerConnectionæ¯4.3.2å®ä¹çå
¼å®¹å¤ç
// 1. 设置è·åicecandidateä¿¡æ¯åè°
pc.onicecandidate = e => onIceCandidate(pc, otherSocketId, e);
// 2. 设置è·å对端streamæ°æ®åè°-trackæ¹å¼ è¿æç§æ¹å¼æ¯onaddstreamï¼ä½è¿ç§æ¹å¼å·²ç»ä¸æ¨è使ç¨äºã
pc.ontrack = e => {
console.log('ææ¥å°æ°æ®æµäºï¼ï¼', pc, otherSocketId, e)
onTrack(pc, otherSocketId, e);
}
// 3. 设置è·å对端streamæ°æ®åè°
pc.onremovestream = e => onRemoveStream(pc, otherSocketId, e);
// 4. peer设置æ¬å°æµ
if (localStream != null) {
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
}
å®ä¾pcå®é å°±æ¯window.RTCPeerConnection对象ï¼è¿ä¸ªå¯¹è±¡æå 个åè°æ¹æ³å¨3.3.1èå·²ç»è®²è¿äºã ï¼1ï¼onicecandidate å½ICEååå®æåï¼æ们å°ååç»æåéè³ä¿¡ä»¤æå¡å¨ï¼è®©å ¶è½¬åç»æå®ç客æ·ç«¯ã 继ç»ç¼è¾sdk.js
/**
* RTCPeerConnection äºä»¶åè°ï¼è·åicecandidateä¿¡æ¯åè°
* @param {*} pc
* @param {*} otherSocketId
* @param {*} event
*/
function onIceCandidate(pc, otherSocketId, event) {
console.log('onIceCandidate to ' + otherSocketId + ' candidate: ', event);
if (event.candidate !== null) {
// æå»ºä¿¡æ¯ [from,to,room,candidate[sdpMid,sdpMLineIndex,sdp]]
const message = {
from: socketId,
to: otherSocketId,
room: roomId,
candidate: {
sdpMid: event.candidate.sdpMid,
sdpMLineIndex: event.candidate.sdpMLineIndex,
sdp: event.candidate.candidate
}
};
console.log('å信令æå¡å¨åécandidate', message)
// å信令æå¡å¨åécandidate
socket.emit('candidate', message);
}
}
è¿ç¨å®¢æ·ç«¯æ¶å°candidateåï¼æ·»å candidateåå³å¯æ¥æ¶å°æ¬æºçé³è§é¢æµï¼ 继ç»ç¼è¾sdk.jsï¼æ·»å çå¬äºä»¶ï¼
/**
* çå¬signal server转åè¿æ¥çcandidateæ¶æ¯
*/
socket.on('candidate', data => {
// data: [from,to,room,candidate[sdpMid,sdpMLineIndex,sdp]]
console.log('candidate: ', data);
const iceData = data.candidate;
// è·åRTCPeerConnection
const pc = getWebRTCConnect(data.from);
const rtcIceCandidate = new RTCIceCandidate({
candidate: iceData.sdp,
sdpMid: iceData.sdpMid,
sdpMLineIndex: iceData.sdpMLineIndex
});
console.log('æ·»å 对端Candidate')
// æ·»å 对端Candidate
pc.addIceCandidate(rtcIceCandidate);
})
ï¼2ï¼ontrack å½çå¬å°å¯¹æ¹ä¼ éè¿æ¥æ¶é³è§é¢æµåï¼å¨æå建ä¸ä¸ªvideoæ ç¾ï¼æ¾ç¤ºæ¥æ¶å°çé³è§é¢æµæ°æ®ã 继ç»ç¼è¾sdk.js
/**
* è·å对端streamæ°æ®åè°-ontrack模å¼
* @param {*} pc
* @param {*} otherSocketId
* @param {*} event
*/
function onTrack(pc, otherSocketId, event) {
console.log('onTrack from: ' + otherSocketId);
let otherVideoDom = $('#' + otherSocketId);
if (otherVideoDom.length === 0) { // TODO æªç¥åå ï¼ä¼ä¸¤æ¬¡onTrackï¼å°±ä¼å¯¼è´å»ºç«ä¸¤æ¬¡dom
const video = document.createElement('video');
video.id = otherSocketId;
video.autoplay = 'autoplay';
video.muted = 'muted';
video.style.width = 200;
video.style.height = 200;
video.style.marginRight = 5;
$('#remoteDiv').append(video);
}
$('#' + otherSocketId)[0][SRC_OBJECT] = event.streams[0];
}
ï¼3ï¼onremovestream çå¬å¯¹æ¹åæ¢ä¼ è¾è§é¢æµçæ¶åï¼ææ¹è¿è¡ç¸åºå¤çï¼ ç»§ç»ç¼è¾sdk.js
/**
* onRemoveStreamåè°
* @param {*} pc
* @param {*} otherSocketId
* @param {*} event
*/
function onRemoveStream(pc, otherSocketId, event) {
console.log('onRemoveStream from: ' + otherSocketId);
// peerå
³é
getWebRTCConnect(otherSocketId).close;
// å é¤peer对象
removeRtcConnect(otherSocketId)
// 移é¤video
$('#' + otherSocketId).remove();
}
ï¼4ï¼æ·»å æ¬å°é³è§é¢æµ å½ææ¹å¼å¯æå头åï¼å ¨å±åélocalStreamå°±ä¸ä¸ºnullï¼æ们éè¦å¾å¯¹æ¹å¡è¿å»æ们ççé³è§é¢æ°æ®ï¼éè¿addTrackæ¹æ³ãè¿æ ·ï¼å¨å¯¹æ¹åæï¼æ·»å ææ¹æè¿°ï¼åï¼å°±å¯ä»¥è·åå°ææ¹çé³è§é¢æ°æ®äºã
4.3.6 å®åé»è¾
åé¢çå 容åºæ¬ææ´ä¸ªé»è¾è®²å®äºï¼ä½æ¯ä½ ç°å¨å¯å¨é¡¹ç®è¿è¡ï¼æ¯ä¸æ¯è¿æ¯åªè½çå°èªå·±ï¼åé¢çæ¥éª¤æ ¹æ¬æ²¡ææ§è¡ï¼ å 为åé¢çæ们åªæå¼äºæå头ï¼è¿æ²¡æ对æ¥åç»æä½ã ç°å¨ç¼è¾main.jsï¼ä¿®æ¹ä¸ä¸ä¹åç代ç ï¼
/**
* domè·å
*/
const btnConnect = $('#connect'); // è¿æ¥dom
const btnLogout = $('#logout'); // æædom
const domLocalVideo = $('#localVideo'); // æ¬å°è§é¢dom
const domRoom = $('#room'); // è·åæ¿é´å·è¾å
¥æ¡dom
/**
* è¿æ¥
*/
btnConnect.click(() => {
const roomid = domRoom.val(); // è·åç¨æ·è¾å
¥çæ¿é´å·
if (!roomid) {
alert('æ¿é´å·ä¸è½ä¸ºç©º');
return;
};
//å¯å¨æå头
if (localStream == null) {
openCamera().then(stream => {
localStream = stream; // ä¿åæ¬å°è§é¢å°å
¨å±åé
pushStreamToVideo(domLocalVideo[0], stream);
connect(roomid); // æåæå¼æå头åï¼å¼å§å建æè
å å
¥è¾å
¥çæ¿é´å·
}).catch(e => alert(`getUserMedia() error: ${e.name}`));
}
});
/**
* ææ
*/
btnLogout.click(() => {
closeCamera(domLocalVideo[0]);
logout(roomId); // éåºæ¿é´
//移é¤è¿ç¨è§é¢
$('#remoteDiv').empty();
})
ç¼è¾sdk.jsï¼æ·»å logout()æ¹æ³ï¼çå¬ä»äººéåºæ¿é´socket.on('exit')ï¼
/**
* ææï¼éåºæ¿é´ï¼
* @param {string} roomid æ¿é´å·
*/
const logout = roomid => {
// æ建æ°æ®
const data = {
from: socketId, // å
¨å±åéï¼ææ¹çsocketId
room: roomid, // å
¨å±åéï¼å½åæ¿é´å·
};
// å信令æå¡å¨ååºéåºä¿¡å·ï¼è®©å
¶è½¬åç»æ¿é´éçå
¶ä»ç¨æ·
socket.emit('exit', data);
// æ°æ®éç½®
socketId = '';
roomId = '';
// å
³éæ¯ä¸ªpeerè¿æ¥
for (let i in rtcPeerConnects) {
let pc = rtcPeerConnects[i];
pc.close();
pc = null;
}
// éç½®RTCPeerConnectionè¿æ¥
rtcPeerConnects = {};
// 移é¤æ¬å°è§é¢
localStream = null;
}
/**
* çå¬signal server转åè¿æ¥çexitæ¶æ¯ï¼åéåºæ¿é´ç客æ·ç«¯æå¼è¿æ¥
*/
socket.on('exit', data => {
// data: [from,room]
console.log('exit: ', data);
// è·åRTCPeerConnection
const pc = rtcPeerConnects[data.from];
if (typeof (pc) == 'undefined') {
return;
} else {
// RTCPeerConnectionå
³é
getWebRTCConnect(data.from).close;
// å é¤peer对象
removeRtcConnect(data.from)
console.log($('#' + data.from))
// 移é¤video
$('#' + data.from).remove();
}
})
4.3.7 å®æ´ä»£ç
https://github.com/DOUBLE-Baller/momo/
äºãæ»ç»
ç°å¨ï¼æ们已ç»åºæ¬å ¥é¨WebRTCäºãå¯è½å3ç« çåè®®ãæå¡å¨ãAPIçå¦ä¹ 让æ们æè§å¾æ¯ç¥ï¼ç¥è¯å¾æä¹±ãææ³ï¼å¤§å®¶éè¿ç¬¬åç« çå®æå¼åï¼å°ä¹åçç¥è¯ç¹ä¸²éèµ·æ¥ï¼æ¯ä¸æ¯æä¸ç¹æè§äºãå ¶å®åä¸¤ç« å¨ç°å¨çæ¥ï¼æ¯å¯ä»¥ä¸å¿ çéå¦ä¹ çã没æè¿äºåè®®åæå¡å¨çæ¯æï¼ä¸æä»ä»¬çè¿æ¥åçï¼åé¢çå¦ä¹ åºè¯¥ä¼æ´å çæå§ã åé¢çå®æå¼åï¼æ¯ä¸ä¸ªå¾ç®åçWeb端çä¾åï¼æ²¡ææ¶åå°å®åãiOS端å¦ä½è¿è¡WebRTCéä¿¡ï¼å¦æéè¦ç»§ç»æ·±å ¥å¦ä¹ ï¼ä¸ä¸æ¥å¯ä»¥å¾ç§»å¨ç«¯WebRTCä¸å¦ä¹ ï¼æ¯å¦ç§»å¨ç«¯æå¼æå头é½åWebä¸åã å¦æææ¶æ²¡ææ·±å ¥WebRTCçå¦ä¹ è¯ï¼å¯ä»¥åºäºè¿ä¸ªå®æ项ç®è¿è¡æ¨ªåçæ©å±ãè¿ä¸ªå®æ项ç®è½ç¶çèµ·æ¥å¾ç®åï¼ä½æ¯ä½ å¯ä»¥ç»å®å åºå¾å¤åè½æ¥ï¼ä¼çèµ·æ¥å¾é«å¤§å°ï¼æ¯å¦ï¼
- å¨çº¿çµè¯ï¼å±ä»¬ç°å¨åªæ¯éè¿æ¿é´å·è¿è¡è¿æ¥ï¼æ们å¯ä»¥è®¾ç½®ä¸ä¸ªç»é页é¢ï¼å°ç¨æ·çidä½ä¸ºæ¿é´å·ï¼æ¯ä¸ªç¨æ·ç»éåç´æ¥å建ä¸ä¸ªæ¿é´ãæ们æ³è¦ç»æ个ç¨æ·æé³è§é¢çµè¯çè¯ï¼æ们å¯ä»¥å å ¥ä»çæ¿é´ï¼å¯¹æ¹ä¹è½æ£æµå°æ¿é´æ¯å¦æ人è¿æ¥ï¼è¿æ ·å¯¹æ¹å¯ä»¥åææ¶å°æ¥çµäºï¼å¯¹æ¹æ¥å¬åï¼æ们就è¿è¡WebRTCè¿æ¥ï¼å®ç°æ¨æçµè¯çåè½ã
- è§é¢ä¼è®®ï¼æ们å¼å好注åç»å½åè½ï¼å建ä¼è®®å°±ç¸å½äºå建ä¸ä¸ªæ¿é´ï¼åªä¸è¿è¿ä¸ªæ¿é´å·æ¯ç±æ们系ç»æ¥èªå¨åé ï¼å«äººç»å½åï¼éè¿è¯¥æ¿é´å·å°±å¯ä»¥å å ¥ï¼å³å¯å®ç°è§é¢ä¼è®®åè½ãå½ç¶è¿å¯ä»¥æ©å±å享å±å¹ãç½æ¿çåè½ã
æ¬æ¬¡WebRTCå ¥é¨å¦ä¹ å°æ¤ç»æäºï¼é常æè°¢æ¨èå¿å°çå®æ¬ç¯é¿æãè¥ææè¿°ä¸å¯¹çå°æ¹ï¼æ¬¢è¿æåºï¼ 对以ä¸æç« ã项ç®åè§é¢çä½è 们ï¼è¡¨ç¤ºé常æè°¢ï¼æè°¢æ¨ä»¬è¾è¦çææï¼ åèæç« ãæç®ãè§èã项ç®ãè§é¢ï¼
[WebRTCåè®®ä»ç»ï¼] https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols
[WebRTCä¸æ社åºï¼]https://webrtc.org.cn/
[RTCå¼åè 社åº:] https://rtcdeveloper.com/
[åæäºWebRTCå®æ¶éä¿¡æå¡å®è·µ:]https://segmentfault.com/a/1190000010339671
[P2Péä¿¡åç:]https://zhuanlan.zhihu.com/p/26796476
[STUNå议详ç»ä»ç»:]https://zhuanlan.zhihu.com/p/26797664
[TURNå议详ç»ä»ç»ï¼]https://zhuanlan.zhihu.com/p/26797422
[ICEå议详ç»ä»ç»]https://zhuanlan.zhihu.com/p/26857913
[WebRTC PeerConnection建ç«è¿æ¥è¿ç¨] ï¼https://aggresss.blog.csdn.net/article/details/106832965
[STUN/TURNæå¡å¨ï¼Cè¯è¨)]ï¼https://github.com/coturn/coturn
[STUNæå¡å¨ï¼nodeï¼]https://github.com/enobufs/stun
[Build Zoom Clone Video Chat Web App in Node.js Express and Socket.io Using WebRTC and PeerJS Libraryï¼]https://www.youtube.com/watch?v=MX_r3Wm_BLE
[Build Video Chat Web App From Scratch in 40 mins:] https://www.youtube.com/watch?v=KLCcCTFivhM
[coturnæå¡å¨æ建:]https://www.jianshu.com/p/915eab39476d
[coturnæå¡å¨æ建]ï¼https://meetrix.io/blog/webrtc/coturn/installation.html
[coturnæå¡å¨æ建]ï¼https://ourcodeworld.com/articles/read/1175/how-to-create-and-configure-your-own-stun-turn-server-with-coturn-in-ubuntu-18-04
[WebRtcRoomServerï¼ä¿¡ä»¤æå¡å¨nodeï¼ï¼]https://github.com/qdgx/WebRtcRoomServer
[MDN Web Docs:]https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API
[webRTC APIä¹RTCPeerConnection:] https://www.cnblogs.com/suRimn/p/11314914.html
[RTPä¸RTCPåè®®ä»ç»ï¼] https://blog.51cto.com/zhangjunhd/25481
ä¸æç(äº.åºæ¬åçï¼ï¼
ä¸æç(å.æ ¸å¿åè®®ï¼ï¼
ä¸æç(äº.webrtcæ使ç¨RTPæ©å±ï¼:
ä¸æç(å .å¢å¼ºä¼ è¾å¯é æ§ï¼ï¼
ä¸æç(ä¸.éçæ§å¶ååªä½éé ï¼ï¼
ä¸æç(å «.æ§è½çæ§ï¼ï¼
ä¸æç(ä¹.æªæ¥æ©å±ï¼ï¼
ä¸æç(å.ä¿¡å·èèï¼ï¼
ä¸æç(åä¸.WebRTC APIçèèï¼ï¼
ä¸æç(åäº.RTPå®ç°ï¼ï¼
ä¸æç(åä¸ï¼éçé®é¢ï¼ï¼
ä¸æç(åäºï¼å®å ¨èèï¼ï¼
ä¸æç(åå ï¼è´è°¢ååèèµæï¼ï¼
ä¸æç(éå½Aï¼æ¯æçRTPææå¾ï¼ï¼
ä¸æç(éå½A1ï¼ç¹å¯¹ç¹ï¼ï¼ Â
ä¸æç(éå½A2ï¼åç¹å¤æï¼ï¼
WebRTCå®æ¹æºç æ ·ä¾ï¼ä¸å«ç§»å¨ç«¯ï¼ï¼http://github.com/webrtc/samples ï¼çåå¤ç论ä¸å¦æ ä¸éæºç ï¼
WebRTCå¨çº¿æ¼ç¤ºææï¼http://webrtc.github.io/samples ï¼å¯ä»¥æ¸
æ¥ççå°æ¯ä¸ªæ¥å£æ¯ææ ·è¢«è°ç¨çï¼
-
äºãåå¦è å ¥é¨
å®æ¹æ¨èçå
¥é¨æç« ï¼http://html5rocks.com/en/tutorials/webrtc/basicsï¼ä¸ªäººæè§è®²çæç¹ç»ï¼è±æä¸å¥½ä¼°è®¡å¾é¾ç解ï¼
使ç¨WebRTCæ建å端è§é¢è天室ââå
¥é¨ç¯ï¼http://chinawebrtc.org/?p=271ï¼æ¨èè¿ç¯ä¸æçå
¥é¨ï¼è®²çå¾ç»ï¼å®çä¸ç¯åç»æç¨ä¹å¾å¼å¾ä¸çï¼
WebRTCä½ç³»ç»æï¼http://chinawebrtc.org/?p=338ï¼å¯¹æ´ä½çææ¡æ¯å¾éè¦çï¼
éè¿WebRTCå®ç°å®æ¶è§é¢éä¿¡ï¼http://chinawebrtc.org/?p=462 ï¼ä¸éçæç¨ï¼
**å®æ¹ç¼è¯æç¨**
ï¼ï¼ç论åï¼å¼å§å®è·µï¼
[js] http://www.webrtc.org/native-code/development
[android] http://www.webrtc.org/native-code/android
[iOS] http://www.webrtc.org/native-code/ios
çç大ççç¼è¯å®è·µï¼
http://chinawebrtc.org/?p=339
http://chinawebrtc.org/?p=340
http://chinawebrtc.org/?p=260
http://chinawebrtc.org/?p=292
http://chinawebrtc.org/?p=391
使ç¨Tokboxç¬é´å®ç°å¨çº¿è§é¢
ï¼https://dashboard.tokbox.com/quickstart#1ï¼éè¦æ³¨åç³è¯·ä¸ä¸ªsdkçkeyçætoken,ä¹åå°±å¾æ¹ä¾¿äºï¼å½å¤å·²ç»æè§é¢æç¨äºï¼http://www.pluralsight.com/courses/webrtc-fundamentalsï¼å¯è¯çï¼åéä¼åï¼
WebRTCå¨android端
çæç¨ï¼https://tech.appear.in/2015/05/25/Introduction-to-WebRTC-on-Android/
WebRTCå¨iOS端çæç¨
ï¼ https://tech.appear.in/2015/05/25/Getting-started-with-WebRTC-on-iOS/
Play With WebRTC
ï¼http://chinawebrtc.org/?p=530
ææææç¨ï¼
http://io2014codelabs.appspot.com/static/codelabs/webrtc-file-sharing/#1
https://bitbucket.org/webrtc/codelab
-
ä¸ãé«çº§æç¨
getUserMedia解é http://www.html5rocks.com/en/tutorials/getusermedia/intro/
信令æºå¶ç解éï¼http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/
使ç¨WebRTCæ建å端è§é¢è天室ââ信令ç¯ï¼ http://chinawebrtc.org/?p=260
使ç¨WebRTCæ建å端è§é¢è天室ââç¹å¯¹ç¹éä¿¡ç¯ï¼ http://chinawebrtc.org/?p=273
使ç¨WebRTCæ建å端è§é¢è天室ââæ°æ®ééç¯ï¼ http://chinawebrtc.org/?p=274
WebRTCé³è§é¢å¼æç 究(1)âæ´ä½æ¶æåæï¼ http://chinawebrtc.org/?p=355
WebRTCé³è§é¢å¼æç 究(2)âVoiceEngineé³é¢ç¼è§£ç å¨æ°æ®ç»æ以ååæ°è®¾ç½®ï¼ http://chinawebrtc.org/?p=356
WebRTC Native APIs[ç¿»è¯]ï¼ http://chinawebrtc.org/?p=357
WebRTCæºç åæ1ââè§é¢æ¾ç¤º: http://chinawebrtc.org/?p=360
WebRTCæºç åæ2ââå¾å缩æ¾ä¸é¢è²ç©ºé´è½¬æ¢ï¼ http://chinawebrtc.org/?p=365
WebRTCæºç åæ3ââjpegç¼è§£ç ï¼ http://chinawebrtc.org/?p=366
WebRTCæºç åæ4ââAVIæ件读åï¼ http://chinawebrtc.org/?p=371
WebRTCæºç åæ5ââVoiceEngine代ç 解æï¼ http://chinawebrtc.org/?p=380
WebRTCæºç åæ6ââé³é¢æ¨¡åç»æåæï¼ http://chinawebrtc.org/?p=379
WebRTCæºç åæ6ââAudioProcessingç使ç¨ï¼ http://chinawebrtc.org/?p=381
webrtc çå声æµæ¶(aecãaecm)ç®æ³ç®ä»ï¼ http://chinawebrtc.org/?p=382
建ç«ä¸ä¸ªWebRtcçAndroid客æ·ç«¯ï¼ http://chinawebrtc.org/?p=260
WebRtc常è§é®é¢éé¦ï¼ http://chinawebrtc.org/?p=327
-
åãæºç æ示ä¾
è¿éé¢åºè¯¥æ¯æå
¨æ详ç»çäº
ï¼https://www.webrtc-experiment.com/
è¿éé¢ä¹æä¸å°
ï¼http://simpl.info/
getUserMedia:
ASCIIç çè§é¢ï¼getUserMedia + Canvas + ASCII conversionï¼:http://idevelop.ro/ascii-camera/
åç§é
·ç«ææï¼è¿è½è¿ä¹ç©å±
ç¶ï¼getUserMedia + WebGLï¼ï¼http://webcamtoy.com
svg滤éhttps://rawgit.com/SenorBlanco/moggy/master/filterbooth.html
WebGlå®ç°äººè¸é¢å
·ï¼http://auduno.github.io/clmtrackr/examples/facedeform.html
ç¨è¸ç©å¤ªç©ºå¤§æï¼http://shinydemos.com/facekat
ä¸ä¸ªå½é³æ¾ç¤ºå£°çº¹æ³¢å¨çdemo:http://webaudiodemos.appspot.com/AudioRecorder
é³é¢Demo大éåï¼http://webaudiodemos.appspot.com/
gUM + WebGLå®ç°å½é³å®¤ï¼http://lab.aerotwist.com/webgl/audio-room
RTCDataChannel
ä¸ä¸ªç®åçä¾åï¼http://simpl.info/dc
æ件å享ï¼https://sharefest.me/
ä¸ä¸ªjsç±»åºï¼http://ozan.io/p/
å®æ¶éä¿¡çTogetherJS ç±»åºï¼https://togetherjs.com/
ç¨WebRTCå®ç°BitTorrent:https://github.com/feross/webtorrent
RTCPeerConnection
ä¸ä¸ªç®åçä¾åï¼http://simpl.info/pc
è§é¢è天示ä¾ï¼https://apprtc.appspot.com/ï¼æºç https://code.google.com/p/webrtc/source/browse/#svn%2Ftrunk%2Fsamples%2Fjs%2Fapprtc
è§é¢è天示ä¾ï¼https://appear.in/ï¼å¼åè
api:https://developer.appear.in/
https://bistri.com/
è§é¢è天示ä¾ï¼https://talky.io/,æºç ï¼https://github.com/henrikjoreteg/SimpleWebRTC
è§é¢è天示ä¾ï¼https://tawk.com/
éè¿githubè§é¢è天ï¼https://gittogether.com/
è§é¢è天示ä¾ï¼http://codassium.com/
è§å±è天示ä¾ï¼https://vline.com/
è§é¢è天示ä¾ï¼https://www.lytespark.com/
è§é¢è天示ä¾ï¼https://vidtok.com/
è§é¢è天示ä¾ï¼http://www.easyrtc.com/ï¼æºç https://github.com/priologic/easyrtc
è§é¢è天示ä¾ï¼å°åº¦çï¼ï¼https://www.miljul.in/
http://chotis2.dit.upm.es/ï¼å¯fork on GitHubï¼
https://janus.conf.meetecho.com/(å¯fork on GitHub)
goToMeetingå¨çº¿çï¼https://free.gotomeeting.com/
å©´å¿çè§å¨ï¼https://webrtchacks.com/baby-motion-detector/
çµè¯é讯ï¼http://zingaya.com/
-
äºãä¸äºapiåç±»åº
å®æ¹çPeerConnectionçapiï¼http://www.webrtc.org/blog/api-description
å®æ¹å
¶å®çä¸äºçapiï¼http://www.webrtc.org/native-code/native-apis
libjingleçææ¡£ä»ç»https://developers.google.com/talk/libjingle/developer_guide?csw=1
getUserMedia.jsï¼https://github.com/addyosmani/getUserMedia.js
adapter.jsï¼https://github.com/webrtc/adapter/blob/master/adapter.js
WebRTCçjsç±»åºéæäºä»ä¹ï¼https://webrtchacks.com/whats-in-a-webrtc-javascript-library/
Web Audio APIï¼http://webaudio.github.io/web-audio-api/
The PeerJS libraryï¼ç®åäºWebRTCä¼ è¾æ°æ®çè¿ç¨http://peerjs.com/
æå
³æµè§å¨éè¯çjsç±»åºï¼http://phono.com/
å°è£
SIPåè®®çjsç±»åºï¼å®¢æ·ç«¯ï¼https://code.google.com/p/sipml5/ï¼http://jssip.net/
é¢é¨è¯å«çjsç±»åºï¼https://github.com/auduno/clmtrackr
头é¨è½¨è¿¹è¯å«çjsç±»åºï¼https://github.com/auduno/headtrackr/ï¼demo,http://simpl.info/headtrackr/
http://rtc.io/
å¼åWebRTCçå·¥å
·å表ï¼ä¸è½æ´å
¨ï¼ï¼https://webrtchacks.com/vendor-directory/
-
å ãä¸äºä¹¦ç±
WebRTC-APIs and RTCWEB Protocols of the HTML5 Real-Time Web, Third Editionï¼http://webrtcbook.com/
Real-Time Communication with WebRTC by Salvatore Loreto & Simon Pietro Romanoï¼https://bloggeek.me/book-webrtc-salvatore-simon/
Getting Started with WebRTCï¼https://www.packtpub.com/web-development/getting-started-webrtc
-
ä¸ãæ åååè®®
WebRTCå·¥ä½å°ç»ï¼http://www.w3.org/2011/04/webrtc/
w3cè§å®çWebRTCåè®®1.0http://www.w3.org/TR/webrtc/
åªä½ææååªä½æµåè®®ï¼http://www.w3.org/TR/mediacapture-streams/
IETFåè®®http://datatracker.ietf.org/wg/rtcweb/documents/
å大æµè§å¨æ¯å¦æ¯æï¼http://iswebrtcreadyyet.com/
-
å «ãå ¶å®
å½å¤Google groupï¼https://groups.google.com/forum/?fromgroups#!forum/discuss-webrtc
å½å
china WebRTC社åºï¼http://chinawebrtc.org/
-
ä¹ãWebRTC 1.0: Real-time Communication Between Browsers åè®®ææ¡£ä¸æçæ±æ»
第ä¸ç¯æ¯æè¿°æ´ä¸ªææ¡£çç¶æåæ¦è¦ï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-1/
第äºç¯æ¯æ´ä¸ªææ¡£çä»ç»åæ¯è¯ï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-2/
第ä¸ç¯ä»åæç4. Network Stream APIå¼å§ï¼ä¸»è¦æè¿°Network APIåMediaStreamæ¥å£ï¼æ£å¼çå
容ä»ç¬¬ä¸ç¯å¼å§ï¼:http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-3/
第åç¯ä»åæç4.3 AudioMediaStreamTrackå¼å§ï¼ä¸»è¦æè¿°AudioMediaStreamTrackç±»ï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-4/
第äºç¯ä»åæç5.Peer-to-peer connectionså¼å§ï¼ä¸»è¦æè¿°RTCPeerConnectionç±»ãåæç第äºèæ¯æ´ä¸ªwebrtcåè®®çéç¹ï¼RTCPeerConnectionæ¯webrtcå®ç°çæ ¸å¿åè½ãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-5/
第å
ç¯ä»åæç5.1 RTCPeerConnectionå¼å§ï¼éç¹æè¿°RTCPeerConnectionçå±æ§åæ¹æ³ãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-6/
第ä¸ç¯ä»åæç5.1.6 RTCPeerState Enumå¼å§ï¼ä»ç¶æ¯åæç第5èç继ç»ãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-7/
第å
«ç¯ä»åæç5.1.9 RTCIceServer ç±»åå¼å§ï¼è®²è§£åICE Server交äºç¸å
³çå
容ãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-8/
第ä¹ç¯ä»6. IANA Registrationså¼å§ï¼ä¸»è¦æè¿°IANA Registrationsç¸å
³çæ å约æãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-9/
第åç¯ä»åæç7. Simple Exampleå¼å§ï¼å±ç¤ºäºä¸ä¸ªç®åçjavascriptçä¾åãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-10/
第åä¸ç¯ä»åæç9. Call Flow Browser to Browserå¼å§ï¼æè¿°æµè§å¨å°æµè§å¨çå¼å«å»ºç«çæµç¨å¾ãï¼æ¤å¤æ¯éç¹å
容ï¼ï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-11/
第åäºç¯ä»åæç10. Call Flow Browser to MCUå¼å§ï¼æè¿°æµè§å¨å°MCUå¼å«å»ºç«çæµç¨å¾ãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-12/
第åä¸ç¯ä»åæ11. Peer-to-peer Data APIå¼å§ï¼æè¿°å建ç¹å°ç¹çæ°æ®ä¼ è¾ééçAPIãï¼è¿ä¸ªå¾æç¨ï¼å¯ä»¥ç¨æ¥ä¼ è¾è¯é³åè§é¢ä¹å¤çæ°æ®ï¼æ¯å¦ç½æ¿ãå
±äº«æ¡é¢çï¼ï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-13/
第ååç¯ä»åæ11.1.1 Attributeså¼å§ï¼æ¥åä¸ç¯ï¼ç»§ç»æè¿°DataChannelçå±æ§çãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-14/
第åäºç¯ä»åæ12. Garbage collectionå¼å§ï¼åå¾æéçç¥ä»¥åäºä»¶æ±æ»ãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-15/
第åå
ç¯ä»åæ15. Security Considerationså¼å§ï¼æè¿°å®å
¨æºå¶ãä¿®æ¹æ¥å¿ãè´è°¢ãåèï¼åºæ¬ä¸è¿ä¸ç¯æ²¡æä¹ç¿»è¯ï¼å¤§é¨åå¯ä»¥ç´æ¥æ è§ãä¿®æ¹æ¥å¿å¯ä»¥æ«ä¸ç¼ï¼åèå
容å¯ä»¥æµè§ä¸ä¸ï¼ãï¼http://www.iwebrtc.com/blog/webrtc-1-0-real-time-communication-between-browsers-16/