WebRTC JS module

This commit is contained in:
Alexandre Iooss 2020-10-21 22:10:39 +02:00
parent 0b3fb87fa2
commit 9d162b13ed
No known key found for this signature in database
GPG Key ID: 6C79278F3FCDCC02
4 changed files with 128 additions and 89 deletions

View File

@ -1,5 +1,6 @@
import { GsWebSocket } from "./modules/websocket.js"; import { GsWebSocket } from "./modules/websocket.js";
import { ViewerCounter } from "./modules/viewerCounter.js"; import { ViewerCounter } from "./modules/viewerCounter.js";
import { GsWebRTC } from "./modules/webrtc.js";
/** /**
* Initialize viewer page * Initialize viewer page
@ -17,7 +18,17 @@ export function initViewerPage(stream, stunServers, viewersCounterRefreshPeriod)
s.open(); s.open();
// Create WebRTC // Create WebRTC
// FIXME startPeerConnection() with stunServers const c = new GsWebRTC(
stunServers,
document.getElementById("connectionIndicator"),
);
c.createOffer();
c.onICECandidate(localDescription => {
s.sendDescription(localDescription, stream, quality);
});
s.onDescription(data => {
c.setRemoteDescription(data);
});
// Register keyboard events // Register keyboard events
const viewer = document.getElementById("viewer"); const viewer = document.getElementById("viewer");

View File

@ -0,0 +1,98 @@
/**
* GsWebRTC to connect to Ghostream
*/
export class GsWebRTC {
/**
* @param {list} stunServers
* @param {HTMLElement} connectionIndicator
*/
constructor(stunServers, connectionIndicator) {
this.connectionIndicator = connectionIndicator;
this.pc = new RTCPeerConnection({
iceServers: [{ urls: stunServers }]
});
// We want to receive audio and video
this.pc.addTransceiver("video", { "direction": "sendrecv" });
this.pc.addTransceiver("audio", { "direction": "sendrecv" });
// Configure events
this.pc.oniceconnectionstatechange = this._onConnectionStateChange;
this.pc.ontrack = this._onTrack;
}
/**
* On connection change, log it and change indicator.
* If connection closed or failed, try to reconnect.
*/
_onConnectionStateChange() {
console.log("ICE connection state changed to " + this.pc.iceConnectionState);
switch (this.pc.iceConnectionState) {
case "disconnected":
this.connectionIndicator.style.fill = "#dc3545";
break;
case "checking":
this.connectionIndicator.style.fill = "#ffc107";
break;
case "connected":
this.connectionIndicator.style.fill = "#28a745";
break;
case "closed":
case "failed":
console.log("Connection closed, restarting...");
/*peerConnection.close();
peerConnection = null;
setTimeout(startPeerConnection, 1000);*/
break;
}
}
/**
* On new track, add it to the player
* @param {Event} event
*/
_onTrack(event) {
console.log(`New ${event.track.kind} track`);
if (event.track.kind === "video") {
const viewer = document.getElementById("viewer");
viewer.srcObject = event.streams[0];
}
}
/**
* Create an offer and set local description.
* After that the browser will fire onicecandidate events.
*/
createOffer() {
this.pc.createOffer().then(offer => {
this.pc.setLocalDescription(offer);
console.log("WebRTC offer created");
}).catch(console.log);
}
/**
* Register a function to call to send local descriptions
* @param {Function} sendFunction Called with a local description to send.
*/
onICECandidate(sendFunction) {
// When candidate is null, ICE layer has run out of potential configurations to suggest
// so let's send the offer to the server.
// FIXME: Send offers progressively to do Trickle ICE
this.pc.onicecandidate = event => {
if (event.candidate === null) {
// Send offer to server
console.log("Sending session description to server");
sendFunction(this.pc.localDescription);
}
};
}
/**
* Set WebRTC remote description
* After that, the connection will be established and ontrack will be fired.
* @param {*} data Session description data
*/
setRemoteDescription(data) {
this.pc.setRemoteDescription(new RTCSessionDescription(data));
}
}

