美文网首页
webrtc进阶实战之完整版

webrtc进阶实战之完整版

作者: 码代码的小公举 | 来源:发表于2023-01-16 09:31 被阅读0次

    目标:

    1. 拨打视频电话
    2. 同意视频电话
    3. 连接出现自己和对方的画面
    4. 单方面挂断电话,另一方会被挂断

    视频电话demo代码:

    <!DOCTYPE html>
    <html>
    
    <head>
    </head>
    
    <body>
        <div class="rows">
            <div class="box">
                <video id="other-video"></video>
                <video id="my-video"></video>
            </div>
            <div id="message">
                <div class="title">消息</div>
            </div>
        </div>
        <div class="row">
            <input placeholder="请输入..." style="width: 380px; padding: 4px 10px;" id="text" />
            <button id="sendText">发送消息</button>
        </div>
        <button id="close" style="display: none; margin-top: 10px;">挂断</button>
        <button id="answer" style="display: none; margin-top: 10px;">calling...同意</button>
        <br>
        <div>在线人员:</div>
        <div id="list"></div>
    </body>
    <script>
        const answerButton = document.getElementById('answer');
        const closeButton = document.getElementById('close');
        const otherVideo = document.getElementById('other-video');
        const myVideo = document.getElementById('my-video');
        const listDiv = document.getElementById('list');
        const name = 'ws11221122'//  + (Math.random() * 1000).toFixed(0);
        console.log(name, 'name')
        let rtc, hasOffer, hasRomve, list, targetName;
        const err = (e) => {
            console.log(e);
        }
        const send = (data) => {
            ws.send(JSON.stringify({ ...data, name, targetName }));
        }
        const getVideo = async (callback) => {
            const gumStream = await navigator.mediaDevices.getUserMedia(
                { video: true, audio: true });
            for (const track of gumStream.getTracks()) {
                rtc.addTrack(track, gumStream);
            }
            rtc.addStream(gumStream);
            myVideo.srcObject = gumStream;
            myVideo.onloadedmetadata = () => {
                myVideo.play();
            }
            callback();
        }
        const closeHandler = () => {
            if (rtc) {
                otherVideo.srcObject && otherVideo.srcObject.getTracks().forEach(v => {
                    v.stop();
                });
                otherVideo.srcObject = null;
                myVideo.srcObject && myVideo.srcObject.getTracks().forEach(v => {
                    v.stop();
                });
                myVideo.srcObject = null;
                // rtc.close();
            }
        }
        const ws = new WebSocket('ws://localhost:3002', name);
        ws.onopen = (evt) => {
            console.log('ws open ...');
            ws.send(JSON.stringify({ name, type: 'init', targetName }));
        };
        ws.onmessage = (evt) => {
            let res = {};
            try {
                res = JSON.parse(evt.data);
            } catch { }
            console.log(res.type);
            if (res.type === 'offer') {
                answerButton.style.display = 'block';
                hasOffer = res.offer;
                targetName = res.name;
            } else if (res.type === 'answer') {
                rtc.setRemoteDescription(new RTCSessionDescription(res.answer), () => {
                    console.log('收到answer setRemoteDescription ok');
                    hasRomve = true;
                })
            } else if (rtc && res.type === 'ice') {
                if (rtc && res.candidate && hasRomve) {
                    rtc.addIceCandidate(new RTCIceCandidate(res.candidate))
                }
            } else if (res.type === 'list') {
                list = res.list;
                listDiv.innerHTML = res.list.sort().map(i => '<p>' + i + (i !== name ? `<button class='button' onclick='callHandler("${i}")' id='${i}' style="display: block;">call</button>` : '(本人)') + '</p>').join('')
            } else if (rtc && res.type === 'close') {
                closeHandler()
            }
    
        };
        ws.onclose = function (evt) {
            console.log('Connection closed.');
        };
        const createOffer = () => {
            rtc.createOffer((offer) => {
                rtc.setLocalDescription(new RTCSessionDescription(offer), () => {
                    send({ type: 'offer', offer })
                }, err)
            }, err, {
                offerToReceiveVideo: 1,
            })
        }
        const createAnswer = () => {
            rtc.setRemoteDescription(new RTCSessionDescription(hasOffer), () => {
                hasRomve = true;
                rtc.createAnswer((answer) => {
                    rtc.setLocalDescription(answer);
                    send({ type: 'answer', answer })
                }, err);
            }, err)
        }
        const createRTC = (callback) => {
            rtc = new RTCPeerConnection();
            getVideo(callback)
            channel = rtc.createDataChannel("sendChannel");
            channel.onopen = () => {
                console.log('channel onopen')
            };
            channel.onclose = () => {
                console.log('channel onclose')
            };
            rtc.ontrack = (s) => {
                console.log('ontrack')
                otherVideo.srcObject = s.streams[0];
                otherVideo.onloadedmetadata = () => {
                    otherVideo.play();
                    closeButton.style.display = 'block';
                }
            }
            rtc.ondatachannel = (e) => {
                console.log('ondatachannel')
                e.channel.onmessage = (ev) => {
                    const message = ev.data;
                    const d = document.createElement('p');
                    d.innerText = message;
                    document.getElementById('message').appendChild(d)
                }
                document.getElementById('sendText').onclick = () => {
                    const value = document.getElementById('text').value;
                    channel.send(value);
                }
            }
            rtc.onicecandidate = (ice) => {
                console.log('onicecandidate')
                if (ice && ice.candidate) {
                    send({ type: 'ice', candidate: ice.candidate })
                }
            }
        }
        answerButton.onclick = () => {
            createRTC(createAnswer);
            answerButton.style.display = 'none';
        };
        const callHandler = (id) => {
            targetName = id;
            createRTC(createOffer);
            // callButton = document.getElementById(id);
            // callButton.innerText = 'calling'
        };
        closeButton.onclick = () => {
            closeHandler();
            send({ type: 'close' })
        }
    </script>
    <style>
        .box {
            position: relative;
            width: 500px;
            height: 400px;
        }
    
        #my-video {
            border: 1px solid #999;
            width: 200px;
            height: 160px;
            position: absolute;
            bottom: 4px;
            right: 4px;
            background: #999;
        }
    
        #other-video {
            border: 1px solid #999;
            width: 500px;
            height: 400px;
            background: #eee;
        }
    
        p {
            display: flex;
            flex-direction: row;
        }
    
        p button {
            margin-left: 10px;
        }
    
        button {
            padding: 4px 12px;
            border: 0;
            background: rgb(14, 81, 251);
            color: #fff;
            border-radius: 4px;
            font-size: 14px;
        }
    
        .rows {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
        }
    
        .row {
            margin-top: 10px;
            width: 500px;
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: space-between;
        }
    
        #message {
            float: right;
            margin-right: 20px;
        }
    
        .title {
            font-size: 18px;
            font-weight: 600;
            margin-bottom: 10px;
            border-bottom: 1px solid #999;
            width: 300px;
            padding-bottom: 4px;
        }
    </style>
    
    </html>
    
    1. 注意 name 需要有且唯一
    2. 很多状态的监听事件还可以完善,这里就感受一下,写的比较简单
    3. 代码粘去就可以用,信令服务器代码在上一篇
    4. 样式比较丑,demo嘛
    5. 为了黏贴就可以运行使用,没有用任何框架,插件等
    6. 消息那块写完没有把自己发送的内容协商,然后一左一右 可能会比较真实感,不想改了,年底了,新年快乐。

    相关文章

      网友评论

          本文标题:webrtc进阶实战之完整版

          本文链接:https://www.haomeiwen.com/subject/mqjacdtx.html