日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 综合教程 >内容正文

综合教程

利用peerjs轻松玩转webrtc

發(fā)布時(shí)間:2023/12/13 综合教程 27 生活家
生活随笔 收集整理的這篇文章主要介紹了 利用peerjs轻松玩转webrtc 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

 隨著5G技術(shù)的推廣,可以預(yù)見(jiàn)在不久的將來(lái)網(wǎng)速將得到極大提升,實(shí)時(shí)音視頻互動(dòng)這類(lèi)對(duì)網(wǎng)絡(luò)傳輸質(zhì)量要求較高的應(yīng)用將是最直接的受益者。而且伴隨著webrtc技術(shù)的成熟,該領(lǐng)域可能將成為下一個(gè)技術(shù)熱點(diǎn),但是傳統(tǒng)的webrtc應(yīng)用開(kāi)發(fā)存在一定的復(fù)雜性,本文將介紹如何利用peerjs這一開(kāi)源框架來(lái)簡(jiǎn)化webrtc開(kāi)發(fā)。

一、webrtc回顧
WebRTC(Web Real-Time Communication)即:網(wǎng)頁(yè)即時(shí)通信。 簡(jiǎn)單點(diǎn)講,它可以實(shí)現(xiàn)瀏覽器網(wǎng)頁(yè)與網(wǎng)頁(yè)之間的音視頻實(shí)時(shí)通信(或傳輸其它任何數(shù)據(jù)),目前主流瀏覽器都支持該API,WebRTC現(xiàn)在已經(jīng)納入W3C標(biāo)準(zhǔn)。

1.1 媒體協(xié)商
通信的主要目的之一是彼此交換信息。打個(gè)比方:“張三”跟“李四”打了一通電話(語(yǔ)音通訊),整個(gè)過(guò)程中“張三”說(shuō)的話被“李四”聽(tīng)到了,“李四”說(shuō)的話被“張三”聽(tīng)到了,雙方交換了語(yǔ)音信息。類(lèi)似的,一個(gè)瀏覽器要與另一個(gè)瀏覽器發(fā)起實(shí)時(shí)音視頻通信,需要交換哪些信息呢? 除了音視頻信息外,至少還有2個(gè)關(guān)鍵信息要交換:媒體信息和網(wǎng)絡(luò)信息。

如上圖:通常某個(gè)瀏覽器所在的電腦,都會(huì)連接具體的多媒體設(shè)備(比如:麥克風(fēng)、攝像頭)。如果A電腦上的攝像頭只支持VP8,H264格式,而另一臺(tái)電腦上的攝像頭只支持H264、MPEG-4格式,它倆要能正常播放彼此的視頻,肯定會(huì)選擇雙方都能識(shí)別的H264格式。這就好比:2個(gè)不同國(guó)籍的人要相互交流,A會(huì)說(shuō)英語(yǔ)、中文;而B(niǎo)只會(huì)說(shuō)英語(yǔ),毫無(wú)懸念,他倆肯定會(huì)用雙方都能聽(tīng)懂的“英語(yǔ)”來(lái)溝通。

網(wǎng)絡(luò)情況也是類(lèi)似的,二個(gè)瀏覽器所在的電腦可能在不同的網(wǎng)絡(luò)環(huán)境中,假如A機(jī)器具備公網(wǎng)+192內(nèi)網(wǎng)網(wǎng)段,而B(niǎo)機(jī)器只有192+198內(nèi)網(wǎng)網(wǎng)段,二臺(tái)電腦要能相互連接,很容易想到,使用雙方都能連通的公共192內(nèi)網(wǎng)網(wǎng)段通信最為方便。
在webrtc中,有一個(gè)特定的協(xié)議用于描述媒體信息、網(wǎng)絡(luò)信息和其它一些關(guān)鍵信息,稱(chēng)為SDP(Session Description Protocol-會(huì)話描述協(xié)議)。而上述介紹的交換媒體信息、網(wǎng)絡(luò)信息的過(guò)程,也被稱(chēng)為媒體協(xié)商,即:交換SDP.

這是一張媒體協(xié)商過(guò)程的經(jīng)典圖例, Amy要跟Bob通信, 要先發(fā)一個(gè)Offer(即: 描述Amy自己會(huì)話的SDP), Bob收到后,做出Answer回應(yīng)(即:描述Bob自己會(huì)話的SDP), 雙方完成SDP交換后, 根據(jù)前面的分析,取出二份SDP的交集, 即完成了媒體協(xié)商.

