WebRTC - Guida rapida
Il Web non è più estraneo alla comunicazione in tempo reale come WebRTC (Web Real-Time Communication)entra in gioco. Sebbene sia stato rilasciato nel maggio 2011, è ancora in fase di sviluppo e i suoi standard stanno cambiando. Una serie di protocolli è standardizzata dalla comunicazione in tempo reale nei browser WEB del gruppo di lavoro all'indirizzohttp://tools.ietf.org/wg/rtcweb/ del IETF (Internet Engineering Task Force)mentre i nuovi set di API sono standardizzati dal Web Real-Time Communications Working Groupe all'indirizzohttp://www.w3.org/2011/04/webrtc/ del W3C (World Wide Web Consortium). Con la comparsa di WebRTC, le moderne applicazioni web possono facilmente trasmettere contenuti audio e video a milioni di persone.
Schema di base
WebRTC ti consente di configurare connessioni peer-to-peer ad altri browser Web in modo rapido e semplice. Per creare un'applicazione di questo tipo da zero, è necessaria una vasta gamma di framework e librerie che si occupano di problemi tipici come la perdita di dati, l'interruzione della connessione e l'attraversamento NAT. Con WebRTC, tutto questo è integrato nel browser fuori dagli schemi. Questa tecnologia non necessita di plug-in o software di terze parti. È open source e il suo codice sorgente è disponibile gratuitamente suhttp://www.webrtc.org/.
L'API WebRTC include l'acquisizione di contenuti multimediali, la codifica e la decodifica di audio e video, il livello di trasporto e la gestione delle sessioni.
Acquisizione multimediale
Il primo passo è ottenere l'accesso alla fotocamera e al microfono del dispositivo dell'utente. Rileviamo il tipo di dispositivi disponibili, otteniamo l'autorizzazione dell'utente per accedere a questi dispositivi e gestire lo streaming.
Codifica e decodifica di audio e video
Non è un compito facile inviare un flusso di dati audio e video su Internet. Qui è dove vengono utilizzate la codifica e la decodifica. Questo è il processo di suddivisione di fotogrammi video e onde audio in parti più piccole e di compressione. Questo algoritmo è chiamatocodec. Esiste un'enorme quantità di codec diversi, gestiti da società diverse con obiettivi di business diversi. Ci sono anche molti codec all'interno di WebRTC come H.264, iSAC, Opus e VP8. Quando due browser si connettono insieme, scelgono il codec supportato più ottimale tra due utenti. Fortunatamente, WebRTC esegue la maggior parte della codifica dietro le quinte.
Livello di trasporto
Il livello di trasporto gestisce l'ordine dei pacchetti, gestisce la perdita di pacchetti e si connette ad altri utenti. Anche in questo caso l'API WebRTC ci dà un facile accesso agli eventi che ci dicono quando ci sono problemi con la connessione.
Gestione delle sessioni
La gestione delle sessioni si occupa di gestire, aprire e organizzare le connessioni. Questo è comunemente chiamatosignaling. Se trasferisci flussi audio e video all'utente, ha senso trasferire anche dati collaterali. Questo viene fatto daRTCDataChannel API.
Ingegneri di aziende come Google, Mozilla, Opera e altri hanno fatto un ottimo lavoro per portare questa esperienza in tempo reale sul Web.
Compatibilità del browser
Gli standard WebRTC sono uno dei più in rapida evoluzione sul web, quindi non significa che ogni browser supporti tutte le stesse funzionalità allo stesso tempo. Per verificare se il tuo browser supporta WebRTC o meno, puoi visitarehttp://caniuse.com/#feat=rtcpeerconnection. In tutti i tutorial, ti consiglio di utilizzare Chrome per tutti gli esempi.
Provando WebRTC
Iniziamo subito a utilizzare WebRTC. Vai con il tuo browser al sito dimostrativo all'indirizzohttps://apprtc.appspot.com/
Fare clic sul pulsante "PARTECIPA". Dovresti vedere una notifica a discesa.
Fare clic sul pulsante "Consenti" per avviare lo streaming di video e audio nella pagina web. Dovresti vedere un flusso video di te stesso.
Ora apri l'URL in cui ti trovi attualmente in una nuova scheda del browser e fai clic su "ISCRIVITI". Dovresti vedere due flussi video: uno dal tuo primo client e un altro dal secondo.
Ora dovresti capire perché WebRTC è uno strumento potente.
Casi d'uso
Il Web in tempo reale apre le porte a una gamma completamente nuova di applicazioni, tra cui chat di testo, condivisione di schermo e file, giochi, chat video e altro ancora. Oltre alla comunicazione puoi utilizzare WebRTC per altri scopi come:
- marketing in tempo reale
- pubblicità in tempo reale
- comunicazioni di back office (CRM, ERP, SCM, FFM)
- Gestione delle risorse umane
- social networking
- servizi di incontri
- consulti medici in linea
- servizi finanziari
- surveillance
- giochi multiplayer
- trasmissione in diretta
- e-learning
Sommario
Ora dovresti avere una chiara comprensione del termine WebRTC. Dovresti anche avere un'idea di quali tipi di applicazioni possono essere create con WebRTC, dato che l'hai già provato nel tuo browser. Per riassumere, WebRTC è una tecnologia abbastanza utile.
L'architettura WebRTC complessiva ha un grande livello di complessità.
Qui puoi trovare tre diversi livelli:
API for web developers - questo livello contiene tutte le API necessarie per gli sviluppatori web, inclusi gli oggetti RTCPeerConnection, RTCDataChannel e MediaStrean.
API per i produttori di browser
API sovrascrivibile, che i produttori di browser possono agganciare.
I componenti di trasporto consentono di stabilire connessioni tra vari tipi di reti, mentre i motori voce e video sono framework responsabili del trasferimento di flussi audio e video da una scheda audio e una telecamera alla rete. Per gli sviluppatori Web, la parte più importante è l'API WebRTC.
Se guardiamo l'architettura WebRTC dal lato client-server possiamo notare che uno dei modelli più comunemente utilizzati è ispirato al Trapezoid SIP (Session Initiation Protocol).
In questo modello, entrambi i dispositivi eseguono un'applicazione Web da server diversi. L'oggetto RTCPeerConnection configura i flussi in modo che possano connettersi tra loro, peer-to-peer. Questa segnalazione viene eseguita tramite HTTP o WebSocket.
Ma il modello più comunemente usato è Triangle -
In questo modello entrambi i dispositivi utilizzano la stessa applicazione web. Offre agli sviluppatori web una maggiore flessibilità nella gestione delle connessioni degli utenti.
L'API WebRTC
Consiste di alcuni oggetti javascript principali:
- RTCPeerConnection
- MediaStream
- RTCDataChannel
L'oggetto RTCPeerConnection
Questo oggetto è il punto di ingresso principale per l'API WebRTC. Ci aiuta a connetterci ai peer, inizializzare le connessioni e allegare flussi multimediali. Gestisce anche una connessione UDP con un altro utente.
Il compito principale dell'oggetto RTCPeerConnection è impostare e creare una connessione peer. Possiamo facilmente agganciare i punti chiave della connessione perché questo oggetto attiva una serie di eventi quando compaiono. Questi eventi ti danno accesso alla configurazione della nostra connessione -
RTCPeerConnection è un semplice oggetto javascript, che puoi creare semplicemente in questo modo:
[code]
var conn = new RTCPeerConnection(conf);
conn.onaddstream = function(stream) {
// use stream here
};
[/code]
L'oggetto RTCPeerConnection accetta un parametro conf , di cui parleremo più avanti in queste esercitazioni. L' evento onaddstream viene generato quando l'utente remoto aggiunge un flusso video o audio alla propria connessione peer.
API MediaStream
I browser moderni consentono a uno sviluppatore di accedere all'API getUserMedia , nota anche come API MediaStream . Ci sono tre punti chiave di funzionalità:
Fornisce a uno sviluppatore l'accesso a un oggetto flusso che rappresenta flussi video e audio
Gestisce la selezione dei dispositivi di input dell'utente nel caso in cui un utente abbia più telecamere o microfoni sul proprio dispositivo
Fornisce un livello di sicurezza che chiede all'utente tutto il tempo che desidera recuperare il flusso
Per testare questa API creiamo una semplice pagina HTML. Mostrerà un singolo elemento <video>, chiederà il permesso all'utente di utilizzare la videocamera e mostrerà un live streaming dalla videocamera sulla pagina. Crea un file index.html e aggiungi -
[code]
<html>
<head>
<meta charset = "utf-8">
</head>
<body>
<video autoplay></video>
<script src = "client.js"></script>
</body>
</html>
[/code]
Quindi aggiungi un file client.js -
[code]
//checks if the browser supports WebRTC
function hasUserMedia() {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia || navigator.msGetUserMedia;
return !!navigator.getUserMedia;
}
if (hasUserMedia()) {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia || navigator.msGetUserMedia;
//get both video and audio streams from user's camera
navigator.getUserMedia({ video: true, audio: true }, function (stream) {
var video = document.querySelector('video');
//insert stream into the video tag
video.src = window.URL.createObjectURL(stream);
}, function (err) {});
}else {
alert("Error. WebRTC is not supported!");
}
[/code]
Ora apri index.html e dovresti vedere il flusso video che mostra il tuo viso.
Ma attenzione, perché WebRTC funziona solo sul lato server. Se apri semplicemente questa pagina con il browser, non funzionerà. È necessario ospitare questi file sui server Apache o Node o su quale si preferisce.
L'oggetto RTCDataChannel
Oltre a inviare flussi multimediali tra peer, puoi anche inviare dati aggiuntivi utilizzando l' API DataChannel . Questa API è semplice come l'API MediaStream. Il compito principale è creare un canale proveniente da un oggetto RTCPeerConnection esistente -
[code]
var peerConn = new RTCPeerConnection();
//establishing peer connection
//...
//end of establishing peer connection
var dataChannel = peerConnection.createDataChannel("myChannel", dataChannelOptions);
// here we can start sending direct messages to another peer
[/code]
Questo è tutto ciò di cui hai bisogno, solo due righe di codice. Tutto il resto viene eseguito sul livello interno del browser. È possibile creare un canale in qualsiasi connessione peer fino alla chiusura dell'oggetto RTCPeerConnection .
Sommario
Ora dovresti avere una solida conoscenza dell'architettura WebRTC. Abbiamo anche trattato le API MediaStream, RTCPeerConnection e RTCDataChannel. L'API WebRTC è un obiettivo mobile, quindi tieniti sempre aggiornato con le specifiche più recenti.
Prima di iniziare a creare le nostre applicazioni WebRTC, dovremmo impostare il nostro ambiente di codifica. Prima di tutto, dovresti avere un editor di testo o IDE in cui puoi modificare HTML e Javascript. È possibile che tu abbia già scelto quello preferito mentre stai leggendo questo tutorial. Per quanto mi riguarda, sto usando WebStorm IDE. Puoi scaricare la sua versione di prova suhttps://www.jetbrains.com/webstorm/. Uso anche Linux Mint come sistema operativo preferito.
L'altro requisito per le comuni applicazioni WebRTC è avere un server per ospitare i file HTML e Javascript. Il codice non funzionerà semplicemente facendo doppio clic sui file perché il browser non può connettersi a telecamere e microfoni a meno che i file non siano serviti da un server reale. Questo viene fatto ovviamente a causa dei problemi di sicurezza.
Ci sono tantissimi server web diversi, ma in questo tutorial useremo Node.js con node-static -
Visitare https://nodejs.org/en/ e scarica l'ultima versione di Node.js.
Decomprimilo nella directory / usr / local / nodejs.
Apri il file /home/YOUR_USERNAME/.profile e aggiungi la seguente riga alla fine: export PATH = $ PATH: / usr / local / nodejs / bin
Puoi riavviare il computer o eseguire source /home/YOUR_USERNAME/.profile
Ora il comando node dovrebbe essere disponibile dalla riga di comando. È disponibile anche il comando npm . NMP è il gestore di pacchetti per Node.js. Puoi saperne di più suhttps://www.npmjs.com/.
Apri un terminale ed esegui sudo npm install -g node-static . Questo installerà il server web statico per Node.js.
Ora vai a qualsiasi directory contenente i file HTML ed esegui il comando statico all'interno della directory per avviare il tuo server web.
Puoi navigare verso http://localhost:8080 per vedere i tuoi file.
C'è un altro modo per installare nodejs. Basta eseguire sudo apt-get install nodejs nella finestra del terminale.
Per testare la tua installazione di Node.js, apri il tuo terminale ed esegui il comando node . Digita alcuni comandi per verificare come funziona -
Node.js esegue file Javascript e comandi digitati nel terminale. Crea un file index.js con il seguente contenuto:
console.log(“Testing Node.js”);
Quindi eseguire il comando node index . Vedrai quanto segue:
Durante la creazione del nostro server di segnalazione utilizzeremo una libreria WebSocket per Node.js. Per installare in run npm install ws nel terminale.
Per testare il nostro server di segnalazione, utilizzeremo l'utilità wscat. Per installarlo, esegui npm install -g wscat nella finestra del tuo terminale.
S.No | Protocolli e descrizione |
---|---|
1 | Protocolli WebRTC Le applicazioni WebRTC utilizzano UDP (User Datagram Protocol) come protocollo di trasporto. La maggior parte delle applicazioni web odierne sono costruite con l'utilizzo del TCP (Transmission Control Protocol) |
2 | Protocollo di descrizione della sessione L'SDP è una parte importante del WebRTC. È un protocollo destinato a descrivere le sessioni di comunicazione con i media. |
3 | Trovare un percorso Per connetterti a un altro utente, dovresti trovare un percorso chiaro attorno alla tua rete e alla rete dell'altro utente. Ma ci sono possibilità che la rete che stai utilizzando abbia diversi livelli di controllo dell'accesso per evitare problemi di sicurezza. |
4 | Protocollo di trasmissione del controllo del flusso Con la connessione peer, abbiamo la possibilità di inviare rapidamente dati video e audio. Il protocollo SCTP viene utilizzato oggi per inviare dati BLOB sulla nostra connessione peer attualmente configurata quando si utilizza l'oggetto RTCDataChannel. |
Sommario
In questo capitolo, abbiamo trattato molte delle tecnologie che abilitano le connessioni peer, come UDP, TCP, STUN, TURN, ICE e SCTP. Ora dovresti avere una comprensione a livello superficiale di come funziona SDP e dei suoi casi d'uso.
L'API MediaStream è stata progettata per accedere facilmente ai flussi multimediali da telecamere e microfoni locali. Il metodo getUserMedia () è il modo principale per accedere ai dispositivi di input locali.
L'API ha alcuni punti chiave:
Un flusso multimediale in tempo reale è rappresentato da un oggetto flusso sotto forma di video o audio
Fornisce un livello di sicurezza tramite le autorizzazioni utente che chiedono all'utente prima che un'applicazione web possa iniziare a recuperare un flusso
La selezione dei dispositivi di input è gestita dall'API MediaStream (ad esempio, quando ci sono due telecamere o microfoni collegati al dispositivo)
Ogni oggetto MediaStream include diversi oggetti MediaStreamTrack. Rappresentano video e audio da diversi dispositivi di input.
Ogni oggetto MediaStreamTrack può includere diversi canali (canali audio destro e sinistro). Queste sono le parti più piccole definite dall'API MediaStream.
Esistono due modi per produrre oggetti MediaStream. Innanzitutto, possiamo rendere l'output in un elemento video o audio. In secondo luogo, possiamo inviare l'output all'oggetto RTCPeerConnection, che quindi lo invia a un peer remoto.
Utilizzo dell'API MediaStream
Creiamo una semplice applicazione WebRTC. Mostrerà un elemento video sullo schermo, chiederà all'utente il permesso di usare la telecamera e mostrerà un flusso video in diretta nel browser. Crea un file index.html -
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "utf-8" />
</head>
<body>
<video autoplay></video>
<script src = "client.js"></script>
</body>
</html>
Quindi creare il file client.js e aggiungere quanto segue;
function hasUserMedia() {
//check if the browser supports the WebRTC
return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia);
}
if (hasUserMedia()) {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia;
//enabling video and audio channels
navigator.getUserMedia({ video: true, audio: true }, function (stream) {
var video = document.querySelector('video');
//inserting our stream to the video tag
video.src = window.URL.createObjectURL(stream);
}, function (err) {});
} else {
alert("WebRTC is not supported");
}
Qui creiamo la funzione hasUserMedia () che controlla se WebRTC è supportato o meno. Quindi accediamo alla funzione getUserMedia dove il secondo parametro è un callback che accetta il flusso proveniente dal dispositivo dell'utente. Quindi carichiamo il nostro flusso nell'elemento video usando window.URL.createObjectURL che crea un URL che rappresenta l'oggetto dato nel parametro.
Ora aggiorna la pagina, fai clic su Consenti e dovresti vedere il tuo viso sullo schermo.
Ricordati di eseguire tutti i tuoi script utilizzando il server web. Ne abbiamo già installato uno nel Tutorial sull'ambiente WebRTC.
API MediaStream
Proprietà
MediaStream.active (read only) - Restituisce true se MediaStream è attivo o false in caso contrario.
MediaStream.ended (read only, deprecated)- Restituisce true se l' evento terminato è stato attivato sull'oggetto, il che significa che lo stream è stato completamente letto o false se la fine dello stream non è stata raggiunta.
MediaStream.id (read only) - Un identificatore univoco per l'oggetto.
MediaStream.label (read only, deprecated) - Un identificatore univoco assegnato dal programma utente.
Puoi vedere come appaiono le proprietà di cui sopra nel mio browser:
Gestori di eventi
MediaStream.onactive- Un gestore per un evento attivo che viene generato quando un oggetto MediaStream diventa attivo.
MediaStream.onaddtrack- Un gestore per un evento addtrack che viene attivato quando viene aggiunto un nuovo oggetto MediaStreamTrack .
MediaStream.onended (deprecated)- Un gestore per un evento terminato che viene attivato quando lo streaming sta terminando.
MediaStream.oninactive- Un gestore per un evento inattivo che viene attivato quando un oggetto MediaStream diventa inattivo.
MediaStream.onremovetrack- Un gestore per un evento di rimozione che viene attivato quando un oggetto MediaStreamTrack viene rimosso da esso.
Metodi
MediaStream.addTrack()- Aggiunge l' oggetto MediaStreamTrack dato come argomento a MediaStream. Se la traccia è già stata aggiunta, non accade nulla.
MediaStream.clone() - Restituisce un clone dell'oggetto MediaStream con un nuovo ID.
MediaStream.getAudioTracks()- Restituisce un elenco degli oggetti MediaStreamTrack audio dall'oggetto MediaStream .
MediaStream.getTrackById()- Restituisce la traccia per ID. Se l'argomento è vuoto o l'ID non viene trovato, restituisce null. Se più tracce hanno lo stesso ID, restituisce il primo.
MediaStream.getTracks()- Restituisce un elenco di tutti MediaStreamTrack oggetti dal MediaStream oggetto.
MediaStream.getVideoTracks()- Restituisce un elenco degli oggetti MediaStreamTrack video dall'oggetto MediaStream .
MediaStream.removeTrack()- Rimuove l' oggetto MediaStreamTrack fornito come argomento da MediaStream. Se la traccia è già stata rimossa, non accade nulla.
Per testare le API di cui sopra, modifica il file index.html nel modo seguente:
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "utf-8" />
</head>
<body>
<video autoplay></video>
<div><button id = "btnGetAudioTracks">getAudioTracks()
</button></div>
<div><button id = "btnGetTrackById">getTrackById()
</button></div>
<div><button id = "btnGetTracks">getTracks()</button></div>
<div><button id = "btnGetVideoTracks">getVideoTracks()
</button></div>
<div><button id = "btnRemoveAudioTrack">removeTrack() - audio
</button></div>
<div><button id = "btnRemoveVideoTrack">removeTrack() - video
</button></div>
<script src = "client.js"></script>
</body>
</html>
Abbiamo aggiunto alcuni pulsanti per provare diverse API MediaStream. Quindi dovremmo aggiungere gestori di eventi per il nostro pulsante appena creato. Modifica il file client.js in questo modo:
var stream;
function hasUserMedia() {
//check if the browser supports the WebRTC
return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia);
}
if (hasUserMedia()) {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia;
//enabling video and audio channels
navigator.getUserMedia({ video: true, audio: true }, function (s) {
stream = s;
var video = document.querySelector('video');
//inserting our stream to the video tag
video.src = window.URL.createObjectURL(stream);
}, function (err) {});
} else {
alert("WebRTC is not supported");
}
btnGetAudioTracks.addEventListener("click", function(){
console.log("getAudioTracks");
console.log(stream.getAudioTracks());
});
btnGetTrackById.addEventListener("click", function(){
console.log("getTrackById");
console.log(stream.getTrackById(stream.getAudioTracks()[0].id));
});
btnGetTracks.addEventListener("click", function(){
console.log("getTracks()");
console.log(stream.getTracks());
});
btnGetVideoTracks.addEventListener("click", function(){
console.log("getVideoTracks()");
console.log(stream.getVideoTracks());
});
btnRemoveAudioTrack.addEventListener("click", function(){
console.log("removeAudioTrack()");
stream.removeTrack(stream.getAudioTracks()[0]);
});
btnRemoveVideoTrack.addEventListener("click", function(){
console.log("removeVideoTrack()");
stream.removeTrack(stream.getVideoTracks()[0]);
});
Ora aggiorna la tua pagina. Fare clic sul pulsante getAudioTracks () , quindi fare clic sul pulsante removeTrack () - audio . La traccia audio dovrebbe ora essere rimossa. Quindi fai lo stesso per la traccia video.
Se fai clic sul pulsante getTracks () dovresti vedere tutti i MediaStreamTracks (tutti gli ingressi video e audio collegati). Quindi fare clic su getTrackById () per ottenere MediaStreamTrack audio.
Sommario
In questo capitolo, abbiamo creato una semplice applicazione WebRTC utilizzando l'API MediaStream. Ora dovresti avere una chiara panoramica delle varie API MediaStream che fanno funzionare WebRTC.
L'API RTCPeerConnection è il cuore della connessione peer-to-peer tra ciascuno dei browser. Per creare gli oggetti RTCPeerConnection è sufficiente scrivere
var pc = RTCPeerConnection(config);
dove l' argomento config contiene almeno una chiave, iceServers. È un array di oggetti URL contenenti informazioni sui server STUN e TURN, utilizzati durante la ricerca dei candidati ICE. Puoi trovare un elenco di server STUN pubblici disponibili su code.google.com
A seconda che tu sia il chiamante o il chiamato, l'oggetto RTCPeerConnection viene utilizzato in modo leggermente diverso su ciascun lato della connessione.
Ecco un esempio del flusso dell'utente:
Registra il gestore onicecandidato . Invia eventuali candidati ICE all'altro peer, non appena vengono ricevuti.
Registrare il gestore onaddstream . Gestisce la visualizzazione del flusso video una volta ricevuto dal peer remoto.
Registra il gestore dei messaggi . Il tuo server di segnalazione dovrebbe anche avere un gestore per i messaggi ricevuti dall'altro peer. Se il messaggio contiene l' oggetto RTCSessionDescription , dovrebbe essere aggiunto all'oggetto RTCPeerConnection utilizzando il metodo setRemoteDescription () . Se il messaggio contiene l' oggetto RTCIceCandidate , dovrebbe essere aggiunto all'oggetto RTCPeerConnection utilizzando il metodo addIceCandidate () .
Utilizza getUserMedia () per configurare il flusso multimediale locale e aggiungilo all'oggetto RTCPeerConnection utilizzando il metodo addStream () .
Avvia il processo di negoziazione dell'offerta / risposta. Questo è l'unico passaggio in cui il flusso del chiamante è diverso da quello del chiamato. Il chiamante avvia la negoziazione utilizzando il metodo createOffer () e registra un callback che riceve l' oggetto RTCSessionDescription . Allora questo callback dovrebbe aggiungere questo RTCSessionDescription oggetto al tuo RTCPeerConnection oggetto utilizzando setLocalDescription () . Infine, il chiamante deve inviare questa RTCSessionDescription al peer remoto utilizzando il server di segnalazione. Il chiamato, dall'altro, registra lo stesso callback, ma nel metodo createAnswer () . Si noti che il flusso del chiamato viene avviato solo dopo che l'offerta è stata ricevuta dal chiamante.
API RTCPeerConnection
Proprietà
RTCPeerConnection.iceConnectionState (read only)- Restituisce un'enumerazione RTCIceConnectionState che descrive lo stato della connessione. Quando questo valore cambia, viene generato un evento iceconnectionstatechange. I possibili valori -
new - l'agente ICE è in attesa di candidati remoti o di raccolta indirizzi
checking - l'agente ICE ha candidati remoti, ma non ha ancora trovato una connessione
connected - l'agente ICE ha trovato una connessione utilizzabile, ma sta ancora verificando un candidato più remoto per una migliore connessione.
completed - l'agente ICE ha trovato una connessione utilizzabile e ha smesso di testare i candidati remoti.
failed - l'agente ICE ha controllato tutti i candidati remoti ma non ha trovato una corrispondenza per almeno un componente.
disconnected - almeno un componente non è più vivo.
closed - l'agente ICE è chiuso.
RTCPeerConnection.iceGatheringState (read only) - Restituisce un'enumerazione RTCIceGatheringState che descrive lo stato di raccolta ICE per la connessione -
new - l'oggetto è stato appena creato.
gathering - l'agente ICE sta raccogliendo candidati
complete l'agente ICE ha completato la raccolta.
RTCPeerConnection.localDescription (read only)- Restituisce una descrizione RTCSessionDescription che descrive la sessione locale. Può essere nullo se non è stato ancora impostato.
RTCPeerConnection.peerIdentity (read only)- Restituisce un RTCIdentityAssertion. Consiste in un idp (nome di dominio) e un nome che rappresenta l'identità del peer remoto.
RTCPeerConnection.remoteDescription (read only)- Restituisce una RTCSessionDescription che descrive la sessione remota. Può essere nullo se non è stato ancora impostato.
RTCPeerConnection.signalingState (read only)- Restituisce un'enumerazione RTCSignalingState che descrive lo stato di segnalazione della connessione locale. Questo stato descrive l'offerta SDP. Quando questo valore cambia, viene generato un evento signalingstatechange. I possibili valori -
stable- Lo stato iniziale. Non è in corso lo scambio di offerte / risposte SDP.
have-local-offer - il lato locale della connessione ha applicato localmente un'offerta SDP.
have-remote-offer - il lato remoto della connessione ha applicato localmente un'offerta SDP.
have-local-pranswer - è stata applicata un'offerta SDP remota e una pranswer SDP applicata localmente.
have-remote-pranswer - è stato applicato un SDP locale e una pranswer SDP è stata applicata da remoto.
closed - la connessione è chiusa.
Gestori di eventi
S.No. | Gestori di eventi e descrizione |
---|---|
1 | RTCPeerConnection.onaddstream Questo gestore viene chiamato quando viene generato l'evento addstream. Questo evento viene inviato quando un MediaStream viene aggiunto a questa connessione dal peer remoto. |
2 | RTCPeerConnection.ondatachannel Questo gestore viene chiamato quando viene attivato l'evento datachannel. Questo evento viene inviato quando un RTCDataChannel viene aggiunto a questa connessione. |
3 | RTCPeerConnection.onicecandidate Questo gestore viene chiamato quando viene generato l'evento icecandidate. Questo evento viene inviato quando un oggetto RTCIceCandidate viene aggiunto allo script. |
4 | RTCPeerConnection.oniceconnectionstatechange Questo gestore viene chiamato quando viene generato l'evento iceconnectionstatechange. Questo evento viene inviato quando il valore di iceConnectionState cambia. |
5 | RTCPeerConnection.onidentityresult Questo gestore viene chiamato quando viene generato l'evento identityresult. Questo evento viene inviato quando viene generata un'asserzione di identità durante la creazione di un'offerta o una risposta tramite getIdentityAssertion (). |
6 | RTCPeerConnection.onidpassertionerror Questo gestore viene chiamato quando viene generato l'evento idpassertionerror. Questo evento viene inviato quando l'IdP (provider di identità) rileva un errore durante la generazione di un'asserzione di identità. |
7 | RTCPeerConnection.onidpvalidation Questo gestore viene chiamato quando viene generato l'evento idpvalidationerror. Questo evento viene inviato quando l'IdP (provider di identità) rileva un errore durante la convalida di un'asserzione di identità. |
8 | RTCPeerConnection.onnegotiationneeded Questo gestore viene chiamato quando viene generato l'evento di negoziazione necessaria. Questo evento viene inviato dal browser per informare che la negoziazione sarà richiesta in futuro. |
9 | RTCPeerConnection.onpeeridentity Questo gestore viene chiamato quando viene generato l'evento peeridentity. Questo evento viene inviato quando un'identità peer è stata impostata e verificata su questa connessione. |
10 | RTCPeerConnection.onremovestream Questo gestore viene chiamato quando viene generato l'evento signalingstatechange. Questo evento viene inviato quando il valore di signalingState cambia. |
11 | RTCPeerConnection.onsignalingstatechange Questo gestore viene chiamato quando viene attivato l'evento removestream. Questo evento viene inviato quando un MediaStream viene rimosso da questa connessione. |
Metodi
S.No. | Metodi e descrizione |
---|---|
1 | RTCPeerConnection() Restituisce un nuovo oggetto RTCPeerConnection. |
2 | RTCPeerConnection.createOffer() Crea un'offerta (richiesta) per trovare un peer remoto. I primi due parametri di questo metodo sono callback di successo ed errore. Il terzo parametro opzionale sono le opzioni, come l'attivazione di flussi audio o video. |
3 | RTCPeerConnection.createAnswer() Crea una risposta all'offerta ricevuta dal peer remoto durante il processo di negoziazione offerta / risposta. I primi due parametri di questo metodo sono callback di successo ed errore. Il terzo parametro opzionale sono le opzioni per la risposta da creare. |
4 | RTCPeerConnection.setLocalDescription() Modifica la descrizione della connessione locale. La descrizione definisce le proprietà della connessione. La connessione deve essere in grado di supportare sia le vecchie che le nuove descrizioni. Il metodo accetta tre parametri, oggetto RTCSessionDescription, callback se la modifica della descrizione ha esito positivo, callback se la modifica della descrizione non riesce. |
5 | RTCPeerConnection.setRemoteDescription() Modifica la descrizione della connessione remota. La descrizione definisce le proprietà della connessione. La connessione deve essere in grado di supportare sia le vecchie che le nuove descrizioni. Il metodo accetta tre parametri, oggetto RTCSessionDescription, callback se la modifica della descrizione ha esito positivo, callback se la modifica della descrizione non riesce. |
6 | RTCPeerConnection.updateIce() Aggiorna il processo dell'agente ICE di ping di candidati remoti e raccolta di candidati locali. |
7 | RTCPeerConnection.addIceCandidate() Fornisce un candidato remoto all'agente ICE. |
8 | RTCPeerConnection.getConfiguration() Restituisce un oggetto RTCConfiguration. Rappresenta la configurazione dell'oggetto RTCPeerConnection. |
9 | RTCPeerConnection.getLocalStreams() Restituisce una matrice di connessione MediaStream locale. |
10 | RTCPeerConnection.getRemoteStreams() Restituisce un array di connessione MediaStream remota. |
11 | RTCPeerConnection.getStreamById() Restituisce MediaStream locale o remoto in base all'ID specificato. |
12 | RTCPeerConnection.addStream() Aggiunge un MediaStream come sorgente locale di video o audio. |
13 | RTCPeerConnection.removeStream() Rimuove un MediaStream come sorgente locale di video o audio. |
14 | RTCPeerConnection.close() Chiude una connessione. |
15 | RTCPeerConnection.createDataChannel() Crea un nuovo RTCDataChannel. |
16 | RTCPeerConnection.createDTMFSender() Crea un nuovo RTCDTMFSender, associato a un MediaStreamTrack specifico. Consente di inviare segnalazioni telefoniche DTMF (Dual-tone multifrequency) tramite la connessione. |
17 | RTCPeerConnection.getStats() Crea un nuovo RTCStatsReport che contiene le statistiche relative alla connessione. |
18 | RTCPeerConnection.setIdentityProvider() Imposta l'IdP. Accetta tre parametri: il nome, il protocollo utilizzato per comunicare e un nome utente opzionale. |
19 | RTCPeerConnection.getIdentityAssertion() Raccoglie un'asserzione di identità. Non è previsto che si occupi di questo metodo nell'applicazione. Quindi puoi chiamarlo esplicitamente solo per anticipare la necessità. |
Stabilire una connessione
Ora creiamo un'applicazione di esempio. In primo luogo, eseguire il server di segnalazione che abbiamo creato nel tutorial "server di segnalazione" tramite "server nodo".
Ci saranno due input di testo nella pagina, uno per un login e uno per un nome utente a cui vogliamo connetterci. Crea un file index.html e aggiungi il seguente codice:
<html lang = "en">
<head>
<meta charset = "utf-8" />
</head>
<body>
<div>
<input type = "text" id = "loginInput" />
<button id = "loginBtn">Login</button>
</div>
<div>
<input type = "text" id = "otherUsernameInput" />
<button id = "connectToOtherUsernameBtn">Establish connection</button>
</div>
<script src = "client2.js"></script>
</body>
</html>
Puoi vedere che abbiamo aggiunto l'immissione di testo per un accesso, il pulsante di accesso, l'immissione di testo per l'altro nome utente peer e il pulsante Connetti a lui. Ora crea un file client.js e aggiungi il seguente codice:
var connection = new WebSocket('ws://localhost:9090');
var name = "";
var loginInput = document.querySelector('#loginInput');
var loginBtn = document.querySelector('#loginBtn');
var otherUsernameInput = document.querySelector('#otherUsernameInput');
var connectToOtherUsernameBtn = document.querySelector('#connectToOtherUsernameBtn');
var connectedUser, myConnection;
//when a user clicks the login button
loginBtn.addEventListener("click", function(event){
name = loginInput.value;
if(name.length > 0){
send({
type: "login",
name: name
});
}
});
//handle messages from the server
connection.onmessage = function (message) {
console.log("Got message", message.data);
var data = JSON.parse(message.data);
switch(data.type) {
case "login":
onLogin(data.success);
break;
case "offer":
onOffer(data.offer, data.name);
break;
case "answer":
onAnswer(data.answer);
break;
case "candidate":
onCandidate(data.candidate);
break;
default:
break;
}
};
//when a user logs in
function onLogin(success) {
if (success === false) {
alert("oops...try a different username");
} else {
//creating our RTCPeerConnection object
var configuration = {
"iceServers": [{ "url": "stun:stun.1.google.com:19302" }]
};
myConnection = new webkitRTCPeerConnection(configuration);
console.log("RTCPeerConnection object was created");
console.log(myConnection);
//setup ice handling
//when the browser finds an ice candidate we send it to another peer
myConnection.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
}
};
connection.onopen = function () {
console.log("Connected");
};
connection.onerror = function (err) {
console.log("Got error", err);
};
// Alias for sending messages in JSON format
function send(message) {
if (connectedUser) {
message.name = connectedUser;
}
connection.send(JSON.stringify(message));
};
Puoi vedere che stabiliamo una connessione socket al nostro server di segnalazione. Quando un utente fa clic sul pulsante di accesso, l'applicazione invia il suo nome utente al server. Se il login ha esito positivo, l'applicazione crea l'oggetto RTCPeerConnection e imposta il gestore onicecandidate che invia tutti i icecandidate trovati all'altro peer. Ora apri la pagina e prova ad accedere. Dovresti vedere il seguente output della console:
Il passaggio successivo consiste nel creare un'offerta per l'altro peer. Aggiungi il seguente codice al tuo file client.js -
//setup a peer connection with another user
connectToOtherUsernameBtn.addEventListener("click", function () {
var otherUsername = otherUsernameInput.value;
connectedUser = otherUsername;
if (otherUsername.length > 0) {
//make an offer
myConnection.createOffer(function (offer) {
console.log();
send({
type: "offer",
offer: offer
});
myConnection.setLocalDescription(offer);
}, function (error) {
alert("An error has occurred.");
});
}
});
//when somebody wants to call us
function onOffer(offer, name) {
connectedUser = name;
myConnection.setRemoteDescription(new RTCSessionDescription(offer));
myConnection.createAnswer(function (answer) {
myConnection.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("oops...error");
});
}
//when another user answers to our offer
function onAnswer(answer) {
myConnection.setRemoteDescription(new RTCSessionDescription(answer));
}
//when we got ice candidate from another user
function onCandidate(candidate) {
myConnection.addIceCandidate(new RTCIceCandidate(candidate));
}
Puoi vedere che quando un utente fa clic sul pulsante "Stabilisci connessione", l'applicazione fa un'offerta SDP all'altro peer. Abbiamo anche impostato i gestori onAnswer e onCandidate . Ricarica la tua pagina, aprila in due schede, accedi con due utenti e prova a stabilire una connessione tra di loro. Dovresti vedere il seguente output della console:
Ora la connessione peer-to-peer è stabilita. Nei prossimi tutorial, aggiungeremo stream video e audio e supporto per chat di testo.
WebRTC non è solo bravo a trasferire flussi audio e video, ma qualsiasi dato arbitrario che potremmo avere. È qui che entra in gioco l'oggetto RTCDataChannel.
API RTCDataChannel
Proprietà
RTCDataChannel.label (read only) - Restituisce una stringa contenente il nome del canale di dati.
RTCDataChannel.ordered (read only) - Restituisce vero se l'ordine di consegna dei messaggi è garantito o falso se non è garantito.
RTCDataChannel.protocol (read only) - Restituisce una stringa contenente il nome del sottoprotocollo utilizzato per questo canale.
RTCDataChannel.id (read only) - Restituisce un ID univoco per il canale che è impostato alla creazione dell'oggetto RTCDataChannel.
RTCDataChannel.readyState (read only)- Restituisce l'enumerazione RTCDataChannelState che rappresenta lo stato della connessione. I possibili valori -
connecting- Indica che la connessione non è ancora attiva. Questo è lo stato iniziale.
open - Indica che la connessione è in esecuzione.
closing- Indica che la connessione è in fase di chiusura. I messaggi memorizzati nella cache sono in fase di invio o ricezione, ma nessuna attività appena creata accetta.
closed - Indica che non è stato possibile stabilire la connessione o che è stata interrotta.
RTCDataChannel.bufferedAmount (read only)- Restituisce la quantità di byte che sono stati accodati per l'invio. Questa è la quantità di dati che non è stata ancora inviata tramite RTCDataChannel.send ().
RTCDataChannel.bufferedAmountLowThreshold- Restituisce il numero di byte in cui RTCDataChannel.bufferedAmount viene occupato il più basso. Quando RTCDataChannel.bufferedAmount scende al di sotto di questa soglia, viene generato l'evento bufferedamountlow.
RTCDataChannel.binaryType- Restituisce il tipo di dati binari trasmessi dalla connessione. Può essere "blob" o "arraybuffer".
RTCDataChannel.maxPacketLifeType (read only) - Restituisce uno short non firmato che indica la lunghezza in millisecondi della finestra quando la messaggistica è in modalità inaffidabile.
RTCDataChannel.maxRetransmits (read only) - Restituisce un breve non firmato che indica il numero massimo di volte che un canale ritrasmetterà i dati se non viene consegnato.
RTCDataChannel.negotiated (read only) - Restituisce un valore booleano che indica se il canale è stato negoziato dallo user-agent o dall'applicazione.
RTCDataChannel.reliable (read only) - Restituisce un valore booleano che indica che la connessione può inviare messaggi in modalità inaffidabile.
RTCDataChannel.stream (read only) - Sinonimo di RTCDataChannel.id
Gestori di eventi
RTCDataChannel.onopen- Questo gestore di eventi viene chiamato quando viene attivato l'evento open. Questo evento viene inviato quando la connessione dati è stata stabilita.
RTCDataChannel.onmessage- Questo gestore di eventi viene chiamato quando viene generato l'evento di messaggio. L'evento viene inviato quando un messaggio è disponibile sul canale dati.
RTCDataChannel.onbufferedamountlow- Questo gestore di eventi viene chiamato quando viene attivato l'evento bufferedamoutlow. Questo evento viene inviato quando RTCDataChannel.bufferedAmount scende al di sotto della proprietà RTCDataChannel.bufferedAmountLowThreshold.
RTCDataChannel.onclose- Questo gestore di eventi viene chiamato quando viene attivato l'evento di chiusura. Questo evento viene inviato quando la connessione dati è stata chiusa.
RTCDataChannel.onerror- Questo gestore di eventi viene chiamato quando viene generato l'evento di errore. Questo evento viene inviato quando si è verificato un errore.
Metodi
RTCDataChannel.close() - Chiude il canale dati.
RTCDataChannel.send()- Invia i dati nel parametro tramite il canale. I dati possono essere un BLOB, una stringa, un ArrayBuffer o un ArrayBufferView.
Ora creiamo un semplice esempio. In primo luogo, eseguire il server di segnalazione che abbiamo creato nel tutorial "server di segnalazione" tramite "server nodo".
Ci saranno tre input di testo nella pagina, uno per un login, uno per un nome utente e uno per il messaggio che vogliamo inviare all'altro peer. Crea un file index.html e aggiungi il seguente codice:
<html lang = "en">
<head>
<meta charset = "utf-8" />
</head>
<body>
<div>
<input type = "text" id = "loginInput" />
<button id = "loginBtn">Login</button>
</div>
<div>
<input type = "text" id = "otherUsernameInput" />
<button id = "connectToOtherUsernameBtn">Establish connection</button>
</div>
<div>
<input type = "text" id = "msgInput" />
<button id = "sendMsgBtn">Send text message</button>
</div>
<script src = "client.js"></script>
</body>
</html>
Abbiamo anche aggiunto tre pulsanti per accedere, stabilire una connessione e inviare un messaggio. Ora crea un file client.js e aggiungi il seguente codice:
var connection = new WebSocket('ws://localhost:9090');
var name = "";
var loginInput = document.querySelector('#loginInput');
var loginBtn = document.querySelector('#loginBtn');
var otherUsernameInput = document.querySelector('#otherUsernameInput');
var connectToOtherUsernameBtn = document.querySelector('#connectToOtherUsernameBtn');
var msgInput = document.querySelector('#msgInput');
var sendMsgBtn = document.querySelector('#sendMsgBtn');
var connectedUser, myConnection, dataChannel;
//when a user clicks the login button
loginBtn.addEventListener("click", function(event) {
name = loginInput.value;
if(name.length > 0) {
send({
type: "login",
name: name
});
}
});
//handle messages from the server
connection.onmessage = function (message) {
console.log("Got message", message.data);
var data = JSON.parse(message.data);
switch(data.type) {
case "login":
onLogin(data.success);
break;
case "offer":
onOffer(data.offer, data.name);
break;
case "answer":
onAnswer(data.answer);
break;
case "candidate":
onCandidate(data.candidate);
break;
default:
break;
}
};
//when a user logs in
function onLogin(success) {
if (success === false) {
alert("oops...try a different username");
} else {
//creating our RTCPeerConnection object
var configuration = {
"iceServers": [{ "url": "stun:stun.1.google.com:19302" }]
};
myConnection = new webkitRTCPeerConnection(configuration, {
optional: [{RtpDataChannels: true}]
});
console.log("RTCPeerConnection object was created");
console.log(myConnection);
//setup ice handling
//when the browser finds an ice candidate we send it to another peer
myConnection.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
openDataChannel();
}
};
connection.onopen = function () {
console.log("Connected");
};
connection.onerror = function (err) {
console.log("Got error", err);
};
// Alias for sending messages in JSON format
function send(message) {
if (connectedUser) {
message.name = connectedUser;
}
connection.send(JSON.stringify(message));
};
Puoi vedere che stabiliamo una connessione socket al nostro server di segnalazione. Quando un utente fa clic sul pulsante di accesso, l'applicazione invia il suo nome utente al server. Se il login ha esito positivo, l'applicazione crea l' oggetto RTCPeerConnection e imposta il gestore onicecandidate che invia tutti i icecandidate trovati all'altro peer. Esegue anche la funzione openDataChannel () che crea un dataChannel. Si noti che durante la creazione dell'oggetto RTCPeerConnection il secondo argomento nel costruttore opzionale: [{RtpDataChannels: true}] è obbligatorio se si utilizza Chrome o Opera. Il passaggio successivo consiste nel creare un'offerta per l'altro peer. Aggiungi il seguente codice al tuo file client.js -
//setup a peer connection with another user
connectToOtherUsernameBtn.addEventListener("click", function () {
var otherUsername = otherUsernameInput.value;
connectedUser = otherUsername;
if (otherUsername.length > 0) {
//make an offer
myConnection.createOffer(function (offer) {
console.log();
send({
type: "offer",
offer: offer
});
myConnection.setLocalDescription(offer);
}, function (error) {
alert("An error has occurred.");
});
}
});
//when somebody wants to call us
function onOffer(offer, name) {
connectedUser = name;
myConnection.setRemoteDescription(new RTCSessionDescription(offer));
myConnection.createAnswer(function (answer) {
myConnection.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("oops...error");
});
}
//when another user answers to our offer
function onAnswer(answer) {
myConnection.setRemoteDescription(new RTCSessionDescription(answer));
}
//when we got ice candidate from another user
function onCandidate(candidate) {
myConnection.addIceCandidate(new RTCIceCandidate(candidate));
}
Puoi vedere che quando un utente fa clic sul pulsante "Stabilisci connessione", l'applicazione fa un'offerta SDP all'altro peer. Abbiamo anche impostato i gestori onAnswer e onCandidate . Infine, implementiamo la funzione openDataChannel () che crea il nostro dataChannel. Aggiungi il seguente codice al tuo file client.js -
//creating data channel
function openDataChannel() {
var dataChannelOptions = {
reliable:true
};
dataChannel = myConnection.createDataChannel("myDataChannel", dataChannelOptions);
dataChannel.onerror = function (error) {
console.log("Error:", error);
};
dataChannel.onmessage = function (event) {
console.log("Got message:", event.data);
};
}
//when a user clicks the send message button
sendMsgBtn.addEventListener("click", function (event) {
console.log("send message");
var val = msgInput.value;
dataChannel.send(val);
});
Qui creiamo il dataChannel per la nostra connessione e aggiungiamo il gestore di eventi per il pulsante "invia messaggio". Ora apri questa pagina in due schede, accedi con due utenti, stabilisci una connessione e prova a inviare messaggi. Dovresti vederli nell'output della console. Si noti che l'esempio sopra è stato testato in Opera.
Ora potresti vedere che RTCDataChannel è una parte estremamente potente dell'API WebRTC. Ci sono molti altri casi d'uso per questo oggetto, come i giochi peer-to-peer o la condivisione di file basata su torrent.
La maggior parte delle applicazioni WebRTC non è solo in grado di comunicare tramite video e audio. Hanno bisogno di molte altre funzionalità. In questo capitolo costruiremo un server di segnalazione di base.
Segnalazione e negoziazione
Per connetterti a un altro utente devi sapere dove si trova sul web. L'indirizzo IP del dispositivo consente ai dispositivi abilitati a Internet di inviare dati direttamente tra loro. L' oggetto RTCPeerConnection è responsabile di ciò. Non appena i dispositivi sanno come trovarsi su Internet, iniziano a scambiarsi dati sui protocolli e codec supportati da ciascun dispositivo.
Per comunicare con un altro utente è sufficiente scambiare le informazioni di contatto e il resto sarà fatto da WebRTC. Il processo di connessione all'altro utente è noto anche come segnalazione e negoziazione. Consiste in pochi passaggi:
Crea un elenco di potenziali candidati per una connessione peer.
L'utente o un'applicazione seleziona un utente con cui stabilire una connessione.
Il livello di segnalazione notifica a un altro utente che qualcuno desidera connettersi a lui. Può accettare o rifiutare.
Il primo utente viene informato dell'accettazione dell'offerta.
Il primo utente avvia RTCPeerConnection con un altro utente.
Entrambi gli utenti scambiano informazioni software e hardware tramite il server di segnalazione.
Entrambi gli utenti si scambiano informazioni sulla posizione.
La connessione ha esito positivo o negativo.
La specifica WebRTC non contiene alcuno standard sullo scambio di informazioni. Quindi tieni presente che quanto sopra è solo un esempio di come può accadere la segnalazione. Puoi utilizzare qualsiasi protocollo o tecnologia che preferisci.
Costruire il server
Il server che stiamo per costruire sarà in grado di connettere due utenti che non si trovano sullo stesso computer. Creeremo il nostro meccanismo di segnalazione. Il nostro server di segnalazione consentirà a un utente di chiamarne un altro. Una volta che un utente ha chiamato un altro, il server passa l'offerta, la risposta, i candidati ICE tra di loro e imposta una connessione WebRTC.
Il diagramma sopra è il flusso di messaggistica tra gli utenti quando si utilizza il server di segnalazione. Prima di tutto, ogni utente si registra al server. Nel nostro caso, questo sarà un semplice nome utente stringa. Una volta che gli utenti si sono registrati, possono chiamarsi a vicenda. L'utente 1 fa un'offerta con l'identificativo utente che desidera chiamare. L'altro utente dovrebbe rispondere. Infine, i candidati ICE vengono inviati tra gli utenti finché non riescono a stabilire una connessione.
Per creare una connessione WebRTC, i client devono essere in grado di trasferire messaggi senza utilizzare una connessione peer WebRTC. Qui è dove useremo HTML5 WebSocket, una connessione socket bidirezionale tra due endpoint: un server web e un browser web. Ora iniziamo a utilizzare la libreria WebSocket. Crea il file server.js e inserisci il seguente codice:
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("user connected");
//when server gets a message from a connected user
connection.on('message', function(message){
console.log("Got message from a user:", message);
});
connection.send("Hello from server");
});
La prima riga richiede la libreria WebSocket che abbiamo già installato. Quindi creiamo un server socket sulla porta 9090. Successivamente, ascoltiamo l' evento di connessione . Questo codice verrà eseguito quando un utente effettua una connessione WebSocket al server. Quindi ascoltiamo tutti i messaggi inviati dall'utente. Infine, inviamo una risposta all'utente connesso dicendo "Hello from server".
Ora esegui il server del nodo e il server dovrebbe iniziare ad ascoltare le connessioni socket.
Per testare il nostro server, utilizzeremo l' utility wscat che abbiamo già installato. Questo strumento aiuta a connettersi direttamente al server WebSocket e testare i comandi. Esegui il nostro server in una finestra di terminale, quindi aprine un'altra ed esegui il comando wscat -c ws: // localhost: 9090 . Dovresti vedere quanto segue sul lato client:
Il server dovrebbe anche registrare l'utente connesso -
Registrazione Utente
Nel nostro server di segnalazione, utilizzeremo un nome utente basato su stringa per ogni connessione in modo da sapere dove inviare i messaggi. Cambiamo un po 'il nostro gestore di connessione -
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
});
In questo modo accettiamo solo messaggi JSON. Successivamente, dobbiamo archiviare tutti gli utenti connessi da qualche parte. Useremo un semplice oggetto Javascript per questo. Cambia la parte superiore del nostro file -
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//all connected to the server users
var users = {};
Aggiungeremo un campo tipo per ogni messaggio proveniente dal client. Ad esempio, se un utente desidera accedere, invia il messaggio del tipo di accesso . Definiamolo -
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged:", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
Se l'utente invia un messaggio con il tipo di login , noi:
Controlla se qualcuno ha già effettuato l'accesso con questo nome utente
In tal caso, informa l'utente che non ha effettuato correttamente l'accesso
Se nessuno utilizza questo nome utente, aggiungiamo nome utente come chiave all'oggetto connessione.
Se un comando non viene riconosciuto inviamo un errore.
Il codice seguente è una funzione di supporto per l'invio di messaggi a una connessione. Aggiungilo al file server.js -
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
La funzione precedente garantisce che tutti i nostri messaggi vengano inviati nel formato JSON.
Quando l'utente si disconnette, dobbiamo pulire la sua connessione. Possiamo eliminare l'utente quando viene attivato l' evento di chiusura . Aggiungere il codice seguente al gestore della connessione :
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
}
});
Ora testiamo il nostro server con il comando login. Tieni presente che tutti i messaggi devono essere codificati nel formato JSON. Esegui il nostro server e prova ad accedere. Dovresti vedere qualcosa del genere -
Effettuare una chiamata
Dopo aver effettuato correttamente l'accesso, l'utente desidera chiamare un altro. Dovrebbe fare un'offerta a un altro utente per ottenerlo. Aggiungi il gestore dell'offerta -
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
In primo luogo, otteniamo la connessione dell'utente che stiamo cercando di chiamare. Se esiste, gli inviamo i dettagli dell'offerta . Aggiungiamo anche otherName al collegamento all'oggetto. Questo è fatto per la semplicità di trovarlo in seguito.
Rispondendo
La risposta alla risposta ha un modello simile che abbiamo utilizzato nel gestore dell'offerta . Il nostro server passa semplicemente attraverso tutti i messaggi come risposta a un altro utente. Aggiungi il seguente codice dopo la consegna dell'offerta :
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
Puoi vedere come questo è simile al gestore dell'offerta . Si noti il codice segue le createOffer e createAnswer funzioni della RTCPeerConnection oggetto.
Ora possiamo testare il nostro meccanismo di offerta / risposta. Connetti due clienti allo stesso tempo e prova a fare un'offerta e rispondere. Dovresti vedere quanto segue:
In questo esempio, offer e answer sono semplici stringhe, ma in un'applicazione reale verranno compilate con i dati SDP.
Candidati ICE
La parte finale è la gestione del candidato ICE tra gli utenti. Usiamo la stessa tecnica solo passando i messaggi tra gli utenti. La differenza principale è che i messaggi candidati potrebbero essere visualizzati più volte per utente in qualsiasi ordine. Aggiungi il gestore candidato -
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
Dovrebbe funzionare in modo simile ai gestori di offerte e risposte .
Lasciando la connessione
Per consentire ai nostri utenti di disconnettersi da un altro utente dovremmo implementare la funzione di riaggancio. Inoltre dirà al server di eliminare tutti i riferimenti utente. Aggiungi illeave gestore -
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
Questo invierà anche all'altro utente l' evento di abbandono in modo che possa disconnettere la sua connessione peer di conseguenza. Dovremmo anche gestire il caso in cui un utente interrompe la connessione dal server di segnalazione. Modifichiamo il nostro gestore vicino -
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
Ora se la connessione termina, i nostri utenti verranno disconnessi. L' evento di chiusura verrà attivato quando un utente chiude la finestra del browser mentre siamo ancora nello stato di offerta , risposta o candidato .
Server di segnalazione completo
Ecco l'intero codice del nostro server di segnalazione -
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//all connected to the server users
var users = {};
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("User connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null) {
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
break;
}
});
//when user exits, for example closes a browser window
//this may help if we are still in "offer","answer" or "candidate" state
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
connection.send("Hello world");
});
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
Quindi il lavoro è finito e il nostro server di segnalazione è pronto. Ricorda che fare cose fuori ordine quando si effettua una connessione WebRTC può causare problemi.
Sommario
In questo capitolo, abbiamo costruito un server di segnalazione semplice e diretto. Abbiamo esaminato il processo di segnalazione, la registrazione dell'utente e il meccanismo di offerta / risposta. Abbiamo anche implementato l'invio di candidati tra utenti.
Il Web si sta muovendo così velocemente e migliora sempre. Ogni giorno vengono creati nuovi standard. I browser consentono l'installazione degli aggiornamenti senza che l'utente lo sappia, quindi è necessario tenere il passo con ciò che sta accadendo nel mondo del Web e WebRTC. Ecco una panoramica di cosa succede oggi.
Supporto browser
Ogni browser non ha tutte le stesse funzionalità WebRTC allo stesso tempo. Browser diversi possono essere all'avanguardia, il che fa sì che alcune funzionalità WebRTC funzionino in un browser e non in un altro. L'attuale supporto per WebRTC nel browser è mostrato nell'immagine seguente.
Puoi controllare lo stato del supporto WebRTC aggiornato su http://caniuse.com/#feat=rtcpeerconnection.
Chrome, Firefox e Opera
Le ultime versioni di Chrome, Firefox e Opera sui principali sistemi operativi per PC come Mac OS X, Windows e Linux, supportano tutte WebRTC out-of-the-box. E, soprattutto, gli ingegneri dei team di sviluppatori di Chrome e Firefox hanno lavorato insieme per risolvere i problemi in modo che questi due browser potessero comunicare facilmente tra loro.
Sistema operativo Android
Sui sistemi operativi Android, le applicazioni WebRTC per Chrome e Firefox dovrebbero funzionare immediatamente. Sono in grado di funzionare con altri browser dopo la versione Android Ice Cream Sandwich (4.0). Ciò è dovuto alla condivisione del codice tra le versioni desktop e mobile.
Mela
Apple non ha ancora fatto alcun annuncio sui piani per supportare WebRTC in Safari su OS X. Una delle possibili soluzioni alternative per il sistema operativo ibrido nativo di iOS per incorporare il codice WebRTC direttamente nell'applicazione e caricare questa app in una WebView.
Internet Explorer
Microsoft non supporta WebRTC sui desktop. Ma hanno ufficialmente confermato che implementeranno ORTC (Object Realtime Communications) nelle versioni future di IE (Edge). Non hanno in programma di supportare WebRTC 1.0. Hanno etichettato il loro ORTC come WebRTC 1.1, sebbene sia solo un miglioramento della comunità e non lo standard ufficiale. Di recente hanno aggiunto il supporto ORTC all'ultima versione di Microsoft Edge. Puoi saperne di più suhttps://blogs.windows.com/msedgedev/2015/09/18/ortc-api-is-now-available-in-microsoftedge/.
Sommario
Si noti che WebRTC è una raccolta di API e protocolli, non una singola API. Il supporto per ciascuno di questi si sta sviluppando su diversi browser e sistemi operativi a un livello diverso. Un ottimo modo per controllare l'ultimo livello di supporto è attraversohttp://canisue.com.Tiene traccia dell'adozione delle API moderne su più browser. È inoltre possibile trovare le informazioni più recenti sui supporti del browser e sulle demo WebRTC all'indirizzohttp://www.webrtc.org, che è supportato da Mozilla, Google e Opera.
Nel mondo mobile, il supporto WebRTC non è allo stesso livello dei desktop. I dispositivi mobili hanno la loro strada, quindi WebRTC è anche qualcosa di diverso sulle piattaforme mobili.
Quando si sviluppa un'applicazione WebRTC per desktop, si considera l'utilizzo di Chrome, Firefox o Opera. Tutti supportano WebRTC fuori dagli schemi. In generale, hai solo bisogno di un browser e non preoccuparti dell'hardware del desktop.
Nel mondo mobile ci sono tre possibili modalità per WebRTC oggi:
- L'applicazione nativa
- L'applicazione browser
- Il browser nativo
Android
Nel 2013, il browser Web Firefox per Android è stato presentato con il supporto WebRTC pronto all'uso. Ora puoi effettuare videochiamate su dispositivi Android utilizzando il browser mobile Firefox.
Ha tre componenti WebRTC principali:
PeerConnection - abilita le chiamate tra i browser
getUserMedia - fornisce l'accesso alla telecamera e al microfono
DataChannels - fornisce il trasferimento dei dati peer-to-peer
Google Chrome per Android fornisce anche il supporto WebRTC. Come hai già notato, le funzionalità più interessanti di solito vengono visualizzate per la prima volta in Chrome.
L'anno scorso, il browser mobile Opera è apparso con il supporto WebRTC. Quindi per Android hai Chrome, Firefox e Opera. Altri browser non supportano WebRTC.
iOS
Sfortunatamente, WebRTC non è supportato su iOS ora. Sebbene WebRTC funzioni bene su Mac quando si utilizza Firefox, Opera o Chrome, non è supportato su iOS.
Al giorno d'oggi, la tua applicazione WebRTC non funzionerà immediatamente sui dispositivi mobili Apple. Ma c'è un browser: Bowser. È un browser web sviluppato da Ericsson e supporta WebRTC immediatamente. Puoi controllare la sua homepage suhttp://www.openwebrtc.org/bowser/.
Oggi è l'unico modo amichevole per supportare la tua applicazione WebRTC su iOS. Un altro modo è sviluppare tu stesso un'applicazione nativa.
Telefoni Windows
Microsoft non supporta WebRTC su piattaforme mobili. Ma hanno ufficialmente confermato che implementeranno ORTC (Object Realtime Communications) nelle versioni future di IE. Non hanno in programma di supportare WebRTC 1.0. Hanno etichettato il loro ORTC come WebRTC 1.1, sebbene sia solo un miglioramento della comunità e non lo standard ufficiale.
Quindi oggi gli utenti di Window Phone non possono utilizzare le applicazioni WebRTC e non c'è modo di superare questa situazione.
Mora
Le applicazioni WebRTC non sono supportate in alcun modo su Blackberry.
Utilizzo di un browser nativo WebRTC
Il caso più comodo e comodo per gli utenti di utilizzare WebRTC è utilizzare il browser nativo del dispositivo. In questo caso, il dispositivo è pronto per eseguire eventuali configurazioni aggiuntive.
Oggi solo i dispositivi Android con versione 4 o successiva forniscono questa funzionalità. Apple continua a non mostrare alcuna attività con il supporto WebRTC. Quindi gli utenti di Safari non possono utilizzare le applicazioni WebRTC. Microsoft inoltre non l'ha introdotto in Windows Phone 8.
Utilizzo di WebRTC tramite applicazioni browser
Ciò significa utilizzare applicazioni di terze parti (browser Web non nativi) per fornire le funzionalità WebRTC. Per ora, ci sono due di queste applicazioni di terze parti. Bowser, che è l'unico modo per portare le funzionalità WebRTC sul dispositivo iOS e Opera, che è una bella alternativa per la piattaforma Android. Il resto dei browser mobili disponibili non supportano WebRTC.
Applicazioni mobili native
Come puoi vedere, WebRTC non ha ancora un ampio supporto nel mondo mobile. Quindi, una delle possibili soluzioni è sviluppare un'applicazione nativa che utilizzi l'API WebRTC. Ma non è la scelta migliore perché la caratteristica principale di WebRTC è una soluzione multipiattaforma. Ad ogni modo, in alcuni casi questo è l'unico modo perché un'applicazione nativa può utilizzare funzioni o caratteristiche specifiche del dispositivo che non sono supportate dai browser HTML5.
Limitazione del flusso video per dispositivi mobili e desktop
Il primo parametro dell'API getUserMedia prevede un oggetto di chiavi e valori che dica al browser come elaborare i flussi. Puoi controllare la serie completa di vincoli suhttps://tools.ietf.org/html/draft-alvestrand-constraints-resolution-03. È possibile impostare le proporzioni video, frameRate e altri parametri opzionali.
Il supporto dei dispositivi mobili è uno dei maggiori problemi perché i dispositivi mobili hanno uno spazio limitato sullo schermo insieme a risorse limitate. È possibile che il dispositivo mobile acquisisca solo una risoluzione 480x320 o un flusso video più piccolo per risparmiare energia e larghezza di banda. L'utilizzo della stringa dell'agente utente nel browser è un buon modo per verificare se l'utente è su un dispositivo mobile o meno. Vediamo un esempio. Crea il file index.html -
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "utf-8" />
</head>
<body>
<video autoplay></video>
<script src = "client.js"></script>
</body>
</html>
Quindi crea il seguente file client.js :
//constraints for desktop browser
var desktopConstraints = {
video: {
mandatory: {
maxWidth:800,
maxHeight:600
}
},
audio: true
};
//constraints for mobile browser
var mobileConstraints = {
video: {
mandatory: {
maxWidth: 480,
maxHeight: 320,
}
},
audio: true
}
//if a user is using a mobile browser
if(/Android|iPhone|iPad/i.test(navigator.userAgent)) {
var constraints = mobileConstraints;
} else {
var constraints = desktopConstraints;
}
function hasUserMedia() {
//check if the browser supports the WebRTC
return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia);
}
if (hasUserMedia()) {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
//enabling video and audio channels
navigator.getUserMedia(constraints, function (stream) {
var video = document.querySelector('video');
//inserting our stream to the video tag
video.src = window.URL.createObjectURL(stream);
}, function (err) {});
} else {
alert("WebRTC is not supported");
}
Esegui il server web utilizzando il comando statico e apri la pagina. Dovresti vedere che è 800x600. Quindi apri questa pagina in una visualizzazione mobile utilizzando gli strumenti di Chrome e controlla la risoluzione. Dovrebbe essere 480 x 320.
I vincoli sono il modo più semplice per aumentare le prestazioni della tua applicazione WebRTC.
Sommario
In questo capitolo abbiamo appreso i problemi che possono verificarsi durante lo sviluppo di applicazioni WebRTC per dispositivi mobili. Abbiamo scoperto diversi limiti nel supportare l'API WebRTC su piattaforme mobili. Abbiamo anche lanciato un'applicazione demo in cui impostiamo diversi vincoli per i browser desktop e mobili.
In questo capitolo, creeremo un'applicazione client che consente a due utenti su dispositivi separati di comunicare utilizzando WebRTC. La nostra applicazione avrà due pagine. Uno per il login e l'altro per chiamare un altro utente.
Le due pagine saranno i tag div . La maggior parte dell'input viene eseguita tramite semplici gestori di eventi.
Server di segnalazione
Per creare una connessione WebRTC, i client devono essere in grado di trasferire messaggi senza utilizzare una connessione peer WebRTC. Qui è dove useremo HTML5 WebSocket, una connessione socket bidirezionale tra due endpoint: un server web e un browser web. Ora iniziamo a utilizzare la libreria WebSocket. Crea il file server.js e inserisci il seguente codice:
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("user connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
console.log("Got message from a user:", message);
});
connection.send("Hello from server");
});
La prima riga richiede la libreria WebSocket che abbiamo già installato. Quindi creiamo un server socket sulla porta 9090. Successivamente, ascoltiamo l' evento di connessione . Questo codice verrà eseguito quando un utente effettua una connessione WebSocket al server. Quindi ascoltiamo tutti i messaggi inviati dall'utente. Infine, inviamo una risposta all'utente connesso dicendo "Hello from server".
Nel nostro server di segnalazione, utilizzeremo un nome utente basato su stringa per ogni connessione in modo da sapere dove inviare i messaggi. Cambiamo un po 'il nostro gestore di connessione -
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
});
In questo modo accettiamo solo messaggi JSON. Successivamente, dobbiamo archiviare tutti gli utenti connessi da qualche parte. Useremo un semplice oggetto Javascript per questo. Cambia la parte superiore del nostro file -
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//all connected to the server users
var users = {};
Aggiungeremo un campo tipo per ogni messaggio proveniente dal client. Ad esempio, se un utente desidera accedere, invia il messaggio del tipo di accesso . Definiamolo -
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged:", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
Se l'utente invia un messaggio con il tipo di login , noi:
Controlla se qualcuno ha già effettuato l'accesso con questo nome utente
In tal caso, informa l'utente che non ha effettuato correttamente l'accesso
Se nessuno utilizza questo nome utente, aggiungiamo nome utente come chiave all'oggetto connessione.
Se un comando non viene riconosciuto inviamo un errore.
Il codice seguente è una funzione di supporto per l'invio di messaggi a una connessione. Aggiungilo al file server.js -
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
Quando l'utente si disconnette, dobbiamo pulire la sua connessione. Possiamo eliminare l'utente quando viene attivato l' evento di chiusura . Aggiungere il codice seguente al gestore della connessione :
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
}
});
Dopo aver effettuato correttamente l'accesso, l'utente desidera chiamare un altro. Dovrebbe fare un'offerta a un altro utente per ottenerlo. Aggiungi il gestore dell'offerta -
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null) {
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
In primo luogo, otteniamo la connessione dell'utente che stiamo cercando di chiamare. Se esiste, gli inviamo i dettagli dell'offerta . Aggiungiamo anche otherName al collegamento all'oggetto. Questo è fatto per la semplicità di trovarlo in seguito.
La risposta alla risposta ha un modello simile che abbiamo utilizzato nel gestore dell'offerta . Il nostro server passa semplicemente attraverso tutti i messaggi come risposta a un altro utente. Aggiungi il codice seguente dopo il gestore dell'offerta :
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
La parte finale è la gestione del candidato ICE tra gli utenti. Usiamo la stessa tecnica solo passando i messaggi tra gli utenti. La differenza principale è che i messaggi candidati potrebbero essere visualizzati più volte per utente in qualsiasi ordine. Aggiungi il gestore candidato -
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
Per consentire ai nostri utenti di disconnettersi da un altro utente dovremmo implementare la funzione di riaggancio. Inoltre dirà al server di eliminare tutti i riferimenti utente. Aggiungi il gestore delle ferie -
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
Questo invierà anche all'altro utente l' evento di abbandono in modo che possa disconnettere la sua connessione peer di conseguenza. Dovremmo anche gestire il caso in cui un utente interrompe la connessione dal server di segnalazione. Modifichiamo il nostro gestore vicino -
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
Di seguito è riportato l'intero codice del nostro server di segnalazione:
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//all connected to the server users
var users = {};
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("User connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null) {
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
break;
}
});
//when user exits, for example closes a browser window
//this may help if we are still in "offer","answer" or "candidate" state
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
connection.send("Hello world");
});
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
Applicazione client
Un modo per testare questa applicazione è aprire due schede del browser e provare a chiamarsi a vicenda.
Prima di tutto, dobbiamo installare la libreria bootstrap . Bootstrap è un framework di frontend per lo sviluppo di applicazioni web. Puoi saperne di più suhttp://getbootstrap.com/.Crea una cartella chiamata, ad esempio, "videochat". Questa sarà la nostra cartella principale dell'applicazione. All'interno di questa cartella creare un file package.json (è necessario per la gestione delle dipendenze npm) e aggiungere quanto segue:
{
"name": "webrtc-videochat",
"version": "0.1.0",
"description": "webrtc-videochat",
"author": "Author",
"license": "BSD-2-Clause"
}
Quindi esegui npm install bootstrap . Questo installerà la libreria bootstrap nella cartella videochat / node_modules .
Ora dobbiamo creare una pagina HTML di base. Crea un file index.html nella cartella principale con il codice seguente:
<html>
<head>
<title>WebRTC Video Demo</title>
<link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
video {
background: black;
border: 1px solid gray;
}
.call-page {
position: relative;
display: block;
margin: 0 auto;
width: 500px;
height: 500px;
}
#localVideo {
width: 150px;
height: 150px;
position: absolute;
top: 15px;
right: 15px;
}
#remoteVideo {
width: 500px;
height: 500px;
}
</style>
<body>
<div id = "loginPage" class = "container text-center">
<div class = "row">
<div class = "col-md-4 col-md-offset-4">
<h2>WebRTC Video Demo. Please sign in</h2>
<label for = "usernameInput" class = "sr-only">Login</label>
<input type = "email" id = "usernameInput" c
lass = "form-control formgroup" placeholder = "Login"
required = "" autofocus = "">
<button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
Sign in</button>
</div>
</div>
</div>
<div id = "callPage" class = "call-page">
<video id = "localVideo" autoplay></video>
<video id = "remoteVideo" autoplay></video>
<div class = "row text-center">
<div class = "col-md-12">
<input id = "callToUsernameInput" type = "text"
placeholder = "username to call" />
<button id = "callBtn" class = "btn-success btn">Call</button>
<button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
</div>
</div>
</div>
<script src = "client.js"></script>
</body>
</html>
Questa pagina dovrebbe esserti familiare. Abbiamo aggiunto il file css bootstrap . Abbiamo anche definito due pagine. Infine, abbiamo creato diversi campi di testo e pulsanti per ottenere informazioni dall'utente. Dovresti vedere i due elementi video per i flussi video locali e remoti. Notare che abbiamo aggiunto un collegamento a un file client.js .
Ora dobbiamo stabilire una connessione con il nostro server di segnalazione. Crea il file client.js nella cartella principale con il codice seguente:
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
Ora esegui il nostro server di segnalazione tramite il server del nodo . Quindi, all'interno della cartella principale, esegui il comando statico e apri la pagina all'interno del browser. Dovresti vedere il seguente output della console:
Il passaggio successivo consiste nell'implementazione di un accesso utente con un nome utente univoco. Inviamo semplicemente un nome utente al server, che poi ci dice se è stato preso o meno. Aggiungi il seguente codice al tuo file client.js -
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
//hide call page
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
//display the call page if login is successful
loginPage.style.display = "none";
callPage.style.display = "block";
//start peer connection
}
};
In primo luogo, selezioniamo alcuni riferimenti agli elementi sulla pagina. Nascondiamo la pagina della chiamata. Quindi, aggiungiamo un listener di eventi sul pulsante di accesso. Quando l'utente fa clic, inviamo il suo nome utente al server. Infine, implementiamo il callback handleLogin. Se il login è andato a buon fine, mostriamo la pagina della chiamata e iniziamo a impostare una connessione peer.
Per avviare una connessione peer abbiamo bisogno di:
- Ottieni uno streaming dalla webcam.
- Crea l'oggetto RTCPeerConnection.
Aggiungi il seguente codice al "blocco dei selettori dell'interfaccia utente":
var localVideo = document.querySelector('#localVideo');
var remoteVideo = document.querySelector('#remoteVideo');
var yourConn;
var stream;
Modifica la funzione handleLogin -
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
//getting local video stream
navigator.webkitGetUserMedia({ video: true, audio: true }, function (myStream) {
stream = myStream;
//displaying local video stream on the page
localVideo.src = window.URL.createObjectURL(stream);
//using Google public stun server
var configuration = {
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
};
yourConn = new webkitRTCPeerConnection(configuration);
// setup stream listening
yourConn.addStream(stream);
//when a remote user adds stream to the peer connection, we display it
yourConn.onaddstream = function (e) {
remoteVideo.src = window.URL.createObjectURL(e.stream);
};
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
}, function (error) {
console.log(error);
});
}
};
Ora, se esegui il codice, la pagina dovrebbe consentirti di accedere e visualizzare il tuo flusso video locale sulla pagina.
Ora siamo pronti per avviare una chiamata. In primo luogo, inviamo un'offerta a un altro utente. Una volta che un utente riceve l'offerta, crea una risposta e inizia a scambiare candidati ICE. Aggiungi il codice seguente al file client.js :
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
Aggiungiamo un gestore di clic al pulsante Chiama, che avvia un'offerta. Quindi implementiamo diversi gestori attesi dal gestore onmessage . Verranno elaborati in modo asincrono finché entrambi gli utenti non avranno stabilito una connessione.
L'ultimo passaggio consiste nell'implementazione della funzione di blocco. Ciò interromperà la trasmissione dei dati e comunicherà all'altro utente di chiudere la chiamata. Aggiungi il seguente codice -
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteVideo.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
};
Quando l'utente fa clic sul pulsante Riaggancia:
- Invierà un messaggio di "permesso" all'altro utente
- Chiuderà RTCPeerConnection e distruggerà la connessione localmente
Ora esegui il codice. Dovresti essere in grado di accedere al server utilizzando due schede del browser. È quindi possibile chiamare la scheda e terminare la chiamata.
Quello che segue è l'intero file client.js -
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
var localVideo = document.querySelector('#localVideo');
var remoteVideo = document.querySelector('#remoteVideo');
var yourConn;
var stream;
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
//getting local video stream
navigator.webkitGetUserMedia({ video: true, audio: true }, function (myStream) {
stream = myStream;
//displaying local video stream on the page
localVideo.src = window.URL.createObjectURL(stream);
//using Google public stun server
var configuration = {
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
};
yourConn = new webkitRTCPeerConnection(configuration);
// setup stream listening
yourConn.addStream(stream);
//when a remote user adds stream to the peer connection, we display it
yourConn.onaddstream = function (e) {
remoteVideo.src = window.URL.createObjectURL(e.stream);
};
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
}, function (error) {
console.log(error);
});
}
};
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteVideo.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
};
Sommario
Questa demo fornisce una linea di base delle funzionalità di cui ogni applicazione WebRTC necessita. Per migliorare questa demo è possibile aggiungere l'identificazione dell'utente tramite piattaforme come Facebook o Google, gestire l'input dell'utente per dati non validi. Inoltre, la connessione WebRTC può non riuscire a causa di diversi motivi, come il mancato supporto della tecnologia o l'impossibilità di attraversare i firewall. È stato fatto un grande lavoro per rendere stabile qualsiasi applicazione WebRTC.
In questo capitolo, creeremo un'applicazione client che consente a due utenti su dispositivi separati di comunicare utilizzando flussi audio WebRTC. La nostra applicazione avrà due pagine. Uno per il login e l'altro per effettuare una chiamata audio a un altro utente.
Le due pagine saranno i tag div . La maggior parte dell'input viene eseguita tramite semplici gestori di eventi.
Server di segnalazione
Per creare una connessione WebRTC, i client devono essere in grado di trasferire messaggi senza utilizzare una connessione peer WebRTC. Qui è dove useremo HTML5 WebSocket, una connessione socket bidirezionale tra due endpoint: un server web e un browser web. Ora iniziamo a utilizzare la libreria WebSocket. Crea il file server.js e inserisci il seguente codice:
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("user connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
console.log("Got message from a user:", message);
});
connection.send("Hello from server");
});
La prima riga richiede la libreria WebSocket che abbiamo già installato. Quindi creiamo un server socket sulla porta 9090. Successivamente, ascoltiamo l' evento di connessione . Questo codice verrà eseguito quando un utente effettua una connessione WebSocket al server. Quindi ascoltiamo tutti i messaggi inviati dall'utente. Infine, inviamo una risposta all'utente connesso dicendo "Hello from server".
Nel nostro server di segnalazione, utilizzeremo un nome utente basato su stringa per ogni connessione in modo da sapere dove inviare i messaggi. Cambiamo un po 'il nostro gestore di connessione -
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
});
In questo modo accettiamo solo messaggi JSON. Successivamente, dobbiamo archiviare tutti gli utenti connessi da qualche parte. Useremo un semplice oggetto Javascript per questo. Cambia la parte superiore del nostro file -
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//all connected to the server users
var users = {};
Aggiungeremo un campo tipo per ogni messaggio proveniente dal client. Ad esempio, se un utente desidera accedere, invia il messaggio del tipo di accesso . Definiamolo -
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged:", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
Se l'utente invia un messaggio con il tipo di login , noi:
- Controlla se qualcuno ha già effettuato l'accesso con questo nome utente.
- In tal caso, informa l'utente che non ha effettuato correttamente l'accesso.
- Se nessuno utilizza questo nome utente, aggiungiamo nome utente come chiave all'oggetto connessione.
- Se un comando non viene riconosciuto inviamo un errore.
Il codice seguente è una funzione di supporto per l'invio di messaggi a una connessione. Aggiungilo al file server.js -
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
Quando l'utente si disconnette, dobbiamo pulire la sua connessione. Possiamo eliminare l'utente quando viene attivato l' evento di chiusura . Aggiungere il codice seguente al gestore della connessione -
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
}
});
Dopo aver effettuato correttamente l'accesso, l'utente desidera chiamare un altro. Dovrebbe fare un'offerta a un altro utente per ottenerlo. Aggiungi il gestore dell'offerta -
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null) {
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
In primo luogo, otteniamo la connessione dell'utente che stiamo cercando di chiamare. Se esiste, gli inviamo i dettagli dell'offerta . Aggiungiamo anche otherName al collegamento all'oggetto. Questo è fatto per la semplicità di trovarlo in seguito.
La risposta alla risposta ha un modello simile che abbiamo utilizzato nel gestore dell'offerta . Il nostro server passa semplicemente attraverso tutti i messaggi come risposta a un altro utente. Aggiungi il codice seguente dopo il gestore dell'offerta :
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
La parte finale è la gestione del candidato ICE tra gli utenti. Usiamo la stessa tecnica solo passando i messaggi tra gli utenti. La differenza principale è che i messaggi candidati potrebbero essere visualizzati più volte per utente in qualsiasi ordine. Aggiungi il gestore candidato -
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
Per consentire ai nostri utenti di disconnettersi da un altro utente dovremmo implementare la funzione di riaggancio. Inoltre dirà al server di eliminare tutti i riferimenti utente. Aggiungi il gestore delle ferie -
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
Questo invierà anche all'altro utente l' evento di abbandono in modo che possa disconnettere la sua connessione peer di conseguenza. Dovremmo anche gestire il caso in cui un utente interrompe la connessione dal server di segnalazione. Modifichiamo il nostro gestore vicino -
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
Di seguito è riportato l'intero codice del nostro server di segnalazione:
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//all connected to the server users
var users = {};
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("User connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null) {
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
break;
}
});
//when user exits, for example closes a browser window
//this may help if we are still in "offer","answer" or "candidate" state
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
connection.send("Hello world");
});
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
Applicazione client
Un modo per testare questa applicazione è aprire due schede del browser e provare a scambiarsi una chiamata audio.
Prima di tutto, dobbiamo installare la libreria bootstrap . Bootstrap è un framework di frontend per lo sviluppo di applicazioni web. Puoi saperne di più suhttp://getbootstrap.com/.Crea una cartella chiamata, ad esempio, "audiochat". Questa sarà la nostra cartella principale dell'applicazione. All'interno di questa cartella creare un file package.json (è necessario per la gestione delle dipendenze npm) e aggiungere quanto segue:
{
"name": "webrtc-audiochat",
"version": "0.1.0",
"description": "webrtc-audiochat",
"author": "Author",
"license": "BSD-2-Clause"
}
Quindi esegui npm install bootstrap . Questo installerà la libreria bootstrap nella cartella audiochat / node_modules .
Ora dobbiamo creare una pagina HTML di base. Crea un file index.html nella cartella principale con il codice seguente:
<html>
<head>
<title>WebRTC Voice Demo</title>
<link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
</style>
<body>
<div id = "loginPage" class = "container text-center">
<div class = "row">
<div class = "col-md-4 col-md-offset-4">
<h2>WebRTC Voice Demo. Please sign in</h2>
<label for = "usernameInput" class = "sr-only">Login</label>
<input type = "email" id = "usernameInput"
class = "form-control formgroup"
placeholder = "Login" required = "" autofocus = "">
<button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
Sign in</button>
</div>
</div>
</div>
<div id = "callPage" class = "call-page">
<div class = "row">
<div class = "col-md-6 text-right">
Local audio: <audio id = "localAudio"
controls autoplay></audio>
</div>
<div class = "col-md-6 text-left">
Remote audio: <audio id = "remoteAudio"
controls autoplay></audio>
</div>
</div>
<div class = "row text-center">
<div class = "col-md-12">
<input id = "callToUsernameInput"
type = "text" placeholder = "username to call" />
<button id = "callBtn" class = "btn-success btn">Call</button>
<button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
</div>
</div>
</div>
<script src = "client.js"></script>
</body>
</html>
Questa pagina dovrebbe esserti familiare. Abbiamo aggiunto il file css bootstrap . Abbiamo anche definito due pagine. Infine, abbiamo creato diversi campi di testo e pulsanti per ottenere informazioni dall'utente. Dovresti vedere i due elementi audio per i flussi audio locali e remoti. Notare che abbiamo aggiunto un collegamento a un file client.js .
Ora dobbiamo stabilire una connessione con il nostro server di segnalazione. Crea il file client.js nella cartella principale con il codice seguente:
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
Ora esegui il nostro server di segnalazione tramite il server del nodo . Quindi, all'interno della cartella principale, esegui il comando statico e apri la pagina all'interno del browser. Dovresti vedere il seguente output della console:
Il passaggio successivo consiste nell'implementazione di un accesso utente con un nome utente univoco. Inviamo semplicemente un nome utente al server, che poi ci dice se è stato preso o meno. Aggiungi il seguente codice al tuo file client.js -
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
}
};
In primo luogo, selezioniamo alcuni riferimenti agli elementi sulla pagina. Nascondiamo la pagina della chiamata. Quindi, aggiungiamo un listener di eventi sul pulsante di accesso. Quando l'utente fa clic, inviamo il suo nome utente al server. Infine, implementiamo il callback handleLogin. Se il login è andato a buon fine, mostriamo la pagina della chiamata e iniziamo a impostare una connessione peer.
Per avviare una connessione peer abbiamo bisogno di:
- Obtain an audio stream from a microphone
- Create the RTCPeerConnection object
Add the following code to the “UI selectors block” −
var localAudio = document.querySelector('#localAudio');
var remoteAudio = document.querySelector('#remoteAudio');
var yourConn;
var stream;
Modify the handleLogin function −
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
//getting local audio stream
navigator.webkitGetUserMedia({ video: false, audio: true }, function (myStream) {
stream = myStream;
//displaying local audio stream on the page
localAudio.src = window.URL.createObjectURL(stream);
//using Google public stun server
var configuration = {
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
};
yourConn = new webkitRTCPeerConnection(configuration);
// setup stream listening
yourConn.addStream(stream);
//when a remote user adds stream to the peer connection, we display it
yourConn.onaddstream = function (e) {
remoteAudio.src = window.URL.createObjectURL(e.stream);
};
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
});
}
};
}, function (error) {
console.log(error);
});
}
};
Now if you run the code, the page should allow you to log in and display your local audio stream on the page.
Now we are ready to initiate a call. Firstly, we send an offer to another user. Once a user gets the offer, he creates an answer and start trading ICE candidates. Add the following code to the client.js file −
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
We add a click handler to the Call button, which initiates an offer. Then we implement several handlers expected by the onmessage handler. They will be processed asynchronously until both the users have made a connection.
The last step is implementing the hang-up feature. This will stop transmitting data and tell the other user to close the call. Add the following code −
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteAudio.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
};
When the user clicks on the Hang Up button −
- It will send a “leave” message to the other user
- It will close the RTCPeerConnection and destroy the connection locally
Now run the code. You should be able to log in to the server using two browser tabs. You can then make an audio call to the tab and hang up the call.
The following is the entire client.js file −
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
var localAudio = document.querySelector('#localAudio');
var remoteAudio = document.querySelector('#remoteAudio');
var yourConn;
var stream;
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
//getting local audio stream
navigator.webkitGetUserMedia({ video: false, audio: true }, function (myStream) {
stream = myStream;
//displaying local audio stream on the page
localAudio.src = window.URL.createObjectURL(stream);
//using Google public stun server
var configuration = {
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
};
yourConn = new webkitRTCPeerConnection(configuration);
// setup stream listening
yourConn.addStream(stream);
//when a remote user adds stream to the peer connection, we display it
yourConn.onaddstream = function (e) {
remoteAudio.src = window.URL.createObjectURL(e.stream);
};
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
}, function (error) {
console.log(error);
});
}
};
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteAudio.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
};
In questo capitolo, creeremo un'applicazione client che consente a due utenti su dispositivi separati di scambiarsi messaggi utilizzando WebRTC. La nostra applicazione avrà due pagine. Uno per il login e l'altro per l'invio di messaggi a un altro utente.
Le due pagine saranno i tag div . La maggior parte dell'input viene eseguita tramite semplici gestori di eventi.
Server di segnalazione
Per creare una connessione WebRTC, i client devono essere in grado di trasferire messaggi senza utilizzare una connessione peer WebRTC. Qui è dove useremo HTML5 WebSocket, una connessione socket bidirezionale tra due endpoint: un server web e un browser web. Ora iniziamo a utilizzare la libreria WebSocket. Crea il file server.js e inserisci il seguente codice:
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("user connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
console.log("Got message from a user:", message);
});
connection.send("Hello from server");
});
La prima riga richiede la libreria WebSocket che abbiamo già installato. Quindi creiamo un server socket sulla porta 9090. Successivamente, ascoltiamo l' evento di connessione . Questo codice verrà eseguito quando un utente effettua una connessione WebSocket al server. Quindi ascoltiamo tutti i messaggi inviati dall'utente. Infine, inviamo una risposta all'utente connesso dicendo "Hello from server".
Nel nostro server di segnalazione, utilizzeremo un nome utente basato su stringa per ogni connessione in modo da sapere dove inviare i messaggi. Cambiamo un po 'il nostro gestore di connessione -
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
});
In questo modo accettiamo solo messaggi JSON. Successivamente, dobbiamo archiviare tutti gli utenti connessi da qualche parte. Useremo un semplice oggetto Javascript per questo. Cambia la parte superiore del nostro file -
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//all connected to the server users
var users = {};
Aggiungeremo un campo tipo per ogni messaggio proveniente dal client. Ad esempio, se un utente desidera accedere, invia il messaggio del tipo di accesso . Definiamolo -
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged:", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
Se l'utente invia un messaggio con il tipo di login , noi:
- Controlla se qualcuno ha già effettuato l'accesso con questo nome utente.
- In tal caso, informa l'utente che non ha effettuato correttamente l'accesso.
- Se nessuno utilizza questo nome utente, aggiungiamo nome utente come chiave all'oggetto connessione.
- Se un comando non viene riconosciuto inviamo un errore.
Il codice seguente è una funzione di supporto per l'invio di messaggi a una connessione. Aggiungilo al file server.js -
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
Quando l'utente si disconnette, dobbiamo pulire la sua connessione. Possiamo eliminare l'utente quando viene attivato l' evento di chiusura . Aggiungere il codice seguente al gestore della connessione :
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
}
});
Dopo aver effettuato correttamente l'accesso, l'utente desidera chiamare un altro. Dovrebbe fare un'offerta a un altro utente per ottenerlo. Aggiungi il gestore dell'offerta -
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
break;
In primo luogo, otteniamo la connessione dell'utente che stiamo cercando di chiamare. Se esiste, gli inviamo i dettagli dell'offerta . Aggiungiamo anche otherName al collegamento all'oggetto. Questo è fatto per la semplicità di trovarlo in seguito.
La risposta alla risposta ha un modello simile che abbiamo utilizzato nel gestore dell'offerta . Il nostro server passa semplicemente attraverso tutti i messaggi come risposta a un altro utente. Aggiungi il codice seguente dopo il gestore dell'offerta :
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
La parte finale è la gestione del candidato ICE tra gli utenti. Usiamo la stessa tecnica solo passando i messaggi tra gli utenti. La differenza principale è che i messaggi candidati potrebbero essere visualizzati più volte per utente in qualsiasi ordine. Aggiungi il gestore candidato -
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
Per consentire ai nostri utenti di disconnettersi da un altro utente dovremmo implementare la funzione di riaggancio. Inoltre dirà al server di eliminare tutti i riferimenti utente. Aggiungi il gestore delle ferie -
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
Questo invierà anche all'altro utente l' evento di abbandono in modo che possa disconnettere la sua connessione peer di conseguenza. Dovremmo anche gestire il caso in cui un utente interrompe la connessione dal server di segnalazione. Modifichiamo il nostro gestore vicino -
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
Di seguito è riportato l'intero codice del nostro server di segnalazione:
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({port: 9090});
//all connected to the server users
var users = {};
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("User connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null) {
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
break;
}
});
//when user exits, for example closes a browser window
//this may help if we are still in "offer","answer" or "candidate" state
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
connection.send("Hello world");
});
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
Applicazione client
Un modo per testare questa applicazione è aprire due schede del browser e provare a inviare un messaggio a vicenda.
Prima di tutto, dobbiamo installare la libreria bootstrap . Bootstrap è un framework di frontend per lo sviluppo di applicazioni web. Puoi saperne di più suhttp://getbootstrap.com/.Crea una cartella chiamata, ad esempio, "chat di testo". Questa sarà la nostra cartella principale dell'applicazione. All'interno di questa cartella creare un file package.json (è necessario per la gestione delle dipendenze npm) e aggiungere quanto segue:
{
"name": "webrtc-textochat",
"version": "0.1.0",
"description": "webrtc-textchat",
"author": "Author",
"license": "BSD-2-Clause"
}
Quindi esegui npm install bootstrap . Questo installerà la libreria bootstrap nella cartella textchat / node_modules .
Ora dobbiamo creare una pagina HTML di base. Crea un file index.html nella cartella principale con il codice seguente:
<html>
<head>
<title>WebRTC Text Demo</title>
<link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
</style>
<body>
<div id = "loginPage" class = "container text-center">
<div class = "row">
<div class = "col-md-4 col-md-offset-4">
<h2>WebRTC Text Demo. Please sign in</h2>
<label for = "usernameInput" class = "sr-only">Login</label>
<input type = "email" id = "usernameInput"
class = "form-control formgroup" placeholder = "Login"
required = "" autofocus = "">
<button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
Sign in</button>
</div>
</div>
</div>
<div id = "callPage" class = "call-page container">
<div class = "row">
<div class = "col-md-4 col-md-offset-4 text-center">
<div class = "panel panel-primary">
<div class = "panel-heading">Text chat</div>
<div id = "chatarea" class = "panel-body text-left"></div>
</div>
</div>
</div>
<div class = "row text-center form-group">
<div class = "col-md-12">
<input id = "callToUsernameInput" type = "text"
placeholder = "username to call" />
<button id = "callBtn" class = "btn-success btn">Call</button>
<button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
</div>
</div>
<div class = "row text-center">
<div class = "col-md-12">
<input id = "msgInput" type = "text" placeholder = "message" />
<button id = "sendMsgBtn" class = "btn-success btn">Send</button>
</div>
</div>
</div>
<script src = "client.js"></script>
</body>
</html>
Questa pagina dovrebbe esserti familiare. Abbiamo aggiunto il file css bootstrap . Abbiamo anche definito due pagine. Infine, abbiamo creato diversi campi di testo e pulsanti per ottenere informazioni dall'utente. Nella pagina "chat" dovresti vedere il tag div con l'ID "chatarea" dove verranno visualizzati tutti i nostri messaggi. Notare che abbiamo aggiunto un collegamento a un file client.js .
Ora dobbiamo stabilire una connessione con il nostro server di segnalazione. Crea il file client.js nella cartella principale con il codice seguente:
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
Ora esegui il nostro server di segnalazione tramite il server del nodo . Quindi, all'interno della cartella principale, esegui il comando statico e apri la pagina all'interno del browser. Dovresti vedere il seguente output della console:
Il passaggio successivo consiste nell'implementazione di un accesso utente con un nome utente univoco. Inviamo semplicemente un nome utente al server, che poi ci dice se è stato preso o meno. Aggiungi il seguente codice al tuo file client.js -
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
}
};
In primo luogo, selezioniamo alcuni riferimenti agli elementi sulla pagina. Nascondiamo la pagina della chiamata. Quindi, aggiungiamo un listener di eventi sul pulsante di accesso. Quando l'utente fa clic, inviamo il suo nome utente al server. Infine, implementiamo il callback handleLogin. Se il login è andato a buon fine, mostriamo la pagina della chiamata, configuriamo una connessione peer e creiamo un canale dati.
Per avviare una connessione peer con un canale dati abbiamo bisogno di:
- Crea l'oggetto RTCPeerConnection
- Crea un canale dati all'interno del nostro oggetto RTCPeerConnection
Aggiungi il seguente codice al "blocco dei selettori dell'interfaccia utente":
var msgInput = document.querySelector('#msgInput');
var sendMsgBtn = document.querySelector('#sendMsgBtn');
var chatArea = document.querySelector('#chatarea');
var yourConn;
var dataChannel;
Modifica la funzione handleLogin -
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
//using Google public stun server
var configuration = {
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
};
yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]});
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
//creating data channel
dataChannel = yourConn.createDataChannel("channel1", {reliable:true});
dataChannel.onerror = function (error) {
console.log("Ooops...error:", error);
};
//when we receive a message from the other peer, display it on the screen
dataChannel.onmessage = function (event) {
chatArea.innerHTML += connectedUser + ": " + event.data + "<br />";
};
dataChannel.onclose = function () {
console.log("data channel is closed");
};
}
};
Se il login ha avuto esito positivo, l'applicazione crea l' oggetto RTCPeerConnection e configura il gestore onicecandidate che invia tutti gli icecandidate trovati all'altro peer. Crea anche un dataChannel. Si noti che durante la creazione dell'oggetto RTCPeerConnection il secondo argomento nel costruttore opzionale: [{RtpDataChannels: true}] è obbligatorio se si utilizza Chrome o Opera. Il passaggio successivo consiste nel creare un'offerta per l'altro peer. Una volta che un utente riceve l'offerta, crea una risposta e inizia a scambiare candidati ICE. Aggiungi il codice seguente al file client.js :
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
Aggiungiamo un gestore di clic al pulsante Chiama, che avvia un'offerta. Quindi implementiamo diversi gestori attesi dal gestore onmessage . Verranno elaborati in modo asincrono finché entrambi gli utenti non avranno stabilito una connessione.
Il passaggio successivo consiste nell'implementazione della funzionalità di blocco. Ciò interromperà la trasmissione dei dati e comunicherà all'altro utente di chiudere il canale dati. Aggiungi il seguente codice -
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
yourConn.close();
yourConn.onicecandidate = null;
};
Quando l'utente fa clic sul pulsante Riaggancia:
- Invierà un messaggio di "permesso" all'altro utente.
- Chiuderà RTCPeerConnection e il canale dati.
L'ultimo passaggio è l'invio di un messaggio a un altro peer. Aggiungi il gestore "clic" al pulsante "invia messaggio" -
//when user clicks the "send message" button
sendMsgBtn.addEventListener("click", function (event) {
var val = msgInput.value;
chatArea.innerHTML += name + ": " + val + "<br />";
//sending a message to a connected peer
dataChannel.send(val);
msgInput.value = "";
});
Ora esegui il codice. Dovresti essere in grado di accedere al server utilizzando due schede del browser. È quindi possibile impostare una connessione peer con l'altro utente e inviargli un messaggio, nonché chiudere il canale dati facendo clic sul pulsante "Riaggancia".
Quello che segue è l'intero file client.js -
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
var msgInput = document.querySelector('#msgInput');
var sendMsgBtn = document.querySelector('#sendMsgBtn');
var chatArea = document.querySelector('#chatarea');
var yourConn;
var dataChannel;
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
//using Google public stun server
var configuration = {
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
};
yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]});
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
//creating data channel
dataChannel = yourConn.createDataChannel("channel1", {reliable:true});
dataChannel.onerror = function (error) {
console.log("Ooops...error:", error);
};
//when we receive a message from the other peer, display it on the screen
dataChannel.onmessage = function (event) {
chatArea.innerHTML += connectedUser + ": " + event.data + "<br />";
};
dataChannel.onclose = function () {
console.log("data channel is closed");
};
}
};
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
yourConn.close();
yourConn.onicecandidate = null;
};
//when user clicks the "send message" button
sendMsgBtn.addEventListener("click", function (event) {
var val = msgInput.value;
chatArea.innerHTML += name + ": " + val + "<br />";
//sending a message to a connected peer
dataChannel.send(val);
msgInput.value = "";
});
In questo capitolo, aggiungeremo funzionalità di sicurezza al server di segnalazione che abbiamo creato nel capitolo "Segnalazione WebRTC". Ci saranno due miglioramenti:
- Autenticazione utente tramite database Redis
- Abilitazione della connessione socket sicura
Innanzitutto, dovresti installare Redis.
Scarica l'ultima versione stabile da http://redis.io/download(3.05 nel mio caso)
Disimballalo
All'interno della cartella scaricata esegui sudo make install
Al termine dell'installazione, eseguire make test per verificare se tutto funziona correttamente.
Redis ha due comandi eseguibili:
redis-cli - interfaccia a riga di comando per Redis (parte client)
redis-server - Archivio dati Redis
Per eseguire il server Redis, digitare redis-server nella console del terminale. Dovresti vedere quanto segue:
Ora apri una nuova finestra di terminale ed esegui redis-cli per aprire un'applicazione client.
Fondamentalmente, Redis è un database di valori-chiave. Per creare una chiave con un valore stringa, è necessario utilizzare il comando SET. Per leggere il valore della chiave è necessario utilizzare il comando GET. Aggiungiamo due utenti e password per loro. Le chiavi saranno i nomi utente ei valori di queste chiavi saranno le password corrispondenti.
Ora dovremmo modificare il nostro server di segnalazione per aggiungere un'autenticazione utente. Aggiungere il codice seguente all'inizio del file server.js :
//require the redis library in Node.js
var redis = require("redis");
//creating the redis client object
var redisClient = redis.createClient();
Nel codice sopra, abbiamo bisogno della libreria Redis per Node.js e la creazione di un client Redis per il nostro server.
Per aggiungere l'autenticazione, modificare il gestore dei messaggi sull'oggetto connessione:
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("user connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//check whether a user is authenticated
if(data.type != "login") {
//if user is not authenticated
if(!connection.isAuth) {
sendTo(connection, {
type: "error",
message: "You are not authenticated"
});
return;
}
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged:", data.name);
//get password for this username from redis database
redisClient.get(data.name, function(err, reply) {
//check if password matches with the one stored in redis
var loginSuccess = reply === data.password;
//if anyone is logged in with this username or incorrect password
then refuse
if(users[data.name] || !loginSuccess) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
connection.isAuth = true;
sendTo(connection, {
type: "login",
success: true
});
}
});
break;
}
});
}
//...
//*****other handlers*******
Nel codice sopra se un utente cerca di accedere otteniamo da Redis la sua password, controlla se corrisponde a quella memorizzata e, se ha successo, memorizziamo il suo nome utente sul server. Aggiungiamo anche il flag isAuth alla connessione per verificare se l'utente è autenticato. Nota questo codice -
//check whether a user is authenticated
if(data.type != "login") {
//if user is not authenticated
if(!connection.isAuth) {
sendTo(connection, {
type: "error",
message: "You are not authenticated"
});
return;
}
}
Se un utente non autenticato tenta di inviare un'offerta o di lasciare la connessione, inviamo semplicemente un messaggio di errore.
Il passaggio successivo consiste nell'abilitare una connessione socket sicura. È altamente raccomandato per le applicazioni WebRTC. PKI (Public Key Infrastructure) è una firma digitale di una CA (Certificate Authority). Gli utenti quindi verificano che la chiave privata utilizzata per firmare un certificato corrisponda alla chiave pubblica del certificato della CA. Ai fini dello sviluppo. useremo un certificato di sicurezza autofirmato.
Useremo il file openssl. È uno strumento open source che implementa i protocolli SSL (Secure Sockets Layer) e TLS (Transport Layer Security). Viene spesso installato di default sui sistemi Unix. Eseguire openssl versione -a per verificare se è installato.
Per generare chiavi del certificato di sicurezza pubbliche e private, è necessario seguire i passaggi indicati di seguito:
Generate a temporary server password key
openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
Generate a server private key
openssl rsa -passin pass:12345 -in server.pass.key -out server.key
Generate a signing request. You will be asked additional questions about your company. Just hit the “Enter” button all the time.
openssl req -new -key server.key -out server.csr
Generate the certificate
openssl x509 -req -days 1095 -in server.csr -signkey server.key -out server.crt
Ora hai due file, il certificato (server.crt) e la chiave privata (server.key). Copiarli nella cartella principale del server di segnalazione.
Per abilitare la connessione tramite presa sicura, modificare il nostro server di segnalazione.
//require file system module
var fs = require('fs');
var httpServ = require('https');
//https://github.com/visionmedia/superagent/issues/205
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
//out secure server will bind to the port 9090
var cfg = {
port: 9090,
ssl_key: 'server.key',
ssl_cert: 'server.crt'
};
//in case of http request just send back "OK"
var processRequest = function(req, res) {
res.writeHead(200);
res.end("OK");
};
//create our server with SSL enabled
var app = httpServ.createServer({
key: fs.readFileSync(cfg.ssl_key),
cert: fs.readFileSync(cfg.ssl_cert)
}, processRequest).listen(cfg.port);
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({server: app});
//all connected to the server users
var users = {};
//require the redis library in Node.js
var redis = require("redis");
//creating the redis client object
var redisClient = redis.createClient();
//when a user connects to our sever
wss.on('connection', function(connection){
//...other code
Nel codice precedente, richiediamo alla libreria fs di leggere la chiave privata e il certificato, creare l' oggetto cfg con la porta di binding ei percorsi per la chiave privata e il certificato. Quindi, creiamo un server HTTPS con le nostre chiavi insieme al server WebSocket sulla porta 9090.
Ora aperto https://localhost:9090in Opera. Dovresti vedere quanto segue:
Fare clic sul pulsante "continua comunque". Dovresti vedere il messaggio "OK".
Per testare il nostro server di segnalazione sicuro, modificheremo l'applicazione di chat che abbiamo creato nel tutorial "WebRTC Text Demo". Dobbiamo solo aggiungere un campo password. Quello che segue è l'intero file index.html -
<html>
<head>
<title>WebRTC Text Demo</title>
<link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
</style>
<body>
<div id = "loginPage" class = "container text-center">
<div class = "row">
<div class = "col-md-4 col-md-offset-4">
<h2>WebRTC Text Demo. Please sign in</h2>
<label for = "usernameInput" class = "sr-only">Login</label>
<input type = "email" id = "usernameInput"
class = "form-control formgroup" placeholder = "Login"
required = "" autofocus = "">
<input type = "text" id = "passwordInput"
class = "form-control form-group" placeholder = "Password"
required = "" autofocus = "">
<button id = "loginBtn" class = "btn btn-lg btn-primary btnblock"
>Sign in</button>
</div>
</div>
</div>
<div id = "callPage" class = "call-page container">
<div class = "row">
<div class = "col-md-4 col-md-offset-4 text-center">
<div class = "panel panel-primary">
<div class = "panel-heading">Text chat</div>
<div id = "chatarea" class = "panel-body text-left"></div>
</div>
</div>
</div>
<div class = "row text-center form-group">
<div class = "col-md-12">
<input id = "callToUsernameInput" type = "text"
placeholder = "username to call" />
<button id = "callBtn" class = "btn-success btn">Call</button>
<button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
</div>
</div>
<div class = "row text-center">
<div class = "col-md-12">
<input id = "msgInput" type = "text" placeholder = "message" />
<button id = "sendMsgBtn" class = "btn-success btn">Send</button>
</div>
</div>
</div>
<script src = "client.js"></script>
</body>
</html>
Abbiamo anche bisogno di abilitare una connessione socket sicura nel file client.js attraverso questa riga var conn = new WebSocket ('wss: // localhost: 9090'); . Notare il protocollo wss . Quindi, il pulsante di accesso deve essere modificato per inviare la password insieme al nome utente -
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
var pwd = passwordInput.value;
if (name.length > 0) {
send({
type: "login",
name: name,
password: pwd
});
}
});
Quello che segue è l'intero file client.js -
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('wss://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var passwordInput = document.querySelector('#passwordInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
var msgInput = document.querySelector('#msgInput');
var sendMsgBtn = document.querySelector('#sendMsgBtn');
var chatArea = document.querySelector('#chatarea');
var yourConn;
var dataChannel;
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
var pwd = passwordInput.value;
if (name.length > 0) {
send({
type: "login",
name: name,
password: pwd
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...incorrect username or password");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
//using Google public stun server
var configuration = {
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
};
yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]});
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
//creating data channel
dataChannel = yourConn.createDataChannel("channel1", {reliable:true});
dataChannel.onerror = function (error) {
console.log("Ooops...error:", error);
};
//when we receive a message from the other peer, display it on the screen
dataChannel.onmessage = function (event) {
chatArea.innerHTML += connectedUser + ": " + event.data + "<br />";
};
dataChannel.onclose = function () {
console.log("data channel is closed");
};
}
};
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
yourConn.close();
yourConn.onicecandidate = null;
};
//when user clicks the "send message" button
sendMsgBtn.addEventListener("click", function (event) {
var val = msgInput.value;
chatArea.innerHTML += name + ": " + val + "<br />";
//sending a message to a connected peer
dataChannel.send(val);
msgInput.value = "";
});
Ora esegui il nostro server di segnalazione sicuro tramite il server del nodo . Esegui node static all'interno della cartella demo della chat modificata. Apertolocalhost:8080in due schede del browser. Prova ad accedere. Ricorda che solo "utente1" con "password1" e "utente2" con "password2" possono accedere. Quindi stabilire la RTCPeerConnection (chiamare un altro utente) e provare a inviare un messaggio.
Di seguito è riportato l'intero codice del nostro server di segnalazione sicuro:
//require file system module
var fs = require('fs');
var httpServ = require('https');
//https://github.com/visionmedia/superagent/issues/205
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
//out secure server will bind to the port 9090
var cfg = {
port: 9090,
ssl_key: 'server.key',
ssl_cert: 'server.crt'
};
//in case of http request just send back "OK"
var processRequest = function(req, res){
res.writeHead(200);
res.end("OK");
};
//create our server with SSL enabled
var app = httpServ.createServer({
key: fs.readFileSync(cfg.ssl_key),
cert: fs.readFileSync(cfg.ssl_cert)
}, processRequest).listen(cfg.port);
//require our websocket library
var WebSocketServer = require('ws').Server;
//creating a websocket server at port 9090
var wss = new WebSocketServer({server: app});
//all connected to the server users
var users = {};
//require the redis library in Node.js
var redis = require("redis");
//creating the redis client object
var redisClient = redis.createClient();
//when a user connects to our sever
wss.on('connection', function(connection) {
console.log("user connected");
//when server gets a message from a connected user
connection.on('message', function(message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//check whether a user is authenticated
if(data.type != "login") {
//if user is not authenticated
if(!connection.isAuth) {
sendTo(connection, {
type: "error",
message: "You are not authenticated"
});
return;
}
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged:", data.name);
//get password for this username from redis database
redisClient.get(data.name, function(err, reply) {
//check if password matches with the one stored in redis
var loginSuccess = reply === data.password;
//if anyone is logged in with this username or incorrect password
then refuse
if(users[data.name] || !loginSuccess) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
connection.isAuth = true;
sendTo(connection, {
type: "login",
success: true
});
}
});
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null) {
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
});
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
//when user exits, for example closes a browser window
//this may help if we are still in "offer","answer" or "candidate" state
connection.on("close", function() {
if(connection.name) {
delete users[connection.name];
}
});
connection.send("Hello from server");
});
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
Sommario
In questo capitolo, abbiamo aggiunto l'autenticazione dell'utente al nostro server di segnalazione. Abbiamo anche imparato come creare certificati SSL autofirmati e utilizzarli nell'ambito delle applicazioni WebRTC.