ì°¸ê³ : ì´ ê¸ì í¸ì§ ë° ê²í ê° íìíë¤. ëìì ì¤ ì ìë ë°©ë²ì ì´í´ë³´ì.WebRTCë ìì§ê¹ì§ ì¤íì ì¸ ê¸°ì ì´ë¤. ì¼ë¶ì 기ì ì¤íì´ ìì íê° ëì§ ìì기 ë문ì ê° ë¸ë¼ì°ì ¸ìì ì¬ì©ê°ë¥í í¸íì± ì 보를 íì¸í´ì¼íë¤. ëí, 기ì ì 문ë²ê³¼ í¨í´ë¤ì ì¤íì´ ë°ëë ê²ì²ë¼ ë¸ë¼ì°ì ¸ì ë²ì ì´ ëìì§ë¤ë©´ ë³ê²½ë ì ìë¤.
SummaryWebRTC ë ë¦¬ì¼ íì ìì±, ìì, ë°ì´í° êµíì í ì ìë ìì í p2p 기ì ì´ë¤. ë¤ë¥¸ ê³³ìì ë ¼ìí ê² ì²ë¼ ìë¡ ë¤ë¥¸ ë¤í¸ìí¬ì ìë 2ê°ì ëë°ì´ì¤ë¤ì ìë¡ ìì¹ìí¤ê¸° ìí´ìë, ê° ëë°ì´ì¤ë¤ì ìì¹ë¥¼ ë°ê²¬íë ë°©ë²ê³¼ 미ëì´ í¬ë§· íìê° íìíë¤. ì´ íë¡ì¸ì¤ë¥¼ ìê·¸ëë§ signaling ì´ë¼ ë¶ë¥´ê³ ê° ëë°ì´ì¤ë¤ì ìí¸ê°ì ëìë ìë²(socket.io í¹ì websocketì ì´ì©í ìë²)ì ì°ê²°ìí¨ë¤. ì´ ìë²ë ê° ëë°ì´ì¤ë¤ì´ negotiation(íì) ë©ì¸ì§ë¤ì êµíí ì ìëë¡ íë¤.
ì´ ê¸ìì ì°ë¦¬ë ë ëìê° ì ì ë¤ê°ì ìë°©í¥ì¼ë¡ íì íµíê° ëë ìì ì¸ WebSocket chat(ì¹ìì¼ ë¬¸ì를 ìì±í기 ìí´ ë§ë¤ì´ì¡ì¼ë©°, ë§í¬ë ê³§ íì±í ë ê²ì´ë¤. ìì§ì ì¨ë¼ì¸ì¼ë¡ í ì¤í¸ê° ë¶ê°ë¥íë¤.)ì ìëì´ ëëë¡ ë§ë¤ ìì ì´ë¤. ì´ê²ì ê´í´ ìí ì íì¸í´ ë³´ê±°ë Githubìì ì ì²´ íë¡ì í¸ë¥¼ íì¸í´ë³¼ ì ìë¤.
ì°¸ê³ : ê¹íì ìë í ì¤í¸ ìë² ì½ëë ìì¼ë¡ ê³µë¶í ìì ì½ëë³´ë¤ ìµì ë²ì ì´ë¤. ì´ ê¸ì íì¬ ì ë°ì´í¸ ì§í ì¤ì´ë©°, ê³§ ìë£ë ìì ì´ë¤. ì ë°ì´í¸ê° ìë£ëë¤ë©´ ì´ ê¸ì ì¬ë¼ì§ ê²ì´ë¤.
ì°¸ê³ : ìì¼ë¡ ëì¬ ìì ë¤ì promise 를 ì¬ì©íë¤. ë§ì½ ëê° ì´ê²ì ì 모른ë¤ë©´ ì´ ê¸ì ì½ì´ 보길 ë°ëë¤.
The signaling serverë ëë°ì´ì¤ë¤ ì¬ì´ì WebRTC 커ë¥ì ì ë§ë¤ê¸° ìí´, ì¸í°ë· ë¤í¸ìí¬ìì ê·¸ ëì ì°ê²° ìí¤ë ìì ì í´ì¤ signaling server ê° íìíë¤. ì´ë»ê² ì´ ìë²ë¥¼ ë§ë¤ê³ ì¤ì ë¡ ìê·¸ëë§ ê³¼ì ì´ ì´ë»ê² ëëì§ ì´í´ë³´ì.
ê°ì¥ 먼ì , ìê·¸ëë§ ìë² ìì²´ê° íìíë¤. WebRTCë ìê·¸ëë§ ì ë³´ì ê´í transport ë©ì»¤ëì¦ì ì ìíì§ ìëë¤. ë í¼ì´ë¤ ì¬ì´ìì í´ë¦¬í¬í°ì ë¶ìì´ì²ë¼ ìê·¸ëë§ì ê´ë ¨ë ì ë³´ë¤ì ì ë¬í´ì¤ ì ìë ê²ì´ë©´ WebSocket ì´ë XMLHttpRequest ë ìê´ìë¤.
ì¬ê¸°ì ì¤ìí ì ì ìê·¸ëë§ ìë²ë ìê·¸ëë§ ë°ì´í° ë´ì©ì 몰ë¼ë ëë¤ë ê²ì´ë¤. ë¹ë¡ ì´ê²ì SDP ì´ì§ë§, 몰ë¼ë í° ë¬¸ì ê° ëì§ ìëë¤. ë©ì¸ì§ì ë´ì©ë¤ì ê·¸ì ìê·¸ëë§ ìë²ë¥¼ íµí´ ìëí¸ì¼ë¡ ê°ê¸°ë§ íë©´ëë¤. ì¤ìí ì ì ICE subsystemì´ ì í¸ ë°ì´í°ë¥¼ ë¤ë¥¸ í¼ì´ìê² ë³´ë´ëë¡ ì§ìíë©´, ë¤ë¥¸ í¼ì´ë ì´ ì 보를 ìì íì¬ ìì²´ ICE subsystemì ì ë¬íë ë°©ë²ì ìê³ ìë¤ë ê²ì´ë¤.
Readying the chat server for signalingì´ chat server ë í´ë¼ì´ì¸í¸ì ìë² ì¬ì´ì WebSocket APIì íµí´ JSON stringì¼ë¡ ë°ì´í°ë¥¼ ì ì¡íë¤. ìë²ë ìë¡ì´ ì ì 를 ë±ë¡íë ê², usernameì ì¸í íë ê², ì±í ë©ì¸ì§ë¥¼ ì ì¡íë ê² ë±ë±ì ìì ë¤ì í기 ìí´ ë¤ìí ë©ì¸ì§ íì ë¤ì ë¤ë£¬ë¤. ìê·¸ëë§ê³¼ ICE negotiation ì ìë²ê° ì²ë¦¬í기 ìí´ì ì½ë를 ìì±í´ì¼íë¤. 모ë ë¡ê·¸ì¸ë ì ì ë¤ìê² ë¸ë¡ëìºì¤í íë ê²ì´ ìëë¼, í¹ì í ì ì ìê² ì§ì ë©ì¸ì§ë¥¼ ì ë¬í´ì¼íë¤. ê·¸ë¦¬ê³ ìë²ê° ë°ë¡ ì²ë¦¬í íì ìì´, ìì ë ìíì§ ìì ë©ì¸ì§ íì ë¤ì ì²ë¦¬íë¤. ì´ë¥¼ íµí´ ì¬ë¬ ìë²ë¥¼ ë§ë¤ íììì´ ëì¼í ìë²ë¥¼ ì´ì©íì¬ ìê·¸ë ë©ìì§ë¥¼ ë³´ë¼ ì ìë¤. ì´ ê°ë ì WebRTCê° ìëë¼ WebSocketì ê´í ê°ë ì´ë¤.
ì´ì , WebRTC ìê·¸ëë§ì ì§ìíë chat server를 ë§ë¤ê¸° ìí´ ì´ë»ê² í´ì¼íëì§ ë³´ì. ìì¼ë¡ ëì¤ë ì½ëë¤ì chatserver.js ìì ìë ì½ëì´ë¤.
ì°ì sendToOneUser()
í¨ì를 ì¶ê°íì. ì´ë¦ì´ ë§íë¯, stringified JSON ë©ì¸ì§ë¥¼ í¹ì í ì ì ìê² ë³´ë´ë ê²ì´ë¤.
function sendToOneUser(target, msgString) {
var isUnique = true;
var i;
for (i = 0; i < connectionArray.length; i++) {
if (connectionArray[i].username === target) {
connectionArray[i].sendUTF(msgString);
break;
}
}
}
ì´ í¨ìë ì°ê²°ë ì ì 리ì¤í¸ë¥¼ íì¸íë©´ì í¹ì í usernameì ê°ì§ë ì ì ì ì°¾ê³ , ì´ ì ì ìê² ë©ì¸ì§ë¥¼ ë³´ë¸ë¤. í¨ìì ì¸ìë¡ ë¤ì´ê°ë ë©ìì§ msgString
ì stringified JSON object ì´ë¤. Stringified ê° ìë ì본ì ë©ì¸ì§ object를 ë°ëë¡ í´ë ëì§ë§, JSONì´ ë ì ì©íê³ í¸íë¤. ì´ ë©ì¸ì§ë ì´ë¯¸ stringified ë ìíë¡ í¨ìì ì ë¬ë기 ë문ì, ë ì´ìì ë©ì¸ì§ì ê´í ì²ë¦¬ ìì´ ë©ì¸ì§ë¥¼ ê·¸ëë¡ ë³´ë´ê¸°ë§ íë©´ ëë¤.
ì본 chat demoë í¹ì ì ì ìê² ë©ì¸ì§ë¥¼ ë³´ë´ë ê²ì ì§ìíì§ ìëë¤. Main WebSocket message handler를 ìì í´ì¼ ì´ê²ì´ ê°ë¥íê² ëë©°, 구체ì ì¼ë¡ëconnection.on()
í¨ìì ë§ì§ë§ ë¶ë¶ì ìì íë©´ ëë¤.
if (sendToClients) {
var msgString = JSON.stringify(msg);
var i;
// If the message specifies a target username, only send the
// message to them. Otherwise, send it to every user.
if (msg.target && msg.target !== undefined && msg.target.length !== 0) {
sendToOneUser(msg.target, msgString);
} else {
for (i = 0; i < connectionArray.length; i++) {
connectionArray[i].sendUTF(msgString);
}
}
}
ì´ ì½ëë ë©ì¸ì§ìì target
í¹ì±ì´ ì ìëìëì§ ì²´í¬íë¤. ì´ í¹ì±ì ë©ì¸ì§ë¥¼ ì ë¬íê³ ì¶ì ì¬ëì usernameì¼ë¡ ì ìí ì ìë¤. ë§ì½ target
íë¼ë¯¸í°ê° ì¡´ì¬íë¤ë©´, sendToOneUser()
í¨ì를 ì½íë©´ì ê·¸ ì ì ìê² ë©ì¸ì§ë¥¼ ì ì¡íë¤. ê·¸ë ì§ ìë¤ë©´, 모ë ì ì ìê² ë©ì¸ì§ë¥¼ ë¸ë¡ëì¼ì¤í¸ë¥¼ íë¤.
ìì ìë ì½ëë ë³ëì ìì ì´ íì ìì´ ììì ë©ì¸ì§ íì ë¤ì ë³´ë¼ ì ìë¤. í´ë¼ì´ì¸í¸ë¤ì ì´ì í¹ì í ì ì ìê² unknown íì ì ë©ì¸ì§ë ë³´ë¼ì ìê³ , ìê·¸ëë§ ë©ì¸ì§ë¥¼ ìíë ëë¡ ë³´ë¼ ì ìë¤. 구체ì ì¸ ë´ì©ì ë¤ìì ì´í´ë³´ì.
Designing the signaling protocolì´ì ì°ë¦¬ë ë©ì¸ì§ë¥¼ êµííë ë©ì»¤ëì¦ì ë§ë¤ìë¤. ì´ì ë©ì¸ì§ë¤ì ì´ë»ê² 구ì±í ì§ì ëí íë¡í ì½ì´ íìíë¤. ì´ê²ì ì¬ë¬ ê°ì§ ë°©ë²ì¼ë¡ ê°ë¥íë°, ì¬ê¸°ì ë¤ë£¨ë ê²ì ê·¸ ì¤ íëì ìê·¸ëë§ ë©ì¸ì§ 구조ì´ë¤.
ì°ë¦¬ê° ì ê³µíë ìê·¸ëë§ ìë²ë stringified JSON object ì ê°ì§ê³ í´ë¼ì¸í¸ê°ì ë°ì´í°ë¥¼ ì£¼ê³ ë°ëë¤. ì¦, ì´ê²ì ìê·¸ëë§ ë©ì¸ì§ë¤ì´ JSON formatì¼ë¡ ëì´ìì¼ë©°, ë©ì¸ì§ì type ë± ë©ì¸ì§ë¥¼ ì ì íê² ì²ë¦¬í ì ìëë¡ ì¬ë¬ ì ë³´ë¤ì´ í¬í¨ëì´ ìë¤.
Exchanging session descriptionsìê·¸ëë§ íë¡ì¸ì¤ë¥¼ ììí ë, callì ìì íë ì ì ê° *offer *ë ê²ì ë§ë ë¤. ì´ offerë ì¸ì
ì 보를 SDP í¬ë§·ì¼ë¡ ê°ì§ê³ ìì¼ë©°, 커ë¥ì
ì´ ì´ì´ì§ê¸°ë¥¼ ìíë ì ì (callee)ìê² ì ë¬ëì´ì¼ íë¤. Callee ë ì´ offerì SDP descriptionì í¬í¨íë *answer *ë©ì¸ì§ë¥¼ ë³´ë´ì¼íë¤. ì°ë¦¬ê° ì¬ì©í offer ë©ì¸ì§ë¤ì "video-offer"
ì´ë¼ë íì
ì ì¬ì©í ê²ì´ê³ answer ë©ì¸ì§ë¤ì "video-answer"
íì
ì ë©ì¸ì§ë¥¼ ì¬ì©í ê²ì´ë¤. ì´ ë©ì¸ì§ë¤ì ìëì ê°ì field를 ê°ì§ë¤.
type
ë©ì¸ì§ì íì
ì´ë¼; "video-offer"
ëë "video-answer"
.
name
ë³´ë´ë ì¬ëì username ì´ë¤.
target
ë°ë ì¬ëì usernameì´ë¤. (ë§ì½ callerê° ë©ì¸ì§ë¥¼ ë³´ë¸ë¤ë©´, targetì callee 를 ë»íë¤, vice-versa.)
sdp
커ë¥ì ì local ì 보를 ì¤ëª íë SDP (Session Description Protocol) ì¤í¸ë§(e.g. ìì ìì ê´ì ì¼ë¡ ë³¼ ë, SDPë 커ë¥ì ì remote ì ë³´ì´ë¤.)
í ìì ìì ë í¼ì´ë¤ì ì´ callì ëí´ ì´ë¤ ì½ë±ë¤ê³¼ ì´ë¤ video parameterë¤ì´ ì¬ì©ë ì§ ìê² ëë¤. íì§ë§, ê·¸ë¤ì ì¬ì í 미ëì´ ë°ì´í° ì체를 ì ì¡íë ë°©ë²ì 모른ë¤. ì¬ê¸°ì Interactive Connectivity Establishment (ICE)ê° ì¬ì©ëë¤.
Exchanging ICE candidatesSDP를 ìë¡ êµíí íì, ë í¼ì´ë¤ì ICE candidate(ICE íë³´)ë¤ì êµíí기 ììíë¤. ê° ICE candidateë ë°ì í¼ì´ ì ì¥ìì íµì ì í ì ìë ë°©ë²ì ì¤ëª íë¤. ê° í¼ì´ë ê²ìëë ììëë¡ candidate를 ë³´ë´ê³ 미ëì´ê° ì´ë¯¸ ì¤í¸ë¦¬ë°ì ìì íëë¼ë 모ë ê°ë¥í candidateê° ì ì¡ ìë£ë ëê¹ì§ ê³ì ë³´ë¸ë¤. ë í¼ì´ê° ìë¡ í¸íëë candidate를 ì ìíë¤ë©´, 미ëì´ë íµì ì ììíë¤. ë§ì½ ëì¤ì ë ëì ë°©ë²ì´ ìë¤ë©´(ë ëì ì±ë¥ì ê°ì§ë), ê·¸ ì¤í¸ë¦¼ì íìì ë°ë¼ í¬ë§·ì ë°ê¿ ìë ìë¤.
ë¹ë¡ ì§ê¸ì ì§ìíì§ ìì§ë§, ì´ ê¸°ì ì ì´ë¡ ì ë®ì bandwidthì ì°ê²°ì ëí´ ë¤ì´ê·¸ë ì´ëì ì¬ì©ë ì ìë¤.
ìê·¸ëë§ ìë²ë¥¼ íµí´ ì ë¬ëë ICE candidateë¤ì ê´í ë©ì¸ì§ì íì
ì "new-ice-candidate"
ì´ë©°, ì´ ë©ì¸ì§ë¤ì ìë field를 ê°ì§ë¤.
type
ë©ì¸ì§ íì
: "new-ice-candidate"
.
target
íì¬ íìì ì§í ì¤ì¸ ì¬ëì username. ìê·¸ëë§ ìë²ë ì´ ì ì ìê²ë§ ì§ì ë©ì¸ì§ë¥¼ ë³´ë¸ë¤.
candidate
ì ìë 커ë¥ì ë°©ë²ì ì¤ëª íë SDP candidate string.
ê° ICE ë©ì¸ì§ë¤ì ë ê°ì ì»´í¨í°ë¥¼ ìë¡ ì°ê²°í기 ìí ì ë³´ë¤ì ë§ë¶ì¬ íë¡í ì½(TCP or UDP), IP 주ì, í¬í¸ ëë², 커ë¥ì íì ë±ì ì ìíë¤. ì¬ê¸°ìë NAT í¹ì ë¤ë¥¸ ë³µì¡í ë¤í¸ìí¹ì í¬í¨íë¤.
ì°¸ê³ : ì¤ì. ICE negotiation ëì ëì ì½ëê° í´ì¼í ê²ì ì¤ì§ ICE layerìì ì¸ë¶ë¡ ëê° candidateë¤ì ì ííë ê²ê³¼, icecandidate_event
handlerê° ë¶ë ¸ì ë ìê·¸ëë§ ìë²ë¥¼ íµí´ ê·¸ê²ë¤ì ë¤ë¥¸ í¼ì´ì ë³´ë´ë ê²ì´ë¤. ê·¸ë¦¬ê³ ìê·¸ëë§ ìë²ë¡ë¶í° ICE candidate ë©ì¸ì§ë¥¼ ë°ê³ RTCPeerConnection.addIceCandidate()
를 í¸ì¶íì¬ ëì ICE layerì ê·¸ë¤ì ì ë¬íë¤. ê·¸ê² ë¿ì´ë¤. ì íí 무ìì íëì§ ì기 ì ê¹ì§, ë ì´ì ê¹ì´ ìê°íì§ ë§ì!
ëì ìê·¸ëë§ ìë²ê° ì´ì í´ì¼í ì¼ì ìì²ë ë©ì¸ì§ë¥¼ ë³´ë´ë ê²ì´ë¤. ë¶ê°ì ì¼ë¡ login/authentication ê°ì 기ë¥ë¤ì´ íìí ìë ìëë°, ìì¸í ë´ì©ì ë¬ë¼ì§ ì ìë¤.
Signaling transaction flowìê·¸ëë§ ì ë³´ë ì°ê²°í ë í¼ì´ë¤ ì¬ì´ìì êµíëë¤. ì주 기ì´ì ì¸ ìì¤ìì ì´ë¤ ë©ì¸ì§ë¤ì´ ëê° ë구ìê² ì ì ë¬í´ì¼íëì§ ë³´ì.
ìê·¸ëë§ íë¡ì¸ì¤ë ë¤ìí ë¶ë¶ìì ë¤ìê³¼ ê°ì ë©ìì§ êµíì í¬í¨íë¤. ê° ì ì ì ì±í ìì¤í ì ì¹ ì í리ì¼ì´ì ì¸ì¤í´ì¤, ê° ì ì ì ë¸ë¼ì°ì , ìê·¸ëë§ ìë² ê·¸ë¦¬ê³ í¸ì¤í ì¹ ìë² ë±.
Naomiì Priyaë ì±í ìíí¸ì¨ì´ë¥¼ ì¬ì©í´ ëíì ì°¸ì¬íê³ Naomië ë ì¬ì´ì ìì íµí를 íê¸°ë¡ ê²°ì íë¤. ë¤ì íë ì´ë²¤í¸ë¤ì´ ë°ìíë ê³¼ì ì´ë¤.
ê³§ ë ìì¸í ì¤ëª ì ë³¼ ì ìë¤.
ICE candidate exchange processê° í¼ì´ë¤ì ICE layerìì candidateë¤ì ë³´ë´ê¸° ììí ë, ë¤ì 그림과 ê°ì êµíì´ ì¼ì´ëë¤.
ê° í¼ì´ë¤ì candidate ë¤ì ì ì¡íê³ , ì¤ë¹ê° ëë©´ ë°ì candidate ë¤ì ì²ë¦¬íë¤. Candidateë¤ì ì í¼ì´ë¤ì´ ëìí ëê¹ì§ ê³ì êµíëë©°, 미ëì´ê° ì¡ìì ëëë¡ ë§ë ë¤. "ICE exchange"ì ìì¸¡ì´ êµëë¡ ì ììíë ê²ì ì미íì§ ìëë¤. ì¬ë°ë¥´ê² ìëí ê²½ì°, ê° í¼ì´ë¤ì 모ë ìì§ëê±°ë ìë¡ ëìí ëê¹ì§ ìëë°©ìê² ì ìí candidate ë¤ì ê³ì ì ì¡íë¤.
ë§ì½ ì¡°ê±´ë¤ì´ ë°ëë¤ë©´, ì를ë¤ì´ ë¤í¸ìí¬ ì»¤ë¥ì ì´ ì íëë©´, íë í¹ì ì í¼ì´ë¤ì ë®ì bandwidthì 미ëì´ í´ìëë¡ ë°ê¾¸ê±°ë ë¤ë¥¸ ì½ë±ì ì¬ì©íìê³ ì ìí ê²ì´ë¤. ë¤ì candidate êµíìì ì í¼ì´ 모ë ìë¡ì´ í¬ë§·ì ëìíë¤ë©´, ë¤ë¥¸ 미ëì´ í¬ë§· í¹ì ë¤ë¥¸ ì½ë±ì¼ë¡ ë°ë ìë ìë¤.
ë¶ê°ì ì¼ë¡ ë§ì½ ICE layer ë´ë¶ì íë¡ì¸ì¤ë¥¼ ë ìì¸í ì´í´íê³ ì¶ë¤ë©´ RFC 5245: Interactive Connectivity Establishment,section 2.6 ("Concluding ICE") 를 참조í´ë¼. ICE layerê° ì¤ë¹ ëìë§ì candiateë¤ì´ êµíëê³ ë¯¸ëì´ë¤ì íµì ë기 ììíë¤ë ê²ì 기ìµí´ë¼. ì´ ëª¨ë ê²ì ë¤ìì ììì ëìê°ë¤. ì°ë¦¬ì ìí ì ê·¸ì ìê·¸ëë§ ìë²ë¥¼ íµí´ candidateë¤ì ìë¡ìê² ë³´ë´ë ê²ì´ë¤.
The client applicationì§ê¸ë¶í° ììì ì¤ëª í ê°ë ë¤ì ìí ì½ë를 íµí´ì ìì¸í ë°°ìë³´ì.
ì´ë¤ ìê·¸ëë§ íë¡ì¸ì¤ë ì§ íµì¬ì ë©ì¸ì§ í¸ë¤ë§ì ìë¤. Websocketì ìê·¸ëë§ì ê¼ ì¬ì©í íìë ìì§ë§, ì¼ë°ì ì¸ ì루ì ì¼ë¡ ì°ì¸ë¤. ë¤ë¥¸ ì루ì ë ì¶©ë¶í ë¹ ë¥´ê³ ê°ì 결과를 ë³¼ ì ìë¤.
Updating the HTMLí´ë¼ì´ì¸í¸ë ë¹ëì¤ë¥¼ íìí ê³µê°ì´ íìíë¤. 2ê°ì videoì ì í를 걸 button ì ì ìí HTML ì½ëì´ë¤.
<div class="flexChild" id="camera-container">
<div class="camera-box">
<video id="received_video" autoplay></video>
<video id="local_video" autoplay muted></video>
<button id="hangup-button" onclick="hangUpCall();" disabled>Hang Up</button>
</div>
</div>
ìì ìë page structureì <div>
í그를 ì´ì©íê³ CSS ì¬ì©ì íì©í¨ì¼ë¡ì¨ íì´ì§ ë ì´ìì ì 체를 구ì±íë¤. ì¬ê¸°ìë ë ì´ììì ê´í ìì¸í ë´ì©ì ì¤íµíì§ë§, ìì ì½ëê° ì´ë»ê² ëìê°ëì§ íì¸í´ë³´ì. take a look at the CSS on Github. ëê°ì <video>
ì¤ íëë ëì self videoì´ê³ ë¤ë¥¸ íëë ìëë°©ì video를 ìí ììì´ë¤.
id
ê° "received_video
" ì¸ <video>
elementë ì°ê²°ë ìëë°©ì¼ë¡ë¶í° ìì ëë ë¹ëì¤ë¥¼ ë³´ì¬ì£¼ë ê³³ì´ë¤. autoplay
attributeë ë¹ëì¤ê° ëë¬í기 ììíë©´ ì¦ì ì¬ììí¤ë ìí ì íë¤. ì´ê²ì ë°ë¡ ì¬ìì ê´ë ¨ë ì½ë를 ì²ë¦¬í íì를 ìì ì¤ë¤. id
ê° "local_video
" ì¸ <video>
elementìë ëì ì¹´ë©ë¼ì ììì´ ëì¤ê²ëë¤. muted
attributeë ëì ë¡ì»¬ ì¤ëì¤ë¥¼ ììê±°íë¤.
ë§ì§ë§ì¼ë¡, íµí를 ëì ì ìë id
ê° "hangup-button
"ì¸ <button>
ì ë¹íì±í ë ìí(ì무 ì íë ì°ê²°ëì§ ìì default ìí)ë¡ êµ¬ì±ëë¤. ê·¸ë¦¬ê³ ì´ ë²í¼ì í´ë¦ìì hangUpCall()
í¨ìê° ì¤í ëë¤. ì´ í¨ìì ìí ì íì¬ ì°ê²°ë callì ëê³ ë¤ë¥¸ í¼ì´ìê² ì°ê²°ì ëì¼ë¼ë ë©ì¸ì§ë¥¼ ì ë¬íë¤.
ì´ë»ê² ëìê°ëì§ ì기 ì½ê² í기 ìí´ ê° ê¸°ë¥ë³ë¡ ì½ë를 ëëìë¤. ì´ ì½ëì ë©ì¸ ë¶ë¶ì connect()
í¨ì ìì ìë¤. ì´ í¨ì ììì 6503 í¬í¸ë¡ WebSocket
serverì ì°ê²°íë©°, JSON object formatì ë©ì¸ì§ë¥¼ ë°ê¸° ìí handler를 ì¤ì íë¤. ~~ì´ ì½ëë ì¼ë°ì ì¼ë¡ ì´ì ì²ë¼ 문ì ì±í
ë©ì¸ì§ë¥¼ ì²ë¦¬íë¤.~~
ì½ë ì ë°ì 걸ì³ì ìê·¸ëë§ ìë²ì ë©ì¸ì§ë¥¼ ë³´ë´ê¸° ìí´ sendToServer()
í¨ì를 í¸ì¶íë¤. ì´ í¨ìë WebSocket 커ë¥ì
ì ì´ì©íì¬ ìëíë¤.
function sendToServer(msg) {
var msgJSON = JSON.stringify(msg);
connection.send(msgJSON);
}
ì ë¬ë ë©ì¸ì§ objectë JSON.stringify()
í¨ìì ìí´ JSON stringì¼ë¡ ë°ëë¤. ê·¸ í, WebSocket 커ë¥ì
ì send()
í¨ì를 íµí´ ìë²ë¡ ì ë¬ëë¤.
"userlist"ì ê´í ì½ëë handleUserlistMsg()
í¨ìì ìë¤. ì¼ìª½ ì±í
í¨ëì ë³´ì¬ì§ë ì ì 리ì¤í¸ì ìë ê° ì°ê²°ë ì ì ë§ë¤ handler 를 걸ì´ì¤ë¤. ì´ í¨ìë (ì¨ë¼ì¸ ìíì¸ ì ì ë¤ì usernameì ë°°ì´ë¡ ì ì¥íê³ ìë) users
property를 ê°ì§ê³ ìë ë©ì¸ì§ object를 ë°ëë¤. ì´í´í기 ì½ëë¡ ì¬ë¬ ì¹ì
ë¤ìì ì´ ì½ë를 ì´í´ ë³´ê² ë¤.
function handleUserlistMsg(msg) {
var i;
var listElem = document.getElementById("userlistbox");
while (listElem.firstChild) {
listElem.removeChild(listElem.firstChild);
}
// â¦
listElem
ë³ì를 íµí´ usernameë¤ì 리ì¤í¸ì¸ <ul>
ì 참조íë¤. ê·¸ë° ë¤ìì ê° child element를 íëì© ì ê±°íë©´ì 목ë¡ì ë¹ì´ë¤ .
ì°¸ê³ : ëª ë°±í, ë°ë ëë§ë¤ ì ì²´ 리ì¤í¸ë¥¼ ìë¡ ë§ëë ê²ë³´ë¤, ê°ê°ì¸ì ì¶ê° ë° ì ê±° í ì ë°ì´í¸íë ê²ì´ ë í¨ì¨ì ì´ë¤. ê·¸ë¬ë, ìì ì´ë¯ë¡ ë¨ìíê² íê² ë¤.
ê·¸ í, ìë¡ì´ user 리ì¤í¸ë¥¼ ë§ë ë¤.
// â¦
for (i=0; i < msg.users.length; i++) {
var item = document.createElement("li");
item.appendChild(document.createTextNode(msg.users[i]));
item.addEventListener("click", invite, false);
listElem.appendChild(item);
}
}
ë¤ìì¼ë¡ (ì±í
ìë²ì) íì¬ ì°ê²°ë ê° ì ì ë¤ ê°ê°ì ëíë´ë <li>
elementë¤ì DOMì ì¶ê°íë¤. ê·¸ë° ë¤ìì, usernameì´ í´ë¦ ëìì ë invite()
í¨ì를 ì¤íìí¤ë listenerì ì¶ê°íë¤. ì´ í¨ì ì´ê²ì ë¤ë¥¸ ì ì ìê² callì íë process를 ììíë¤.
íµí를 íê³ ì¶ì ì ì ì usernameì í´ë¦ì íë©´, click
eventì handlerì¸invite()
í¨ìê° ì¤íëë¤.
var mediaConstraints = {
audio: true, // We want an audio track
video: true, // ...and we want a video track
};
function invite(evt) {
if (myPeerConnection) {
alert("You can't start a call because you already have one open!");
} else {
var clickedUsername = evt.target.textContent;
if (clickedUsername === myUsername) {
alert(
"I'm afraid I can't let you talk to yourself. That would be weird.",
);
return;
}
targetUsername = clickedUsername;
createPeerConnection();
navigator.mediaDevices
.getUserMedia(mediaConstraints)
.then(function (localStream) {
document.getElementById("local_video").srcObject = localStream;
myPeerConnection.addStream(localStream);
})
.catch(handleGetUserMediaError);
}
}
ê°ì¥ 먼ì í´ì¼í ì¼ì ë¹ ë¥´ê² ì¬ë¬ ìíë¤ì ì ê²íë ê²ì´ë¤. ì ì ê° ì´ë¯¸ callì ì´ìëì§, í¹ì ì ì ê° ìì ìê² callì ì ì²íëì§ ë±, ì´ ì¼ì´ì¤ë¤ìë ìë¡ì´ callì ìëí ì´ì ê° ìë¤. ë°ë¼ì ì callì íì§ ëª»íëì§ alert()
를 íµí´ ì¤ëª
íë¤.
ê·¸ ë¤ìì callì íë ¤ë ì ì ì ì´ë¦ì targetUsername
ë³ì ìì ë£ê³ createPeerConnection()
í¨ì를 ì¤íìí¨ë¤. ì´ í¨ìë RTCPeerConnection
ì 기본ì ì¸ êµ¬ì±ê³¼ 기ë¥ì ìííë¤.
RTCPeerConnection
ì´ ìì±ëë©´, Navigator.mediaDevices.getUserMedia
í¨ì를 íµí´ ì ì ì ì¹´ë©ë¼ì ë§ì´í¬ì ê¶íì ìì²íë¤. ì¹´ë©ë¼ì ë§ì´í¬ìì ëì¤ë ë¡ì»¬ ì¤í¸ë¦¼ì ë¡ì»¬ ë¹ëì¤ previewì srcObject
propertyì ì¤ì íë¤. ê·¸ë¦¬ê³ <video>
elementê° ìëì¼ë¡ ë¤ì´ì¤ë ë¹ëì¤ë¥¼ ì¬ìíëë¡ êµ¬ì±ëì기 ë문ì, streamì ë¡ì»¬ preview boxìì ì¬ìì ììíë¤.
ê·¸ ë¤ìì RTCPeerConnection
ì streamì ì¶ê°í기 ìí´ myPeerConnection.addStream()
í¨ì를 ì¤ííë¤. WebRTC 커ë
ì
ì´ ìì í ì¤ë¹ëì§ ììëë¼ë WebRTC 커ë¥ì
ì streamì ë³´ë´ê¸° ììíë¤.
ë§ì½ local media streamì ê°ì ¸ì¤ë ëì ìë¬ê° ë°ìíë¤ë©´, catch
clauseê° handleGetUserMediaError()
í¨ì를 ë¶ë¬ íìì ë°ë¼ ì ì ìê² ì ì í ìë¬ ë©ì¸ì§ë¥¼ ë³´ì¬ì¤ ê²ì´ë¤.
getUserMedia()
ì ìí´ ë¦¬í´ë promise
ê° ì¤í¨ë¡ ëëë©´, handleGetUserMediaError()
í¨ìê° ì¤íëë¤.
function handleGetUserMediaError(e) {
switch (e.name) {
case "NotFoundError":
alert(
"Unable to open your call because no camera and/or microphone" +
"were found.",
);
break;
case "SecurityError":
case "PermissionDeniedError":
// Do nothing; this is the same as the user canceling the call.
break;
default:
alert("Error opening your camera and/or microphone: " + e.message);
break;
}
closeVideoCall();
}
ìë¬ ë©ì¸ì§ë 모ë ì¼ì´ì¤ ì¤ íëë§ íìëë¤. ì´ ìì ìì callì ì·¨ìíë ê±°ì ê°ì´, 미ëì´ íëì¨ì´ì ì ê·¼ ê¶íì ê±°ë¶íë ê²ì ëí´ ë°ìíë ìë¬ë¤( "SecurityError"
ì"PermissionDeniedError"
)ì 무ìíë¤.
Streamì ê°ì ¸ì¤ë ê²ì ì¤í¨íë ì´ì ì ê´ê³ ìì´, RTCPeerConnection
ì ë«ê¸° ìí´ closeVideoCall()
function를 ë¶ë¥¸ë¤. ê·¸ë¦¬ê³ callì í기 ìí´ í ë¹ë 리ìì¤ë¤ì ë°ë©íë¤. ì´ ì½ëë ì¼ë¶ë¶ë§ ì¤íë callì ìì íê² ì²ë¦¬í ì ìëë¡ ì¤ê³ëìë¤.
createPeerConnection()
í¨ìë callerì calleeìì WebRTC 커ë¥ì
ì ê° ì¢
ì ì ëíë´ë RTCPeerConnection
object를 ìì±íëë° ì¬ì©ëë¤. Callerë invite()
í¨ì를 íµí´, calleeë handleVideoOfferMsg()
ì ìí´ ì¤íëë¤.
ì´ê²ì ìë¹í ëª ë£íë¤:
var myHostname = window.location.hostname;
function createPeerConnection() {
myPeerConnection = new RTCPeerConnection({
iceServers: [ // Information about ICE servers - Use your own!
{
urls: "turn:" + myHostname, // A TURN server
username: "webrtc",
credential: "turnserver"
}
]
});
// â¦
ì¹ìë²ì ê°ì í¸ì¤í¸ì STUN/TURN ìë²ë¥¼ ëë¦¬ê³ ì기 ë문ì, STUN/TURN ìë²ì ëë©ì¸ ì´ë¦ì location.hostname
ì ì¬ì©íì¬ ì¤ì íë¤. ë§ì½ ë¤ë¥¸ ìë²ì STUN/TURN ìë²ë¥¼ ì¬ì©íë¤ë©´ urls ê°ì ê·¸ ìë²ë¡ ë°ê¿ì£¼ë©´ ëë¤.
RTCPeerConnection
ì ë§ë¤ ë, callì 구ì±íë íë¼ë¯¸í°ë¤ì ëª
ìí´ì¤ì¼íë¤. ê°ì¥ ì¤ìí ê²ì STUN/TURN ìë²ì 리ì¤í¸(ICE layerìì callerì calleeì ê²½ë¡ë¥¼ ì°¾ëë° ì¬ì©ëë ìë²)를 ë´ê³ ìë iceServers
ì´ë¤. (주ì. ì¹ìì¼ì ì´ì©í ìê·¸ëë§ ìë²ì ì í ë¤ë¥¸ ê°ë
ì´ë¤). WebRTCë ë í¼ì´ê° ë°©íë²½ì´ë NAT ë¤ì ì¨ì´ ìì´ë, ê° í¼ì´ë¤ì ìë¡ ì°ê²°ë ì ìëë¡ í¼ì´ê° ì°ê²° ê²½ë¡ë¥¼ ì°¾ì주ë íë¡í ì½(STUN, TURN)ì ì¬ì©íë¤.
ì°¸ê³ : ì§ì ë§ë í¹ì ì¬ì©í ê¶íì ê°ì§ê³ ìë STUN/TURN ìë²ë¥¼ ì¬ì©í´ì¼ íë¤.
iceServers
parameterë objectì ë°°ì´ì´ê³ ê°ê°ì STUN/TURN ìë²ì URLì¸ urls
field를 무조건 í¬í¨íë¤. ìì ìì, ICE layerìì ë¤ë¥¸ í¼ì´ë¥¼ ì°¾ì ì°ê²° ìí¤ê¸° ìí ìë²ë¥¼ ì ê³µíë¤. ì´ ìë²ë TURN ìë²ì´ë©°, Web ìë²ì ê°ì hostnameìì ëìê°ë¤. TURN ìë²ì descriptionì username
ê³¼credential
fieldì ê°ê° usernameê³¼ password ì 보를 íì í¬í¨ìì¼ì¼íë¤ë ê²ì ì ìí´ë¼.
RTCPeerConnection
ì´ ìì±ëë©´, ì¤ìí ì´ë²¤í¸ë¤ì ìí handler를 ì¤ì í´ì¼íë¤.
// â¦
myPeerConnection.onicecandidate = handleICECandidateEvent;
myPeerConnection.onaddstream = handleAddStreamEvent;
myPeerConnection.onremovestream = handleRemoveStreamEvent;
myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
}
ìì ìë ì´ë²¤í¸ í¸ë¤ë¬ ì¤ ì²ì ë ê°ë íìì´ë¤. WebRTCë¡ ì¤í¸ë¦¬ë°ë 미ëì´ì ê´ë ¨ë ê²ë¤ì ë¤ë£¨ê¸°ìí´ ë í¸ë¤ë¬ë¥¼ ì¤ì í´ì¼íë¤. removestream
eventë ì¤í¸ë¦¬ë°ì´ ì¤ë¨ë ê²ì ê°ì§íëë° ì ì©íë¤. ë°ë¼ì ìë§ ì´ê²ë ì¬ì©íê² ë ê²ì´ë¤. ë¨ì ìë 4ê°ë íìì ì¸ ê²ì ìëë, ì§ì ì¬ì©í´ë³´ì. ì´ê²ë¤ ì¸ìë ë¤ë¥¸ ì´ë²¤í¸ë¤ì ì¬ì©í ì ìì¼ë ì¬ê¸°ììë ë¤ë£¨ì§ ìê² ë¤. ê° í¸ë¤ë¬ì ê´í ìì½ ì¤ëª
ì´ë¤.
RTCPeerConnection.onicecandidate
ë¡ì»¬ ICE layerë ìê·¸ëë§ ìë²ë¥¼ íµí´ ë¤ë¥¸ í¼ì´ì ICE candidate를 ì ì¡íê³ ì í ë, ëì icecandidate
event handler를 í¸ì¶íë¤.
RTCPeerConnection.onaddstream
addstream
event를 ìí ì´ í¸ë¤ë¬ë ëì 커ë¥ì
ì remote streamì´ ì¶ê°ë ê²ì ìë ¤ì£¼ê¸° ìí´, ë¡ì»¬ WebRTC layerì ìí´ ë¶ë ¤ì§ë¤. ì를ë¤ì´, ì´ê²ì ë¤ì´ì¤ë streamì elementì ì°ê²°ìì¼ ëì¤íë ì´ ëê² ë§ë¤ ë ì¬ì©ëë¤. ë ìì¸í ë´ì©ì Receiving new streams ì 참조í´ë¼.
RTCPeerConnection.onremovestream
커ë¥ì
ìì remoteê° streamì ì ê±°í ë, onaddstream
ì ë°ëì¸ onremovestreamì
removestream
eventì ì²ë¦¬í기ìí´ ì¤íëë¤.
RTCPeerConnection.oniceconnectionstatechange
ICE 커ë¥ì
ì ìí ë³ê²½ì ì리기ìí´ ICE layerê° iceconnectionstatechange
event 를 ë³´ë¸ë¤. ì´ê²ì íµí´ 커ë¥ì
ì´ ì¤í¨íê±°ë ëì´ì§ë ê²ì ì ì ìë¤. ì´ ê²ì ëí ìì 를 ìëì ICE connection state ìì ë³¼ ê²ì´ë¤.
RTCPeerConnection.onicegatheringstatechange
íëì ìíìì ë¤ë¥¸ ìí(ì를ë¤ì´, candidate를 모ì¼ê¸° ììíê±°ë negotiationì´ ëë¬ì ë)ë¡ ICE agentì candidate ìì§ íë¡ì¸ì¤ê° ë³íë©´, ICE layerë icegatheringstatechange
event를 ë³´ë¸ë¤. ìëì ICE gathering state ì 참조í´ë¼.
RTCPeerConnection.onsignalingstatechange
ìê·¸ëë§ íë¡ì¸ì¤ì stateê° ë°ëê² ë ë, WebRTC ì¸íë¼ë ëìê² signalingstatechange
message를 ë³´ë¸ë¤. Signaling state ìì ì½ë를 ë³¼ ì ìë¤.
RTCPeerConnection.onnegotiationneeded
ì´ í¨ìë WebRTC ì¸íë¼ê° session negotiation íë¡ì¸ì¤ë¥¼ ìë¡ ììí´ì¼í ëë§ë¤ ë¶ë¦°ë¤. ì´ê²ì ì¼ì calleeìê² offer를 ìì± í ì ë¬íê³ , ì°ë¦¬ìê² ì°ê²°ì í ê²ì¸ì§ 물ì´ë³´ë ê²ì´ë¤. ì´ë»ê² ì²ë¦¬íëì§ Starting negotiation 를 참조í´ë¼.
Callerê° ìì ì RTCPeerConnection
ê³¼ media streamì ìì±íê³ Starting a callìì ë³´ì´ë ê²ì²ë¼ 커ë¥ì
ì ì¶ê°íë©´, ë¸ë¼ì°ì ¸ë ë¤ë¥¸ í¼ì´ì 커ë¥ì
ì´ ì¤ë¹ê° ë ë negotiationneeded
event를 íì±í ìí¬ ê²ì´ë¤. ë°ìë ì´ë²¤í¸ë¥¼ í¸ë¤ë§íë ì½ëì´ë¤.
function handleNegotiationNeededEvent() {
myPeerConnection
.createOffer()
.then(function (offer) {
return myPeerConnection.setLocalDescription(offer);
})
.then(function () {
sendToServer({
name: myUsername,
target: targetUsername,
type: "video-offer",
sdp: myPeerConnection.localDescription,
});
})
.catch(reportError);
}
Negotiation íë¡ì¸ì¤ë¥¼ ììí기 ìí´, ì°ë¦¬ê° ì°ê²°íê³ ì íë í¼ì´ìê² SDP offer를 ìì±íê³ ì ì¡í´ì¼íë¤. ì´ offerë 커ë¥ì
ì ë¡ì»¬ë¡ ì¶ê°í media stream ì ë³´(callì ë¤ë¥¸ í¼ì´ìê² ì ë¬íê³ ì¶ì ë¹ëì¤)ì ICE layerì ìí´ ë¯¸ë¦¬ 모ì ëì ICE candidates ì ë³´ë¤ì í¬í¨í´, 커ë¥ì
ì ì§ìëë êµ¬ì± ëª©ë¡ë¤ì í¬í¨íë¤. myPeerConnection.createOffer()
를 í¸ì¶í¨ì¼ë¡ì¨ ì´ offer를 ìì±íë¤. ì´ ê²ì´ ì±ê³µíë¤ë©´(promiseìì fulfillëë©´), myPeerConnection.setLocalDescription()
ì¼ë¡ ìì±ë offer ì 보를 ì ë¬íë¤.myPeerConnection.setLocalDescription()
ì 커ë¥ì
ìì ìì ì 미ëì´ êµ¬ì± ìíë ì°ê²° ì ë³´ë¤ì 구ì±íë¤.
ì°¸ê³ : 기ì ì ì¼ë¡ ë§íìë©´, createOffer()
ì ìí´ ë¦¬í´ëë blobì RFC 3264 offer ì´ë¤.
setLocalDescription()
ì´ ìë£ëì´ promise를 리í´íë©´, description ì´ ì í¨íê³ ì¸í
ëììì ì ì ìë¤. ê·¸ ì´íì local descriptionì í¬í¨íë ìë¡ì´ "video-offer"
message를 ë§ë¤ì´ ìê·¸ëë§ ìë²ë¥¼ íµí´ ë¤ë¥¸ í¼ì´ìê² ì ì¡íë¤. ì´ offerë ë¤ìê³¼ ê°ì ë´ì©ì ê°ì§ë¤.
type
ë©ì¸ì§ì íì
ì "video-offer"
.
name
callerì username.
target
callì íê³ ì íë userì name.
sdp
offerì ê´í ì¤ëª ì íë SDP blob.
createOffer()
ì´ë ë¤ë¥¸ fulfillment í¸ë¤ë¬ìì ìë¬ê° ë°ìíë¤ë©´, reportError()
í¨ìê° ì¤íëì´ ìë¬ë¥¼ ë³´ê³ íë¤.
setLocalDescription()
ì fulfillment í¸ë¤ë¬ê° ì¤íëë©´, ICE agentë icecandidate
eventë¤ì ì²ë¦¬í기 ììíë¤.
ì´ì ë¤ë¥¸ í¼ì´ì íìì í ê²ì´ë¤. ë¤ë¥¸ í¼ì´ë ì°ë¦¬ì offer를 ë°ì ê²ì´ê³ , handleVideoOfferMsg()
ì ì ë¬íë¤. Calleeìê² "video-offer"
messageê° ëì°© íì ëì ì´ì¼ê¸°ë¥¼ ê³ìí´ë³´ì.
offerê° ëì°©í ë, calleeì handleVideoOfferMsg()
í¨ìê° ì¤íëê³ , offer를 í¬í¨í "video-offer"
message를 ë°ì ê²ì´ë¤. ì´ ì½ëë 2ê°ì§ë¥¼ í´ì¼íë¤. 첫째, ì기 ìì ì RTCPeerConnection
ê³¼ media streamì ìì±í´ì¼ íë¤. ëë²ì§¸, ë°ì offer를 ë¶ìíê³ ì ì´ì ëí answer를 ë§ë¤ì´ ë³´ë´ì¼íë¤.
function handleVideoOfferMsg(msg) {
var localStream = null;
targetUsername = msg.name;
createPeerConnection();
var desc = new RTCSessionDescription(msg.sdp);
myPeerConnection.setRemoteDescription(desc).then(function () {
return navigator.mediaDevices.getUserMedia(mediaConstraints);
})
.then(function(stream) {
localStream = stream;
document.getElementById("local_video").srcObject = localStream;
return myPeerConnection.addStream(localStream);
})
// â¦
ì´ ì½ëë Starting a callì ìë invite()
í¨ìì ë§¤ì° ë¹ì·íë¤. 먼ì , createPeerConnection()
í¨ì를 ì´ì©í´ì RTCPeerConnection
를 ìì±íê³ êµ¬ì±íë¤. ê·¸ íì, "video-offer"
messageë¡ë¶í° ì»ì SDP offer를 ê°ì§ê³ callerì session descriptionì ëíë´ë RTCSessionDescription
object를 ìì±íë¤.
ê·¸ íì, session descriptionì myPeerConnection.setRemoteDescription()
ìì¼ë¡ ì ë¬ëë¤. ì´ë¥¼ íµí´, ë°ì offer를 callerì session ì ë³´ë¡ ì ì¥íë¤. ì¤ì ì ì±ê³µíë¤ë©´, promise fulfillment handler(then()
clause)ì calleeì ì¹´ë©ë¼ì ë§ì´í¬ì ì ê·¼íê³ streamì ì¤ì íë ë± ì´ì ì invite()
ìì 본 ê²ê³¼ ê°ì íë¡ì¸ì¤ë¥¼ ììíë¤.
local streamì´ ìëíë¤ë©´, ì´ì SDP answer를 ë§ë í callerìê² ë³´ë´ì¼ íë¤.
.then(function() {
return myPeerConnection.createAnswer();
})
.then(function(answer) {
return myPeerConnection.setLocalDescription(answer);
})
.then(function() {
var msg = {
name: myUsername,
target: targetUsername,
type: "video-answer",
sdp: myPeerConnection.localDescription
};
sendToServer(msg);
})
.catch(handleGetUserMediaError);
}
RTCPeerConnection.addStream()
ì´ ì±ê³µì ì¼ë¡ ìë£ëìë¤ë©´, ê·¸ ë¤ì fulfillment handlerê° ì¤íë ê²ì´ë¤. SDP answer stringì ë§ë¤ê¸° ìí´ myPeerConnection.createAnswer()
를 ì¤ííë¤. 커ë¥ì
ìì calleeì ë¡ì»¬ descriptionì ì¤ì í기 ìí´ myPeerConnection.setLocalDescription
ì ìì±í SDP를 ì ë¬íë¤.
ìµì¢
answerë callerìê² ë³´ë´ì ¸ì, ì´ë»ê² calleeìê² ë¿ì ì ìëì§ ìê²í´ì¤ë¤. "video-answer"
messageì sdp
propertyì calleeì answer를 í¬í¨íê³ , callerìê² ì´ ë©ì¸ì§ë¥¼ ì ë¬íë¤.
ìë¬ê° ë°ìíë©´ handleGetUserMediaError()
ì¼ë¡ ì ë¬ëê³ , Handling getUserMedia() errorsì ì ì¤ëª
ëì´ ìë¤.
ì°¸ê³ : callerì ë§ì°¬ê°ì§ë¡ setLocalDescription()
fulfillment handlerê° ì¤íëë©´, ë¸ë¼ì°ì ¸ë calleeê° ë°ëì ì²ë¦¬í´ì¼íë icecandidate
eventë¤ì ì²ë¦¬í기 ììíë¤.
callerê° calleeë¡ë¶í° answer를 ë°ì¼ë©´ 모ë ê²ì´ ëë¬ë¤ê³ ìê°í ì ìì§ë§, ê·¸ë ì§ ìë¤. ë·ë¨ ììë ê° í¼ì´ë¤ì ICE agentë¤ì´ ì´ì¬í ICE candidate messageë¤ì êµííë¤. 미ëì´ íµì ì´ ì´ë»ê² ì°ê²°ë ì ìëì§ì ëí ë°©ë²ë¤ì ì릴 ëê¹ì§, ê° í¼ì´ë¤ì ìëë°©ìê² ê³ìí´ì candidateë¤ì ë³´ë¸ë¤. ì´ candidateë¤ì ëì ìê·¸ëë§ ìë²ë¥¼ íµí´ì ì ì¡ëì´ì¼ íë¤. ICEë ëì ìê·¸ëë§ ìë²ì ëí´ ëª¨ë¥´ê¸° ë문ì, ëë icecandidate
event를 ìí í¸ë¤ë¬ë¥¼ ë¶ë¬ì ì ì¡ë candidate ë¤ì ëì ì½ëë¡ ì§ì ì²ë¦¬í´ì¼íë¤.
ëì onicecandidate
handlerë candidate
propertyê° candidateì ì 보를 ë´ê³ ìë SDP(ë¨, candidateë¤ì ëìënull
ì´ ì°íìë¤) ì¸ ì´ë²¤í¸ë¤ì ë°ëë¤. ì´ê²ì´ ëì ìê·¸ëë§ ìë²ë¥¼ íµí´ ë¤ë¥¸ í¼ì´ìê² ì ì¡í´ì¼í ê²ë¤ì´ë¤. ë°ì 구í ìì ê° ìë¤.
function handleICECandidateEvent(event) {
if (event.candidate) {
sendToServer({
type: "new-ice-candidate",
target: targetUsername,
candidate: event.candidate,
});
}
}
ì´ ì½ëìì candidate를 í¬í¨íë object를 ë§ë¤ê³ ë¤ë¥¸ í¼ì´ì ë³´ë¸ë¤. sendToServer()
í¨ìë ììì ì´ë¯¸ ë¤ë¤ì¼ë©° Sending messages to the signaling serverì ì½ëê° ìë¤. messageì propertyë¤ì´ ì미íë ê²ì ë¤ìê³¼ ê°ë¤.
target
ICE candidateê° ë³´ë´ì¼íë ê³³ì username. ì´ê²ì íµí´ ìê·¸ëë§ ìë²ê° ë©ì¸ì§ë¥¼ íê²ìê² ì ë¬íë¤.
type
ë©ì¸ì§ íì
ì "new-ice-candidate"
.
candidate
ICE layerê° ë¤ë¥¸ í¼ì´ìê² ì ì¡íê³ ìíë candidate object.
ë©ì¸ì§ì í¬ë§·(ìê·¸ëë§ì ì²ë¦¬íë 모ë ë©ì¸ì§ë¤ì)ì 모ë ëì ììì´ê³ , ëê° íìí ê²ì ë¬ë ¸ë¤. ëê° ëë¤ë¥¸ íìí ì ë³´ê° ìë¤ë©´ ì¶ê°í ì ìë¤. ë©ì¸ì§ë ê·¸ì JSON stringfied ëì´ ìëë°©ìê² ì ë¬ë ë¿ì´ë¤.
ì°¸ê³ : Callì ë¤ë¥¸ í¼ì´ë¡ë¶í° ICE candidateê° ëì°©í ë, icecandidate
eventê° ì ì¡ëë ê²ì´ ìëì íì ëª
ì¬í´ë¼. ëì ì ë ìì ì´ callì í ë ë³´ë´ë ê²ì¼ë¡, ëê° ìíë ì±ëì íµí´ data를 ë³´ë¼ ì ìë¤. WebRTC를 ì²ì ì íë¤ë©´ ë§¤ì° í·ê°ë¦´ ê²ì´ë¤.
ìê·¸ëë§ ìë²ë ì´ë¤ ë°©ë²ì ê³ ë¥´ë ê°ì ê° ICE candidate를 목ì ì§ê¹ì§ ë°°ë¬íë¤. ì´ë² ìì ììë typeì´ "new-ice-candidate"
ì¸ JSON object를 ì¬ì©íë¤. handleNewICECandidateMsg()
í¨ìë ì´ ë©ì¸ì§ë¤ì ì²ë¦¬í기 ìí´ ì¤íëë¤.
function handleNewICECandidateMsg(msg) {
var candidate = new RTCIceCandidate(msg.candidate);
myPeerConnection.addIceCandidate(candidate).catch(reportError);
}
ìì ë SDP를 RTCIceCandidate
ìì±ìì ì¸ìë¡ì ì ë¬íì¬ object를 ìì±íê³ , ì´ object를 myPeerConnection.addIceCandidate()
ì ì ë¬íë¤. ì´ í¨ì를 íµí´ ìë¡ì´ ICE candidate를 local ICE layerì ì ë¬íê³ , ëëì´ candidate 를 í¸ë¤ë§íë íë¡ì¸ì¤ìì ì°ë¦¬ì ìí ì ëë¬ë¤.
ê° í¼ì´ë ìëí ê²ì¼ë¡ ë³´ì´ë ê° ì»¤ë¥ì ë©ìëì candidate를 ë¤ë¥¸ í¼ì´ìê² ë³´ë¸ë¤. ì측ì í©ìì ëë¬íê³ ì»¤ë¥ì ì openíë¤. íì½ì ì§í ì¤ìë ë ëì 커ë¥ì ë©ìë를 찾거ë, ë¨ìí í¼ì´ê° 커ë¥ì ì ì¤ì í ë candidate êµíì´ ì§í ì¤ì´ìì ì ì기 ë문ì, candidateë ì¬ì í ì¡,ìì ë ì ììì 기ìµí´ë¼.
Receiving new streamsë¦¬ëª¨í¸ í¼ì´ê° RTCPeerConnection.addStream()
를 ë¶ë¦ì¼ë¡ì¨, ëë stream formatì ëí renegotiation(ì¬íì)ì ìí´ ìë¡ì´ ì¤í¸ë¦¼ì´ 커ë¥ì
ì ì¶ê°ëìì ë, addstream
eventê° ë°ìíë¤. ì´ë»ê² ì²ë¦¬íëì§ ìë ì½ë를 ë³´ì.
function handleAddStreamEvent(event) {
document.getElementById("received_video").srcObject = event.stream;
document.getElementById("hangup-button").disabled = false;
}
ì´ í¨ìë ë¤ì´ì¤ë streamì idê° "received_video"
ì¸ <video>
elementì í ë¹íê³ , ì ì ê° ì í를 ë°ì ì ìëë¡ ë²í¼ì íì±ííë¤.
ì´ ì½ëê° ì ëë¡ ì¤íëë¤ë©´, ëëì´ ë¤ë¥¸ í¼ì´ìì ì¤ë ë¹ëì¤ë¥¼ ë¡ì»¬ ë¸ë¼ì°ì ìì ë³¼ ì ìê² ëë¤!
Handling the removal of streamsë¦¬ëª¨í¸ í¼ì´ê° RTCPeerConnection.removeStream()
를 í¸ì¶íì¬ ì»¤ë¥ì
ì¼ë¡ë¶í° ì¤í¸ë¦¼ì ìì ë©´, removestream
eventê° ë°ìíê² ëë¤.
function handleRemoveStreamEvent(event) {
closeVideoCall();
}
ì´ í¨ìë closeVideoCall()
í¨ì를 ì¤íìì¼ callì´ ë«íëë¡ ë§ë¤ê³ , ë¤ë¥¸ 커ë¥ì
ì ììí ì ìëë¡ ê¸°ì¡´ ì¸í°íì´ì¤ë¥¼ ë²ë¦°ë¤. ì´ë»ê² ì½ëê° ëìíëì§ Ending the callì 참조í´ë¼.
There are many reasons why calls may end. A call might have completed, with one or both sides having hung up. Perhaps a network failure has occurred. Or one user might have quit their browser, or had a systen crash.
Hanging upWhen the user clicks the "Hang Up" button to end the call, the hangUpCall()
function is apllied:
function hangUpCall() {
closeVideoCall();
sendToServer({
name: myUsername,
target: targetUsername,
type: "hang-up",
});
}
hangUpCall()
executes closeVideoCall()
, shutting down and resetting the connection and related resources. We then build a "hang-up"
message, sending this to the other end of the call, allowing the other peer to neatly shut down.
ìëì ìë closeVideoCall()
í¨ìë streamë¤ì ë©ì¶ê³ ì§ì´ íì,RTCPeerConnection
object를 ìì¤ë¤.
function closeVideoCall() {
var remoteVideo = document.getElementById("received_video");
var localVideo = document.getElementById("local_video");
if (myPeerConnection) {
if (remoteVideo.srcObject) {
remoteVideo.srcObject.getTracks().forEach((track) => track.stop());
remoteVideo.srcObject = null;
}
if (localVideo.srcObject) {
localVideo.srcObject.getTracks().forEach((track) => track.stop());
localVideo.srcObject = null;
}
myPeerConnection.close();
myPeerConnection = null;
}
document.getElementById("hangup-button").disabled = true;
targetUsername = null;
}
2ê°ì <video>
element를 참조í ì´íì, WebRTC 커ë¥ì
ì´ ì¡´ì¬íëì§ ì²´í¬íë¤. ë§ì½ ìë¤ë©´, callì ëê³ ë«ëë¤:
MediaTrack.stop()
를 ì¤íìí¨ë¤.HTMLMediaElement.srcObject
property를 null
ë¡ ë°ê¿ streamì ê´í 모ë 참조를 í¼ë¤.myPeerConnection.close()
를 ë¶ë¬ RTCPeerConnection
ì ë«ëë¤.myPeerConnection
ë³ìì ê°ì null
ë¡ ë°ê¿ì ê³ì ì§íì¤ì¸ callì´ ìë¤ë ê²ì ì ì²´ ì½ëê° ìê² íë¤. ì´ê²ì ì ì ê° ì ì 리ì¤í¸ìì usernameì í´ë¦í ë ì¬ì©ëë¤.ë§ì§ë§ì¼ë¡, "Hang Up" ë²í¼ì disabled
property를 true
ë¡ ë°ê¿ì callì´ ìë ëììë í´ë¦ì´ ë¶ê°ë¥íê² ë§ë ë¤. ê·¸ ë¤ìì ëì´ì íµí를 íì§ ìì¼ë¯ë¡ targetUsername
ì null
ë¡ ë°ê¾¼ë¤. ì´ê²ì íµí´ ë ë¤ë¥¸ ì ì ìê² callì íê±°ë ìë¡ì´ callì ë°ì ì ìë¤.
ë¤ìí ìí ë³í를 ëì ì½ëì ì리기 ìí´ listener를 ì¸í
í ì ìë ë¤ìí ì´ë²¤í¸ë¤ì´ ìë¤. ê·¸ ì¤ì ë¤ì 3ê°ì§ë¥¼ ì¬ì©íê² ë¤.: iceconnectionstatechange
, icegatheringstatechange
, and signalingstatechange
.
커ë¥ì
stateê° ë°ëë©´(ì를ë¤ì´, callì´ ë¤ë¥¸ìª½ìì ì¤ë¨ ë ë) ICE layerê° iceconnectionstatechange
event를 ì°ë¦¬ìê² ë³´ë¸ë¤.
function handleICEConnectionStateChangeEvent(event) {
switch (myPeerConnection.iceConnectionState) {
case "closed":
case "failed":
case "disconnected":
closeVideoCall();
break;
}
}
ICE connection stateê° "closed"
, ëë"failed"
, ëë "disconnected"
ì¼ë¡ ë°ë ë closeVideoCall()
í¨ì를 ì¤ííë¤. 커ë¥ì
ì ëì¼ë©°, ì²ì(ëë accept) call ìíë¡ ëìê°ë¤.
ë§ì°¬ê°ì§ë¡ signalingstatechange
event를 ë°ì ì ìëë°, ìê·¸ëë§ ìíê° "closed"
ì¼ë¡ ë°ëë©´ callì ìì í ì¢
ë£ìí¨ë¤.
myPeerConnection.onsignalingstatechange = function (event) {
switch (myPeerConnection.signalingState) {
case "closed":
closeVideoCall();
break;
}
};
ICE gathering state
icegatheringstatechange
events are used to let you know when the ICE candidate gathering process state changes. Our example doesn't use this for anything, but we're implementing it for logging, observing via the console log how the whole process works.
function handleICEGatheringStateChangeEvent(event) {
// Our sample just logs information to console here,
// but you can do whatever you need.
}
Next steps
You can now play with this sample to see it in action. Open the Web console on both devices and look at the logged outputâalthough you don't see it in the code as shown above, the code on the server (and on GitHub) has a lot of console output so you can see the signaling and connection processes at work.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4