php swoole实现webrtc支持内网外网p2p视频通话的html代码

php swoole实现webrtc支持内网外网p2p视频通话的html代码

webrt内网不需要turn及stun服务器就能实现p2p链接,但是如果是通讯设备在各种的内网中的话就需要stun及turn来进行打通或中继了,首先

1、交换sdp信息

SDP(会话描述协议)信息包含了一些关键的元数据,用于建立和维护音视频通信会话。SDP通常包括以下主要信息:

会话名称(Session Name): 会话的名称或标识符,用于标识这个会话。

会话信息(Session Information): 会话的一般信息,通常是一段描述性的文本。

媒体类型(Media Types): 描述会话中使用的媒体类型,通常包括音频和视频。媒体类型通常由"M="行指定,例如"M=audio 12345 RTP/AVP 0"表示音频。

媒体描述(Media Description): 描述媒体流的详细信息,包括媒体格式、端口号、传输协议等。媒体描述通常由"a=rtpmap"和"a=fmtp"等行指定。

媒体连接信息(Connection Information): 描述媒体的连接信息,包括IP地址和端口号。这对于NAT穿越和防火墙遍历非常重要。

会话带宽(Session Bandwidth): 描述会话的带宽要求,以确保适当的网络资源分配。

时间描述(Timing Description): 描述会话的时间信息,通常包括会话的开始时间和结束时间。

加密和安全性信息(Encryption and Security Information): 描述媒体流的加密和安全性设置,用于确保通信的安全性和隐私。

ICE(Interactive Connectivity Establishment)候选者(ICE Candidates): 描述网络候选者,用于建立P2P连接,包括IP地址和端口号。

传输层协议(Transport Layer Protocol): 描述用于传输媒体的协议,通常是RTP(实时传输协议)。

媒体方向(Media Direction): 描述媒体流的方向,包括发送方向、接收方向或双向。

其他定制属性(Custom Attributes): 可能包括其他自定义的SDP属性,用于特定的应用或需求。

通过offer与answer来交换,这时候需要用到信令服务器进行sdp的交换

sdp交换完成后就尝试p2p直连,首先尝试局域网,连不上就打洞进行互联网直连,还是连不上就直接走中继服务器中转数据。

800_auto

代码如下:

首先搭建一个信令服务器,我们采用swoole来搭建一个websocket服务器

<?php

$userlist = [];
$server = new Swoole\Websocket\Server("0.0.0.0", 9502, SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL);

$server->set([
    'ssl_cert_file' => '/data/cert/6284283_web.debug.only.bfw.wiki.pem',
    'ssl_key_file' => '/data/cert/6284283_web.debug.only.bfw.wiki.key',

]);


$server->on('open', function($server, $req) {
    $_get = $req->get;
    $_username = $_get['username'];
    global $userlist;
    $userlist[$_username] = $req->fd;
    echo "connection open: {$req->fd}{$_username}\n";
});

$server->on('message', function($server, $frame) {

    $data = json_decode($frame->data, true);


    //消息类型
    $type = $data["type"];
    $_ret = ["type" => $type];

    //to user
    $toUser = $data["toUser"];

    $fromUser = $data["fromUser"];

    $msg = isset($data["msg"])?$data["msg"]:"";
    //msg

    $sdp = isset($data["sdp"])?$data["sdp"]:"";
    //sdp
    $iceCandidate = isset($data["iceCandidate"])?$data["iceCandidate"]:"";
    //ice




    //对方挂断
    if ("hangup" == $type) {
        $_ret['fromUser'] = $fromUser;
        $_ret['msg'] = "对方挂断";

    }

    //视频通话请求
    if ("call_start" == $type) {

        $_ret['fromUser'] = $fromUser;
        $_ret['msg'] = "1";

    }

    //视频通话请求回应
    if ("call_back" == $type) {
        $_ret['fromUser'] = $fromUser;
        $_ret['msg'] = $msg;

    }

    //offer
    if ("offer" == $type) {

        $_ret['fromUser'] = $toUser;
        $_ret['sdp'] = $sdp;

    }

    //answer
    if ("answer" == $type) {
        $_ret['fromUser'] = $toUser;
        $_ret['sdp'] = $sdp;

    }

    //ice
    if ("_ice" == $type) {
        $_ret['fromUser'] = $toUser;
        $_ret['iceCandidate'] = $iceCandidate;

    }
    if ($toUser != "") {
        global $userlist;
        if (!isset($userlist[$toUser])) {
            $_ret['msg'] = "Sorry,呼叫的用户不在线!";
            $_ret['type'] = "call_back";
            $_ret['fromUser'] = "系统消息";


            $_senddata = json_encode($_ret);
            $server->push($frame->fd, $_senddata);
        } else {
            foreach ($userlist as $key => $val) {
                if ($key == $toUser) {
                    $_senddata = json_encode($_ret);
                    echo "send message: {$_senddata}\n";
                    $server->push($val, $_senddata);
                }
            }
        }


    }

    // echo "received message: {$frame->data}\n";

});