1.2 主要處理過(guò)程

這是mozilla開(kāi)發(fā)者官網(wǎng)上的一張圖, 大致描述了webrtc的處理過(guò)程:

A通過(guò)STUN服務(wù)器,收集自己的網(wǎng)絡(luò)信息
A創(chuàng)建Offer SDP,通過(guò)Signal Channel(信令服務(wù)器)給到B
B做出回應(yīng)生成Answer SDP,通過(guò)Signal Channel給到A
B通過(guò)STUN收集自己的網(wǎng)絡(luò)信息,通過(guò)Signal Channel給到A

注:如果A,B之間無(wú)法直接穿透(即:無(wú)法建立點(diǎn)對(duì)點(diǎn)的P2P直連),將通過(guò)TURN服務(wù)器中轉(zhuǎn)。

二、peerjs介紹
從上面的回顧可以看出,要?jiǎng)?chuàng)建一個(gè)真正的webrtc應(yīng)用還是有些小復(fù)雜的,特別是SDP交換(createOffer及createAnswer)、網(wǎng)絡(luò)候選信息收集(ICE candidate),這些都需要開(kāi)發(fā)人員對(duì)webrtc的機(jī)制有足夠的了解,對(duì)webrtc初學(xué)者來(lái)講有一定的開(kāi)發(fā)門(mén)檻。

而peerjs開(kāi)源項(xiàng)目簡(jiǎn)化了webrtc的開(kāi)發(fā)過(guò)程,把SDP交換、ICE candidate這些偏底層的細(xì)節(jié)都做了封裝,開(kāi)發(fā)人員只需要關(guān)注應(yīng)用本身就行了。
peerjs的核心對(duì)象Peer,它有幾個(gè)常用方法:

peer.connect 創(chuàng)建點(diǎn)對(duì)點(diǎn)的連接
peer.call 向另1個(gè)peer端發(fā)起音視頻實(shí)時(shí)通信
peer.on 對(duì)各種事件的監(jiān)控回調(diào)
peer.disconnect 斷開(kāi)連接
peer.reconnect 重新連接
peer.destroy 銷(xiāo)毀對(duì)象

另外還有二個(gè)重要對(duì)象DataConnection、MediaConnection,其中:

DataConnection用于收發(fā)數(shù)據(jù)(對(duì)應(yīng)于webrtc中的DataChannel),它的所有方法中有一個(gè)重要的send方法,用于向另一個(gè)peer端發(fā)送數(shù)據(jù);
MediaConnection用于處理媒體流,它有一個(gè)重要的stream屬性,表示關(guān)聯(lián)的媒體流。

更多細(xì)節(jié)可查閱peerjs的api在線文檔 (注:peerjs的所有api只有一頁(yè),估計(jì)15分鐘左右就全部看一圈)

peerjs的服務(wù)端(即信令服務(wù)器)很簡(jiǎn)單,只需要下面這段nodejs代碼即可:

var fs = require('fs');
var PeerServer = require('peer').PeerServer;

var options = {
  //webrtc要求SSL安全傳輸,所以要設(shè)置證書(shū)
  key: fs.readFileSync('key/server.key'),
  cert: fs.readFileSync('key/server.crt')
}

var server = PeerServer({
  port: 9000,
  ssl: options,
  path:"/"
});

本地啟用成功后,瀏覽https://localhost:9000 可以看到

三、實(shí)戰(zhàn)練習(xí)
下面選幾個(gè)常用的場(chǎng)景,利用peerjs實(shí)戰(zhàn)一番(文末最后有示例源碼鏈接) - 注:建議使用chrome谷歌瀏覽器運(yùn)行下面的示例。

3.1 文本聊天
運(yùn)行效果如下(假設(shè)有Jack、Rose二個(gè)用戶(hù)在各自的瀏覽器頁(yè)面上相互聊天)

主要流程:

Jack和Rose先連接到PeerJs服務(wù)器
Rose指定要建立p2p連接的對(duì)方名稱(chēng)(即:Jack),然后發(fā)送消息
Jack在自己的頁(yè)面上,可以實(shí)時(shí)收到Rose發(fā)送過(guò)來(lái)的文字,并回復(fù)

客戶(hù)端的js代碼如下:(不到100行)

var txtSelfId = document.querySelector("input#txtSelfId");
var txtTargetId = document.querySelector("input#txtTargetId");
var txtMsg = document.querySelector("input#txtMsg");
var tdBox = document.querySelector("td#tdBox");
var btnRegister = document.querySelector("button#btnRegister");
var btnSend = document.querySelector("button#btnSend");

