Skip to content

Commit 7d104ea

Browse files
committed
Add publisher stats
1 parent ae31d41 commit 7d104ea

File tree

2 files changed

+155
-6
lines changed

2 files changed

+155
-6
lines changed

assets/publisher.js

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export const Publisher = {
1616

1717
this.previewPlayer = document.getElementById("previewPlayer");
1818

19+
this.audioBitrate = document.getElementById("audio-bitrate");
20+
this.videoBitrate = document.getElementById("video-bitrate");
21+
this.packetLoss = document.getElementById("packet-loss");
22+
this.status = document.getElementById("status");
23+
this.time = document.getElementById("time");
24+
1925
this.audioApplyButton = document.getElementById("audioApplyButton");
2026
this.videoApplyButton = document.getElementById("videoApplyButton");
2127
this.button = document.getElementById("button");
@@ -181,6 +187,96 @@ export const Publisher = {
181187
view.pc = new RTCPeerConnection();
182188

183189
// handle local events
190+
view.pc.onconnectionstatechange = () => {
191+
if (view.pc.connectionState === "connected") {
192+
view.startTime = new Date();
193+
view.status.classList.remove("bg-red-500");
194+
// TODO use tailwind
195+
view.status.style.backgroundColor = "rgb(34, 197, 94)";
196+
197+
view.statsIntervalId = setInterval(async function () {
198+
if (!view.pc) {
199+
clearInterval(view.statsIntervalId);
200+
view.statsIntervalId = undefined;
201+
return;
202+
}
203+
204+
view.time.innerText = view.toHHMMSS(new Date() - view.startTime);
205+
206+
const stats = await view.pc.getStats(null);
207+
let bitrate;
208+
209+
stats.forEach((report) => {
210+
if (report.type === "outbound-rtp" && report.kind === "video") {
211+
if (!view.lastVideoReport) {
212+
bitrate = (report.bytesSent * 8) / 1000;
213+
} else {
214+
const timeDiff =
215+
(report.timestamp - view.lastVideoReport.timestamp) / 1000;
216+
if (timeDiff == 0) {
217+
// this should never happen as we are getting stats every second
218+
bitrate = 0;
219+
} else {
220+
bitrate =
221+
((report.bytesSent - view.lastVideoReport.bytesSent) * 8) /
222+
timeDiff;
223+
}
224+
}
225+
226+
view.videoBitrate.innerText = (bitrate / 1000).toFixed();
227+
view.lastVideoReport = report;
228+
} else if (
229+
report.type === "outbound-rtp" &&
230+
report.kind === "audio"
231+
) {
232+
if (!view.lastAudioReport) {
233+
bitrate = report.bytesSent;
234+
} else {
235+
const timeDiff =
236+
(report.timestamp - view.lastAudioReport.timestamp) / 1000;
237+
if (timeDiff == 0) {
238+
// this should never happen as we are getting stats every second
239+
bitrate = 0;
240+
} else {
241+
bitrate =
242+
((report.bytesSent - view.lastAudioReport.bytesSent) * 8) /
243+
timeDiff;
244+
}
245+
}
246+
247+
view.audioBitrate.innerText = (bitrate / 1000).toFixed();
248+
view.lastAudioReport = report;
249+
}
250+
});
251+
252+
// calculate packet loss
253+
if (!view.lastAudioReport || !view.lastVideoReport) {
254+
view.packetLoss.innerText = 0;
255+
} else {
256+
const packetsSent =
257+
view.lastVideoReport.packetsSent +
258+
view.lastAudioReport.packetsSent;
259+
const rtxPacketsSent =
260+
view.lastVideoReport.retransmittedPacketsSent +
261+
view.lastAudioReport.retransmittedPacketsSent;
262+
const nackReceived =
263+
view.lastVideoReport.nackCount + view.lastAudioReport.nackCount;
264+
265+
if (nackReceived == 0) {
266+
view.packetLoss.innerText = 0;
267+
} else {
268+
view.packetLoss.innerText = (
269+
(nackReceived / (packetsSent - rtxPacketsSent)) *
270+
100
271+
).toFixed();
272+
}
273+
}
274+
}, 1000);
275+
} else if (view.pc.connectionState === "failed") {
276+
view.stopStreaming(view);
277+
}
278+
};
279+
184280
view.pc.onicecandidate = (ev) => {
185281
view.pushEventTo(view.el, "ice", JSON.stringify(ev.candidate));
186282
};
@@ -205,16 +301,45 @@ export const Publisher = {
205301
},
206302

207303
stopStreaming(view) {
208-
view.button.innerText = "Start Streaming";
209-
view.button.onclick = function () {
210-
view.startStreaming(view);
211-
};
212-
213304
if (view.pc) {
214305
view.pc.close();
215306
view.pc = undefined;
216307
}
217308

309+
view.resetStats(view);
310+
218311
view.enableControls(view);
312+
313+
view.button.innerText = "Start Streaming";
314+
view.button.onclick = function () {
315+
view.startStreaming(view);
316+
};
317+
},
318+
319+
resetStats(view) {
320+
view.startTime = undefined;
321+
view.lastAudioReport = undefined;
322+
view.lastVideoReport = undefined;
323+
view.audioBitrate.innerText = 0;
324+
view.videoBitrate.innerText = 0;
325+
view.packetLoss.innerText = 0;
326+
view.time.innerText = "00:00:00";
327+
view.status.style.backgroundColor = "rgb(239, 68, 68)";
328+
},
329+
330+
toHHMMSS(milliseconds) {
331+
// Calculate hours
332+
let hours = Math.floor(milliseconds / (1000 * 60 * 60));
333+
// Calculate minutes, subtracting the hours part
334+
let minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
335+
// Calculate seconds, subtracting the hours and minutes parts
336+
let seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
337+
338+
// Formatting each unit to always have at least two digits
339+
hours = hours < 10 ? "0" + hours : hours;
340+
minutes = minutes < 10 ? "0" + minutes : minutes;
341+
seconds = seconds < 10 ? "0" + seconds : seconds;
342+
343+
return hours + ":" + minutes + ":" + seconds;
219344
},
220345
};

lib/live_ex_webrtc/publisher.ex

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,34 @@ defmodule LiveExWebRTC.Publisher do
8686
</div>
8787
<button id="videoApplyButton" class="rounded-lg px-10 py-2.5 bg-brand disabled:bg-brand/50 hover:bg-brand/90 text-white font-bold" disabled>Apply</button>
8888
</details>
89-
<div id="videoplayer-wrapper" class="flex flex-1 flex-col min-h-0 py-2.5">
89+
<div id="videoplayer-wrapper" class="flex flex-1 flex-col min-h-0 pt-2.5">
9090
<video id="previewPlayer" class="m-auto rounded-lg bg-black h-full" autoplay controls muted>
9191
</video>
9292
</div>
93+
<div id="stats", class="flex justify-between w-full text-[#606060] ">
94+
<div class="flex p-1 gap-4">
95+
<div class="flex flex-col">
96+
<label for="audio-bitrate">Audio Bitrate (kbps): </label>
97+
<span id="audio-bitrate">0</span>
98+
</div>
99+
<div class="flex flex-col">
100+
<label for="video-bitrate">Video Bitrate (kbps): </label>
101+
<span id="video-bitrate">0</span>
102+
</div>
103+
<div class="flex flex-col">
104+
<label for="packet-loss">Packet loss (%): </label>
105+
<span id="packet-loss">0</span>
106+
</div>
107+
<div class="flex flex-col">
108+
<label for="time">Time: </label>
109+
<span id="time">00:00:00</span>
110+
</div>
111+
</div>
112+
<div class="p-1 flex items-center">
113+
<div id="status" class="w-3 h-3 rounded-full bg-red-500">
114+
</div>
115+
</div>
116+
</div>
93117
<div class="py-2.5">
94118
<button
95119
id="button"

0 commit comments

Comments
 (0)