2 #include "AudioTools/AudioCodecs/AudioEncoded.h"
3 #include "AudioConfig.h"
6 #include "AudioTools/CoreAudio/AudioBasic/Str.h"
7 #include "AudioTools/CoreAudio/AudioHttp/URLStream.h"
8 #include "AudioTools/AudioLibs/Concurrency.h"
10 #define MAX_HLS_LINE 512
11 #define START_URLS_LIMIT 4
12 #define HLS_BUFFER_COUNT 10
19 virtual bool begin() = 0;
20 virtual void end() = 0;
21 virtual void addUrl(
const char *url) = 0;
22 virtual int urlCount() = 0;
23 virtual int available() {
return 0; }
24 virtual size_t readBytes(uint8_t *data,
size_t len) {
return 0; }
25 const char *contentType() {
return nullptr; }
26 int contentLength() {
return 0; }
28 virtual void setBuffer(
int size,
int count) {}
38 virtual bool begin() {
return true; };
40 virtual void addUrl(
const char *url) {
41 LOGI(
"saving data for %s", url);
42 url_stream.
begin(url);
44 copier.
begin(*p_print, url_stream);
45 int bytes_copied = copier.
copyAll();
46 LOGI(
"Copied %d of %d", bytes_copied, url_stream.contentLength());
47 assert(bytes_copied == url_stream.contentLength());
50 virtual int urlCount() {
return 0; }
74 bool begin()
override {
77 buffer.resize(buffer_size * buffer_count);
80 buffer.resize(buffer_size * buffer_count);
92 if (p_stream !=
nullptr) p_stream->end();
99 void addUrl(
const char *url)
override {
100 LOGI(
"Adding %s", url);
102 char *str =
new char[url_str.
length() + 1];
103 memcpy(str, url_str.
c_str(), url_str.
length() + 1);
107 urls.push_back((
const char*)str);
116 if (!active)
return 0;
126 if (!active)
return 0;
131 if (buffer.
available() < len) LOGW(
"Buffer underflow");
135 const char *contentType() {
136 if (p_stream ==
nullptr)
return nullptr;
137 return p_stream->
httpRequest().reply().get(CONTENT_TYPE);
140 int contentLength() {
141 if (p_stream ==
nullptr)
return 0;
142 return p_stream->contentLength();
145 void setBuffer(
int size,
int count)
override {
147 buffer_count = count;
151 Vector<const char *> urls{10};
153 BufferRTOS<uint8_t> buffer{0};
154 Task task{
"Refill", 1024 * 5, 1, 1};
157 RingBuffer<uint8_t> buffer{0};
160 int buffer_size = DEFAULT_BUFFER_SIZE;
161 int buffer_count = HLS_BUFFER_COUNT;
162 URLStream default_stream;
163 URLStream *p_stream = &default_stream;
164 const char *url_to_play =
nullptr;
182 if (!*p_stream && !urls.empty()) {
184 if (url_to_play !=
nullptr) {
187 url_to_play = urls[0];
188 LOGI(
"playing %s", url_to_play);
189 p_stream->setTimeout(5000);
190 p_stream->begin(url_to_play);
191 p_stream->waitForData(500);
199 LOGI(
"Free heap: %u", (
unsigned)ESP.getFreeHeap());
201 LOGI(
"Playing %s of %d", p_stream->urlStr(), (
int)urls.size());
208 while (to_write > 0) {
209 uint8_t tmp[to_write] = {0};
210 int read = p_stream->readBytes(tmp, to_write);
215 LOGI(
"buffer add %d -> %d:", read, buffer.
available());
222 LOGW(
"No data idx %d: available: %d", failed, p_stream->available());
224 LOGE(
"No data idx %d: available: %d", failed, p_stream->available());
225 if (p_stream->available() == 0) p_stream->end();
230 if (p_stream->totalRead() == p_stream->contentLength()) {
234 LOGD(
"Refilled with %d now %d available to write", total,
246 bool add(
const char *url) {
249 for (
int j = 0; j < history.size(); j++) {
250 if (url_str.
equals(history[j])) {
256 char *str =
new char[url_str.
length() + 1];
257 memcpy(str, url, url_str.
length() + 1);
258 history.push_back((
const char*)str);
259 if (history.size() > 20) {
267 void clear() { history.clear(); }
269 int size() {
return history.size(); }
283 bool begin(
const char *urlStr) {
284 index_url_str = urlStr;
290 custom_log_level.set();
291 segments_url_str =
"";
297 if (!parseSegments()) {
302 if (!p_url_loader->begin()) {
308 segment_load_task.begin(std::bind(&HLSParser::reloadSegments,
this));
311 custom_log_level.reset();
318 custom_log_level.set();
322 if (active) result = p_url_loader->available();
323 custom_log_level.reset();
327 size_t readBytes(uint8_t *data,
size_t len) {
330 custom_log_level.set();
334 if (active) result = p_url_loader->readBytes(data, len);
335 custom_log_level.reset();
339 const char *indexUrl() {
return index_url_str; }
341 const char *segmentsUrl() {
342 if (segments_url_str ==
nullptr)
return nullptr;
343 return segments_url_str.
c_str();
352 int contentLength() {
return p_url_loader->contentLength(); }
358 segment_load_task.end();
361 segments_url_str.
clear();
372 void setLogLevel(AudioLogger::LogLevel level) { custom_log_level.set(level); }
374 void setBuffer(
int size,
int count) { p_url_loader->setBuffer(size, count); }
376 void setUrlLoader(URLLoaderHLSBase &loader) { p_url_loader = &loader; }
379 CustomLogLevel custom_log_level;
382 bool url_active =
false;
383 bool is_extm3u =
false;
385 Str segments_url_str;
387 const char *index_url_str =
nullptr;
388 URLStream url_stream;
389 URLLoaderHLS default_url_loader;
390 URLLoaderHLSBase *p_url_loader = &default_url_loader;
391 URLHistory url_history;
393 Task segment_load_task{
"Refill", 1024 * 5, 1, 1};
396 bool parse_segments_active =
false;
397 int media_sequence = 0;
398 int tartget_duration_ms = 5000;
399 int segment_count = 0;
400 uint64_t next_sement_load_time = 0;
403 void reloadSegments() {
406 if (!segments_url_str.isEmpty()) {
414 url_stream.setTimeout(5000);
418 url_stream.setAutoCreateLines(
false);
419 bool rc = url_stream.begin(index_url_str);
421 rc = parseIndexLines();
426 bool parseIndexLines() {
428 char tmp[MAX_HLS_LINE];
433 memset(tmp, 0, MAX_HLS_LINE);
435 memset(tmp, 0, MAX_HLS_LINE);
437 url_stream.httpRequest().readBytesUntil(
'\n', tmp, MAX_HLS_LINE);
438 if (len == 0 && url_stream.available() == 0)
break;
442 if (str.indexOf(
"#EXTM3U") >= 0) {
447 if (!parseIndexLine(str)) {
457 bool parseSegments() {
459 if (parse_segments_active) {
464 if (
millis() < next_sement_load_time && p_url_loader->urlCount() > 1) {
468 parse_segments_active =
true;
470 LOGI(
"Available urls: %d", p_url_loader->urlCount());
472 if (url_stream) url_stream.clear();
473 LOGI(
"parsing %s", segments_url_str.c_str());
475 if (segments_url_str.isEmpty()) {
477 parse_segments_active =
false;
481 if (!url_stream.begin(segments_url_str.c_str())) {
483 parse_segments_active =
false;
488 if (!parseSegmentLines()) {
490 parse_segments_active =
false;
495 next_sement_load_time =
millis() + (segment_count * tartget_duration_ms);
499 if (url_history.size() > START_URLS_LIMIT) active =
true;
500 parse_segments_active =
false;
506 bool parseSegmentLines() {
508 char tmp[MAX_HLS_LINE];
513 memset(tmp, 0, MAX_HLS_LINE);
515 memset(tmp, 0, MAX_HLS_LINE);
517 url_stream.httpRequest().readBytesUntil(
'\n', tmp, MAX_HLS_LINE);
518 if (len == 0 && url_stream.available() == 0)
break;
522 if (str.indexOf(
"#EXTM3U") >= 0) {
527 if (!parseSegmentLine(str)) {
536 bool parseSegmentLine(StrView &str) {
538 LOGI(
"> %s", str.c_str());
540 int pos = str.indexOf(
"#");
542 LOGI(
"-> Segment: %s", str.c_str());
544 pos = str.indexOf(
"#EXT-X-MEDIA-SEQUENCE:");
546 int new_media_sequence = atoi(str.c_str() + pos + 22);
547 LOGI(
"media_sequence: %d", new_media_sequence);
548 if (new_media_sequence == media_sequence) {
549 LOGW(
"MEDIA-SEQUENCE already loaded: %d", media_sequence);
552 media_sequence = new_media_sequence;
555 pos = str.indexOf(
"#EXT-X-TARGETDURATION:");
557 const char *duration_str = str.c_str() + pos + 22;
558 tartget_duration_ms = 1000 * atoi(duration_str);
559 LOGI(
"tartget_duration_ms: %d (%s)", tartget_duration_ms, duration_str);
563 if (url_history.add(str.c_str())) {
565 if (str.startsWith(
"http")) {
569 url_str = segments_url_str;
571 url_str.add(str.c_str());
573 p_url_loader->addUrl(url_str.c_str());
575 LOGD(
"Duplicate ignored: %s", str.c_str());
582 bool parseIndexLine(StrView &str) {
584 LOGI(
"> %s", str.c_str());
586 if (str.indexOf(
"EXT-X-STREAM-INF") >= 0) {
588 int pos = str.indexOf(
"BANDWIDTH=");
590 StrView num(str.c_str() + pos + 10);
591 tmp_bandwidth = num.toInt();
592 url_active = (tmp_bandwidth < bandwidth || bandwidth == 0);
594 bandwidth = tmp_bandwidth;
595 LOGD(
"-> bandwith: %d", bandwidth);
599 pos = str.indexOf(
"CODECS=");
602 int end = str.indexOf(
'"', pos + 10);
603 codec.substring(str, start,
end);
604 LOGI(
"-> codec: %s", codec.c_str());
608 if (str.startsWith(
"http")) {
610 segments_url_str.set(str);
611 LOGD(
"segments_url_str = %s", str.c_str());
630 HLSStream(
const char *ssid,
const char *password) {
635 bool begin(
const char *urlStr) {
639 bool rc = parser.begin(urlStr);
646 bool rc = parser.begin();
651 void end() { parser.
end(); }
654 void setSSID(
const char *ssid) { this->ssid = ssid; }
657 void setPassword(
const char *password) { this->password = password; }
662 const char *contentType() {
return parser.
contentType(); }
664 int contentLength() {
return parser.contentLength(); }
666 int available()
override {
668 return parser.available();
671 size_t readBytes(uint8_t *data,
size_t len)
override {
673 return parser.readBytes(data, len);
680 void setBuffer(
int size,
int count) { parser.setBuffer(size, count); }
684 const char *ssid =
nullptr;
685 const char *password =
nullptr;
689 if (ssid !=
nullptr && password !=
nullptr &&
690 WiFi.status() != WL_CONNECTED) {
692 WiFi.begin(ssid, password);
693 while (WiFi.status() != WL_CONNECTED) {
699 LOGW(
"login not supported");
Definition: HLSStream.h:244