let peer = null;
let conn = null;

//peer連接時(shí),id不允許有中文,所以轉(zhuǎn)換成hashcode數(shù)字
hashCode = function (str) {
    var hash = 0;
    if (str.length == 0) return hash;
    for (i = 0; i < str.length; i++) {
        char = str.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash;
    }
    return hash;
}

sendMessage = function (message) {
    conn.send(JSON.stringify(message));
    console.log(message);
    tdBox.innerHTML = tdBox.innerHTML += "<div class='align_left'>" + message.from + " : " + message.body + "</div>";
}

window.onload = function () {

    //peerserver的連接選項(xiàng)(debug:3表示打開(kāi)調(diào)試,將在瀏覽器的console輸出詳細(xì)日志)
    let connOption = { host: 'localhost', port: 9000, path: '/', debug: 3 };

    //register處理
    btnRegister.onclick = function () {
        if (!peer) {
            if (txtSelfId.value.length == 0) {
                alert("please input your name");
                txtSelfId.focus();
                return;
            }
            //創(chuàng)建peer實(shí)例 
            peer = new Peer(hashCode(txtSelfId.value), connOption);

            //register成功的回調(diào)
            peer.on('open', function (id) {
                tdBox.innerHTML = tdBox.innerHTML += "<div class='align_right'>system : register success " + id + "</div>";
            });

            peer.on('connection', (conn) => {
                //收到對(duì)方消息的回調(diào)
                conn.on('data', (data) => {
                    var msg = JSON.parse(data);
                    tdBox.innerHTML = tdBox.innerHTML += "<div class='align_right'>" + msg.from + " : " + msg.body + "</div>";
                    if (txtTargetId.value.length == 0) {
                        txtTargetId.value = msg.from;
                    }
                });
            });
        }
    }

    //發(fā)送消息處理
    btnSend.onclick = function () {
        //消息體
        var message = { "from": txtSelfId.value, "to": txtTargetId.value, "body": txtMsg.value };
        if (!conn) {
            if (txtTargetId.value.length == 0) {
                alert("please input target name");
                txtTargetId.focus();
                return;
            }
            if (txtMsg.value.length == 0) {
                alert("please input message");
                txtMsg.focus();
                return;
            }

            //創(chuàng)建到對(duì)方的連接
            conn = peer.connect(hashCode(txtTargetId.value));
            conn.on('open', () => {
                //首次發(fā)送消息
                sendMessage(message);
            });
        }

        //發(fā)送消息
        if (conn.open) {
            sendMessage(message);
        }
    }
}

有幾點(diǎn)說(shuō)明一下:

89行首次發(fā)送消息,這時(shí)conn還沒(méi)有準(zhǔn)備好(open狀態(tài)為false),此時(shí)send不會(huì)成功,參考下面的調(diào)試截圖


要在conn.on('open',{...})事件回調(diào)里完成首次消息的發(fā)送,這時(shí)候open狀態(tài)是true,send才能成功

從瀏覽器的console控制臺(tái)日志可以清楚的看到peerjs,已經(jīng)把createOffer、createAnswer,以及ICE candidate這些細(xì)節(jié)都內(nèi)部消化掉了。

這是Rose端的日志

這是Jack端的日志

從日志可以看到,剛開(kāi)始Rose→Create Offer->Jack,然后Jack→Create Answer→ Rose,Rose→Jack的連接建立好了; Jack收到第一句話"how are you"后,回復(fù)"fine, thank you"時(shí), 過(guò)程反過(guò)來(lái) Jack → Create Offer → Rose,然后Rose → Create Answer → Jack, Jack→Rose的連接也建好了,后面再聊天,就可以直接相互send文字消息了。另外ICE candidate 、set localDescription、set remoteDescription這些peerjs也一并幫我們做掉了,對(duì)普通開(kāi)發(fā)人員而言,不再需要關(guān)心這些細(xì)節(jié)。強(qiáng)烈建議大家將這2份日志與“第1部分Amy與Bob交換SDP"那張圖對(duì)照體會(huì)一下。

另外,雖然這個(gè)示例是在本機(jī)運(yùn)行的,但是原理跟2臺(tái)不同的電腦之間(或不同的網(wǎng)絡(luò)環(huán)境,比如Rose在美國(guó)、Jack在中國(guó))端對(duì)端通信是完全相同的,只不過(guò)如果二端的瀏覽器如果不在一個(gè)網(wǎng)段,需要配置stun或turn服務(wù)器,參考下面的配置:

