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