2#include "AudioTools/AudioCodecs/AudioEncoded.h"
3#include "AudioTools/CoreAudio/AudioBasic/Str.h"
4#include "AudioTools/CoreAudio/AudioHttp/URLStream.h"
5#include "AudioTools/CoreAudio/StreamCopy.h"
6#include "AudioToolsConfig.h"
8#define MAX_HLS_LINE 512
9#define START_URLS_LIMIT 4
10#define HLS_BUFFER_COUNT 2
11#define HLS_MAX_NO_READ 2
12#define HLS_MAX_URL_LEN 256
13#define HLS_TIMEOUT 5000
14#define HLS_UNDER_OVERFLOW_WAIT_TIME 10
27template <
typename URLStream>
36 buffer.resize(buffer_size * buffer_count);
51 LOGI(
"Adding %s", url);
53 char *str =
new char[url_str.length() + 1];
54 memcpy(str, url_str.c_str(), url_str.length() + 1);
55 urls.push_back((
const char *)str);
64 if (!active)
return 0;
73 if (!active)
return 0;
77 if (buffer.
available() < len) LOGW(
"Buffer underflow");
81 const char *contentType() {
82 return url_stream.httpRequest().reply().get(CONTENT_TYPE);
85 int contentLength() {
return url_stream.contentLength(); }
87 void setBufferSize(
int size,
int count) {
91 if (buffer.
size() != 0) {
92 buffer.resize(buffer_size * buffer_count);
96 void setCACert(
const char *cert) { url_stream.setCACert(cert); }
99 Vector<const char *> urls{10};
100 RingBuffer<uint8_t> buffer{0};
102 int buffer_size = DEFAULT_BUFFER_SIZE;
103 int buffer_count = HLS_BUFFER_COUNT;
105 const char *url_to_play =
nullptr;
113 delay(HLS_UNDER_OVERFLOW_WAIT_TIME);
118 delay(HLS_UNDER_OVERFLOW_WAIT_TIME);
123 if (!url_stream && !urls.empty()) {
125 if (url_to_play !=
nullptr) {
128 url_to_play = urls[0];
129 LOGI(
"playing %s", url_to_play);
131 url_stream.setConnectionClose(
true);
132 url_stream.setTimeout(HLS_TIMEOUT);
133 url_stream.begin(url_to_play);
134 url_stream.waitForData(HLS_TIMEOUT);
138 LOGI(
"Playing %s of %d", url_stream.urlStr(), (
int)urls.size());
145 while (to_write > 0) {
146 uint8_t tmp[to_write] = {0};
147 int read = url_stream.readBytes(tmp, to_write);
152 LOGD(
"buffer add %d -> %d:", read, buffer.
available());
159 if (url_stream.totalRead() == url_stream.contentLength()) {
160 LOGI(
"Closing stream because all bytes were processed: available: %d",
161 url_stream.available());
165 LOGD(
"Refilled with %d now %d available to write", total,
177 bool add(
const char *url) {
178 if (url ==
nullptr)
return true;
180 StrView url_str(url);
181 for (
int j = 0; j < history.size(); j++) {
182 if (url_str.equals(history[j])) {
188 char *str =
new char[url_str.length() + 1];
189 memcpy(str, url, url_str.length() + 1);
190 history.push_back((
const char *)str);
191 if (history.size() > 20) {
199 void clear() { history.clear(); }
201 int size() {
return history.size(); }
204 Vector<const char *> history;
212template <
typename URLStream>
216 bool begin(
const char *
urlStr) {
223 segments_url_str =
"";
233 if (url_loader.urlCount() == 0) {
239 segments_url_str = index_url_str;
243 if (!url_loader.begin()) {
256 if (active) result = url_loader.available();
260 size_t readBytes(uint8_t *data,
size_t len) {
265 if (active) result = url_loader.readBytes(data, len);
266 total_read += result;
270 const char *indexUrl() {
return index_url_str; }
272 const char *segmentsUrl() {
return segments_url_str.c_str(); }
287 segments_url_str.clear();
299 url_loader.setBufferSize(size, count);
302 void setCACert(
const char *cert) {
303 url_stream.setCACert(cert);
304 url_loader.setCACert(cert);
307 void setPowerSave(
bool flag) { url_stream.setPowerSave(flag); }
309 void setURLResolver(
const char *(*cb)(
const char *segment,
310 const char *reqURL)) {
314 const char *
urlStr() {
return url_str.c_str(); }
320 enum class URLType { Undefined, Index, Segment };
321 URLType next_url_type = URLType::Undefined;
324 size_t total_read = 0;
325 bool url_active =
false;
326 bool is_extm3u =
false;
328 Str segments_url_str;
330 const char *index_url_str =
nullptr;
331 URLStream url_stream;
332 URLLoaderHLS<URLStream> url_loader;
333 URLHistory url_history;
335 bool parse_segments_active =
false;
336 int media_sequence = 0;
337 int segment_count = 0;
338 uint64_t next_sement_load_time_planned = 0;
340 uint64_t next_sement_load_time = 0;
341 const char *(*resolve_url)(
const char *segment,
346 static const char *
resolveURL(
const char *segment,
const char *reqURL) {
348 static char result[HLS_MAX_URL_LEN] = {0};
349 StrView result_str(result, HLS_MAX_URL_LEN);
350 StrView index_url(reqURL);
352 int end = index_url.lastIndexOf(
"?");
354 result_str.substring(reqURL, 0,
end);
356 end = index_url.lastIndexOf(
"/");
358 result_str.substring(reqURL, 0,
end);
362 if (result_str.isEmpty()) {
366 if (!result_str.endsWith(
"/")) {
370 result_str.add(segment);
371 LOGI(
">> relative addr: %s for %s", segment, reqURL);
372 LOGD(
">> -> %s", result);
346 static const char *
resolveURL(
const char *segment,
const char *reqURL) {
…}
380 if (!segments_url_str.isEmpty()) {
389 url_stream.setTimeout(HLS_TIMEOUT);
390 url_stream.setConnectionClose(
true);
391 if (!url_stream.begin(index_url_str))
return false;
399 char tmp[MAX_HLS_LINE];
404 memset(tmp, 0, MAX_HLS_LINE);
406 memset(tmp, 0, MAX_HLS_LINE);
408 url_stream.httpRequest().readBytesUntil(
'\n', tmp, MAX_HLS_LINE);
410 if (len == 0 && url_stream.available() == 0)
break;
414 if (str.startsWith(
"#EXTM3U")) {
432 LOGI(
"> %s", str.c_str());
433 parseIndexLineMetaData(str);
435 parseSegmentLineMetaData(str);
440 bool parseIndexLineMetaData(StrView &str) {
442 if (str.startsWith(
"#")) {
443 if (str.indexOf(
"EXT-X-STREAM-INF") >= 0) {
444 next_url_type = URLType::Index;
446 int pos = str.indexOf(
"BANDWIDTH=");
448 StrView num(str.c_str() + pos + 10);
449 tmp_bandwidth = num.toInt();
450 url_active = (tmp_bandwidth < bandwidth || bandwidth == 0);
452 bandwidth = tmp_bandwidth;
453 LOGD(
"-> bandwith: %d", bandwidth);
457 pos = str.indexOf(
"CODECS=");
460 int end = str.indexOf(
'"', pos + 10);
461 codec.substring(str, start,
end);
462 LOGI(
"-> codec: %s", codec.c_str());
469 void resetTimings() {
470 next_sement_load_time_planned = millis();
472 next_sement_load_time = 0xFFFFFFFFFFFFFFFF;
478 if (parse_segments_active) {
483 if (millis() < next_sement_load_time && url_loader.urlCount() > 1) {
487 parse_segments_active =
true;
489 LOGI(
"Available urls: %d", url_loader.urlCount());
491 if (url_stream) url_stream.clear();
492 LOGI(
"parsing %s", segments_url_str.c_str());
494 if (segments_url_str.isEmpty()) {
496 parse_segments_active =
false;
500 if (!url_stream.begin(segments_url_str.c_str())) {
502 parse_segments_active =
false;
509 parse_segments_active =
false;
518 void segmentsActivate() {
519 LOGI(
"Reloading in %f sec", play_time / 1000.0);
521 next_sement_load_time = next_sement_load_time_planned + play_time;
525 if (url_history.size() > START_URLS_LIMIT) active =
true;
526 parse_segments_active =
false;
532 char tmp[MAX_HLS_LINE];
537 memset(tmp, 0, MAX_HLS_LINE);
539 memset(tmp, 0, MAX_HLS_LINE);
541 url_stream.httpRequest().readBytesUntil(
'\n', tmp, MAX_HLS_LINE);
542 if (len == 0 && url_stream.available() == 0)
break;
546 if (str.startsWith(
"#EXTM3U")) {
563 LOGI(
"> %s", str.c_str());
564 if (!parseSegmentLineMetaData(str))
return false;
569 bool parseSegmentLineMetaData(StrView &str) {
570 if (str.startsWith(
"#")) {
571 if (str.startsWith(
"#EXT-X-MEDIA-SEQUENCE:")) {
572 int new_media_sequence = atoi(str.c_str() + 22);
573 LOGI(
"media_sequence: %d", new_media_sequence);
574 if (new_media_sequence == media_sequence) {
575 LOGW(
"MEDIA-SEQUENCE already loaded: %d", media_sequence);
578 media_sequence = new_media_sequence;
582 if (str.startsWith(
"#EXTINF")) {
583 next_url_type = URLType::Segment;
584 StrView sec_str(str.c_str() + 8);
585 float sec = sec_str.toFloat();
586 LOGI(
"adding play time: %f sec", sec);
587 play_time += (sec * 1000.0);
593 bool parseLineURL(StrView &str) {
594 if (!str.startsWith(
"#")) {
595 switch (next_url_type) {
596 case URLType::Undefined:
601 if (str.startsWith(
"http")) {
602 segments_url_str.set(str);
604 segments_url_str.set(resolve_url(str.c_str(), index_url_str));
606 LOGD(
"segments_url_str = %s", segments_url_str.c_str());
608 case URLType::Segment:
610 if (url_history.add(str.c_str())) {
612 if (str.startsWith(
"http")) {
616 url_str = resolve_url(str.c_str(), index_url_str);
618 url_loader.addUrl(url_str.c_str());
620 LOGD(
"Duplicate ignored: %s", str.c_str());
624 next_url_type = URLType::Undefined;
643template <
typename URLStream>
660 bool rc = parser.begin(
urlStr);
668 bool rc = parser.begin();
673 void end() { parser.end(); }
676 void setSSID(
const char *ssid) { this->ssid = ssid; }
679 void setPassword(
const char *password) { this->password = password; }
682 const char *
codec() {
return parser.getCodec(); }
693 return parser.available();
699 return parser.readBytes(data, len);
703 void setBufferSize(
int size,
int count) { parser.setBufferSize(size, count); }
706 void setCACert(
const char *cert) { parser.setCACert(cert); }
714 const char *
codec = parser.getCodec();
715 const char *result =
nullptr;
716 if (
StrView(header).equalsIgnoreCase(CONTENT_TYPE)) {
717 result = parser.contentType();
719 if (result) LOGI(
"-> Format: %s", result);
726 const char *reqURL)) {
727 parser.setURLResolver(cb);
730 const char *
urlStr()
override {
return parser.urlStr(); }
732 size_t totalRead()
override {
return parser.totalRead(); };
740 const char *ssid =
nullptr;
741 const char *password =
nullptr;
745 if (ssid !=
nullptr && password !=
nullptr &&
746 WiFi.status() != WL_CONNECTED) {
749 WiFi.begin(ssid, password);
750 while (WiFi.status() != WL_CONNECTED) {
756 LOGW(
"login not supported");
761 bool begin(
const char *
urlStr,
const char *acceptMime, MethodID action = GET,
762 const char *reqMime =
"",
const char *reqData =
"") {
761 bool begin(
const char *
urlStr,
const char *acceptMime, MethodID action = GET, {
…}
778using HLSStream = HLSStreamT<URLStream>;
Definition HLSStream.h:175