var peer = new Peer({
  config: {'iceServers': [
    { url: 'stun:stun.l.google.com:19302' },
    { url: 'turn:homeo@turn.bistri.com:80', credential: 'homeo' }
  ]} /* Sample servers, please use appropriate ones */
});

注:關(guān)于stun或turn的細(xì)節(jié),建議閱讀本文最后的參考文章。

3.2 視頻通話
運(yùn)行效果如下(視頻轉(zhuǎn)成gif文件尺寸太大,這里就只截了幾張運(yùn)行中的關(guān)鍵圖片)

注:為了模擬2個(gè)人分別在不同的頁(yè)面實(shí)時(shí)視頻通話, 我在本機(jī)插了2個(gè)USB攝像頭(1個(gè)橫著放,1個(gè)豎著放),打開(kāi)2個(gè)瀏覽器頁(yè)面并啟用攝像頭后,1個(gè)頁(yè)面選擇攝像頭1,另1個(gè)頁(yè)面選擇攝像頭2(通過(guò)下圖中攝像頭下拉框切換)。

如上圖,在1個(gè)頁(yè)面上輸入”張三“并點(diǎn)擊register,同時(shí)允許使用攝像頭,然后在另1個(gè)頁(yè)面輸入”李四“,也點(diǎn)擊register,并允許使用攝像頭,然后把攝像頭切換到另1個(gè),這樣2個(gè)頁(yè)面看到的本地視頻就不一樣了(相當(dāng)于2個(gè)端各自的視頻流)。然后在"李四"的頁(yè)面上,target name這里輸入"張三",并點(diǎn)擊call按鈕發(fā)起視頻通話,此時(shí)"張三"的頁(yè)面上會(huì)馬上收到邀請(qǐng)確認(rèn)(如下圖)

”張三“選擇Accept同意后,二端就相互建立連接,開(kāi)始實(shí)時(shí)視頻通話。

注:首次運(yùn)行時(shí),瀏覽器會(huì)彈出類(lèi)似下圖的提示框詢(xún)問(wèn)是否同意啟用攝像頭/麥克風(fēng)(出于安全隱私考慮),如果手一抖選擇了不允許,就算刷新頁(yè)面,也不會(huì)再?gòu)棾鎏崾究颉?/p>

對(duì)于chrome瀏覽器,可在"設(shè)置→ 高級(jí)→ 內(nèi)容設(shè)置→ 攝像頭/麥克風(fēng)" 手動(dòng)重新設(shè)置。

從上面這一系列的運(yùn)行截圖可以看到,“李四”與“張三”在發(fā)起視頻通話過(guò)程中涉及到一些交互(即:“李四”發(fā)起,“張三”可以選擇同意或拒絕),這些交互的指令(也稱(chēng)為"信令")可以通過(guò)上一個(gè)場(chǎng)景"文字聊天"中的聊天消息Message作為載體,簡(jiǎn)單起見(jiàn),message可以用一個(gè)json格式來(lái)表示:

{
    "from": "李四",
    "to": "張三",
    "action": "call"
}

action代表具體的指令動(dòng)作類(lèi)型,在這個(gè)場(chǎng)景中有3個(gè):call(發(fā)起視頻通話),accept(對(duì)方同意視頻通話),accept-ok(發(fā)起方通知對(duì)方接收媒體流)-注:指令類(lèi)型的名字可以隨便起,不一定非得叫call/accept/accept-ok,容易理解即可。

關(guān)鍵的幾處代碼如下:call按鈕的處理邏輯

btnCall.onclick = function () {
    if (txtTargetId.value.length == 0) {
        alert("please input target name");
        txtTargetId.focus();
        return;
    }
    sendMessage(txtSelfId.value, txtTargetId.value, "call");
}

其中sendMessage即發(fā)送消息

function sendMessage(from, to, action) {
    var message = { "from": from, "to": to, "action": action };
    if (!localConn) {
        localConn = peer.connect(hashCode(to));
        localConn.on('open', () => {
            localConn.send(JSON.stringify(message));
            console.log(message);
        });
    }
    if (localConn.open){
        localConn.send(JSON.stringify(message));
        console.log(message);
    }
}

register按鈕處理邏輯:

