Skip to content

Commit e5117fd

Browse files
committed
Merge branch 'feature/http_stream_support_range' into 'master'
http_stream: Support range request See merge request adf/esp-adf-internal!1205
2 parents ccbea64 + f4f953b commit e5117fd

File tree

2 files changed

+160
-89
lines changed

2 files changed

+160
-89
lines changed

components/audio_stream/http_stream.c

Lines changed: 152 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,12 @@ typedef struct http_stream {
9494
esp_err_t (*crt_bundle_attach)(void *conf); /* Function pointer to esp_crt_bundle_attach*/
9595
#endif
9696
bool gzip_encoding; /* Content is encoded */
97-
gzip_miniz_handle_t gzip; /* GZIP instance */
97+
gzip_miniz_handle_t gzip; /* GZIP instance */
9898
http_stream_hls_key_t *hls_key;
9999
hls_handle_t *hls_media;
100+
int request_range_size;
101+
int64_t request_range_end;
102+
bool is_last_range;
100103
} http_stream_t;
101104

102105
static esp_err_t http_stream_auto_connect_next_track(audio_element_handle_t el);
@@ -174,6 +177,22 @@ static esp_err_t _http_event_handle(esp_http_client_event_t *evt)
174177
return ESP_FAIL;
175178
}
176179
}
180+
else if (strcasecmp(evt->header_key, "Content-Range") == 0) {
181+
http_stream_t *http = (http_stream_t *)audio_element_getdata(el);
182+
if (http->request_range_size) {
183+
char* end_pos = strchr(evt->header_value, '-');
184+
http->is_last_range = true;
185+
if (end_pos) {
186+
end_pos++;
187+
int64_t range_end = atoll(end_pos);
188+
if (range_end == http->request_range_end) {
189+
http->is_last_range = false;
190+
}
191+
// Update total bytes to range end
192+
audio_element_set_total_bytes(el, range_end+1);
193+
}
194+
}
195+
}
177196
return ESP_OK;
178197
}
179198

@@ -413,80 +432,40 @@ static char *_playlist_get_next_track(audio_element_handle_t self)
413432
return NULL;
414433
}
415434

