Um Audio-/Videokonferenzen besser zu unterstützen, ermöglicht WebRTC das Senden von DTMF an den Remote-Partner auf einer RTCPeerConnection
. Dieser Artikel bietet einen kurzen Ãberblick darüber, wie DTMF über WebRTC funktioniert, und stellt dann einen Leitfaden für Entwickler bereit, wie DTMF über eine RTCPeerConnection
gesendet wird. Das DTMF-System wird oft als "Tonwahl" bezeichnet, nach einem alten Handelsnamen für das System.
WebRTC sendet keine DTMF-Codes als Audiodaten. Stattdessen werden sie als RTP-Nutzdaten gesendet. Beachten Sie jedoch, dass es zwar möglich ist, DTMF mit WebRTC zu senden, es jedoch derzeit keine Möglichkeit gibt, eingehendes DTMF zu erkennen oder zu empfangen. WebRTC ignoriert diese Nutzdaten derzeit; dies liegt daran, dass die DTMF-Unterstützung von WebRTC hauptsächlich für die Verwendung mit älteren Telefonsystemen gedacht ist, die auf DTMF-Töne angewiesen sind, um Aufgaben wie folgende auszuführen:
Hinweis: Obwohl DTMF nicht als Audio an den Remote-Partner gesendet wird, können Browser den entsprechenden Ton dem lokalen Benutzer als Teil der Benutzererfahrung vorspielen, da Benutzer es gewohnt sind, die Töne ihres Telefons zu hören.
Senden von DTMF auf einer RTCPeerConnectionEine gegebene RTCPeerConnection
kann mehrere Medien-Tracks senden oder empfangen. Wenn Sie DTMF-Signale übertragen möchten, müssen Sie zuerst entscheiden, auf welchem Track Sie sie senden möchten, da DTMF als eine Reihe von nicht-bandgebundenen Nutzdaten auf dem RTCRtpSender
gesendet wird, der für die Ãbertragung der Track-Daten an den anderen Partner verantwortlich ist.
Sobald der Track ausgewählt ist, können Sie von dessen RTCRtpSender
das RTCDTMFSender
-Objekt abrufen, das Sie zum Senden von DTMF verwenden. Von dort aus können Sie RTCDTMFSender.insertDTMF()
aufrufen, um DTMF-Signale in die Warteschlange zu stellen, um sie auf dem Track an den anderen Partner zu senden. Der RTCRtpSender
sendet die Töne dann als Pakete zusammen mit den Audiodaten des Tracks an den anderen Partner.
Jedes Mal, wenn ein Ton gesendet wird, erhält die RTCPeerConnection
ein tonechange
-Ereignis mit einer tone
-Eigenschaft, die angibt, welcher Ton gerade fertig abgespielt wurde, was eine Gelegenheit bietet, zum Beispiel Benutzerschnittstellenelemente zu aktualisieren. Wenn der Tonpuffer leer ist, was anzeigt, dass alle Töne gesendet wurden, wird ein tonechange
-Ereignis mit seiner tone
-Eigenschaft, die auf "" (eine leere Zeichenkette) gesetzt ist, an 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 dazu, wie DTMF-Nutzdaten auf RTP behandelt werden, gehen über den Rahmen dieses Artikels hinaus. Stattdessen konzentrieren wir uns darauf, wie DTMF im Kontext einer RTCPeerConnection
verwendet wird, indem wir untersuchen, wie ein Beispiel funktioniert.
Dieses einfache Beispiel konstruiert zwei RTCPeerConnection
s, stellt eine Verbindung zwischen ihnen her und wartet dann, bis der Benutzer auf eine "Wählen"-Schaltfläche klickt. Wenn die Schaltfläche angeklickt wird, wird eine DTMF-Zeichenkette über die Verbindung mit RTCDTMFSender.insertDTMF()
gesendet. Sobald die Töne fertig übertragen sind, 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 Signalisieren über das Netzwerk erfolgen würde, anstatt dass alles intern verknüpft ist, wie es hier der Fall ist.
Das HTML für dieses Beispiel ist sehr einfach; es gibt nur drei wichtige Elemente:
<audio>
-Element, um das von der RTCPeerConnection
empfangene Audio "abzuspielen".<button>
-Element, um die Erstellung und Verbindung der beiden RTCPeerConnection
-Objekte auszulösen und anschlieÃend die DTMF-Töne zu senden.<div>
, um Protokolltext 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
Lassen Sie uns als Nächstes den JavaScript-Code betrachten. Beachten Sie, dass der Prozess des Verbindungsaufbaus hier etwas konstruiert ist; normalerweise entwickeln Sie nicht beide Enden der Verbindung im selben Dokument.
Globale VariablenZuerst 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 dialButton = null;
let logElement = null;
Diese sind in der Reihenfolge:
dialString
Die DTMF-Zeichenfolge, die der Anrufer senden wird, wenn die "Wählen"-Schaltfläche angeklickt wird.
callerPC
und receiverPC
Die RTCPeerConnection
-Objekte, die den Anrufer bzw. den Empfänger darstellen. Diese werden initialisiert, wenn der Anruf in unserer connectAndDial()
-Funktion beginnt, wie unten in Starten des Verbindungsprozesses gezeigt.
dtmfSender
Das RTCDTMFSender
-Objekt für die Verbindung. Dies wird beim Einrichten der Verbindung abgerufen, in der gotStream()
-Funktion, die in Hinzufügen des Audios zur Verbindung gezeigt wird.
hasAddTrack
Da einige Browser RTCPeerConnection.addTrack()
noch nicht implementiert haben und daher die Verwendung der veralteten addStream()
-Methode erfordern, verwenden wir diesen Boolean, um festzustellen, ob der User-Agent addTrack()
unterstützt oder nicht; wenn nicht, werden wir auf addStream()
zurückgreifen. Dies wird in connectAndDial()
ermittelt, wie in Starten des Verbindungsprozesses gezeigt.
mediaConstraints
Ein Objekt, das die Einschränkungen angibt, die beim Starten der Verbindung verwendet werden sollen. Wir möchten eine reine Audio-Verbindung, daher ist video
false
, während audio
true
ist.
dialButton
und logElement
Diese Variablen werden verwendet, um Referenzen auf die Wählen-Schaltfläche und das <div>
, in das Protokollinformationen geschrieben werden, zu speichern. Sie werden beim ersten Laden der Seite eingerichtet. Siehe Initialisierung unten.
Wenn die Seite geladen wird, führen wir einige grundlegende Setups durch: Wir holen Referenzen auf die Wählen-Schaltfläche und das Protokoll-Ausgabefeld ein, und wir verwenden addEventListener()
, um der Wählen-Schaltfläche einen Ereignis-Listener 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 die Wählen-Schaltfläche angeklickt 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));
}
Nach der Erstellung der RTCPeerConnection
für den Anrufer (callerPC
) 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
erstellt, die das empfangende Ende des Anrufs darstellt, 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 Zugriff auf das Mikrofon des Anrufers zu erhalten. Bei Erfolg wird die Funktion gotStream()
aufgerufen, andernfalls protokollieren wir den Fehler, da der Anruf fehlgeschlagen ist.
Wie oben erwähnt, wird gotStream()
aufgerufen, wenn der Audioeingang vom Mikrofon erfasst wird. Seine Aufgabe besteht darin, den zu sendenden Stream an den Empfänger zu bauen, sodass der eigentliche Ãbertragungsprozess beginnen kann. Es erhält auÃerdem Zugriff auf den RTCDTMFSender
, den wir zum Senden von DTMF auf der Verbindung verwenden.
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 Audiotracks des Streams vom Mikrofon des Benutzers gesetzt wurde, ist es an der Zeit, die Medien zur RTCPeerConnection
des Anrufers hinzuzufügen. Wenn addTrack()
auf der RTCPeerConnection
verfügbar ist, fügen wir jeden einzelnen Audiotrack des Streams einzeln zur Verbindung hinzu, indem wir RTCPeerConnection.addTrack()
verwenden. Andernfalls rufen wir RTCPeerConnection.addStream()
auf, um den Stream als eine Einheit zum Anruf hinzuzufügen.
Als Nächstes prüfen wir, ob die RTCPeerConnection.getSenders()
-Methode implementiert ist. Wenn ja, rufen wir sie auf callerPC
auf und erhalten den ersten Eintrag in der zurückgegebenen Liste der Sender; dies ist der RTCRtpSender
, der für die Ãbertragung der Daten für den ersten Audiotrack des Anrufs verantwortlich ist (der Track, über den wir DTMF senden werden). Wir rufen dann die Eigenschaft dtmf
des RTCRtpSender
-Objekts ab, die ein RTCDTMFSender
-Objekt ist, das DTMF auf der Verbindung vom Anrufer zum Empfänger senden kann.
Wenn 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, um es älteren Browsern (und denen, die noch nicht aktualisiert wurden, um die aktuelle WebRTC-DTMF-API zu unterstützen) zu ermöglichen, das Beispiel auszuführen.
SchlieÃlich richten wir den ontonechange
-Ereignishandler des DTMF-Senders so ein, dass wir jedes Mal benachrichtigt werden, wenn ein DTMF-Ton fertig abgespielt wird.
Sie finden die Protokollierungsfunktion am Ende der Dokumentation.
Wenn ein Ton die Wiedergabe beendetJedes Mal, wenn ein DTMF-Ton die Wiedergabe beendet, wird ein tonechange
-Ereignis an callerPC
geliefert. Der Ereignis-Listener für diese Ereignisse ist als 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 anzugeben, wann ein einzelner Ton abgespielt wurde, als auch wann alle Töne fertig abgespielt wurden. Die Eigenschaft tone
des Ereignisses ist eine Zeichenkette, die angibt, welcher Ton gerade fertig abgespielt wurde. Wenn alle Töne fertig abgespielt wurden, ist tone
eine leere Zeichenkette; wenn dies der Fall ist, ist RTCDTMFSender.toneBuffer
leer.
In diesem Beispiel protokollieren wir auf dem Bildschirm, welcher Ton gerade fertig abgespielt wurde. In einer fortschrittlicheren Anwendung könnten Sie die Benutzeroberfläche aktualisieren, um beispielsweise anzuzeigen, welcher Ton gerade spielt.
Andererseits, wenn der Tonpuffer leer ist, ist unser Beispiel so konzipiert, dass der Anruf getrennt wird. Dies geschieht, indem jeder Stream sowohl beim Anrufer als auch beim Empfänger gestoppt wird, indem über die Track-Liste jeder RTCPeerConnection
(wie sie mit der Methode getTracks()
zurückgegeben wird) iteriert und die Methode stop()
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 trennt den Audiostream vom <audio>
-Element.
Dann werden schlieÃlich beide RTCPeerConnection
geschlossen, indem ihre Methode close()
aufgerufen wird.
Wenn die ICE-Schicht der RTCPeerConnection
des Anrufers einen neuen Kandidaten vorschlägt, gibt sie ein icecandidate
-Ereignis an callerPC
aus. Die Aufgabe des icecandidate
-Ereignishandlers ist es, den Kandidaten an den Empfänger weiterzugeben. In unserem Beispiel steuern wir direkt sowohl den Anrufer als auch den Empfänger, sodass wir den Kandidaten einfach direkt durch Aufrufe der Methode addIceCandidate()
dem Empfänger hinzufügen können. Das wird von handleCallerIceEvent()
gehandhabt:
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
-candidate
-Eigenschaft hat, erstellen wir ein neues RTCIceCandidate
-Objekt aus der event.candidate
-Zeichenkette und "übermitteln" es an den Empfänger, indem wir receiverPC.addIceCandidate()
aufrufen und das neue RTCIceCandidate
als Eingabe bereitstellen. Wenn addIceCandidate()
fehlschlägt, gibt die catch()
-Klausel den Fehler in unserem Protokollfeld aus.
Wenn event.candidate
null
ist, zeigt das an, dass keine weiteren Kandidaten verfügbar sind, und wir protokollieren diese Information.
Unser Design erfordert, dass beim Aufbau der Verbindung sofort die DTMF-Zeichenfolge gesendet wird. Um dies zu erreichen, beobachten wir, dass beim Anrufer ein iceconnectionstatechange
-Ereignis empfangen wird. Dieses Ereignis wird gesendet, wenn eine Anzahl von Ãnderungen am Zustand des ICE-Verbindungsprozesses auftreten, einschlieÃlich dem erfolgreichen Aufbau 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 enthält selbst nicht den neuen Zustand, daher erhalten wir den aktuellen Zustand des Verbindungsprozesses aus der Eigenschaft RTCPeerConnection.iceConnectionState
von callerPC
. Nachdem wir den neuen Zustand protokolliert haben, prüfen wir, ob der Zustand "connected"
ist. Wenn ja, protokollieren wir die Tatsache, dass wir im Begriff sind, die DTMF zu senden, und dann rufen wir dtmf.insertDTMF()
auf, um die DTMF über denselben Track wie die Audiodatenmethode auf dem RTCDTMFSender
-Objekt zu senden, das wir zuvor in dtmfSender
gespeichert haben.
Unser Aufruf von insertDTMF()
gibt nicht nur die zu sendende DTMF-Zeichenfolge (dialString
) an, sondern auch die Länge jedes Tons in Millisekunden (400 ms) und die Zeit zwischen den Tönen (50 ms).
Wenn die anrufende RTCPeerConnection
beginnt, Medien zu empfangen (nachdem der Mikrofon-Stream hinzugefügt wurde), wird ein negotiationneeded
-Ereignis an den Anrufer geliefert und informiert ihn darüber, dass es an der Zeit ist, die Verbindung mit dem Empfänger zu verhandeln. Wie bereits erwähnt, ist unser Beispiel aufgrund der direkten Kontrolle über Anrufer und Empfänger vereinfacht, sodass handleCallerNegotiationNeeded()
in der Lage ist, die Verbindung schnell durch Aufrufen der Methoden sowohl für den Anrufer als auch den Empfänger zu konstruieren, wie unten gezeigt.
// Offer to receive audio but not video
const constraints = { audio: true, video: false };
async function handleCallerNegotiationNeeded() {
log("Negotiatingâ¦");
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
for (const track of stream.getTracks()) {
pc.addTrack(track, stream);
}
const offer = await callerPC.createOffer();
log(`Setting caller's local description: ${offer.sdp}`);
await callerPC.setLocalDescription(offer);
log("Setting receiver's remote description to the same as caller's local");
await receiverPC.setRemoteDescription(callerPC.localDescription);
log("Creating answer");
const answer = await receiverPC.createAnswer();
log(`Setting receiver's local description to ${answer.sdp}`);
await receiverPC.setLocalDescription(answer);
log("Setting caller's remote description to match");
await callerPC.setRemoteDescription(receiverPC.localDescription);
} catch (err) {
log(`Error during negotiation: ${err.message}`);
}
}
Da die verschiedenen Methoden, die an der Verhandlung der Verbindung beteiligt sind, promise
s zurückgeben, können wir sie wie folgt verketten:
callerPC.createOffer()
auf, um ein Angebot zu erhalten.callerPC.setLocalDescription()
ein.receiverPC.setRemoteDescription()
aufrufen. Dies konfiguriert den Empfänger, sodass er weiÃ, wie der Anrufer konfiguriert ist.receiverPC.createAnswer()
aufruft.receiverPC.setLocalDescription()
aufruft.callerPC.setRemoteDescription()
aufgerufen wird. Dadurch erfährt der Anrufer, wie die Konfiguration des Empfängers aussieht.catch()
-Klausel eine Fehlermeldung im Protokoll aus.Wir können auch Ãnderungen an den Signalzustand (indem wir signalingstatechange
-Ereignisse akzeptieren) und den ICE-Sammeleinzustand (indem wir icegatheringstatechange
-Ereignisse akzeptieren) verfolgen. Wir verwenden diese für nichts Spezielles, sodass wir sie nur protokollieren. Wir hätten diese Ereignis-Listener überhaupt nicht einrichten müssen.
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 des Empfängers RTCPeerConnection
einen neuen Kandidaten vorschlägt, gibt sie ein icecandidate
-Ereignis an receiverPC
aus. Die Aufgabe des icecandidate
-Ereignishandlers ist es, den Kandidaten an den Anrufer weiterzugeben. In unserem Beispiel steuern wir direkt sowohl den Anrufer als auch den Empfänger, sodass wir den Kandidaten einfach direkt durch Aufrufe der Methode addIceCandidate()
dem Anrufer hinzufügen können. Das wird von handleReceiverIceEvent()
gehandhabt.
Dieser Code ist analog zum icecandidate
-Ereignishandler für den Anrufer, wie in Hinzufügen von Kandidaten zum Anrufer oben gezeigt.
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
-candidate
-Eigenschaft hat, erstellen wir ein neues RTCIceCandidate
-Objekt aus der event.candidate
-Zeichenkette und liefern es an den Anrufer, indem wir es in callerPC.addIceCandidate()
übergeben. Wenn addIceCandidate()
fehlschlägt, gibt die catch()
-Klausel den Fehler in unserem Protokollfeld aus.
Wenn event.candidate
null
ist, zeigt das an, dass keine weiteren Kandidaten verfügbar sind, und wir protokollieren diese Information.
Wenn der Empfänger beginnt, Medien zu empfangen, wird ein Ereignis an die RTCPeerConnection
des Empfängers, receiverPC
, geliefert. Wie in Starten des Verbindungsprozesses erklärt, verwendet die aktuelle WebRTC-Spezifikation das track
-Ereignis dafür. Da einige Browser noch nicht aktualisiert wurden, um dies zu unterstützen, müssen wir zusätzlich das addstream
-Ereignis behandeln. Dies wird in den Methoden handleReceiverTrackEvent()
und handleReceiverAddStreamEvent()
gezeigt.
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, denen der Track angehört (ein Track kann Teil mehrerer Streams sein). Wir nehmen den ersten Stream und hängen ihn an das <audio>
-Element an.
Das addstream
-Ereignis enthält eine stream
-Eigenschaft, die einen Stream angibt, der dem Track hinzugefügt wurde. Wir hängen ihn an das <audio>
-Element an.
Eine einfache log()
-Funktion wird im gesamten Code verwendet, um Text an ein <div>
-Feld anzuhängen, um Status und Fehler an den Benutzer anzuzeigen.
function log(msg) {
logElement.innerText += `${msg}\n`;
}
Ergebnis
Sie können dieses Beispiel hier ausprobieren. Wenn Sie auf die Schaltfläche "Wählen" klicken, sollten Sie eine Reihe von Protokollnachrichten sehen, die ausgegeben werden; dann beginnt das Wählen. Wenn Ihr Browser die Töne als Teil seiner Benutzererfahrung hörbar wiedergibt, sollten Sie diese hören, während sie übertragen werden.
Nachdem 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 auchRetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4