$server->on('close', function($server, $fd) {
    echo "connection close: {$fd}\n";
});

$server->start();

实现sdp及ice的信息交换

编写html代码实现p2p视频通话

<!DOCTYPE html>

<head>
    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
    <style>
        html,body{
            margin: 0;
            padding: 0;
        }
        #main{
            position: absolute;
            width: 370px;
            height: 550px;
        }
        #localVideo{
            position: absolute;
            background: #757474;
            top: 10px;
            right: 10px;
            width: 100px;
            height: 150px;
            z-index: 2;
        }
        #remoteVideo{
            position: absolute;
            top: 0px;
            left: 0px;
            width: 100%;
            height: 100%;
            background: #222;
        }
        #buttons{
            z-index: 3;
            bottom: 20px;
            left: 90px;
            position: absolute;
        }
        #toUser{
            border: 1px solid #ccc;
            padding: 7px 0px;
            border-radius: 5px;
            padding-left: 5px;
            margin-bottom: 5px;
        }
        #toUser:focus{
            border-color: #66afe9;
            outline: 0;
            -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
            box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)
        }
        #call{
            width: 70px;
            height: 35px;
            background-color: #00BB00;
            border: none;
            margin-right: 25px;
            color: white;
            border-radius: 5px;
        }
        #yourno{
            color: WHITE;
        }
        #hangup{
            width:70px;
            height:35px;
            background-color:#FF5151;
            border:none;
            color:white;
            border-radius: 5px;
        }
    </style>
</head>

