Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StatsForNerds: cleanup and plot packets lost #1414

Merged
merged 1 commit into from
Oct 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 34 additions & 23 deletions src/components/VideoPlayerStatsForNerds.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<script lang="ts" setup>
import { WebRTCStats } from '@peermetrics/webrtc-stats'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'

import { useVideoStore } from '@/stores/video'
import { WebRTCStatsEvent } from '@/types/video'
Expand Down Expand Up @@ -34,6 +34,7 @@ const props = defineProps({
const canvasRef = ref(null)
const framerateData = ref([])
const bitrateData = ref([])
const packetLostData = ref([])
let animationFrameId = null
let intervalId = null
let bitrate = 0
Expand All @@ -54,11 +55,13 @@ let framedrops = 0

let packetLossPercentage = 0
let framerate = 0
let packetLostDelta = 0
let videoHeight = 0

const maxDataPoints = 100
let maxBitrateReceived = 1000 // max bitrate received, used for scaling the plot
let maxFramerateReceived = 30 // max framerate received, used for scaling the plot
let maxPacketLost = 10 // max packet lost, used for scaling the plot
let absoluteMaxFrameRate = 120 // Absolute maximum framerate, used for dealing with outliers

const plotHeight = 60 // Height of the plot area
Expand All @@ -82,28 +85,29 @@ function draw(): void {

ctx.clearRect(0, 0, width, height)

// Draw bitrate plot
ctx.strokeStyle = 'rgb(255, 165, 0)' // Orange
ctx.lineWidth = 1
ctx.beginPath()
for (let i = 0; i < bitrateData.value.length; i++) {
const x = (i / (maxDataPoints - 1)) * width
const y = height - normalizeValue(bitrateData.value[i], maxBitrateReceived)
if (i === 0) ctx.moveTo(x, y)
else ctx.lineTo(x, y)
}
ctx.stroke()

// Draw framerate plot
ctx.strokeStyle = 'rgb(0, 255, 0)' // Green
ctx.beginPath()
for (let i = 0; i < framerateData.value.length; i++) {
const x = (i / (maxDataPoints - 1)) * width
const y = height - normalizeValue(framerateData.value[i], maxFramerateReceived)
if (i === 0) ctx.moveTo(x, y)
else ctx.lineTo(x, y)
/**
*
* @param {number[]} data - The data to plot
* @param {string} color - The color of the plot
* @param {number} max - The maximum value of the data, used for scaling the plot
*/
function drawPlot(data: number[], color: string, max: number): void {
ctx.strokeStyle = color
ctx.lineWidth = 1
ctx.beginPath()
for (let i = 0; i < data.length; i++) {
const x = (i / (maxDataPoints - 1)) * width
const y = height - normalizeValue(data[i], max)
if (i === 0) ctx.moveTo(x, y)
else ctx.lineTo(x, y)
}
ctx.stroke()
}
ctx.stroke()

// Draw the plots
drawPlot(bitrateData.value, 'rgb(255, 165, 0)', maxBitrateReceived)
drawPlot(framerateData.value, 'rgb(0, 255, 0)', maxFramerateReceived)
drawPlot(packetLostData.value, 'rgb(255, 0, 0)', maxPacketLost)

// Print text stats
const color = connectionLost ? 'red' : 'white'
Expand Down Expand Up @@ -138,13 +142,15 @@ const webrtcStats = new WebRTCStats({ getStatsInterval: 100 })
function update(): void {
framerateData.value.push(framerate)
bitrateData.value.push(bitrate)
packetLostData.value.push(Math.min(packetLostDelta, maxPacketLost))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this put a roof on 10?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. the reasoning here is that the number of lost packets is not as relevant as the fact that packets were lost, so if I left it "unbounded", and we had 1000 packets lost between two messages, our ceiling would go to 1000. If we then lost 1 packet, it would be so small in the plot we wouldn't be able to see them.

image
In this image, for instance, I don't really care about how many packets were lost. I care that they were lost and we managed to recover.
Does that make sense?

TLDR: I put a low value so we can always notice EVERY packet lost

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes a lot of sense!

if (framerateData.value.length > maxDataPoints) framerateData.value.shift()
if (bitrateData.value.length > maxDataPoints) bitrateData.value.shift()
if (packetLostData.value.length > maxDataPoints) packetLostData.value.shift()

// Update max values
maxBitrateReceived = Math.max(maxBitrateReceived, ...bitrateData.value)
maxFramerateReceived = Math.max(maxFramerateReceived, ...framerateData.value)
if (maxFramerateReceived > absoluteMaxFrameRate) maxFramerateReceived = absoluteMaxFrameRate
maxFramerateReceived = Math.min(maxFramerateReceived, absoluteMaxFrameRate)
}

watch(videoStore.activeStreams, (streams): void => {
Expand Down Expand Up @@ -174,6 +180,7 @@ onMounted(() => {
const newBitrate = videoData.bitrate / 1000
bitrate = bitrate * 0.8 + newBitrate * 0.2
}
packetLostDelta = videoData.packetsLost - packetsLost
packetsLost = videoData.packetsLost
nackCount = videoData.nackCount
pliCount = videoData.pliCount
Expand All @@ -200,6 +207,10 @@ onUnmounted(() => {
clearInterval(intervalId)
cancelAnimationFrame(animationFrameId)
})

onBeforeUnmount(() => {
webrtcStats.destroy()
})
</script>

<style scoped>
Expand Down
Loading