416-
static esp_err_t _http_open(audio_element_handle_t self)
435+
static void _prepare_range(http_stream_t *http, int64_t pos)
417436
{
418-
http_stream_t *http = (http_stream_t *)audio_element_getdata(self);
419-
esp_err_t err;
420-
char *uri = NULL;
421-
audio_element_info_t info;
422-
ESP_LOGD(TAG, "_http_open");
423-
424-
if (http->is_open) {
425-
ESP_LOGE(TAG, "already opened");
426-
return ESP_OK;
427-
}
428-
http->_errno = 0;
429-
audio_element_getinfo(self, &info);
430-
_stream_open_begin:
431-
if (http->hls_key && http->hls_key->key_loaded == false) {
432-
uri = http->hls_key->key_url;
433-
} else if (info.byte_pos == 0) {
434-
uri = _playlist_get_next_track(self);
435-
} else if (http->is_playlist_resolved) {
436-
uri = http_playlist_get_last_track(http->playlist);
437-
}
438-
if (uri == NULL) {
439-
if (http->is_playlist_resolved && http->enable_playlist_parser) {
440-
if (dispatch_hook(self, HTTP_STREAM_FINISH_PLAYLIST, NULL, 0) != ESP_OK) {
441-
ESP_LOGE(TAG, "Failed to process user callback");
442-
return ESP_FAIL;
437+
if (http->request_range_size > 0 || pos != 0) {
438+
char range_header[64] = {0};
439+
if (http->request_range_size == 0) {
440+
snprintf(range_header, sizeof(range_header), "bytes=%lld-", pos);
441+
} else {
442+
int64_t end_pos = pos + http->request_range_size - 1;
443+
if (pos < 0 && end_pos > 0) {
444+
end_pos = 0;
443445
}
444-
goto _stream_open_begin;
446+
snprintf(range_header, sizeof(range_header), "bytes=%lld-%lld", pos, end_pos);
447+
http->request_range_end = end_pos;
445448
}
446-
uri = audio_element_get_uri(self);
447-
}
448-
449-
if (uri == NULL) {
450-
ESP_LOGE(TAG, "Error open connection, uri = NULL");
451-
return ESP_FAIL;
452-
}
453-
audio_element_getinfo(self, &info);
454-
ESP_LOGD(TAG, "URI=%s", uri);
455-
// if not initialize http client, initial it
456-
if (http->client == NULL) {
457-
esp_http_client_config_t http_cfg = {
458-
.url = uri,
459-
.event_handler = _http_event_handle,
460-
.user_data = self,
461-
.timeout_ms = 30 * 1000,
462-
.buffer_size = HTTP_STREAM_BUFFER_SIZE,
463-
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0)
464-
.buffer_size_tx = 1024,
465-
#endif
466-
.cert_pem = http->cert_pem,
467-
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 4)) && defined CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
468-
.crt_bundle_attach = http->crt_bundle_attach,
469-
#endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)) && defined CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
470-
};
471-
http->client = esp_http_client_init(&http_cfg);
472-
AUDIO_MEM_CHECK(TAG, http->client, return ESP_ERR_NO_MEM);
473-
} else {
474-
esp_http_client_set_url(http->client, uri);
475-
}
476-
477-
if (info.byte_pos) {
478-
char rang_header[32];
479-
snprintf(rang_header, 32, "bytes=%d-", (int)info.byte_pos);
480-
esp_http_client_set_header(http->client, "Range", rang_header);
449+
esp_http_client_set_header(http->client, "Range", range_header);
481450
} else {
482451
esp_http_client_delete_header(http->client, "Range");
483452
}
453+
}
454+
455+
static esp_err_t _http_load_uri(audio_element_handle_t self, audio_element_info_t* info)
456+
{
457+
esp_err_t err;
458+
http_stream_t *http = (http_stream_t *)audio_element_getdata(self);
459+
460+
esp_http_client_close(http->client);
484461

485462
if (dispatch_hook(self, HTTP_STREAM_PRE_REQUEST, NULL, 0) != ESP_OK) {
486463
ESP_LOGE(TAG, "Failed to process user callback");
487464
return ESP_FAIL;
488465
}
489466

467+
_prepare_range(http, info->byte_pos);
468+
490469
if (http->stream_type == AUDIO_STREAM_WRITER) {
491470
err = esp_http_client_open(http->client, -1);
492471
if (err == ESP_OK) {
@@ -530,32 +509,95 @@ static esp_err_t _http_open(audio_element_handle_t self)
530509
* Due to the total byte of content has been changed after seek, set info.total_bytes at beginning only.
531510
*/
532511
int64_t cur_pos = esp_http_client_fetch_headers(http->client);
533-
audio_element_getinfo(self, &info);
534-
if (info.byte_pos <= 0) {
535-
info.total_bytes = cur_pos;
512+
audio_element_getinfo(self, info);
513+
if (info->byte_pos <= 0) {
514+
info->total_bytes = cur_pos;
515+
ESP_LOGI(TAG, "total_bytes=%d", (int)info->total_bytes);
516+
audio_element_set_total_bytes(self, info->total_bytes);
536517
}
537-
538-
ESP_LOGI(TAG, "total_bytes=%d", (int)info.total_bytes);
539518
int status_code = esp_http_client_get_status_code(http->client);
540519
if (status_code == 301 || status_code == 302) {
541520
esp_http_client_set_redirection(http->client);
542521
goto _stream_redirect;
543522
}
544523
if (status_code != 200
545-
&& (esp_http_client_get_status_code(http->client) != 206)) {
524+
&& (esp_http_client_get_status_code(http->client) != 206)
525+
&& (esp_http_client_get_status_code(http->client) != 416)) {
546526
ESP_LOGE(TAG, "Invalid HTTP stream, status code = %d", status_code);
547527
if (http->enable_playlist_parser) {
548528
http_playlist_clear(http->playlist);
549529
http->is_playlist_resolved = false;
550530
}
551531
return ESP_FAIL;
552532
}
553-
/**
554-
* `audio_element_setinfo` is risky affair.
555-
* It overwrites URI pointer as well. Pay attention to that!
556-
*/
557-
audio_element_set_total_bytes(self, info.total_bytes);
533+
return err;
534+
}
535+
536+
static esp_err_t _http_open(audio_element_handle_t self)
537+
{
538+
http_stream_t *http = (http_stream_t *)audio_element_getdata(self);
539+
char *uri = NULL;
540+
audio_element_info_t info;
541+
ESP_LOGD(TAG, "_http_open");
542+
543+
if (http->is_open) {
544+
ESP_LOGE(TAG, "already opened");
545+
return ESP_OK;
546+
}
547+
http->_errno = 0;
548+
audio_element_getinfo(self, &info);
549+
_stream_open_begin:
550+
if (http->hls_key && http->hls_key->key_loaded == false) {
551+
uri = http->hls_key->key_url;
552+
} else if (info.byte_pos == 0) {
553+
uri = _playlist_get_next_track(self);
554+
} else if (http->is_playlist_resolved) {
555+
uri = http_playlist_get_last_track(http->playlist);
556+
}
557+
if (uri == NULL) {
558+
if (http->is_playlist_resolved && http->enable_playlist_parser) {
559+
if (dispatch_hook(self, HTTP_STREAM_FINISH_PLAYLIST, NULL, 0) != ESP_OK) {
560+
ESP_LOGE(TAG, "Failed to process user callback");
561+
return ESP_FAIL;
562+
}
563+
goto _stream_open_begin;
564+
}
565+
uri = audio_element_get_uri(self);
566+
}
567+
568+
if (uri == NULL) {
569+
ESP_LOGE(TAG, "Error open connection, uri = NULL");
570+
return ESP_FAIL;
571+
}
558572

573+
ESP_LOGD(TAG, "URI=%s", uri);
574+
// if not initialize http client, initial it
575+
if (http->client == NULL) {
576+
esp_http_client_config_t http_cfg = {
577+
.url = uri,
578+
.event_handler = _http_event_handle,
579+
.user_data = self,
580+
.timeout_ms = 30 * 1000,
581+
.buffer_size = HTTP_STREAM_BUFFER_SIZE,
582+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0)
583+
.buffer_size_tx = 1024,
584+
#endif
585+
.cert_pem = http->cert_pem,
586+
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)) && defined CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
587+
.crt_bundle_attach = http->crt_bundle_attach,
588+
#endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)) && defined CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
589+
};
590+
http->client = esp_http_client_init(&http_cfg);
591+
AUDIO_MEM_CHECK(TAG, http->client, return ESP_ERR_NO_MEM);
592+
} else {
593+
esp_http_client_set_url(http->client, uri);
594+
}
595+
audio_element_getinfo(self, &info);
596+
597+
if (_http_load_uri(self, &info) != ESP_OK) {
598+
return ESP_FAIL;
599+
}
600+
559601
if (_is_playlist(&info, uri) == true) {
560602
/**
561603
* `goto _stream_open_begin` blocks on http_open until it gets valid URL.
@@ -594,20 +636,22 @@ static esp_err_t _http_close(audio_element_handle_t self)
594636
{
595637
http_stream_t *http = (http_stream_t *)audio_element_getdata(self);
596638
ESP_LOGD(TAG, "_http_close");
597-
while (http->is_open) {
639+
if (http->is_open) {
598640
http->is_open = false;
599-
if (http->stream_type != AUDIO_STREAM_WRITER) {
600-
break;
601-
}
602-
if (dispatch_hook(self, HTTP_STREAM_POST_REQUEST, NULL, 0) < 0) {
603-
break;
604-
}
605-
esp_http_client_fetch_headers(http->client);
606-
607-
if (dispatch_hook(self, HTTP_STREAM_FINISH_REQUEST, NULL, 0) < 0) {
608-
break;
609-
}
641+
do {
642+
if (http->stream_type != AUDIO_STREAM_WRITER) {
643+
break;
644+
}
645+
if (dispatch_hook(self, HTTP_STREAM_POST_REQUEST, NULL, 0) < 0) {
646+
break;
647+
}
648+
esp_http_client_fetch_headers(http->client);
649+
if (dispatch_hook(self, HTTP_STREAM_FINISH_REQUEST, NULL, 0) < 0) {
650+
break;
651+
}
652+
} while (0);
610653
}
654+
611655
if (AEL_STATE_PAUSED != audio_element_get_state(self)) {
612656
if (http->enable_playlist_parser) {
613657
http_playlist_clear(http->playlist);
@@ -645,6 +689,19 @@ static esp_err_t _http_reconnect(audio_element_handle_t self)
645689
return err;
646690
}
647691

692+
static bool _check_range_done(audio_element_handle_t self)
693+
{
694+
http_stream_t *http = (http_stream_t *)audio_element_getdata(self);
695+
bool last_range = http->is_last_range;
696+
audio_element_info_t info = {};
697+
audio_element_getinfo(self, &info);
698+
// If not last range need reload uri from last position
699+
if (last_range == false && _http_load_uri(self, &info) != ESP_OK) {
700+
return true;
701+
}
702+
return last_range;
703+
}
704+
648705
static int _http_read(audio_element_handle_t self, char *buffer, int len, TickType_t ticks_to_wait, void *context)
649706
{
650707
http_stream_t *http = (http_stream_t *)audio_element_getdata(self);
@@ -655,6 +712,11 @@ static int _http_read(audio_element_handle_t self, char *buffer, int len, TickTy
655712
if (rlen == 0) {
656713
rlen = _http_read_data(http, buffer, len);
657714
}
715+
if (rlen <= 0 && http->request_range_size) {
716+
if (_check_range_done(self) == false) {
717+
rlen = _http_read_data(http, buffer, len);
718+
}
719+
}
658720
if (rlen <= 0 && http->auto_connect_next_track) {
659721
if (http_stream_auto_connect_next_track(self) == ESP_OK) {
660722
rlen = _http_read_data(http, buffer, len);
@@ -841,6 +903,10 @@ audio_element_handle_t http_stream_init(http_stream_cfg_t *config)
841903
} else if (config->type == AUDIO_STREAM_WRITER) {
842904
cfg.write = _http_write;
843905
}
906+
http->request_range_size = config->request_range_size;
907+
if (config->request_size) {
908+
cfg.buffer_len = config->request_size;
909+
}
844910

845911
el = audio_element_init(&cfg);
846912
AUDIO_MEM_CHECK(TAG, el, {

components/audio_stream/include/http_stream.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,16 @@ typedef struct {
8686
bool enable_playlist_parser; /*!< Enable playlist parser*/
8787
int multi_out_num; /*!< The number of multiple output */
8888
const char *cert_pem; /*!< SSL server certification, PEM format as string, if the client requires to verify server */
89-
esp_err_t (*crt_bundle_attach)(void *conf); /*!< Function pointer to esp_crt_bundle_attach. Enables the use of certification
90-
bundle for server verification, must be enabled in menuconfig */
89+
esp_err_t (*crt_bundle_attach)(void *conf); /*!< Function pointer to esp_crt_bundle_attach. Enables the use of certification
90+
bundle for server verification, must be enabled in menuconfig */
91+
int request_size; /*!< Request data size each time from `http_client`
92+
Defaults use DEFAULT_ELEMENT_BUFFER_LENGTH if set to 0
93+
Need care this setting if audio frame size is small and want low latency playback */
94+
int request_range_size; /*!< Range size setting for header `Range: bytes=start-end`
95+
Request full range of resource if set to 0
96+
Range size bigger than request size is recommended */
9197
} http_stream_cfg_t;
9298

93-
9499
#define HTTP_STREAM_TASK_STACK (6 * 1024)
95100
#define HTTP_STREAM_TASK_CORE (0)
96101
#define HTTP_STREAM_TASK_PRIO (4)

0 commit comments

Comments
 (0)