WebRTC 簡(jiǎn)單入門(mén)

WebRTC DEMO

花了兩天時(shí)間簡(jiǎn)單了解了一下WEB RTC烹笔,并由此寫(xiě)入三個(gè)DEMO。

  1. p2p 點(diǎn)對(duì)點(diǎn)
  2. o2m 一對(duì)多
  3. live 直播

目前主要都是按p2p進(jìn)行的簡(jiǎn)單擴(kuò)展打厘。

WebRTC 簡(jiǎn)單了解

目前資料不算少,不過(guò)確實(shí)也不多,而且理論偏多玻驻,新手入門(mén)其實(shí)還是有點(diǎn)壓力的供常。

這邊推薦幾個(gè)資料和視頻摊聋。

MDN 文檔 記得出問(wèn)題看看文檔先

WebRTC samples 沒(méi)有思路的時(shí)候記得看看

嗶哩嗶哩 - 一只斌 這個(gè)b站up,大概算是由淺入深講了這個(gè)東西栈暇,但是有些基礎(chǔ)概念被帶過(guò)了(應(yīng)該主要是我基礎(chǔ)較薄弱)麻裁,其中圣誕特輯中的流程其實(shí)還是比較清晰的。

知乎 - 為什么webrtc那么貴? 注意看評(píng)論區(qū)

知乎 - 可以用WebRTC來(lái)做視頻直播嗎煎源? 注意看評(píng)論區(qū)

WebRTC 的一些概念

WebRTC基礎(chǔ)情況下只需要一個(gè) ‘信令服務(wù)’ 作為業(yè)務(wù)需求色迂,并不需要直接管理流。

p2p

點(diǎn)對(duì)點(diǎn)通信(pc與pc直接通信)薪夕,不通過(guò)服務(wù)器

信令服務(wù)

用于建立通信和業(yè)務(wù)交互的服務(wù)端

SDP

存放媒體信息脚草、會(huì)話的描述。如編碼解碼信息

NAT / STUN / TURN / ice

strn