<body>
    <div id="main">
        <video id="remoteVideo" playsinline autoplay></video>
        <video id="localVideo" playsinline autoplay muted></video>

        <div id="buttons">
            <span id="yourno"></span>
            <input id="toUser" placeholder="输入在线好友账号" /><br/>
            <button id="call">视频通话</button>
            <button id="hangup">挂断</button>
        </div>
    </div>


    <script type="text/javascript" >
    var icecanarr=[];
        let username = randomString(6);
        document.getElementById('yourno').innerHTML="您的账户名是:"+username;
        
        let localVideo = document.getElementById('localVideo');
        let remoteVideo = document.getElementById('remoteVideo');
        let websocket = null;
        let peer = null;
     
    
        WebSocketInit();
        ButtonFunInit();
    
    function randomString(len) {
      len = len || 32;
      var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
      var maxPos = $chars.length;
      var pwd = '';
      for (i = 0; i < len; i++) {
        pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
      }
      return pwd;
    }
    
    
        /* WebSocket */
        function WebSocketInit(){
            //判断当前浏览器是否支持WebSocket
            if ('WebSocket' in window) {
                websocket = new WebSocket("wss:/web.debug.only.bfw.wiki:9502/?username="+username);
            } else {
                alert("当前浏览器不支持WebSocket!");
            }
    
            //连接发生错误的回调方法
            websocket.onerror = function (e) {
                alert("WebSocket连接发生错误!");
            };
    
            //连接关闭的回调方法
            websocket.onclose = function () {
                console.error("WebSocket连接关闭");
            };
    
            //连接成功建立的回调方法
            websocket.onopen = function () {
                console.log("WebSocket连接成功");
            };
    
            //接收到消息的回调方法
            websocket.onmessage = async function (event) {
              let { type, fromUser, msg, sdp, iceCandidate } = JSON.parse(event.data.replace(/\n/g,"\\n").replace(/\r/g,"\\r"));


                console.log(type);
                
               

    
                if (type === 'hangup') {
                    console.log(msg);
                    document.getElementById('hangup').click();
                    return;
                }
    
                if (type === 'call_start') {
                    let msg = "0"
                    if(confirm(fromUser + "发起视频通话,确定接听吗")==true){
                        document.getElementById('toUser').value = fromUser;
                        WebRTCInit();
                         
                        msg = "1"
                    }
    
                    websocket.send(JSON.stringify({
                        type:"call_back",
                        toUser:fromUser,
                        fromUser:username,
                        msg:msg
                    }));
    
                    return;
                }
    
                if (type === 'call_back') {
                    if(msg === "1"){
                        console.log(document.getElementById('toUser').value + "同意视频通话");
    
                        //创建本地视频并发送offer
                        let stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
                        localVideo.srcObject = stream;
                        stream.getTracks().forEach(track => {
                            peer.addTrack(track, stream);
                        });
    
                        let offer = await peer.createOffer();
                        await peer.setLocalDescription(offer);
    
                        let newOffer = offer.toJSON();
                        newOffer["fromUser"] = username;
                        newOffer["toUser"] = document.getElementById('toUser').value;
                        websocket.send(JSON.stringify(newOffer));
                    }else if(msg === "0"){
                        alert(document.getElementById('toUser').value + "拒绝视频通话");
                        document.getElementById('hangup').click();
                    }else{
                        alert(msg);
                        document.getElementById('hangup').click();
                    }
    
                    return;
                }
    
                if (type === 'offer') {
                    let stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
                    localVideo.srcObject = stream;
                    stream.getTracks().forEach(track => {
                        peer.addTrack(track, stream);
                    });
    
                    await peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
                     for (let i = 0; i < icecanarr.length; i++) {
                         peer.addIceCandidate(icecanarr[i]);
 
                     }
                    console.log(peer.remoteDescription)
                    let answer = await peer.createAnswer();
                    let newAnswer = answer.toJSON();
    
                    newAnswer["fromUser"] = username;
                    newAnswer["toUser"] = document.getElementById('toUser').value;
                    websocket.send(JSON.stringify(newAnswer));
    
                    await peer.setLocalDescription(answer);
                    
                     //ice
           
    
                    return;
                }
    
                if (type === 'answer') {
                       console.log(type)
                    console.log(sdp)
                    
                   
                    peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
                    for (let i = 0; i < icecanarr.length; i++) {
                         peer.addIceCandidate(icecanarr[i]);
 
                     }
                            
                    return;
                }
    
                if (type === '_ice') {
                  if(peer.remoteDescription!=null){
                      peer.addIceCandidate(iceCandidate);
                  }else{
                      icecanarr.push(iceCandidate);
                  }


                    // console.log(iceCandidate);
                    // peer.addIceCandidate(iceCandidate);
                    return;
                }
    
            }
        }
    
        /* WebRTC */
        function WebRTCInit(){
            
            const config = {
 'iceServers': [
        {
            'url': 'stun:web.debug.only.bfw.wiki:3478',
        },
        {
            'url': 'turn:web.debug.only.bfw.wiki:3478',
            'username': 'bfw',
            'credential': 'bfw'
        }
    ]
};


            peer = new RTCPeerConnection(config);
     peer.onicecandidate = function (e) {
                if (e.candidate) {
                    
   websocket.send(JSON.stringify({
                        type: '_ice',
                        toUser:document.getElementById('toUser').value,
                        fromUser:username,
                        iceCandidate: e.candidate
                    }));

                }
              
            };
           
            //track
            peer.ontrack = function (e) {
                if (e && e.streams) {
                    remoteVideo.srcObject = e.streams[0];
                }
            };
        }
        
        



        /* 按钮事件 */
        function ButtonFunInit(){
            //视频通话
            document.getElementById('call').onclick = function (e){
                document.getElementById('toUser').style.visibility = 'hidden';
    
                let toUser = document.getElementById('toUser').value;
                if(!toUser){
                    alert("请先指定好友账号,再发起视频通话!");
                    return;
                }
    
                if(peer == null){
                    WebRTCInit();
                }
    
                websocket.send(JSON.stringify({
                    type:"call_start",
                    fromUser:username,
                    toUser:toUser,
                }));
            }
    
            //挂断
            document.getElementById('hangup').onclick = function (e){
                document.getElementById('toUser').style.visibility = 'unset';
    
                if(localVideo.srcObject){
                    const videoTracks = localVideo.srcObject.getVideoTracks();
                    videoTracks.forEach(videoTrack => {
                        videoTrack.stop();
                        localVideo.srcObject.removeTrack(videoTrack);
                    });
                }
    
                if(remoteVideo.srcObject){
                    const videoTracks = remoteVideo.srcObject.getVideoTracks();
                    videoTracks.forEach(videoTrack => {
                        videoTrack.stop();
                        remoteVideo.srcObject.removeTrack(videoTrack);
                    });
    
                    //挂断同时,通知对方
                    websocket.send(JSON.stringify({
                        type:"hangup",
                        fromUser:username,
                        toUser:document.getElementById('toUser').value,
                    }));
                }
    
                if(peer){
                    peer.ontrack = null;
                    peer.onremovetrack = null;
                    peer.onremovestream = null;
                    peer.onicecandidate = null;
                    peer.oniceconnectionstatechange = null;
                    peer.onsignalingstatechange = null;
                    peer.onicegatheringstatechange = null;
                    peer.onnegotiationneeded = null;
    
                    peer.close();
                    peer = null;
                }
    
                localVideo.srcObject = null;
                remoteVideo.srcObject = null;
            }
        }
    </script>
</body>

</html>


{{collectdata}}

网友评论