+ Stream Performance
+
+ Data has not yet been collected. Once a stream has begun and viewers are watching this page
+ will be available.
+
+
+ );
+ if (!errors?.length) {
+ return noData;
+ }
+
+ if (!latency?.length) {
+ return noData;
+ }
+
+ if (!segmentDownloadDurations?.length) {
+ return noData;
+ }
+
+ const errorChart = [
+ {
+ name: 'Errors',
+ color: '#B63FFF',
+ options: { radius: 3 },
+ data: errors,
+ },
+ {
+ name: 'Quality changes',
+ color: '#2087E2',
+ options: { radius: 2 },
+ data: qualityVariantChanges,
+ },
+ ];
+
+ const latencyChart = [
+ {
+ name: 'Average stream latency',
+ color: '#B63FFF',
+ options: { radius: 2 },
+ data: latency,
+ },
+ ];
+
+ const segmentDownloadDurationChart = [
+ {
+ name: 'Average download duration',
+ color: '#B63FFF',
+ options: { radius: 2 },
+ data: segmentDownloadDurations,
+ },
+ {
+ name: `Approximate limit`,
+ color: '#003FFF',
+ data: segmentDownloadDurations.map(item => ({
+ time: item.time,
+ value: segmentLength,
+ })),
+ options: { radius: 0 },
+ },
+ ];
+
+ const bitrateChart = [
+ {
+ name: 'Lowest viewer bitrate',
+ color: '#B63FFF',
+ data: minimumPlayerBitrate,
+ options: { radius: 2 },
+ },
+ ];
+
+ availableBitrates.forEach(bitrate => {
+ bitrateChart.push({
+ name: `${bitrate} kbps stream`,
+ color: '#003FFF',
+ data: minimumPlayerBitrate.map(item => ({
+ time: item.time,
+ value: 1200,
+ })),
+ options: { radius: 0 },
+ });
+ });
+
+ const currentSpeed = bitrateChart[0]?.data[bitrateChart[0].data.length - 1]?.value;
+ const currentDownloadSeconds =
+ segmentDownloadDurations[segmentDownloadDurations.length - 1]?.value;
+ const lowestVariant = availableBitrates[0]; // TODO: get lowest bitrate from available bitrates
+ const latencyStat = latencyChart[0]?.data[latencyChart[0].data.length - 1]?.value || 0;
+
+ let recentErrorCount = 0;
+ const errorValueCount = errorChart[0]?.data.length || 0;
+ if (errorValueCount > 5) {
+ const values = errorChart[0].data.slice(-5);
+ recentErrorCount = values.reduce((acc, curr) => acc + Number(curr.value), 0);
+ } else {
+ recentErrorCount = errorChart[0].data.reduce((acc, curr) => acc + Number(curr.value), 0);
+ }
+ const showStats = currentSpeed > 0 || currentDownloadSeconds > 0 || recentErrorCount > 0;
+ let bitrateError = null;
+ let speedError = null;
+
+ if (currentSpeed !== 0 && currentSpeed < lowestVariant) {
+ bitrateError = `At least one of your viewers is playing your stream at ${currentSpeed}kbps, slower than ${lowestVariant}kbps, the lowest quality you made available, experiencing buffering. Consider adding a lower quality with a lower bitrate if the errors over time warrant this.`;
+ }
+
+ if (currentDownloadSeconds > segmentLength) {
+ speedError =
+ 'Your viewers may be consuming your video slower than required. This may be due to slow networks or your latency configuration. Consider adding a lower quality with a lower bitrate or experiment with increasing the latency buffer setting.';
+ }
+
+ const errorStatColor = recentErrorCount > 0 ? '#B63FFF' : '#FFFFFF';
+ const statStyle = {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '80px',
+ };
+ return (
+ <>
+ Stream Performance
+
+ This tool hopes to help you identify and troubleshoot problems you may be experiencing with
+ your stream. It aims to aggregate experiences across your viewers, meaning one viewer with
+ an exceptionally bad experience may throw off numbers for the whole, especially with a low
+ number of viewers.
+
+
+ The data is only collected by those using the Owncast web interface and is unable to gain
+ insight into external players people may be using such as VLC, MPV, QuickTime, etc.
+
+
+
+
+
+
+ }
+ precision={0}
+ suffix="kbps"
+ />
+
+
+
+
+
+
+ }
+ precision={0}
+ suffix="seconds"
+ />
+
+
+
+
+
+
+ }
+ suffix=""
+ />{' '}
+
+
+
+
+
+
+
+
+ Once a video segment takes too long to download a viewer will experience
+ buffering. If you see slow downloads you should offer a lower quality for your
+ viewers, or find other ways, possibly an external storage provider, a CDN or a
+ faster network, to improve your stream quality. Increasing your latency buffer can
+ also help for some viewers.
+
+
+ In short, once the pink line consistently gets near the blue line, your stream is
+ likely experiencing problems for viewers.
+
+ >
+ }
+ />
+ {speedError && (
+
+ )}
+
+
+
+
+
+ The slowest bitrate of any of your viewers. Once somebody's bitrate drops below
+ the lowest video variant bitrate they will experience buffering. If you see
+ viewers with slow connections trying to play your video you should consider
+ offering an additional, lower quality.
+
+
+ In short, once the pink line gets near the lowest blue line, your stream is likely
+ experiencing problems for at least one of your viewers.
+
+ >
+ }
+ />
+ {bitrateError && (
+
+ )}
+
+
+
+
+
+ Recent number of errors, including buffering, and quality changes from across all
+ your viewers. Errors can occur for many reasons, including browser issues,
+ plugins, wifi problems, and they don't all represent fatal issues or something you
+ have control over.
+
+ A quality change is not necessarily a negative thing, but if it's excessive and
+ coinciding with errors you should consider adding another quality variant.
+
+ >
+ }
+ />
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/styles/globals.scss b/web/styles/globals.scss
index c444c28bd..49d63e870 100644
--- a/web/styles/globals.scss
+++ b/web/styles/globals.scss
@@ -64,6 +64,7 @@ strong {
.line-chart-container {
margin: 2em auto;
+ margin-bottom: 0px;
padding: 1em;
border: 1px solid var(--gray-dark);
diff --git a/web/utils/apis.ts b/web/utils/apis.ts
index 74e3c658c..b8f792524 100644
--- a/web/utils/apis.ts
+++ b/web/utils/apis.ts
@@ -106,6 +106,8 @@ export const SET_FOLLOWER_APPROVAL = `${API_LOCATION}followers/approve`;
// List of inbound federated actions that took place.
export const FEDERATION_ACTIONS = `${API_LOCATION}federation/actions`;
+export const API_STREAM_HEALTH_METRICS = `${API_LOCATION}metrics/video`;
+
export const API_YP_RESET = `${API_LOCATION}yp/reset`;
export const TEMP_UPDATER_API = LOGS_ALL;