status: hls download stats (last segment speed, failed requests, last error)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 20:09:58 +10:00
parent 7419cde174
commit 856d062269
4 changed files with 54 additions and 3 deletions
+49 -2
View File
@@ -1,9 +1,11 @@
#include "hls.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
#include "esp_crt_bundle.h"
#include "esp_heap_caps.h"
#include "esp_http_client.h"
@@ -39,6 +41,44 @@ static char s_media_url[URL_MAX];
static uint8_t *s_seg; // current segment (PSRAM)
static volatile unsigned s_suspended; // HLS_SUSPEND_* bits
// Download statistics for the status (hls_status), guarded by s_stat_lock.
static portMUX_TYPE s_stat_lock = portMUX_INITIALIZER_UNLOCKED;
static struct {
uint32_t seg_bytes, seg_ms; // last complete segment
uint32_t errors; // failed requests since boot
char error[128]; // last one, with the uptime it happened at
} s_stat;
// Log a failed request and keep it for the status.
static void fail(const char *fmt, ...)
{
char msg[96];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
ESP_LOGW(TAG, "%s", msg);
taskENTER_CRITICAL(&s_stat_lock);
s_stat.errors++;
snprintf(s_stat.error, sizeof(s_stat.error), "%s (at %lld s)", msg, esp_timer_get_time() / 1000000);
taskEXIT_CRITICAL(&s_stat_lock);
}
void hls_status(cJSON *st)
{
taskENTER_CRITICAL(&s_stat_lock);
typeof(s_stat) t = s_stat;
taskEXIT_CRITICAL(&s_stat_lock);
cJSON *o = cJSON_AddObjectToObject(st, "hls");
cJSON_AddNumberToObject(o, "seg_bytes", t.seg_bytes);
cJSON_AddNumberToObject(o, "seg_ms", t.seg_ms);
cJSON_AddNumberToObject(o, "seg_mbit", t.seg_ms ? t.seg_bytes * 8.0 / t.seg_ms / 1000 : 0);
cJSON_AddNumberToObject(o, "errors", t.errors);
if (t.error[0]) {
cJSON_AddStringToObject(o, "error", t.error);
}
}
void hls_suspend(unsigned reason, bool suspend)
{
if (suspend) {
@@ -89,7 +129,7 @@ static esp_http_client_handle_t http_open(const char *url)
for (int i = 0; i <= MAX_REDIRECTS; i++) {
esp_err_t err = esp_http_client_open(c, 0);
if (err != ESP_OK) {
ESP_LOGW(TAG, "open %s: %s", url, esp_err_to_name(err));
fail("open %s: %s", url, esp_err_to_name(err));
break;
}
esp_http_client_fetch_headers(c);
@@ -103,7 +143,7 @@ static esp_http_client_handle_t http_open(const char *url)
if (st == 200) {
return c;
}
ESP_LOGW(TAG, "GET %s: HTTP %d", url, st);
fail("GET %s: HTTP %d", url, st);
break;
}
esp_http_client_cleanup(c);
@@ -238,10 +278,17 @@ static bool fetch_segment(long long seq, const char *url)
}
if (r < 0 || total == SEG_MAX) {
ok = false;
fail("segment %lld: %s after %u bytes", seq, r < 0 ? "read error" : "too large", (unsigned)total);
}
esp_http_client_close(c);
esp_http_client_cleanup(c);
int64_t us = esp_timer_get_time() - t0;
if (ok) {
taskENTER_CRITICAL(&s_stat_lock);
s_stat.seg_bytes = total;
s_stat.seg_ms = us / 1000;
taskEXIT_CRITICAL(&s_stat_lock);
}
ESP_LOGD(TAG, "segment %lld: %u bytes in %.2f s (%.1f Mbit/s)%s", seq, (unsigned)total, us / 1e6,
us ? total * 8.0 / us : 0.0, ok ? "" : ", failed");
if (ok && s_sink) {
+3
View File
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <stdint.h>
#include "cJSON.h"
#include "esp_err.h"
// Receives segment data in chunks (MPEG-TS etc.). Return false to abort the segment.
@@ -13,6 +14,8 @@ typedef bool (*hls_sink_t)(const uint8_t *data, size_t len, bool segment_start);
#define HLS_URL_MAX 384
esp_err_t hls_start(hls_sink_t sink);
// Adds status.hls: last segment download (bytes, ms, Mbit/s incl. connect + TLS), failed requests.
void hls_status(cJSON *st);
// The playlist URL to play ("" = none). A different URL restarts fetching with it.
void hls_set_url(const char *url);
// Stop fetching (current download is abandoned) until resumed. Reasons are independent:
+1
View File
@@ -395,6 +395,7 @@ static void player_status(cJSON *st)
cJSON_AddStringToObject(st, "spotify_error", err);
}
cJSON_AddNumberToObject(st, "buffer_ms", (double)(audio_ring_level() * 1000 / RATE));
hls_status(st);
}
/* ----- Player API ----- */