//register處理
btnRegister.onclick = function () {
    if (!peer) {
        if (txtSelfId.value.length == 0) {
            alert("please input your name");
            txtSelfId.focus();
            return;
        }
        peer = new Peer(hashCode(txtSelfId.value), connOption);
        peer.on('open', function (id) {
            console.log("register success. " + id);
        });
        peer.on('call', function (call) {
            call.answer(localStream);
        });
        peer.on('connection', (conn) => {
            conn.on('data', (data) => {
                var msg = JSON.parse(data);
                console.log(msg);
                //“接收方“收到邀請(qǐng)時(shí),彈出詢(xún)問(wèn)對(duì)話框
                if (msg.action === "call") {
                    lblFrom.innerText = msg.from;
                    txtTargetId.value = msg.from;
                    $("#dialog-confirm").dialog({
                        resizable: false,
                        height: "auto",
                         400,
                        modal: true,
                        buttons: {
                            "Accept": function () {
                                $(this).dialog("close");
                                sendMessage(msg.to, msg.from, "accept");
                            },
                            Cancel: function () {
                                $(this).dialog("close");
                            }
                        }
                    });
                }
                 
                //“發(fā)起方“發(fā)起視頻call,并綁定媒體流
                if (msg.action === "accept") {
                    console.log("accept call => " + JSON.stringify(msg));
                    var call = peer.call(hashCode(msg.from), localStream);
                    call.on('stream', function (stream) {
                        console.log('received remote stream');
                        remoteVideo.srcObject = stream;
                        sendMessage(msg.to, msg.from, "accept-ok");
                    });
                }
 
                //"接收方"發(fā)起視頻call,并綁定媒體流   
                if (msg.action === "accept-ok") {
                    console.log("accept-ok call => " + JSON.stringify(msg));
                    var call = peer.call(hashCode(msg.from), localStream);
                    call.on('stream', function (stream) {
                        console.log('received remote stream');
                        remoteVideo.srcObject = stream;                           
                    });
                }
            });
        });
    }
}

  

3.3 白板共享
運(yùn)行效果如下:在2個(gè)頁(yè)面上,仍然模擬2個(gè)用戶(hù)“張三”與“李四”,都register到peerjs服務(wù)器后,輸入對(duì)方的名稱(chēng),然后點(diǎn)擊share,就可以在canvas上共享白板一起涂鴉了。

關(guān)鍵點(diǎn):send方法不僅僅可以用來(lái)發(fā)送文字消息,同樣也可以發(fā)送其它內(nèi)容,每次在canvas上的的涂鴉,本質(zhì)上就是調(diào)用canvas的api在一系列的坐標(biāo)點(diǎn)上連續(xù)畫(huà)線。只要把1個(gè)頁(yè)面上畫(huà)線經(jīng)過(guò)的坐標(biāo)點(diǎn)發(fā)送到另1個(gè)頁(yè)面上,再還原出來(lái)就可以了。

核心代碼:

window.onload = function () {
    if (!navigator.mediaDevices ||
        !navigator.mediaDevices.getUserMedia) {
        console.log('webrtc is not supported!');
        alert("webrtc is not supported!");
        return;
    }
 
    let connOption = { host: 'localhost', port: 9000, path: '/', debug: 3 };
 
    context = demoCanvas.getContext('2d');
 
    //canvas鼠標(biāo)按下的處理
    demoCanvas.onmousedown = function (e) {
        e.preventDefault();
        context.strokeStyle='#00f';
        context.beginPath();
        started = true;
        buffer.push({ "x": e.offsetX, "y": e.offsetY });
    }
 
     //canvas鼠標(biāo)移動(dòng)的處理
    demoCanvas.onmousemove = function (e) {
        if (started) {
            context.lineTo(e.offsetX, e.offsetY);
            context.stroke();
            buffer.push({ "x": e.offsetX, "y": e.offsetY });
        }
    }
 
     //canvas鼠標(biāo)抬起的處理
    demoCanvas.onmouseup = function (e) {
        if (started) {
            started = false;
            //鼠標(biāo)抬起時(shí),發(fā)送坐標(biāo)數(shù)據(jù)
            sendData(txtSelfId.value, txtTargetId.value, buffer);
            buffer = [];
        }
    }
 
    //register按鈕處理
    btnRegister.onclick = function () {
        if (!peer) {
            if (txtSelfId.value.length == 0) {
                alert("please input your name");
                txtSelfId.focus();
                return;
            }
            peer = new Peer(hashCode(txtSelfId.value), connOption);
            peer.on('open', function (id) {
                console.log("register success. " + id);
            });
            peer.on('connection', (conn) => {
                conn.on('data', (data) => {
                    let msg = JSON.parse(data);
                    console.log(msg);
                    txtTargetId.value = msg.from;
                    //還原canvas
                    context.strokeStyle='#f00';
                    context.beginPath();
                    context.moveTo(msg.data[0].x,msg.data[0].y);
                    for (const pos in msg.data) {
                        context.lineTo(msg.data[pos].x,msg.data[pos].y);
                    }
                    context.stroke();
                });
            });
        }
    }
 
    //share按鈕處理
    btnShare.onclick = function () {
        if (txtTargetId.value.length == 0) {
            alert("please input target name");
            txtTargetId.focus();
            return;
        }
    }
    start();
}