View File

@ -13,7 +13,6 @@ export class GsWebSocket {
/** /**
* Open websocket. * Open websocket.
*
* @param {Function} openCallback Function called when connection is established. * @param {Function} openCallback Function called when connection is established.
* @param {Function} closeCallback Function called when connection is lost. * @param {Function} closeCallback Function called when connection is lost.
*/ */
@ -34,19 +33,31 @@ export class GsWebSocket {
/** /**
* Exchange WebRTC session description with server. * Exchange WebRTC session description with server.
* * @param {SessionDescription} localDescription WebRTC local SDP
* @param {string} data JSON formated data * @param {string} stream Name of the stream
* @param {Function} receiveCallback Function called when data is received * @param {string} quality Requested quality
*/ */
exchangeDescription(data, receiveCallback) { sendDescription(localDescription, stream, quality) {
if (this.socket.readyState !== 1) { if (this.socket.readyState !== 1) {
console.log("WebSocket not ready to send data"); console.log("WebSocket not ready to send data");
return; return;
} }
this.socket.send(data); this.socket.send(JSON.stringify({
"webRtcSdp": localDescription,
"stream": stream,
"quality": quality
}));
}
/**
* Set callback function on new session description.
* @param {Function} callback Function called when data is received
*/
onDescription(callback) {
this.socket.addEventListener("message", (event) => { this.socket.addEventListener("message", (event) => {
// FIXME: json to session description
console.log("Message from server ", event.data); console.log("Message from server ", event.data);
receiveCallback(event); callback(event.data);
}); });
} }
} }

View File

@ -1,81 +0,0 @@
let peerConnection;
let streamPath = window.location.href;
let stream = streamPath;
let quality = "source";
const startPeerConnection = () => {
// Init peer connection
peerConnection = new RTCPeerConnection({
iceServers: [{ urls: stunServers }]
});
// On connection change, change indicator color
// if connection failed, restart peer connection
peerConnection.oniceconnectionstatechange = e => {
console.log("ICE connection state changed, " + peerConnection.iceConnectionState);
switch (peerConnection.iceConnectionState) {
case "disconnected":
document.getElementById("connectionIndicator").style.fill = "#dc3545";
break;
case "checking":
document.getElementById("connectionIndicator").style.fill = "#ffc107";
break;
case "connected":
document.getElementById("connectionIndicator").style.fill = "#28a745";
break;
case "closed":
case "failed":
console.log("Connection failed, restarting...");
peerConnection.close();
peerConnection = null;
setTimeout(startPeerConnection, 1000);
break;
}
};
// We want to receive audio and video
peerConnection.addTransceiver("video", { "direction": "sendrecv" });
peerConnection.addTransceiver("audio", { "direction": "sendrecv" });
// Create offer and set local description
peerConnection.createOffer().then(offer => {
// After setLocalDescription, the browser will fire onicecandidate events
peerConnection.setLocalDescription(offer);
}).catch(console.log);
// When candidate is null, ICE layer has run out of potential configurations to suggest
// so let's send the offer to the server
peerConnection.onicecandidate = event => {
if (event.candidate === null) {
// Send offer to server
// The server replies with its description
// After setRemoteDescription, the browser will fire ontrack events
console.log("Sending session description to server");
fetch(streamPath, {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
"webRtcSdp": peerConnection.localDescription,
"stream": stream,
"quality": quality
})
})
.then(response => response.json())
.then((data) => peerConnection.setRemoteDescription(new RTCSessionDescription(data)))
.catch(console.log);
}
};
// When video track is received, configure player
peerConnection.ontrack = function (event) {
console.log(`New ${event.track.kind} track`);
if (event.track.kind === "video") {
const viewer = document.getElementById("viewer");
viewer.srcObject = event.streams[0];
}
};
};