I'm now making a broadcasting web using socket.io and webRTC. I can successly broadcast the video stream from the broadcaster to the viewers. However, I want to limit the bandwidth of the viewers/broadcasters so that if more viewers/broadcaster join the server, the quality of the video can be reduced to allow more users. It seems that I cannot change the videoWidth and videoHeight as they are read-only so I cannot directly change the resolution of the video. So I wonder if I can setBitrate to limit the performance of the video.
Broadcast.js
const peerConnections = {};
const config = {
iceServers: [
{
"urls": "stun:stun.l.google.com:19302"
}
],
sdpSemantics: "plan-b"
};
const socket = io.connect(window.location.origin);
const video = document.querySelector("video");
// Media contrains
function getStream() {
if (window.stream) {
window.stream.getTracks().forEach(track =>{
track.stop();
});
}
const constraints = {
video: true,
audio: true,
};
return navigator.mediaDevices
.getUserMedia(constraints)
.then(gotStream)
.catch(handleError);
}
function gotStream(stream){
window.stream = stream;
video.srcObject = stream;
socket.emit("broadcaster");
}
function handleError(error) {
console.error("Error: ", error);
}
function setBandwidth(peerConnections, bandwidthInKbps) {
// In modern browsers, use RTCRtpSender.setParameters to change bandwidth without
// (local) renegotiation. Note that this will be within the envelope of
// the initial maximum bandwidth negotiated via SDP.
if ((adapter.browserDetails.browser === 'chrome' ||
adapter.browserDetails.browser === 'safari' ||
(adapter.browserDetails.browser === 'firefox' &&
adapter.browserDetails.version >= 64)) &&
'RTCRtpSender' in window &&
'setParameters' in window.RTCRtpSender.prototype) {
const sender = peerConnections.getSenders()[0];
console.log("Console Object: " + sender);
const parameters = sender.getParameters();
if (!parameters.encodings) {
parameters.encodings = [{}];
}
if (bandwidthInKbps === 'unlimited') {
delete parameters.encodings[0].maxBitrate;
} else {
parameters.encodings[0].maxBitrate = bandwidthInKbps *1000;
}
console.log("Changing bandwidth in browser to " + parameters.encodings[0].maxBitrate);
return sender.setParameters(parameters);
}
// Fallback to the SDP changes with local renegotiation as way of limiting
// the bandwidth.
return peerConnections.createOffer()
.then(offer => peerConnections.setLocalDescription(offer))
.then(() => {
const desc = {
type: peerConnections.remoteDescription.type,
sdp: updateBandwidthRestriction(peerConnections.remoteDescription.sdp, bandwidthInKbps)
};
//console.log('Applying bandwidth restriction to setRemoteDescription:\n' +
//desc.sdp);
console.log("type is: " + peerConnections.remoteDescription.type +"\n");
console.log("bandwidthInKbps now: " + bandwidthInKbps)
return peerConnections.setRemoteDescription(desc);
})
.catch(onSetSessionDescriptionError);
};
function updateBandwidthRestriction(sdp, bandwidth) {
let modifier = 'AS';
if (adapter.browserDetails.browser === 'firefox') {
bandwidth = (bandwidth >>> 0) * 1000;
modifier = 'TIAS';
}
if (sdp.indexOf('b=' + modifier + ':') === -1) {
// insert b= after c= line.
sdp = sdp.replace(/c=IN (.*)\r\n/, 'c=IN $1\r\nb=' + modifier + ':' + bandwidth + '\r\n');
} else {
sdp = sdp.replace(new RegExp('b=' + modifier + ':.*\r\n'), 'b=' + modifier + ':' + bandwidth + '\r\n');
}
return sdp;
}
function setdefaultscreen(){
video.srcObject = null;
video.style.backgroundColor = "black";
}
socket.on("watcher", id => {
console.log("viewerConnect!!");
const peerConnection = new RTCPeerConnection(config);
console.log("peerConnection configuration set");
let stream = video.srcObject;
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
console.log("Track added to the video stream");
peerConnections[id] = peerConnection;
setBandwidth(peerConnection,25);
console.log("peerConnection[" +id+"]" );
peerConnection.onicecandidate = event => {
if (event.candidate) {
socket.emit("candidate", id, event.candidate);
}
};
peerConnection
.createOffer()
.then(sdp => {
//sdp.sdp = setBitrate(sdp.sdp, 5);
return peerConnection.setLocalDescription(sdp)})
.then(() => {
socket.emit("offer", id, peerConnection.localDescription);
});
});
socket.on("answer", (id, description) => {
peerConnections[id].setRemoteDescription(description);
});
socket.on("candidate", (id, candidate) => {
peerConnections[id].addIceCandidate(new RTCIceCandidate(candidate));
});
socket.on("disconnect" ,() =>{
socket.emit("disconnect");
console.log("disconnect from server");
if (peerConnection) {
peerConnection.close();
}
setdefaultscreen();
})
socket.on("disconnectPeer", id => {
console.log("disconnect peer from client");
peerConnections[id].close();
delete peerConnections[id];
});
window.onunload = window.onbeforeunload = () => {
socket.close();
};
getStream();
Viewer.js
let peerConnection;
const config = {
iceServers: [
{
"urls": "stun:stun.l.google.com:19302",
},
// {
// "urls": "turn:TURN_IP?transport=tcp",
// "username": "TURN_USERNAME",
// "credential": "TURN_CREDENTIALS"
// }
],
sdpSemantics: "plan-b",
};
const socket = io.connect(window.location.origin);
const video = document.querySelector("video");
socket.on("offer", (id, description) => {
peerConnection = new RTCPeerConnection(config);
console.log("Offer is \n" + description.sdp);
peerConnection
.setRemoteDescription(description)
.then(() =>{
console.log("peerconnection creating answer")
peerConnection.createAnswer()} )
.then(sdp => {
console.log("returning local description")
return peerConnection.setLocalDescription(sdp)}
)
.then(() => {
console.log("Socket emitting answer")
socket.emit("answer", id, peerConnection.localDescription);
});
peerConnection.ontrack = event => {
video.srcObject = event.streams[0];
};
peerConnection.onicecandidate = event => {
if (event.candidate) {
socket.emit("candidate", id, event.candidate);
}
};
});
socket.on("candidate", (id, candidate) => {
peerConnection
.addIceCandidate(new RTCIceCandidate(candidate))
.catch(e => console.error(e));
});
socket.on("connect", () => {
socket.emit("watcher");
});
socket.on("broadcaster", () => {
socket.emit("watcher");
});
function setdefaultscreen(){
video.srcObject = null;
video.style.backgroundColor = "black";
}
socket.on("disconnectServer", () => {
console.log("disconnect from server");
setdefaultscreen();
});
window.onunload = window.onbeforeunload = () => {
socket.close();
peerConnection.close();
setdefaultscreen();
};
Server.js
const express = require("express");
const app = express();
let broadcaster;
const port = 4000;
const http = require("http");
const server = http.createServer(app);
const io = require("socket.io")(server);
app.use(express.static(__dirname + "/public"));
io.sockets.on("error", e => console.log(e));
io.sockets.on("connection", socket => {
socket.on("broadcaster", () => {
broadcaster = socket.id;
socket.broadcast.emit("broadcaster");
});
socket.on("watcher", () => {
socket.to(broadcaster).emit("watcher", socket.id);
});
socket.on("offer", (id, message) => {
socket.to(id).emit("offer", socket.id, message);
});
socket.on("answer", (id, message) => {
socket.to(id).emit("answer", socket.id, message);
});
socket.on("candidate", (id, message) => {
socket.to(id).emit("candidate", socket.id, message);
});
socket.on("disconnect", () => {
if (socket.id === broadcaster) {
socket.broadcast.emit("disconnectServer");
}
socket.broadcast.emit("disconnectPeer", socket.id);
});
});
server.listen(port, () => console.log(`Server is running on port ${port}`));
Noe I set the maxBitrate to 25. It is supposed that the video should be vert laggy and the framerate will be low. However, it seems there is no difference for the video quality of the viewers. When I use similar code from sample in https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/bandwidth, which applies bandwidth limitation among two pc, it works.