其中sendData方法如下:

function sendData(from, to, data) {
    if (from.length == 0 || to.length == 0 || data.length == 0) {
        return;
    }
    let message = { "from": from, "to": to, "data": data };
    if (!localConn) {
        localConn = peer.connect(hashCode(to));
        localConn.on('open', () => {
            localConn.send(JSON.stringify(message));
            console.log(message);
        });
    }
    if (localConn.open) {
        localConn.send(JSON.stringify(message));
        console.log(message);
    }
}

說(shuō)明一下:這里我們用一個(gè)buffer數(shù)組來(lái)保存每次畫(huà)線的坐數(shù),然后在畫(huà)線結(jié)束時(shí),再調(diào)用sendData發(fā)送到對(duì)方。

3.4 圖片傳輸
運(yùn)行效果:在2個(gè)瀏覽器頁(yè)面上,分別register2個(gè)用戶(hù),然后在其中1個(gè)頁(yè)面上,輸入對(duì)方的名字,然后選擇一張圖片,另1個(gè)頁(yè)面將會(huì)收到傳過(guò)來(lái)的圖片。

核心仍然利用的是DataConnection的send方法,只不過(guò)發(fā)送的內(nèi)容里包含了圖片對(duì)應(yīng)的blob對(duì)象,核心代碼如下:

btnRegister.onclick = function () {
    if (!peer) {
        if (txtSelfId.value.length == 0) {
            alert("please input your name");
            txtSelfId.focus();
            return;
        }
        peer = new Peer(hashCode(txtSelfId.value), connOption);
        peer.on('open', function (id) {
            console.log("register success. " + id);
            lblStatus.innerHTML = "scoket open"
        });
 
        peer.on('connection', (conn) => {
            conn.on('data', (data) => {
                console.log("receive remote data");
                lblStatus.innerHTML = "receive data from " + data.from;
                txtTargetId.value = data.from
                if (data.filetype.includes('image')) {
                    lblStatus.innerHTML = data.filename + "(" + data.filetype + ") from:" + data.from
                    const bytes = new Uint8Array(data.file)
                    //用base64編碼,還原圖片
                    img.src = 'data:image/png;base64,' + encode(bytes)
                }
            });
        });
    }
}
 
//文件變化時(shí),觸發(fā)sendFile
inputFile.onchange = function (event) {
    if (txtTargetId.value.length == 0) {
        alert("please input target name");
        txtTargetId.focus();
        return;
    }
    const file = event.target.files[0]  
    //構(gòu)造圖片對(duì)應(yīng)的blob對(duì)象    
    const blob = new Blob(event.target.files, { type: file.type });
    img.src = window.URL.createObjectURL(file);
    sendFile(txtSelfId.value, txtTargetId.value, blob, file.name, file.type);
}

sendFile方法如下:

function sendFile(from, to, blob, fileName, fileType) {
    var message = { "from": from, "to": to, "file": blob, "filename": fileName, "filetype": fileType };
    if (!localConn) {
        localConn = peer.connect(hashCode(to));
        localConn.on('open', () => {
            localConn.send(message);
            console.log('onopen sendfile');
        });
    }
    localConn.send(message);
    console.log('send file');
}

上述示例的源碼已上傳至github,地址:https://github.com/yjmyzz/peerjs-sample

參考文章:

https://hpbn.co/webrtc/
https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Connectivity
https://peerjs.com/docs.html
https://www.cnblogs.com/yjmyzz/p/how-to-install-coturn-on-ubuntu.html
https://webrtc.github.io/samples/

總結(jié)

以上是生活随笔為你收集整理的利用peerjs轻松玩转webrtc的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。