arduino-audio-tools
Loading...
Searching...
No Matches
HLSStream.h
1#pragma once
2#include "AudioTools/AudioCodecs/AudioEncoded.h"
3#include "AudioTools/CoreAudio/AudioBasic/Str.h"
4#include "AudioTools/Communication/HTTP/URLStream.h"
5#include "AudioTools/CoreAudio/StreamCopy.h"
6#include "AudioToolsConfig.h"
7
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
15
17
18namespace audio_tools_hls {
19
34template <typename URLStream>
36 public:
37 URLLoaderHLS() = default;
38
39 ~URLLoaderHLS() { end(); }
40
41 bool begin() {
42 TRACED();
43 buffer.resize(buffer_size * buffer_count);
44
45 active = true;
46 return true;
47 }
48
49 void end() {
50 TRACED();
51 url_stream.end();
52 buffer.clear();
53 active = false;
54 }
55
57 void addUrl(const char *url) {
58 LOGI("Adding %s", url);
59 StrView url_str(url);
60 char *str = new char[url_str.length() + 1];
61 memcpy(str, url_str.c_str(), url_str.length() + 1);
62 urls.push_back((const char *)str);
63 }
64
67 int urlCount() { return urls.size(); }
68
70 int available() {
71 if (!active) return 0;
72 TRACED();
74
75 return buffer.available();
76 }
77
79 size_t readBytes(uint8_t *data, size_t len) {
80 if (!active) return 0;
81 TRACED();
83
84 if (buffer.available() < len) LOGW("Buffer underflow");
85 return buffer.readArray(data, len);
86 }
87
88 const char *contentType() {
89 return url_stream.httpRequest().reply().get(CONTENT_TYPE);
90 }
91
92 int contentLength() { return url_stream.contentLength(); }
93
94 void setBufferSize(int size, int count) {
95 buffer_size = size;
96 buffer_count = count;
97 // support call after begin()!
98 if (buffer.size() != 0) {
99 buffer.resize(buffer_size * buffer_count);
100 }
101 }
102
103 void setCACert(const char *cert) { url_stream.setCACert(cert); }
104
105 protected:
106 Vector<const char *> urls{10};
107 RingBuffer<uint8_t> buffer{0};
108 bool active = false;
109 int buffer_size = DEFAULT_BUFFER_SIZE;
110 int buffer_count = HLS_BUFFER_COUNT;
111 URLStream url_stream;
112 const char *url_to_play = nullptr;
113
116 TRACED();
117 // we have nothing to do
118 if (urls.empty()) {
119 LOGD("urls empty");
120 delay(HLS_UNDER_OVERFLOW_WAIT_TIME);
121 return;
122 }
123 if (buffer.availableForWrite() == 0) {
124 LOGD("buffer full");
125 delay(HLS_UNDER_OVERFLOW_WAIT_TIME);
126 return;
127 }
128
129 // switch current stream if we have no more data
130 if (!url_stream && !urls.empty()) {
131 LOGD("Refilling");
132 if (url_to_play != nullptr) {
133 delete url_to_play;
134 }
135 url_to_play = urls[0];
136 LOGI("playing %s", url_to_play);
137 url_stream.end();
138 url_stream.setConnectionClose(true);
139 url_stream.setTimeout(HLS_TIMEOUT);
140 url_stream.begin(url_to_play);
141 url_stream.waitForData(HLS_TIMEOUT);
142 urls.pop_front();
143 // assert(urls[0]!=url);
144
145 LOGI("Playing %s of %d", url_stream.urlStr(), (int)urls.size());
146 }
147
148 int total = 0;
149 int failed = 0;
150 int to_write = min(buffer.availableForWrite(), DEFAULT_BUFFER_SIZE);
151 // try to keep the buffer filled
152 while (to_write > 0) {
153 uint8_t tmp[to_write];
154 memset(tmp, 0, to_write);
155 int read = url_stream.readBytes(tmp, to_write);
156 total += read;
157 if (read > 0) {
158 failed = 0;
159 buffer.writeArray(tmp, read);
160 LOGD("buffer add %d -> %d:", read, buffer.available());
161
162 to_write = min(buffer.availableForWrite(), DEFAULT_BUFFER_SIZE);
163 } else {
164 delay(10);
165 }
166 // After we processed all data we close the stream to get a new url
167 if (url_stream.totalRead() == url_stream.contentLength()) {
168 LOGI("Closing stream because all bytes were processed: available: %d",
169 url_stream.available());
170 url_stream.end();
171 break;
172 }
173 LOGD("Refilled with %d now %d available to write", total,
174 buffer.availableForWrite());
175 }
176 }
177};
178
184 public:
185 bool add(const char *url) {
186 if (url == nullptr) return true;
187 bool found = false;
188 StrView url_str(url);
189 for (int j = 0; j < history.size(); j++) {
190 if (url_str.equals(history[j])) {
191 found = true;
192 break;
193 }
194 }
195 if (!found) {
196 char *str = new char[url_str.length() + 1];
197 memcpy(str, url, url_str.length() + 1);
198 history.push_back((const char *)str);
199 if (history.size() > 20) {
200 delete (history[0]);
201 history.pop_front();
202 }
203 }
204 return !found;
205 }
206
207 void clear() { history.clear(); }
208
209 int size() { return history.size(); }
210
211 protected:
212 Vector<const char *> history;
213};
214
220template <typename URLStream>
222 public:
223 // loads the index url
224 bool begin(const char *urlStr) {
225 index_url_str = urlStr;
226 return begin();
227 }
228
229 bool begin() {
230 TRACEI();
231 segments_url_str = "";
232 bandwidth = 0;
233 total_read = 0;
234
235 if (!parseIndex()) {
236 TRACEE();
237 return false;
238 }
239
240 // in some exceptional cases the index provided segement info
241 if (url_loader.urlCount() == 0) {
242 if (!parseSegments()) {
243 TRACEE();
244 return false;
245 }
246 } else {
247 segments_url_str = index_url_str;
248 segmentsActivate();
249 }
250
251 if (!url_loader.begin()) {
252 TRACEE();
253 return false;
254 }
255
256 return true;
257 }
258
259 int available() {
260 TRACED();
261 int result = 0;
263
264 if (active) result = url_loader.available();
265 return result;
266 }
267
268 size_t readBytes(uint8_t *data, size_t len) {
269 TRACED();
270 size_t result = 0;
272
273 if (active) result = url_loader.readBytes(data, len);
274 total_read += result;
275 return result;
276 }
277
278 const char *indexUrl() { return index_url_str; }
279
280 const char *segmentsUrl() { return segments_url_str.c_str(); }
281
283 const char *getCodec() { return codec.c_str(); }
284
286 const char *contentType() { return url_loader.contentType(); }
287
289 int contentLength() { return url_loader.contentLength(); }
290
292 void end() {
293 TRACEI();
294 codec.clear();
295 segments_url_str.clear();
296 url_stream.end();
297 url_loader.end();
298 url_history.clear();
299 active = false;
300 }
301
303 void setUrlCount(int count) { url_count = count; }
304
306 void setBufferSize(int size, int count) {
307 url_loader.setBufferSize(size, count);
308 }
309
310 void setCACert(const char *cert) {
311 url_stream.setCACert(cert);
312 url_loader.setCACert(cert);
313 }
314
315 void setPowerSave(bool flag) { url_stream.setPowerSave(flag); }
316
317 void setURLResolver(const char *(*cb)(const char *segment,
318 const char *reqURL)) {
319 resolve_url = cb;
320 }
322 const char *urlStr() { return url_str.c_str(); }
323
325 size_t totalRead() { return total_read; };
326
327 protected:
328 enum class URLType { Undefined, Index, Segment };
329 URLType next_url_type = URLType::Undefined;
330 int bandwidth = 0;
331 int url_count = 5;
332 size_t total_read = 0;
333 bool url_active = false;
334 bool is_extm3u = false;
335 Str codec;
336 Str segments_url_str;
337 Str url_str;
338 const char *index_url_str = nullptr;
339 URLStream url_stream;
340 URLLoaderHLS<URLStream> url_loader;
341 URLHistory url_history;
342 bool active = false;
343 bool parse_segments_active = false;
344 int media_sequence = 0;
345 int segment_count = 0;
346 uint64_t next_sement_load_time_planned = 0;
347 float play_time = 0;
348 uint64_t next_sement_load_time = 0;
349 const char *(*resolve_url)(const char *segment,
350 const char *reqURL) = resolveURL;
351
354 static const char *resolveURL(const char *segment, const char *reqURL) {
355 // avoid dynamic memory allocation
356 static char result[HLS_MAX_URL_LEN] = {0};
357 StrView result_str(result, HLS_MAX_URL_LEN);
358 StrView index_url(reqURL);
359 // Use prefix up to ? or laast /
360 int end = index_url.lastIndexOf("?");
361 if (end >= 0) {
362 result_str.substring(reqURL, 0, end);
363 } else {
364 end = index_url.lastIndexOf("/");
365 if (end >= 0) {
366 result_str.substring(reqURL, 0, end);
367 }
368 }
369 // Use the full url
370 if (result_str.isEmpty()) {
371 result_str = reqURL;
372 }
373 // add trailing /
374 if (!result_str.endsWith("/")) {
375 result_str.add("/");
376 }
377 // add relative segment
378 result_str.add(segment);
379 LOGI(">> relative addr: %s for %s", segment, reqURL);
380 LOGD(">> -> %s", result);
381 return result;
382 }
383
386 TRACED();
387 // get new urls
388 if (!segments_url_str.isEmpty()) {
390 }
391 }
392
394 bool parseIndex() {
395 TRACED();
396 url_stream.end();
397 url_stream.setTimeout(HLS_TIMEOUT);
398 url_stream.setConnectionClose(true);
399 if (!url_stream.begin(index_url_str)) return false;
400 url_active = true;
401 return parseIndexLines();
402 }
403
406 TRACEI();
407 char tmp[MAX_HLS_LINE];
408 bool result = true;
409 is_extm3u = false;
410
411 // parse lines
412 memset(tmp, 0, MAX_HLS_LINE);
413 while (true) {
414 memset(tmp, 0, MAX_HLS_LINE);
415 size_t len =
416 url_stream.httpRequest().readBytesUntil('\n', tmp, MAX_HLS_LINE);
417 // stop when there is no more data
418 if (len == 0 && url_stream.available() == 0) break;
419 StrView str(tmp);
420
421 // check header
422 if (str.startsWith("#EXTM3U")) {
423 is_extm3u = true;
424 // reset timings
425 resetTimings();
426 }
427
428 if (is_extm3u) {
429 if (!parseIndexLine(str)) {
430 return false;
431 }
432 }
433 }
434 return result;
435 }
436
438 bool parseIndexLine(StrView &str) {
439 TRACED();
440 LOGI("> %s", str.c_str());
441 parseIndexLineMetaData(str);
442 // in some exceptional cases the index provided segement info
443 parseSegmentLineMetaData(str);
444 parseLineURL(str);
445 return true;
446 }
447
448 bool parseIndexLineMetaData(StrView &str) {
449 int tmp_bandwidth;
450 if (str.startsWith("#")) {
451 if (str.indexOf("EXT-X-STREAM-INF") >= 0) {
452 next_url_type = URLType::Index;
453 // determine min bandwidth
454 int pos = str.indexOf("BANDWIDTH=");
455 if (pos > 0) {
456 StrView num(str.c_str() + pos + 10);
457 tmp_bandwidth = num.toInt();
458 url_active = (tmp_bandwidth < bandwidth || bandwidth == 0);
459 if (url_active) {
460 bandwidth = tmp_bandwidth;
461 LOGD("-> bandwith: %d", bandwidth);
462 }
463 }
464
465 pos = str.indexOf("CODECS=");
466 if (pos > 0) {
467 int start = pos + 8;
468 int end = str.indexOf('"', pos + 10);
469 codec.substring(str, start, end);
470 LOGI("-> codec: %s", codec.c_str());
471 }
472 }
473 }
474 return true;
475 }
476
477 void resetTimings() {
478 next_sement_load_time_planned = millis();
479 play_time = 0;
480 next_sement_load_time = 0xFFFFFFFFFFFFFFFF;
481 }
482
485 TRACED();
486 if (parse_segments_active) {
487 return false;
488 }
489
490 // make sure that we load at relevant schedule
491 if (millis() < next_sement_load_time && url_loader.urlCount() > 1) {
492 delay(1);
493 return false;
494 }
495 parse_segments_active = true;
496
497 LOGI("Available urls: %d", url_loader.urlCount());
498
499 if (url_stream) url_stream.clear();
500 LOGI("parsing %s", segments_url_str.c_str());
501
502 if (segments_url_str.isEmpty()) {
503 TRACEE();
504 parse_segments_active = false;
505 return false;
506 }
507
508 if (!url_stream.begin(segments_url_str.c_str())) {
509 TRACEE();
510 parse_segments_active = false;
511 return false;
512 }
513
514 segment_count = 0;
515 if (!parseSegmentLines()) {
516 TRACEE();
517 parse_segments_active = false;
518 // do not display as error
519 return true;
520 }
521
522 segmentsActivate();
523 return true;
524 }
525
526 void segmentsActivate() {
527 LOGI("Reloading in %f sec", play_time / 1000.0);
528 if (play_time > 0) {
529 next_sement_load_time = next_sement_load_time_planned + play_time;
530 }
531
532 // we request a minimum of collected urls to play before we start
533 if (url_history.size() > START_URLS_LIMIT) active = true;
534 parse_segments_active = false;
535 }
536
539 TRACEI();
540 char tmp[MAX_HLS_LINE];
541 bool result = true;
542 is_extm3u = false;
543
544 // parse lines
545 memset(tmp, 0, MAX_HLS_LINE);
546 while (true) {
547 memset(tmp, 0, MAX_HLS_LINE);
548 size_t len =
549 url_stream.httpRequest().readBytesUntil('\n', tmp, MAX_HLS_LINE);
550 if (len == 0 && url_stream.available() == 0) break;
551 StrView str(tmp);
552
553 // check header
554 if (str.startsWith("#EXTM3U")) {
555 is_extm3u = true;
556 resetTimings();
557 }
558
559 if (is_extm3u) {
560 if (!parseSegmentLine(str)) {
561 return false;
562 }
563 }
564 }
565 return result;
566 }
567
569 bool parseSegmentLine(StrView &str) {
570 TRACED();
571 LOGI("> %s", str.c_str());
572 if (!parseSegmentLineMetaData(str)) return false;
573 parseLineURL(str);
574 return true;
575 }
576
577 bool parseSegmentLineMetaData(StrView &str) {
578 if (str.startsWith("#")) {
579 if (str.startsWith("#EXT-X-MEDIA-SEQUENCE:")) {
580 int new_media_sequence = atoi(str.c_str() + 22);
581 LOGI("media_sequence: %d", new_media_sequence);
582 if (new_media_sequence == media_sequence) {
583 LOGW("MEDIA-SEQUENCE already loaded: %d", media_sequence);
584 return false;
585 }
586 media_sequence = new_media_sequence;
587 }
588
589 // add play time to next_sement_load_time_planned
590 if (str.startsWith("#EXTINF")) {
591 next_url_type = URLType::Segment;
592 StrView sec_str(str.c_str() + 8);
593 float sec = sec_str.toFloat();
594 LOGI("adding play time: %f sec", sec);
595 play_time += (sec * 1000.0);
596 }
597 }
598 return true;
599 }
600
601 bool parseLineURL(StrView &str) {
602 if (!str.startsWith("#")) {
603 switch (next_url_type) {
604 case URLType::Undefined:
605 // we should not get here
606 assert(false);
607 break;
608 case URLType::Index:
609 if (str.startsWith("http")) {
610 segments_url_str.set(str);
611 } else {
612 segments_url_str.set(resolve_url(str.c_str(), index_url_str));
613 }
614 LOGD("segments_url_str = %s", segments_url_str.c_str());
615 break;
616 case URLType::Segment:
617 segment_count++;
618 if (url_history.add(str.c_str())) {
619 // provide audio urls to the url_loader
620 if (str.startsWith("http")) {
621 url_str = str;
622 } else {
623 // we create the complete url
624 url_str = resolve_url(str.c_str(), index_url_str);
625 }
626 url_loader.addUrl(url_str.c_str());
627 } else {
628 LOGD("Duplicate ignored: %s", str.c_str());
629 }
630 }
631 // clear url type
632 next_url_type = URLType::Undefined;
633 }
634 return true;
635 }
636};
637
638} // namespace audio_tools_hls
639
640namespace audio_tools {
652template <typename URLStream>
654 public:
656 HLSStreamT() = default;
657
659 HLSStreamT(const char *ssid, const char *password) {
660 setSSID(ssid);
661 setPassword(password);
662 }
663
665 bool begin(const char *urlStr) {
666 TRACEI();
667 login();
668 // parse the url to the HLS
669 bool rc = parser.begin(urlStr);
670 return rc;
671 }
672
674 bool begin() override {
675 TRACEI();
676 login();
677 bool rc = parser.begin();
678 return rc;
679 }
680
682 void end() override { parser.end(); }
683
685 void setSSID(const char *ssid) override { this->ssid = ssid; }
686
688 void setPassword(const char *password) override { this->password = password; }
689
691 const char *codec() { return parser.getCodec(); }
692
694 const char *contentType() { return parser.contentType(); }
695
697 int contentLength() override { return parser.contentLength(); }
698
700 int available() override {
701 TRACED();
702 return parser.available();
703 }
704
706 size_t readBytes(uint8_t *data, size_t len) override {
707 TRACED();
708 return parser.readBytes(data, len);
709 }
710
712 void setBufferSize(int size, int count) { parser.setBufferSize(size, count); }
713
715 void setCACert(const char *cert) override { parser.setCACert(cert); }
716
718 void setPowerSave(bool flag) override { parser.setPowerSave(flag); }
719
722 const char *getReplyHeader(const char *header) override {
723 const char *codec = parser.getCodec();
724 const char *result = nullptr;
725 if (StrView(header).equalsIgnoreCase(CONTENT_TYPE)) {
726 result = parser.contentType();
727 }
728 if (result) LOGI("-> Format: %s", result);
729 return result;
730 }
731
734 void setURLResolver(const char *(*cb)(const char *segment,
735 const char *reqURL)) {
736 parser.setURLResolver(cb);
737 }
738
739 const char *urlStr() override { return parser.urlStr(); }
740
741 size_t totalRead() override { return parser.totalRead(); };
743 void setConnectionClose(bool flag) override {};
745 bool waitForData(int timeout) override { return false; }
746
747 protected:
749 const char *ssid = nullptr;
750 const char *password = nullptr;
751
752 void login() {
753#ifdef USE_WIFI
754 if (ssid != nullptr && password != nullptr &&
755 WiFi.status() != WL_CONNECTED) {
756 TRACED();
757 delay(1000);
758 WiFi.begin(ssid, password);
759 while (WiFi.status() != WL_CONNECTED) {
760 Serial.print(".");
761 delay(500);
762 }
763 }
764#else
765 LOGW("login not supported");
766#endif
767 }
768
770 bool begin(const char *urlStr, const char *acceptMime, MethodID action = GET,
771 const char *reqMime = "", const char *reqData = "") override {
772 return begin(urlStr);
773 }
774
776 static HttpRequest dummy;
777 return dummy;
778 }
779
781 void setClient(Client &clientPar) override {}
782
784 void addRequestHeader(const char *header, const char *value) override {}
785};
786
787using HLSStream = HLSStreamT<URLStream>;
788
789} // namespace audio_tools
Abstract Base class for all URLStream implementations.
Definition AbstractURLStream.h:17
virtual int readArray(T data[], int len)
reads multiple values
Definition Buffers.h:33
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition Buffers.h:55
void clear()
same as reset
Definition Buffers.h:95
Definition NoArduino.h:169
HTTP Live Streaming using HLS: The resulting .ts data is provided via readBytes() that dynamically re...
Definition HLSStream.h:653
void setBufferSize(int size, int count)
Redefines the read buffer size.
Definition HLSStream.h:712
size_t totalRead() override
Total amout of data that was consumed so far.
Definition HLSStream.h:741
const char * getReplyHeader(const char *header) override
Definition HLSStream.h:722
const char * codec()
Returns the string representation of the codec of the audio stream.
Definition HLSStream.h:691
void addRequestHeader(const char *header, const char *value) override
Not implemented.
Definition HLSStream.h:784
bool waitForData(int timeout) override
not implemented
Definition HLSStream.h:745
size_t readBytes(uint8_t *data, size_t len) override
Provides the data fro the next .ts Segment.
Definition HLSStream.h:706
HLSStreamT(const char *ssid, const char *password)
Convenience constructor which logs in to the WiFi.
Definition HLSStream.h:659
void end() override
ends the request
Definition HLSStream.h:682
int available() override
Provides number of available bytes in the read buffer.
Definition HLSStream.h:700
bool begin(const char *urlStr)
Open an HLS url.
Definition HLSStream.h:665
void setURLResolver(const char *(*cb)(const char *segment, const char *reqURL))
Definition HLSStream.h:734
const char * contentType()
Provides the content type from the http reply.
Definition HLSStream.h:694
HttpRequest & httpRequest() override
provides access to the HttpRequest
Definition HLSStream.h:775
void setSSID(const char *ssid) override
Sets the ssid that will be used for logging in (when calling begin)
Definition HLSStream.h:685
const char * urlStr() override
Provides the url as string.
Definition HLSStream.h:739
void setConnectionClose(bool flag) override
not implemented
Definition HLSStream.h:743
void setClient(Client &clientPar) override
Not implemented: potential future improvement.
Definition HLSStream.h:781
void setCACert(const char *cert) override
Defines the certificate.
Definition HLSStream.h:715
HLSStreamT()=default
Empty constructor.
bool begin() override
Reopens the last url.
Definition HLSStream.h:674
int contentLength() override
Provides the content length of the actual .ts Segment.
Definition HLSStream.h:697
bool begin(const char *urlStr, const char *acceptMime, MethodID action=GET, const char *reqMime="", const char *reqData="") override
Added to comply with AbstractURLStream.
Definition HLSStream.h:770
void setPassword(const char *password) override
Sets the password that will be used for logging in (when calling begin)
Definition HLSStream.h:688
void setPowerSave(bool flag) override
Changes the Wifi to power saving mode.
Definition HLSStream.h:718
Simple API to process get, put, post, del http requests I tried to use Arduino HttpClient,...
Definition HttpRequest.h:25
virtual int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:413
virtual size_t size() override
Returns the maximum capacity of the buffer.
Definition Buffers.h:428
virtual bool resize(int len)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:418
virtual int available() override
provides the number of entries that are available to read
Definition Buffers.h:410
A simple wrapper to provide string functions on existing allocated char*. If the underlying char* is ...
Definition StrView.h:28
Simple Parser for HLS data.
Definition HLSStream.h:221
int contentLength()
Provides the http content lengh.
Definition HLSStream.h:289
void setBufferSize(int size, int count)
Redefines the buffer size.
Definition HLSStream.h:306
bool parseIndexLines()
parse the index file
Definition HLSStream.h:405
void setUrlCount(int count)
Defines the number of urls that are preloaded in the URLLoaderHLS.
Definition HLSStream.h:303
bool parseSegmentLine(StrView &str)
Add all segments to queue.
Definition HLSStream.h:569
bool parseIndexLine(StrView &str)
Determine codec for min bandwidth.
Definition HLSStream.h:438
size_t totalRead()
Povides the number of bytes read.
Definition HLSStream.h:325
const char * contentType()
Provides the content type of the audio data.
Definition HLSStream.h:286
bool parseSegments()
parse the segment url provided by the index
Definition HLSStream.h:484
void reloadSegments()
trigger the reloading of segments if the limit is underflowing
Definition HLSStream.h:385
bool parseSegmentLines()
parse the segments
Definition HLSStream.h:538
void end()
Closes the processing.
Definition HLSStream.h:292
const char * urlStr()
Provides the hls url as string.
Definition HLSStream.h:322
const char * getCodec()
Provides the codec.
Definition HLSStream.h:283
bool parseIndex()
parse the index file and the segments
Definition HLSStream.h:394
static const char * resolveURL(const char *segment, const char *reqURL)
Definition HLSStream.h:354
Definition HLSStream.h:183
We feed the URLLoaderHLS with some url strings. The data of the related segments are provided via the...
Definition HLSStream.h:35
void bufferRefill()
try to keep the buffer filled
Definition HLSStream.h:115
int urlCount()
Definition HLSStream.h:67
int available()
Available bytes of the audio stream.
Definition HLSStream.h:70
size_t readBytes(uint8_t *data, size_t len)
Provides data from the audio stream.
Definition HLSStream.h:79
void addUrl(const char *url)
Adds the next url to be played in sequence.
Definition HLSStream.h:57
hide hls implementation in it's own namespace
Definition HLSStream.h:18
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
URLStreamESP32 URLStream
Support URLStream w/o Arduino.
Definition URLStreamESP32.h:429