用于p2p連接原献。(下面僅個(gè)人理解

由于沒(méi)有公網(wǎng)ip的兩個(gè)主機(jī)沒(méi)有辦法直接進(jìn)行直接通信馏慨,所以需要一個(gè)“中轉(zhuǎn)的服務(wù)器”,但是由于中轉(zhuǎn)服務(wù)器過(guò)于依賴服務(wù)器帶寬姑隅,所以采用NAT穿刺写隶,這樣雙方通信就不需要依賴服務(wù)器。

ice
整合了STUN和TURN的框架

實(shí)際具體不需要管讲仰,ice 服務(wù)器可以使用公開(kāi)的

實(shí)踐

webrtc建立還是很簡(jiǎn)單的慕趴,只需要交換雙方sdpice-candidate,即可建立通信鄙陡。

具體流程

p2p 1對(duì)1 視頻

呼叫方創(chuàng)建 offer sdp 接收方根據(jù) offer sdp 創(chuàng)建 answer sdp

一冕房、 sdp 交換

  1. 呼叫方 建立WebRTC
  2. 接收方 等待 信令服務(wù)器 轉(zhuǎn)發(fā) 類型為offersdp
  3. 呼叫方 監(jiān)聽(tīng)onnegotiationneeded并創(chuàng)建 offer sdp 并調(diào)用 setLocalDescription 設(shè)置為本地描述
  4. 呼叫方 向 信令服務(wù)器 發(fā)送 offer sdp 并監(jiān)聽(tīng) answer sdp
  5. 接收方 得到 offer sdp 并調(diào)用 setRemoteDescription 設(shè)置為遠(yuǎn)程描述
  6. 接收方 創(chuàng)建 answer sdp 并設(shè)置本地描述(setLocalDescription) 同時(shí)向 信令服務(wù)器 發(fā)送 answer sdp
  7. 呼叫方 收到 answer sdp 并設(shè)遠(yuǎn)程描述(setRemoteDescription

二、 ice-candidate 交換

  1. 監(jiān)聽(tīng) onicecandidate 得到 candidate 后進(jìn)行發(fā)送
  2. 監(jiān)聽(tīng) 信令服務(wù)器 ice-candidate 得到后調(diào)用 addIceCandidate

獲取媒體 流

function getUserMedia(constrains) {
  let promise = null;
  if (navigator.mediaDevices.getUserMedia) {
    //最新標(biāo)準(zhǔn)API
    promise = navigator.mediaDevices.getUserMedia(constrains)
  } else if (navigator.webkitGetUserMedia) {
    //webkit內(nèi)核瀏覽器
    promise = navigator.webkitGetUserMedia(constrains)
  } else if (navigator.mozGetUserMedia) {
    //Firefox瀏覽器
    promise = navagator.mozGetUserMedia(constrains)
  } else if (navigator.getUserMedia) {
    //舊版API
    promise = navigator.getUserMedia(constrains);
  }
  return promise;
}
// 得到流
const stream = await getUserMedia({
  video: true,
  audio: true,
});
// 展示
$('video').srcObject = stream;

信令服務(wù)器

demo使用 node + socket-io 做信令服務(wù)器

目前邏輯很簡(jiǎn)單趁矾,只為數(shù)據(jù)定向轉(zhuǎn)發(fā)

目前主要對(duì)sdpice candidate進(jìn)行一個(gè)定向轉(zhuǎn)發(fā)

const SocketIo = require('socket.io');
const consola = require('consola'); // log 工具

const users = new Map(); // 用戶存儲(chǔ)

/**
 * 
 * @param {http.Server} server 
 */
const signaling = (server) => {
  // 創(chuàng)建socketio服務(wù)
  const io = new SocketIo.Server(server);

  const p2p = io.of('/p2p');
  // 連接
  p2p.on('connect', (socket) => {
    consola.info('[%s] connect', socket.id);

    // **********
    // 用戶操作
  
    // sdp 轉(zhuǎn)發(fā)
    socket.on('sdp', (data) => {
      console.log('sdp data.to[%s] type[%s]', data.to, data.type);
      const user = users.get(data.to)
      if (user) {
        user.emit('sdp', data);
      }
    });
    // ice-candidate 轉(zhuǎn)發(fā)
    socket.on('ice-candidate', (data) => {
      console.log('ice-candidate data.to', data.to);
      const user = users.get(data.to)
      if (user) {
        user.emit('ice-candidate', data);
      }
    });

    // 用戶操作
    // **********
    
    // ----------
    // 用戶操作
    socket.once('disconnect', () => {
      consola.info('[%s] disconnect', socket.id);
      users.delete(socket.id);
      p2p.emit('leave', {
        user: socket.id
      });
    });
    socket.emit('users', {
      users: Array.from(users.keys())
    });
    p2p.emit('join', {
      user: socket.id
    });
    users.set(socket.id, socket);
    // 用戶操作
    // ----------
  });

};

module.exports = signaling;

建立WebRTC

這里在代碼中區(qū)分了發(fā)送和接收具體可參考業(yè)務(wù)

呼叫方

// ************
// 呼叫方
// ************

// 1. 建立rct連接
const pc = new RTCPeerConnection({
  iceServers: [
    {
      urls: ["stun:stun.counterpath.net:3478"] // 可以直接百度找一些開(kāi)放的stun服務(wù)器
    }
  ]
});
// 2. 綁定流
const stream = await getUserMedia({
  video: true,
  audio: true,
});
// 添加媒體軌道 如果 video 和 audio 都為true 則 getTracks 可以獲得兩個(gè)軌道
stream.getTracks().forEach(track => pc[toUser].addTrack(track, stream));

// 3. 監(jiān)聽(tīng)
pc.onnegotiationneeded = ()=>{
  pc
    .createOffer() // 創(chuàng)建 offer sdp
    .then((offer) => {
      // 設(shè)置為本地描述
      return pc.setLocalDescription(offer);
    })
    .then(() => {
      // 定向轉(zhuǎn)發(fā) sdp
      socket.emit('sdp', {
        type: 'sender',
        value: pc.localDescription
      });
    });
}
pc.onicecandidate = (ev)=>{
  // 轉(zhuǎn)發(fā) ice-candidate
  socket.emit('ice-candidate', {
    type: 'sender',
    value: ev.candidate,
  });
}
pc.ontrack = (ev)=>{
  // 這里可以的得到對(duì)方的流
  let stream = ev.streams[0];
}

// 監(jiān)聽(tīng) ice 和 sdp
socket.on('ice-candidate', (data)=>{
  if(data.type === 'receive'){
    const candidate = new RTCIceCandidate(data.value);
    pc.addIceCandidate(candidate)
  }
});
socket.on('sdp', (data)=>{
  if(data.type === 'receive'){
    const sdp = new RTCSessionDescription(data.value);
    pc.setRemoteDescription(sdp);
  }
});

接收方

// ************
// 接收方
// ************

//  建立rct連接 
const pc = new RTCPeerConnection({
  iceServers: [
    {
      urls: ["stun:stun.counterpath.net:3478"] // 可以直接百度找一些開(kāi)放的stun服務(wù)器
    }
  ]
});

socket.on('sdp', async (data)=>{
  if(data.type === 'sender'){
    const sdp = new RTCSessionDescription(data.value);
    pc.setRemoteDescription(sdp);
    //  綁定流
    const stream = await getUserMedia({
      video: true,
      audio: true,
    });
    // 添加媒體軌道 如果 video 和 audio 都為true 則 getTracks 可以獲得兩個(gè)軌道
    stream.getTracks().forEach(track => pc[toUser].addTrack(track, stream));

    // 監(jiān)聽(tīng)
    pc.onnegotiationneeded = ()=>{
      pc
        .createAnswer() // 創(chuàng)建 offer sdp
        .then((answer) => {
          // 設(shè)置為本地描述
          return pc.setLocalDescription(answer);
        })
        .then(() => {
          // 定向轉(zhuǎn)發(fā) sdp
          socket.emit('sdp', {
            type: 'receive',
            value: pc.localDescription
          });
        });
    }
    pc.onicecandidate = (ev)=>{
      // 轉(zhuǎn)發(fā) ice-candidate
      socket.emit('ice-candidate', {
        type: 'receive',
        value: ev.candidate,
      });
    }
    pc.ontrack = (ev)=>{
      // 這里可以的得到對(duì)方的流
      let stream = ev.streams[0];
    }

  }
});

// 監(jiān)聽(tīng) ice 和 sdp
socket.on('ice-candidate', (data)=>{
  if(data.type === 'sender'){
    const candidate = new RTCIceCandidate(data.value);
    pc.addIceCandidate(candidate)
  }
});

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耙册,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子毫捣,更是在濱河造成了極大的恐慌详拙,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔓同,死亡現(xiàn)場(chǎng)離奇詭異饶辙,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)斑粱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)弃揽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人则北,你說(shuō)我怎么就攤上這事蹋宦。” “怎么了咒锻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)守屉。 經(jīng)常有香客問(wèn)我惑艇,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任滨巴,我火速辦了婚禮思灌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恭取。我一直安慰自己泰偿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布蜈垮。 她就那樣靜靜地躺著耗跛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪攒发。 梳的紋絲不亂的頭發(fā)上调塌,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音惠猿,去河邊找鬼羔砾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛偶妖,可吹牛的內(nèi)容都是我干的姜凄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼趾访,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼态秧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起腹缩,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤屿聋,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后藏鹊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體润讥,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年盘寡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了楚殿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竿痰,死狀恐怖脆粥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情影涉,我是刑警寧澤变隔,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站蟹倾,受9級(jí)特大地震影響匣缘,放射性物質(zhì)發(fā)生泄漏猖闪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一肌厨、第九天 我趴在偏房一處隱蔽的房頂上張望培慌。 院中可真熱鬧,春花似錦柑爸、人聲如沸吵护。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)馅而。三九已至,卻和暖如春进胯,著一層夾襖步出監(jiān)牢的瞬間用爪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工胁镐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留偎血,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓盯漂,卻偏偏與公主長(zhǎng)得像颇玷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子就缆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容