Verwendung von DTMF mit WebRTC
Um Audio-/Videokonferenzen umfassender zu unterstützen, ermöglicht WebRTC das Senden von DTMF an den entfernten Teilnehmer über eine RTCPeerConnection
. Dieser Artikel bietet einen kurzen Überblick darüber, wie DTMF über WebRTC funktioniert, und liefert dann einen Leitfaden für Entwickler, wie man DTMF über eine RTCPeerConnection
sendet. Das DTMF-System wird oft als "Tonwahl" bezeichnet, nach einem alten Markennamen für das System.
WebRTC sendet DTMF-Codes nicht als Audiodaten. Stattdessen werden sie außerhalb der Bandbreite als RTP-Nutzlast gesendet. Beachten Sie jedoch, dass es zwar möglich ist, DTMF mit WebRTC zu senden, es jedoch derzeit keinen Weg gibt, eingehende DTMF zu erkennen oder zu empfangen. WebRTC ignoriert aktuell diese Nutzlasten, da die Unterstützung von DTMF in WebRTC in erster Linie für die Verwendung mit älteren Telefonsystemen gedacht ist, die auf DTMF-Töne angewiesen sind, um Aufgaben wie folgende auszuführen:
- Telefonkonferenzsysteme
- Menüs
- Voicemail-Systeme
- Eingabe von Kreditkarten- oder anderen Zahlungsinformationen
- Eingabe von Passcodes
Hinweis: Während DTMF nicht als Audio an den entfernten Teilnehmer gesendet wird, können Browser wählen, den entsprechenden Ton dem lokalen Benutzer als Teil ihrer Benutzererfahrung abzuspielen, da Benutzer typischerweise daran gewöhnt sind, ihre Telefone die Töne hörbar abspielen zu lassen.
Senden von DTMF auf einer RTCPeerConnection
Eine gegebene RTCPeerConnection
kann mehrere Medientracks senden oder empfangen. Wenn Sie DTMF-Signale übertragen möchten, müssen Sie zuerst entscheiden, auf welchem Track Sie diese senden möchten, da DTMF als eine Reihe von außerhalb der Bandbreite gesendeten Nutzlasten auf dem RTCRtpSender
gesendet wird, der für die Übertragung der Daten dieses Tracks an den anderen Teilnehmer verantwortlich ist.
Sobald der Track ausgewählt ist, können Sie vom RTCRtpSender
das RTCDTMFSender
Objekt erhalten, das Sie zum Senden von DTMF verwenden werden. Von dort aus können Sie RTCDTMFSender.insertDTMF()
aufrufen, um DTMF-Signale in die Warteschlange zu stellen, die auf dem Track an den anderen Teilnehmer gesendet werden sollen. Der RTCRtpSender
wird dann die Töne als Pakete neben den Audiodaten des Tracks an den anderen Teilnehmer senden.
Jedes Mal, wenn ein Ton gesendet wird, erhält die RTCPeerConnection
ein tonechange
Ereignis mit einer tone
Eigenschaft, die angibt, welcher Ton gerade beendet wurde, was eine Gelegenheit bietet, beispielsweise Interface-Elemente zu aktualisieren. Wenn der Ton-Puffer leer ist und alle Töne gesendet wurden, wird ein tonechange
Ereignis mit seiner tone
Eigenschaft auf "" (ein leerer String) auf das Verbindungsobjekt geliefert.
Wenn Sie mehr darüber erfahren möchten, wie dies funktioniert, lesen Sie RFC 3550: RTP: A Transport Protocol for Real-Time Applications und RFC 4733: RTP Payload for DTMF Digits, Telephony Tones, and Telephony Signals. Die Details, wie DTMF-Nutzlasten auf RTP behandelt werden, liegen außerhalb des Geltungsbereichs dieses Artikels. Stattdessen konzentrieren wir uns darauf, wie man DTMF im Kontext einer RTCPeerConnection
verwendet, indem wir untersuchen, wie ein Beispiel funktioniert.
Einfaches Beispiel
Dieses einfache Beispiel konstruiert zwei RTCPeerConnection
-Objekte, stellt eine Verbindung zwischen ihnen her und wartet dann, bis der Benutzer auf einen "Wählen"-Button klickt. Wenn der Button geklickt wird, wird eine DTMF-Zeichenkette über die Verbindung gesendet, indem RTCDTMFSender.insertDTMF()
verwendet wird. Sobald die Töne übertragen wurden, wird die Verbindung geschlossen.
Hinweis:
Dieses Beispiel ist offensichtlich etwas konstruiert, da normalerweise die beiden RTCPeerConnection
Objekte auf verschiedenen Geräten existieren würden und das Signaling über das Netzwerk stattfindet, anstatt dass alles hier inline verbunden wird.
HTML
Das HTML für dieses Beispiel ist sehr einfach; es gibt nur drei wichtige Elemente:
- Ein
<audio>
-Element, um das Audio abzuspielen, das von der "zu rufenden"RTCPeerConnection
empfangen wird. - Ein
<button>
-Element, um das Erstellen und Verbinden der beidenRTCPeerConnection
-Objekte zu triggern und dann die DTMF-Töne zu senden. - Ein
<div>
, um Log-Text zu empfangen und anzuzeigen, um Statusinformationen zu zeigen.
<p>
This example demonstrates the use of DTMF in WebRTC. Note that this example is
"cheating" by generating both peers in one code stream, rather than having
each be a truly separate entity.
</p>
<audio id="audio" autoplay controls></audio><br />
<button name="dial" id="dial">Dial</button>
<div class="log"></div>
JavaScript
Schauen wir uns als Nächstes den JavaScript-Code an. Beachten Sie, dass der Prozess des Aufbauens der Verbindung hier etwas konstruiert ist; normalerweise baut man nicht beide Enden der Verbindung im selben Dokument auf.
Globale Variablen
Zuerst legen wir globale Variablen fest.
let dialString = "12024561111";
let callerPC = null;
let receiverPC = null;
let dtmfSender = null;
let hasAddTrack = false;
let mediaConstraints = {
audio: true,
video: false,
};
let offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 0,
};
let dialButton = null;
let logElement = null;
Dies sind in der Reihenfolge:
dialString
-
Die DTMF-Zeichenkette, die gesendet wird, wenn der "Wählen"-Button geklickt wird.
callerPC
undreceiverPC
-
Die
RTCPeerConnection
Objekte, die den Anrufer bzw. den Empfänger darstellen. Diese werden bei Beginn des Anrufs in unsererconnectAndDial()
Funktion initialisiert, wie im Abschnitt Starten des Verbindungsprozesses unten gezeigt. dtmfSender
-
Das
RTCDTMFSender
Objekt für die Verbindung. Dieses wird während der Einrichtung der Verbindung in dergotStream()
Funktion, die im Abschnitt Hinzufügen des Audios zur Verbindung gezeigt wird, erhalten. hasAddTrack
-
Da einige Browser
RTCPeerConnection.addTrack()
noch nicht implementiert haben und daher die Verwendung der veraltetenaddStream()
Methode erfordern, verwenden wir diese Boolesche Variable, um zu bestimmen, ob der BenutzeragentaddTrack()
unterstützt; wenn nicht, fallen wir aufaddStream()
zurück. Dies wird inconnectAndDial()
festgestellt, wie im Abschnitt Starten des Verbindungsprozesses gezeigt. mediaConstraints
-
Ein Objekt, das die Einschränkungen angibt, die beim Starten der Verbindung verwendet werden sollen. Wir wollen eine audio-only Verbindung, daher ist
video
auffalse
undaudio
auftrue
. offerOptions
-
Ein Objekt, das Optionen bereitstellt, die beim Aufrufen von
RTCPeerConnection.createOffer()
spezifiziert werden sollen. In diesem Fall geben wir an, dass wir Audio, aber kein Video empfangen möchten. -
Diese Variablen werden verwendet, um Referenzen auf den Wählbutton und das
<div>
, in das Protokollinformationen geschrieben werden, zu speichern. Sie werden eingerichtet, wenn die Seite zum ersten Mal geladen wird. Siehe Initialisierung unten.
Initialisierung
Wenn die Seite geladen wird, führen wir einige grundlegende Setups durch: Wir holen Referenzen auf den Wählbutton und das Log-Ausgabeelement ab, und verwenden addEventListener()
, um einen Ereignis-Listener zum Wählbutton hinzuzufügen, sodass beim Klicken darauf die connectAndDial()
Funktion aufgerufen wird, um den Verbindungsprozess zu beginnen.
window.addEventListener("load", () => {
logElement = document.querySelector(".log");
dialButton = document.querySelector("#dial");
dialButton.addEventListener("click", connectAndDial, false);
});
Starten des Verbindungsprozesses
Wenn der Wählbutton geklickt wird, wird connectAndDial()
aufgerufen. Dies beginnt mit dem Aufbau der WebRTC-Verbindung zur Vorbereitung des Sendens der DTMF-Codes.
function connectAndDial() {
callerPC = new RTCPeerConnection();
hasAddTrack = callerPC.addTrack !== undefined;
callerPC.onicecandidate = handleCallerIceEvent;
callerPC.onnegotiationneeded = handleCallerNegotiationNeeded;
callerPC.oniceconnectionstatechange = handleCallerIceConnectionStateChange;
callerPC.onsignalingstatechange = handleCallerSignalingStateChangeEvent;
callerPC.onicegatheringstatechange = handleCallerGatheringStateChangeEvent;
receiverPC = new RTCPeerConnection();
receiverPC.onicecandidate = handleReceiverIceEvent;
if (hasAddTrack) {
receiverPC.ontrack = handleReceiverTrackEvent;
} else {
receiverPC.onaddstream = handleReceiverAddStreamEvent;
}
navigator.mediaDevices
.getUserMedia(mediaConstraints)
.then(gotStream)
.catch((err) => log(err.message));
}
Nachdem die RTCPeerConnection
für den Anrufer (callerPC
) erstellt wurde, prüfen wir, ob sie eine addTrack()
Methode hat. Wenn dies der Fall ist, setzen wir hasAddTrack
auf true
; andernfalls setzen wir es auf false
. Diese Variable ermöglicht es dem Beispiel, auch in Browsern zu funktionieren, die die neuere addTrack()
Methode noch nicht implementiert haben; wir tun dies, indem wir auf die ältere addStream()
Methode zurückgreifen.
Als Nächstes werden die Ereignishandler für den Anrufer eingerichtet. Wir werden diese später im Detail behandeln.
Dann wird eine zweite RTCPeerConnection
, die das Empfangsende des Anrufs darstellt, erstellt und in receiverPC
gespeichert; sein onicecandidate
Ereignishandler wird ebenfalls eingerichtet.
Wenn addTrack()
unterstützt wird, richten wir den ontrack
Ereignishandler des Empfängers ein; andernfalls richten wir onaddstream
ein. Die track
und addstream
Ereignisse werden gesendet, wenn Medien zur Verbindung hinzugefügt werden.
Schließlich rufen wir getUserMedia()
auf, um Zugang zum Mikrofon des Anrufers zu erhalten. Bei Erfolg wird die Funktion gotStream()
aufgerufen, andernfalls protokollieren wir den Fehler, weil der Anruf fehlgeschlagen ist.
Hinzufügen des Audios zur Verbindung
Wie oben erwähnt, wird gotStream()
aufgerufen, wenn das Audioeingangssignal vom Mikrofon erfasst wird. Die Aufgabe von gotStream()
ist es, den Stream aufzubauen, der an den Empfänger gesendet wird, damit der eigentliche Übertragungsprozess beginnen kann. Es erhält auch Zugang zu dem RTCDTMFSender
, den wir verwenden werden, um DTMF auf der Verbindung auszugeben.
function gotStream(stream) {
log("Got access to the microphone.");
let audioTracks = stream.getAudioTracks();
if (hasAddTrack) {
if (audioTracks.length > 0) {
audioTracks.forEach((track) => callerPC.addTrack(track, stream));
}
} else {
log(
"Your browser doesn't support RTCPeerConnection.addTrack(). Falling " +
"back to the <strong>deprecated</strong> addStream() method…",
);
callerPC.addStream(stream);
}
if (callerPC.getSenders) {
dtmfSender = callerPC.getSenders()[0].dtmf;
} else {
log(
"Your browser doesn't support RTCPeerConnection.getSenders(), so " +
"falling back to use <strong>deprecated</strong> createDTMFSender() " +
"instead.",
);
dtmfSender = callerPC.createDTMFSender(audioTracks[0]);
}
dtmfSender.ontonechange = handleToneChangeEvent;
}
Nachdem audioTracks
auf eine Liste der Audiospuren des Streams vom Mikrofon des Benutzers gesetzt wurde, ist es an der Zeit, die Medien zur RTCPeerConnection
des Anrufers hinzuzufügen. Falls addTrack()
auf der RTCPeerConnection
verfügbar ist, fügen wir jede der Audiospuren des Streams einzeln mithilfe von RTCPeerConnection.addTrack()
zur Verbindung hinzu. Andernfalls rufen wir RTCPeerConnection.addStream()
auf, um den Stream als Einheit dem Anruf hinzuzufügen.
Als Nächstes überprüfen wir, ob die Methode RTCPeerConnection.getSenders()
implementiert ist. Falls ja, rufen wir sie auf callerPC
auf und holen den ersten Eintrag in der zurückgegebenen Liste von Sendern; dies ist der RTCRtpSender
, der für die Übertragung von Daten für die erste Audiospur des Anrufs verantwortlich ist (was die Spur ist, über die wir DTMF senden werden). Dann erhalten wir die dtmf
Eigenschaft des RTCRtpSender
, welche ein RTCDTMFSender
Objekt ist, das DTMF auf der Verbindung vom Anrufer zum Empfänger senden kann.
Falls getSenders()
nicht verfügbar ist, rufen wir stattdessen RTCPeerConnection.createDTMFSender()
auf, um das RTCDTMFSender
Objekt zu erhalten. Obwohl diese Methode veraltet ist, unterstützt dieses Beispiel sie als Fallback, damit ältere Browser (und solche, die noch nicht aktualisiert wurden, um die aktuelle WebRTC DTMF API zu unterstützen) das Beispiel ausführen können.
Schließlich setzen wir den ontonechange
Ereignishandler des DTMF-Senders, damit wir jedes Mal benachrichtigt werden, wenn ein DTMF-Ton zu Ende gespielt ist.
Die Log-Funktion finden Sie am Ende der Dokumentation.
Wenn ein Ton zu Ende gespielt ist
Jedes Mal, wenn ein DTMF-Ton zu Ende gespielt ist, wird ein tonechange
Ereignis an callerPC
gesendet. Der Ereignis-Listener für diese ist als die Funktion handleToneChangeEvent()
implementiert.
function handleToneChangeEvent(event) {
if (event.tone !== "") {
log(`Tone played: ${event.tone}`);
} else {
log("All tones have played. Disconnecting.");
callerPC.getLocalStreams().forEach((stream) => {
stream.getTracks().forEach((track) => {
track.stop();
});
});
receiverPC.getLocalStreams().forEach((stream) => {
stream.getTracks().forEach((track) => {
track.stop();
});
});
audio.pause();
audio.srcObject = null;
receiverPC.close();
callerPC.close();
}
}
Das tonechange
Ereignis wird sowohl verwendet, um anzuzeigen, wann ein individueller Ton gespielt wurde, als auch wann alle Töne zu Ende gespielt sind. Die Eigenschaft tone
des Ereignisses ist eine Zeichenkette, die angibt, welcher Ton gerade beendet wurde. Wenn alle Töne zu Ende gespielt sind, ist tone
eine leere Zeichenkette; in diesem Fall ist der toneBuffer
des RTCDTMFSender
leer.
In diesem Beispiel protokollieren wir auf dem Bildschirm, welcher Ton gerade zu Ende gespielt wurde. In einer fortgeschritteneren Anwendung könnten Sie das Benutzerinterface aktualisieren, beispielsweise um anzugeben, welcher Ton gerade gespielt wird.
Wenn der Ton-Puffer leer ist, ist unser Beispiel so gestaltet, dass der Anruf getrennt wird. Dies geschieht, indem jeder Stream sowohl auf der Seite des Anrufers als auch des Empfängers gestoppt wird, indem über jede Trackliste des RTCPeerConnection
(wie sie durch die Methode getTracks()
zurückgegeben wird) iteriert und die stop()
Methode jedes Tracks aufgerufen wird.
Sobald alle Medienspuren des Anrufers und des Empfängers gestoppt sind, pausieren wir das <audio>
-Element und setzen dessen srcObject
auf null
. Dies löst den Audio-Stream vom HTMLMediaElement
.
Schließlich wird jede RTCPeerConnection
durch Aufrufen ihrer close()
-Methode geschlossen.
Hinzufügen von Kandidaten zum Anrufer
Wenn die ICE-Schicht von RTCPeerConnection
des Anrufers einen neuen Vorschlag für einen Kandidaten hat, gibt sie ein icecandidate
Ereignis an callerPC
aus. Der icecandidate
Ereignishhandler hat die Aufgabe, den Kandidaten an den Empfänger zu übermitteln. In unserem Beispiel kontrollieren wir sowohl den Anrufer als auch den Empfänger direkt, also können wir den Kandidaten einfach direkt an den Empfänger anfügen, indem wir dessen Methode addIceCandidate()
aufrufen. Das wird von handleCallerIceEvent()
behandelt:
function handleCallerIceEvent(event) {
if (event.candidate) {
log(`Adding candidate to receiver: ${event.candidate.candidate}`);
receiverPC
.addIceCandidate(new RTCIceCandidate(event.candidate))
.catch((err) => log(`Error adding candidate to receiver: ${err}`));
} else {
log("Caller is out of candidates.");
}
}
Wenn das icecandidate
Ereignis eine nicht-null
Eigenschaft candidate
hat, erzeugen wir ein neues RTCIceCandidate
Objekt aus der event.candidate
Zeichenkette und "übermitteln" es an den Empfänger, indem wir receiverPC.addIceCandidate()
mit dem neuen RTCIceCandidate
als Eingabe aufrufen. Wenn addIceCandidate()
fehlschlägt, gibt der catch()
-Klausel den Fehler in unserem Protokollfeld aus.
Wenn event.candidate
null
ist, bedeutet das, dass keine weiteren Kandidaten verfügbar sind, und wir protokollieren diese Information.
Wählen, sobald die Verbindung offen ist
Unser Entwurf erfordert, dass, sobald die Verbindung hergestellt ist, wir sofort die DTMF-Zeichenkette senden. Um dies zu erreichen, überwachen wir, ob der Anrufer ein iceconnectionstatechange
Ereignis erhält. Dieses Ereignis wird gesendet, wenn eine der zahlreichen Änderungen am Zustand des ICE-Verbindungsprozesses auftritt, einschließlich der erfolgreichen Herstellung einer Verbindung.
function handleCallerIceConnectionStateChange() {
log(`Caller's connection state changed to ${callerPC.iceConnectionState}`);
if (callerPC.iceConnectionState === "connected") {
log(`Sending DTMF: "${dialString}"`);
dtmfSender.insertDTMF(dialString, 400, 50);
}
}
Das iceconnectionstatechange
Ereignis beinhaltet nicht direkt den neuen Zustand, daher erhalten wir den aktuellen Zustand des Verbindungsprozesses von der iceConnectionState
Eigenschaft von callerPC
. Nach dem Protokollieren des neuen Zustands prüfen wir, ob der Zustand connected
ist. Wenn dies der Fall ist, protokollieren wir, dass wir dabei sind, die DTMF zu senden, und rufen dann dtmf.insertDTMF()
auf, um die DTMF auf dem gleichen Track wie die Audiodaten auszugeben mit der Methode RTCDTMFSender
Objekt, das wir zuvor gespeichert haben in dtmfSender
.
Unser Aufruf von insertDTMF()
spezifiziert nicht nur die DTMF, die gesendet werden soll (dialString
), sondern auch die Länge jedes Tons in Millisekunden (400 ms) und die Zeitspanne zwischen den Tönen (50 ms).
Aushandeln der Verbindung
Wenn die anrufende RTCPeerConnection
beginnt, Medien zu empfangen (nachdem der Mikrofonstream hinzugefügt wurde), wird ein negotiationneeded
Ereignis an den Anrufer gesendet, das ihn darüber informiert, dass es Zeit ist, die Verbindung mit dem Empfänger auszuhandeln. Wie bereits erwähnt, ist unser Beispiel somewhat vereinfacht, da wir sowohl den Anrufer als auch den Empfänger kontrollieren, sodass handleCallerNegotiationNeeded()
in der Lage ist, die Verbindung schnell zu konstruieren, indem es die erforderlichen Aufrufe sowohl für den Anrufer als auch den Empfänger zusammen-kettet, wie unten gezeigt.
function handleCallerNegotiationNeeded() {
log("Negotiating…");
callerPC
.createOffer(offerOptions)
.then((offer) => {
log(`Setting caller's local description: ${offer.sdp}`);
return callerPC.setLocalDescription(offer);
})
.then(() => {
log(
"Setting receiver's remote description to the same as caller's local",
);
return receiverPC.setRemoteDescription(callerPC.localDescription);
})
.then(() => {
log("Creating answer");
return receiverPC.createAnswer();
})
.then((answer) => {
log(`Setting receiver's local description to ${answer.sdp}`);
return receiverPC.setLocalDescription(answer);
})
.then(() => {
log("Setting caller's remote description to match");
return callerPC.setRemoteDescription(receiverPC.localDescription);
})
.catch((err) => log(`Error during negotiation: ${err.message}`));
}
Da die verschiedenen Methoden, die zur Aushandlung der Verbindung erforderlich sind, promise
-Objekte zurückgeben, können wir sie wie folgt verketten:
- Rufen Sie
callerPC.createOffer()
auf, um ein Angebot zu erhalten. - Nehmen Sie dann dieses Angebot und setzen Sie die lokale Beschreibung des Anrufers entsprechend durch Aufrufen von
callerPC.setLocalDescription()
. - "Übermitteln" Sie dann das Angebot an den Empfänger, indem Sie
receiverPC.setRemoteDescription()
aufrufen. Dies konfiguriert den Empfänger so, dass er weiß, wie der Anrufer konfiguriert ist. - Lassen Sie dann den Empfänger eine Antwort erstellen, indem Sie
receiverPC.createAnswer()
aufrufen. - Lassen Sie den Empfänger seine lokale Beschreibung entsprechend der neu erstellten Antwort durch Aufrufen von
receiverPC.setLocalDescription()
anpassen. - "Übermitteln" Sie dann die Antwort an den Anrufer, indem Sie
callerPC.setRemoteDescription()
aufrufen. Dies teilt dem Anrufer mit, wie die Konfiguration des Empfängers ist. - Wenn zu irgendeinem Zeitpunkt ein Fehler auftritt, gibt die
catch()
-Klausel eine Fehlermeldung in das Protokoll aus.
Überwachen von anderen Statusänderungen
Wir können auch Änderungen des Signalisierungszustands (durch Akzeptieren von signalingstatechange
Ereignissen) und des ICE-Sammezustands (durch Akzeptieren von icegatheringstatechange
Ereignissen) beobachten. Wir verwenden diese für nichts, daher protokollieren wir sie nur. Wir hätten diese Ereignis-Listener auch gar nicht einrichten können.
function handleCallerSignalingStateChangeEvent() {
log(`Caller's signaling state changed to ${callerPC.signalingState}`);
}
function handleCallerGatheringStateChangeEvent() {
log(`Caller's ICE gathering state changed to ${callerPC.iceGatheringState}`);
}
Hinzufügen von Kandidaten zum Empfänger
Wenn die ICE-Schicht von RTCPeerConnection
des Empfängers einen neuen Vorschlag für einen Kandidaten hat, gibt sie ein icecandidate
Ereignis an receiverPC
aus. Der icecandidate
Ereignis-Handler ist dafür verantwortlich, den Kandidaten an den Anrufer zu übermitteln. In unserem Beispiel kontrollieren wir sowohl den Anrufer als auch den Empfänger direkt, sodass wir den Kandidaten einfach direkt an den Anrufer anfügen können, indem wir dessen Methode addIceCandidate()
aufrufen. Das wird von handleReceiverIceEvent()
behandelt.
Dieser Code ist analog zum icecandidate
Ereignis-Handler für den Anrufer, wie im Abschnitt Hinzufügen von Kandidaten zum Anrufer oben zu sehen ist.
function handleReceiverIceEvent(event) {
if (event.candidate) {
log(`Adding candidate to caller: ${event.candidate.candidate}`);
callerPC
.addIceCandidate(new RTCIceCandidate(event.candidate))
.catch((err) => log(`Error adding candidate to caller: ${err}`));
} else {
log("Receiver is out of candidates.");
}
}
Wenn das icecandidate
Ereignis eine nicht-null
Eigenschaft candidate
hat, erzeugen wir ein neues RTCIceCandidate
Objekt aus der event.candidate
Zeichenkette und überliefern es an den Anrufer, indem wir dieses an callerPC.addIceCandidate()
übergeben. Wenn addIceCandidate()
fehlschlägt, gibt der catch()
-Klausel den Fehler in unser Protokollfeld aus.
Wenn event.candidate
null
ist, bedeutet das, dass keine weiteren Kandidaten verfügbar sind, und wir protokollieren diese Information.
Hinzufügen von Medien zum Empfänger
Wenn der Empfänger beginnt, Medien zu empfangen, wird ein Ereignis an die RTCPeerConnection
des Empfängers, receiverPC
, ausgeliefert. Wie im Abschnitt Starten des Verbindungsprozesses erläutert, verwendet die aktuelle WebRTC-Spezifikation das track
Ereignis hierfür. Da einige Browser hierfür noch nicht aktualisiert wurden, müssen wir auch das addstream
Ereignis behandeln. Dies wird in den Methoden handleReceiverTrackEvent()
und handleReceiverAddStreamEvent()
unten dargestellt.
function handleReceiverTrackEvent(event) {
audio.srcObject = event.streams[0];
}
function handleReceiverAddStreamEvent(event) {
audio.srcObject = event.stream;
}
Das track
Ereignis enthält eine streams
Eigenschaft, die ein Array der Streams enthält, deren Mitglied der Track ist (ein Track kann Teil vieler Streams sein). Wir nehmen den ersten Stream und binden ihn an das <audio>
-Element.
Das addstream
Ereignis enthält eine stream
Eigenschaft, die einen einzelnen zum Track hinzugefügten Stream angibt. Wir binden ihn an das <audio>
-Element.
Protokollierung
Eine einfache log()
Funktion wird im gesamten Code verwendet, um Text an eine <div>
-Box anzuhängen, in der dem Benutzer Status und Fehler angezeigt werden.
function log(msg) {
logElement.innerText += `${msg}\n`;
}
Ergebnis
Sie können dieses Beispiel hier ausprobieren. Wenn Sie den "Wählen"-Button klicken, sollten Sie eine Reihe von Protokollnachrichten angezeigt bekommen; dann beginnt das Wählen. Wenn Ihr Browser die Töne hörbar wiedergibt als Teil seiner Benutzererfahrung, sollten Sie sie hören, während sie gesendet werden.
Sobald die Übertragung der Töne abgeschlossen ist, wird die Verbindung geschlossen. Sie können erneut auf "Wählen" klicken, um die Verbindung wiederherzustellen und die Töne zu senden.
Siehe auch
- WebRTC API
- Lebensdauer einer WebRTC-Sitzung
- Signalisierung und Videoanrufe (ein Tutorial und Beispiel, das den Signalisierungsprozess detaillierter erklärt)
- Einführung in WebRTC-Protokolle