|
@@ -1,32 +1,31 @@
|
|
|
-/** @type {RTCPeerConnection} */
|
|
|
-let rtc;
|
|
|
const iceConnectionLog = document.getElementById('ice-connection-state'),
|
|
|
iceGatheringLog = document.getElementById('ice-gathering-state'),
|
|
|
signalingLog = document.getElementById('signaling-state'),
|
|
|
dataChannelLog = document.getElementById('data-channel');
|
|
|
|
|
|
-function randomString(len) {
|
|
|
- const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
- let randomString = '';
|
|
|
- for (let i = 0; i < len; i++) {
|
|
|
- const randomPoz = Math.floor(Math.random() * charSet.length);
|
|
|
- randomString += charSet.substring(randomPoz, randomPoz + 1);
|
|
|
- }
|
|
|
- return randomString;
|
|
|
-}
|
|
|
+const clientId = randomId(10);
|
|
|
+const websocket = new WebSocket('ws://127.0.0.1:8000/' + clientId);
|
|
|
|
|
|
-const receiveID = randomString(10);
|
|
|
-const websocket = new WebSocket('ws://127.0.0.1:8000/' + receiveID);
|
|
|
-websocket.onopen = function () {
|
|
|
+websocket.onopen = () => {
|
|
|
document.getElementById('start').disabled = false;
|
|
|
}
|
|
|
|
|
|
-// data channel
|
|
|
-let dc = null, dcTimeout = null;
|
|
|
+websocket.onmessage = async (evt) => {
|
|
|
+ if (typeof evt.data !== 'string') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const message = JSON.parse(evt.data);
|
|
|
+ if (message.type == "offer") {
|
|
|
+ document.getElementById('offer-sdp').textContent = message.sdp;
|
|
|
+ await handleOffer(message)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let pc = null;
|
|
|
+let dc = null;
|
|
|
|
|
|
function createPeerConnection() {
|
|
|
const config = {
|
|
|
- sdpSemantics: 'unified-plan',
|
|
|
bundlePolicy: "max-bundle",
|
|
|
};
|
|
|
|
|
@@ -36,64 +35,59 @@ function createPeerConnection() {
|
|
|
|
|
|
let pc = new RTCPeerConnection(config);
|
|
|
|
|
|
- // register some listeners to help debugging
|
|
|
- pc.addEventListener('icegatheringstatechange', function () {
|
|
|
- iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState;
|
|
|
- }, false);
|
|
|
- iceGatheringLog.textContent = pc.iceGatheringState;
|
|
|
-
|
|
|
- pc.addEventListener('iceconnectionstatechange', function () {
|
|
|
- iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState;
|
|
|
- }, false);
|
|
|
+ // Register some listeners to help debugging
|
|
|
+ pc.addEventListener('iceconnectionstatechange', () =>
|
|
|
+ iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState);
|
|
|
iceConnectionLog.textContent = pc.iceConnectionState;
|
|
|
|
|
|
- pc.addEventListener('signalingstatechange', function () {
|
|
|
- signalingLog.textContent += ' -> ' + pc.signalingState;
|
|
|
- }, false);
|
|
|
+ pc.addEventListener('icegatheringstatechange', () =>
|
|
|
+ iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState);
|
|
|
+ iceGatheringLog.textContent = pc.iceGatheringState;
|
|
|
+
|
|
|
+ pc.addEventListener('signalingstatechange', () =>
|
|
|
+ signalingLog.textContent += ' -> ' + pc.signalingState);
|
|
|
signalingLog.textContent = pc.signalingState;
|
|
|
|
|
|
- // connect audio / video
|
|
|
- pc.addEventListener('track', function (evt) {
|
|
|
+ // Receive audio/video track
|
|
|
+ pc.ontrack = (evt) => {
|
|
|
document.getElementById('media').style.display = 'block';
|
|
|
- const videoTag = document.getElementById('video');
|
|
|
- videoTag.srcObject = evt.streams[0];
|
|
|
- videoTag.play();
|
|
|
- });
|
|
|
-
|
|
|
- let time_start = null;
|
|
|
-
|
|
|
- function current_stamp() {
|
|
|
- if (time_start === null) {
|
|
|
- time_start = new Date().getTime();
|
|
|
- return 0;
|
|
|
- } else {
|
|
|
- return new Date().getTime() - time_start;
|
|
|
+ const video = document.getElementById('video');
|
|
|
+ if (!video.srcObject) {
|
|
|
+ video.srcObject = evt.streams[0]; // The stream groups audio and video tracks
|
|
|
+ video.play();
|
|
|
}
|
|
|
- }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Receive data channel
|
|
|
+ pc.ondatachannel = (evt) => {
|
|
|
+ dc = evt.channel;
|
|
|
|
|
|
- pc.ondatachannel = function (event) {
|
|
|
- dc = event.channel;
|
|
|
- dc.onopen = function () {
|
|
|
+ dc.onopen = () => {
|
|
|
dataChannelLog.textContent += '- open\n';
|
|
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
|
|
};
|
|
|
- dc.onmessage = function (evt) {
|
|
|
+
|
|
|
+ let dcTimeout = null;
|
|
|
+ dc.onmessage = (evt) => {
|
|
|
+ if (typeof evt.data !== 'string') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
dataChannelLog.textContent += '< ' + evt.data + '\n';
|
|
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
|
|
|
|
|
- dcTimeout = setTimeout(function () {
|
|
|
- if (dc == null && dcTimeout != null) {
|
|
|
- dcTimeout = null;
|
|
|
- return
|
|
|
+ dcTimeout = setTimeout(() => {
|
|
|
+ if (!dc) {
|
|
|
+ return;
|
|
|
}
|
|
|
- const message = 'Pong ' + current_stamp();
|
|
|
+ const message = `Pong ${currentTimestamp()}`;
|
|
|
dataChannelLog.textContent += '> ' + message + '\n';
|
|
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
|
|
dc.send(message);
|
|
|
}, 1000);
|
|
|
}
|
|
|
- dc.onclose = function () {
|
|
|
+
|
|
|
+ dc.onclose = () => {
|
|
|
clearTimeout(dcTimeout);
|
|
|
dcTimeout = null;
|
|
|
dataChannelLog.textContent += '- close\n';
|
|
@@ -104,61 +98,52 @@ function createPeerConnection() {
|
|
|
return pc;
|
|
|
}
|
|
|
|
|
|
-function sendAnswer(pc) {
|
|
|
- return pc.createAnswer()
|
|
|
- .then((answer) => rtc.setLocalDescription(answer))
|
|
|
- .then(function () {
|
|
|
- // wait for ICE gathering to complete
|
|
|
- return new Promise(function (resolve) {
|
|
|
+async function waitGatheringComplete() {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ if (pc.iceGatheringState === 'complete') {
|
|
|
+ resolve();
|
|
|
+ } else {
|
|
|
+ pc.addEventListener('icegatheringstatechange', () => {
|
|
|
if (pc.iceGatheringState === 'complete') {
|
|
|
resolve();
|
|
|
- } else {
|
|
|
- function checkState() {
|
|
|
- if (pc.iceGatheringState === 'complete') {
|
|
|
- pc.removeEventListener('icegatheringstatechange', checkState);
|
|
|
- resolve();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pc.addEventListener('icegatheringstatechange', checkState);
|
|
|
}
|
|
|
});
|
|
|
- }).then(function () {
|
|
|
- const answer = pc.localDescription;
|
|
|
-
|
|
|
- document.getElementById('answer-sdp').textContent = answer.sdp;
|
|
|
-
|
|
|
- return websocket.send(JSON.stringify(
|
|
|
- {
|
|
|
- id: "server",
|
|
|
- type: answer.type,
|
|
|
- sdp: answer.sdp,
|
|
|
- }));
|
|
|
- }).catch(function (e) {
|
|
|
- alert(e);
|
|
|
- });
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
-function handleOffer(offer) {
|
|
|
- rtc = createPeerConnection();
|
|
|
- return rtc.setRemoteDescription(offer)
|
|
|
- .then(() => sendAnswer(rtc));
|
|
|
+async function sendAnswer(pc) {
|
|
|
+ await pc.setLocalDescription(await pc.createAnswer());
|
|
|
+ await waitGatheringComplete();
|
|
|
+
|
|
|
+ const answer = pc.localDescription;
|
|
|
+ document.getElementById('answer-sdp').textContent = answer.sdp;
|
|
|
+
|
|
|
+ websocket.send(JSON.stringify({
|
|
|
+ id: "server",
|
|
|
+ type: answer.type,
|
|
|
+ sdp: answer.sdp,
|
|
|
+ }));
|
|
|
}
|
|
|
|
|
|
-function sendStreamRequest() {
|
|
|
- websocket.send(JSON.stringify(
|
|
|
- {
|
|
|
- id: "server",
|
|
|
- type: "streamRequest",
|
|
|
- receiver: receiveID,
|
|
|
- }));
|
|
|
+async function handleOffer(offer) {
|
|
|
+ pc = createPeerConnection();
|
|
|
+ await pc.setRemoteDescription(offer);
|
|
|
+ await sendAnswer(pc);
|
|
|
}
|
|
|
|
|
|
-async function start() {
|
|
|
+function sendRequest() {
|
|
|
+ websocket.send(JSON.stringify({
|
|
|
+ id: "server",
|
|
|
+ type: "request",
|
|
|
+ }));
|
|
|
+}
|
|
|
+
|
|
|
+function start() {
|
|
|
document.getElementById('start').style.display = 'none';
|
|
|
document.getElementById('stop').style.display = 'inline-block';
|
|
|
document.getElementById('media').style.display = 'block';
|
|
|
- sendStreamRequest();
|
|
|
+ sendRequest();
|
|
|
}
|
|
|
|
|
|
function stop() {
|
|
@@ -173,16 +158,16 @@ function stop() {
|
|
|
}
|
|
|
|
|
|
// close transceivers
|
|
|
- if (rtc.getTransceivers) {
|
|
|
- rtc.getTransceivers().forEach(function (transceiver) {
|
|
|
+ if (pc.getTransceivers) {
|
|
|
+ pc.getTransceivers().forEach((transceiver) => {
|
|
|
if (transceiver.stop) {
|
|
|
transceiver.stop();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // close local audio / video
|
|
|
- rtc.getSenders().forEach(function (sender) {
|
|
|
+ // close local audio/video
|
|
|
+ pc.getSenders().forEach((sender) => {
|
|
|
const track = sender.track;
|
|
|
if (track !== null) {
|
|
|
sender.track.stop();
|
|
@@ -190,18 +175,27 @@ function stop() {
|
|
|
});
|
|
|
|
|
|
// close peer connection
|
|
|
- setTimeout(function () {
|
|
|
- rtc.close();
|
|
|
- rtc = null;
|
|
|
+ setTimeout(() => {
|
|
|
+ pc.close();
|
|
|
+ pc = null;
|
|
|
}, 500);
|
|
|
}
|
|
|
|
|
|
+// Helper function to generate a random ID
|
|
|
+function randomId(length) {
|
|
|
+ const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
|
+ const pickRandom = () => characters.charAt(Math.floor(Math.random() * characters.length));
|
|
|
+ return [...Array(length) ].map(pickRandom).join('');
|
|
|
+}
|
|
|
|
|
|
-websocket.onmessage = async function (evt) {
|
|
|
- const received_msg = evt.data;
|
|
|
- const object = JSON.parse(received_msg);
|
|
|
- if (object.type == "offer") {
|
|
|
- document.getElementById('offer-sdp').textContent = object.sdp;
|
|
|
- await handleOffer(object)
|
|
|
+// Helper function to generate a timestamp
|
|
|
+let startTime = null;
|
|
|
+function currentTimestamp() {
|
|
|
+ if (startTime === null) {
|
|
|
+ startTime = Date.now();
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ return Date.now() - startTime;
|
|
|
}
|
|
|
}
|
|
|
+
|