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];
147 memset(tmp, 0, to_write);
148 int read = url_stream.readBytes(tmp, to_write);
149 total += read;
150 if (read > 0) {
151 failed = 0;
152 buffer.writeArray(tmp, read);
153 LOGD("buffer add %d -> %d:", read, buffer.available());
154
155 to_write = min(buffer.availableForWrite(), DEFAULT_BUFFER_SIZE);
156 } else {
157 delay(10);
158 }
159 // After we processed all data we close the stream to get a new url
160 if (url_stream.totalRead() == url_stream.contentLength()) {
161 LOGI("Closing stream because all bytes were processed: available: %d",
162 url_stream.available());
163 url_stream.end();
164 break;
165 }
166 LOGD("Refilled with %d now %d available to write", total,
167 buffer.availableForWrite());
168 }
169 }
170};
171
177 public:
178 bool add(const char *url) {
179 if (url == nullptr) return true;
180 bool found = false;
181 StrView url_str(url);
182 for (int j = 0; j < history.size(); j++) {
183 if (url_str.equals(history[j])) {
184 found = true;
185 break;
186 }
187 }
188 if (!found) {
189 char *str = new char[url_str.length() + 1];
190 memcpy(str, url, url_str.length() + 1);
191 history.push_back((const char *)str);
192 if (history.size() > 20) {
193 delete (history[0]);
194 history.pop_front();
195 }
196 }
197 return !found;
198 }
199
200 void clear() { history.clear(); }
201
202 int size() { return history.size(); }
203
204 protected:
205 Vector<const char *> history;
206};
207
213template <typename URLStream>
215 public:
216 // loads the index url
217 bool begin(const char *urlStr) {
218 index_url_str = urlStr;
219 return begin();
220 }
221
222 bool begin() {
223 TRACEI();
224 segments_url_str = "";
225 bandwidth = 0;
226 total_read = 0;
227
228 if (!parseIndex()) {
229 TRACEE();
230 return false;
231 }
232
233 // in some exceptional cases the index provided segement info
234 if (url_loader.urlCount() == 0) {
235 if (!parseSegments()) {
236 TRACEE();
237 return false;
238 }
239 } else {
240 segments_url_str = index_url_str;
241 segmentsActivate();
242 }
243
244 if (!url_loader.begin()) {
245 TRACEE();
246 return false;
247 }
248
249 return true;
250 }
251
252 int available() {
253 TRACED();
254 int result = 0;
256
257 if (active) result = url_loader.available();
258 return result;
259 }
260
261 size_t readBytes(uint8_t *data, size_t len) {
262 TRACED();
263 size_t result = 0;
265
266 if (active) result = url_loader.readBytes(data, len);
267 total_read += result;
268 return result;
269 }
270
271 const char *indexUrl() { return index_url_str; }
272
273 const char *segmentsUrl() { return segments_url_str.c_str(); }
274
276 const char *getCodec() { return codec.c_str(); }
277
279 const char *contentType() { return url_loader.contentType(); }
280
282 int contentLength() { return url_loader.contentLength(); }
283
285 void end() {
286 TRACEI();
287 codec.clear();
288 segments_url_str.clear();
289 url_stream.end();
290 url_loader.end();
291 url_history.clear();
292 active = false;
293 }
294
296 void setUrlCount(int count) { url_count = count; }
297
299 void setBufferSize(int size, int count) {
300 url_loader.setBufferSize(size, count);
301 }
302
303 void setCACert(const char *cert) {
304 url_stream.setCACert(cert);
305 url_loader.setCACert(cert);
306 }
307
308 void setPowerSave(bool flag) { url_stream.setPowerSave(flag); }
309
310 void setURLResolver(const char *(*cb)(const char *segment,
311 const char *reqURL)) {
312 resolve_url = cb;
313 }
315 const char *urlStr() { return url_str.c_str(); }
316
318 size_t totalRead() { return total_read; };
319
320 protected:
321 enum class URLType { Undefined, Index, Segment };
322 URLType next_url_type = URLType::Undefined;
323 int bandwidth = 0;
324 int url_count = 5;
325 size_t total_read = 0;
326 bool url_active = false;
327 bool is_extm3u = false;
328 Str codec;
329 Str segments_url_str;
330 Str url_str;
331 const char *index_url_str = nullptr;
332 URLStream url_stream;
333 URLLoaderHLS<URLStream> url_loader;
334 URLHistory url_history;
335 bool active = false;
336 bool parse_segments_active = false;
337 int media_sequence = 0;
338 int segment_count = 0;
339 uint64_t next_sement_load_time_planned = 0;
340 float play_time = 0;
341 uint64_t next_sement_load_time = 0;
342 const char *(*resolve_url)(const char *segment,
343 const char *reqURL) = resolveURL;
344
347 static const char *resolveURL(const char *segment, const char *reqURL) {
348 // avoid dynamic memory allocation
349 static char result[HLS_MAX_URL_LEN] = {0};
350 StrView result_str(result, HLS_MAX_URL_LEN);
351 StrView index_url(reqURL);
352 // Use prefix up to ? or laast /
353 int end = index_url.lastIndexOf("?");
354 if (end >= 0) {
355 result_str.substring(reqURL, 0, end);
356 } else {
357 end = index_url.lastIndexOf("/");
358 if (end >= 0) {
359 result_str.substring(reqURL, 0, end);
360 }
361 }
362 // Use the full url
363 if (result_str.isEmpty()) {
364 result_str = reqURL;
365 }
366 // add trailing /
367 if (!result_str.endsWith("/")) {
368 result_str.add("/");
369 }
370 // add relative segment
371 result_str.add(segment);
372 LOGI(">> relative addr: %s for %s", segment, reqURL);
373 LOGD(">> -> %s", result);
374 return result;
375 }
376
379 TRACED();
380 // get new urls
381 if (!segments_url_str.isEmpty()) {
383 }
384 }
385
387 bool parseIndex() {
388 TRACED();
389 url_stream.end();
390 url_stream.setTimeout(HLS_TIMEOUT);
391 url_stream.setConnectionClose(true);
392 if (!url_stream.begin(index_url_str)) return false;
393 url_active = true;
394 return parseIndexLines();
395 }
396
399 TRACEI();
400 char tmp[MAX_HLS_LINE];
401 bool result = true;
402 is_extm3u = false;
403
404 // parse lines
405 memset(tmp, 0, MAX_HLS_LINE);
406 while (true) {
407 memset(tmp, 0, MAX_HLS_LINE);
408 size_t len =
409 url_stream.httpRequest().readBytesUntil('\n', tmp, MAX_HLS_LINE);
410 // stop when there is no more data
411 if (len == 0 && url_stream.available() == 0) break;
412 StrView str(tmp);
413
414 // check header
415 if (str.startsWith("#EXTM3U")) {
416 is_extm3u = true;
417 // reset timings
418 resetTimings();
419 }
420
421 if (is_extm3u) {
422 if (!parseIndexLine(str)) {
423 return false;
424 }
425 }
426 }
427 return result;
428 }
429
431 bool parseIndexLine(StrView &str) {
432 TRACED();
433 LOGI("> %s", str.c_str());
434 parseIndexLineMetaData(str);
435 // in some exceptional cases the index provided segement info
436 parseSegmentLineMetaData(str);
437 parseLineURL(str);
438 return true;
439 }
440
441 bool parseIndexLineMetaData(StrView &str) {
442 int tmp_bandwidth;
443 if (str.startsWith("#")) {
444 if (str.indexOf("EXT-X-STREAM-INF") >= 0) {
445 next_url_type = URLType::Index;
446 // determine min bandwidth
447 int pos = str.indexOf("BANDWIDTH=");
448 if (pos > 0) {
449 StrView num(str.c_str() + pos + 10);
450 tmp_bandwidth = num.toInt();
451 url_active = (tmp_bandwidth < bandwidth || bandwidth == 0);
452 if (url_active) {
453 bandwidth = tmp_bandwidth;
454 LOGD("-> bandwith: %d", bandwidth);
455 }
456 }
457
458 pos = str.indexOf("CODECS=");
459 if (pos > 0) {
460 int start = pos + 8;
461 int end = str.indexOf('"', pos + 10);
462 codec.substring(str, start, end);
463 LOGI("-> codec: %s", codec.c_str());
464 }
465 }
466 }
467 return true;
468 }
469
470 void resetTimings() {
471 next_sement_load_time_planned = millis();
472 play_time = 0;
473 next_sement_load_time = 0xFFFFFFFFFFFFFFFF;
474 }
475
478 TRACED();
479 if (parse_segments_active) {
480 return false;
481 }
482
483 // make sure that we load at relevant schedule
484 if (millis() < next_sement_load_time && url_loader.urlCount() > 1) {
485 delay(1);
486 return false;
487 }
488 parse_segments_active = true;
489
490 LOGI("Available urls: %d", url_loader.urlCount());
491
492 if (url_stream) url_stream.clear();
493 LOGI("parsing %s", segments_url_str.c_str());
494
495 if (segments_url_str.isEmpty()) {
496 TRACEE();
497 parse_segments_active = false;
498 return false;
499 }
500
501 if (!url_stream.begin(segments_url_str.c_str())) {
502 TRACEE();
503 parse_segments_active = false;
504 return false;
505 }
506
507 segment_count = 0;
508 if (!parseSegmentLines()) {
509 TRACEE();
510 parse_segments_active = false;
511 // do not display as error
512 return true;
513 }
514
515 segmentsActivate();
516 return true;
517 }
518
519 void segmentsActivate() {
520 LOGI("Reloading in %f sec", play_time / 1000.0);
521 if (play_time > 0) {
522 next_sement_load_time = next_sement_load_time_planned + play_time;
523 }
524
525 // we request a minimum of collected urls to play before we start
526 if (url_history.size() > START_URLS_LIMIT) active = true;
527 parse_segments_active = false;
528 }
529
532 TRACEI();
533 char tmp[MAX_HLS_LINE];
534 bool result = true;
535 is_extm3u = false;
536
537 // parse lines
538 memset(tmp, 0, MAX_HLS_LINE);
539 while (true) {
540 memset(tmp, 0, MAX_HLS_LINE);
541 size_t len =
542 url_stream.httpRequest().readBytesUntil('\n', tmp, MAX_HLS_LINE);
543 if (len == 0 && url_stream.available() == 0) break;
544 StrView str(tmp);
545
546 // check header
547 if (str.startsWith("#EXTM3U")) {
548 is_extm3u = true;
549 resetTimings();
550 }
551
552 if (is_extm3u) {
553 if (!parseSegmentLine(str)) {
554 return false;
555 }
556 }
557 }
558 return result;
559 }
560
562 bool parseSegmentLine(StrView &str) {
563 TRACED();
564 LOGI("> %s", str.c_str());
565 if (!parseSegmentLineMetaData(str)) return false;
566 parseLineURL(str);
567 return true;
568 }
569
570 bool parseSegmentLineMetaData(StrView &str) {
571 if (str.startsWith("#")) {
572 if (str.startsWith("#EXT-X-MEDIA-SEQUENCE:")) {
573 int new_media_sequence = atoi(str.c_str() + 22);
574 LOGI("media_sequence: %d", new_media_sequence);
575 if (new_media_sequence == media_sequence) {
576 LOGW("MEDIA-SEQUENCE already loaded: %d", media_sequence);
577 return false;
578 }
579 media_sequence = new_media_sequence;
580 }
581
582 // add play time to next_sement_load_time_planned
583 if (str.startsWith("#EXTINF")) {
584 next_url_type = URLType::Segment;
585 StrView sec_str(str.c_str() + 8);
586 float sec = sec_str.toFloat();
587 LOGI("adding play time: %f sec", sec);
588 play_time += (sec * 1000.0);
589 }
590 }
591 return true;
592 }
593
594 bool parseLineURL(StrView &str) {
595 if (!str.startsWith("#")) {
596 switch (next_url_type) {
597 case URLType::Undefined:
598 // we should not get here
599 assert(false);
600 break;
601 case URLType::Index:
602 if (str.startsWith("http")) {
603 segments_url_str.set(str);
604 } else {
605 segments_url_str.set(resolve_url(str.c_str(), index_url_str));
606 }
607 LOGD("segments_url_str = %s", segments_url_str.c_str());
608 break;
609 case URLType::Segment:
610 segment_count++;
611 if (url_history.add(str.c_str())) {
612 // provide audio urls to the url_loader
613 if (str.startsWith("http")) {
614 url_str = str;
615 } else {
616 // we create the complete url
617 url_str = resolve_url(str.c_str(), index_url_str);
618 }
619 url_loader.addUrl(url_str.c_str());
620 } else {
621 LOGD("Duplicate ignored: %s", str.c_str());
622 }
623 }
624 // clear url type
625 next_url_type = URLType::Undefined;
626 }
627 return true;
628 }
629};
630
631} // namespace audio_tools_hls
632
633namespace audio_tools {
644template <typename URLStream>
646 public:
648 HLSStreamT() = default;
649
651 HLSStreamT(const char *ssid, const char *password) {
652 setSSID(ssid);
653 setPassword(password);
654 }
655
657 bool begin(const char *urlStr) {
658 TRACEI();
659 login();
660 // parse the url to the HLS
661 bool rc = parser.begin(urlStr);
662 return rc;
663 }
664
666 bool begin() override {
667 TRACEI();
668 login();
669 bool rc = parser.begin();
670 return rc;
671 }
672
674 void end() override { parser.end(); }
675
677 void setSSID(const char *ssid) override { this->ssid = ssid; }
678
680 void setPassword(const char *password) override { this->password = password; }
681
683 const char *codec() { return parser.getCodec(); }
684
686 const char *contentType() { return parser.contentType(); }
687
689 int contentLength() override { return parser.contentLength(); }
690
692 int available() override {
693 TRACED();
694 return parser.available();
695 }
696
698 size_t readBytes(uint8_t *data, size_t len) override {
699 TRACED();
700 return parser.readBytes(data, len);
701 }
702
704 void setBufferSize(int size, int count) { parser.setBufferSize(size, count); }
705
707 void setCACert(const char *cert) override { parser.setCACert(cert); }
708
710 void setPowerSave(bool flag) override { parser.setPowerSave(flag); }
711
714 const char *getReplyHeader(const char *header) override {
715 const char *codec = parser.getCodec();
716 const char *result = nullptr;
717 if (StrView(header).equalsIgnoreCase(CONTENT_TYPE)) {
718 result = parser.contentType();
719 }
720 if (result) LOGI("-> Format: %s", result);
721 return result;
722 }
723
726 void setURLResolver(const char *(*cb)(const char *segment,
727 const char *reqURL)) {
728 parser.setURLResolver(cb);
729 }
730
731 const char *urlStr() override { return parser.urlStr(); }
732
733 size_t totalRead() override { return parser.totalRead(); };
735 void setConnectionClose(bool flag) override {};
737 bool waitForData(int timeout) override { return false; }
738
739 protected:
741 const char *ssid = nullptr;
742 const char *password = nullptr;
743
744 void login() {
745#ifdef USE_WIFI
746 if (ssid != nullptr && password != nullptr &&
747 WiFi.status() != WL_CONNECTED) {
748 TRACED();
749 delay(1000);
750 WiFi.begin(ssid, password);
751 while (WiFi.status() != WL_CONNECTED) {
752 Serial.print(".");
753 delay(500);
754 }
755 }
756#else
757 LOGW("login not supported");
758#endif
759 }
760
762 bool begin(const char *urlStr, const char *acceptMime, MethodID action = GET,
763 const char *reqMime = "", const char *reqData = "") override {
764 return begin(urlStr);
765 }
766
768 static HttpRequest dummy;
769 return dummy;
770 }
771
773 void setClient(Client &clientPar) override {}
774
776 void addRequestHeader(const char *header, const char *value) override {}
777};
778
779using HLSStream = HLSStreamT<URLStream>;
780
781} // 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:645
void setBufferSize(int size, int count)
Redefines the read buffer size.
Definition HLSStream.h:704
size_t totalRead() override
Total amout of data that was consumed so far.
Definition HLSStream.h:733
const char * getReplyHeader(const char *header) override
Definition HLSStream.h:714
const char * codec()
Returns the string representation of the codec of the audio stream.
Definition HLSStream.h:683
void addRequestHeader(const char *header, const char *value) override
Not implemented.
Definition HLSStream.h:776
bool waitForData(int timeout) override
not implemented
Definition HLSStream.h:737
size_t readBytes(uint8_t *data, size_t len) override
Provides the data fro the next .ts Segment.
Definition HLSStream.h:698
HLSStreamT(const char *ssid, const char *password)
Convenience constructor which logs in to the WiFi.
Definition HLSStream.h:651
void end() override
ends the request
Definition HLSStream.h:674
int available() override
Provides number of available bytes in the read buffer.
Definition HLSStream.h:692
bool begin(const char *urlStr)
Open an HLS url.
Definition HLSStream.h:657
void setURLResolver(const char *(*cb)(const char *segment, const char *reqURL))
Definition HLSStream.h:726
const char * contentType()
Provides the content type from the http reply.
Definition HLSStream.h:686
HttpRequest & httpRequest() override
provides access to the HttpRequest
Definition HLSStream.h:767
void setSSID(const char *ssid) override
Sets the ssid that will be used for logging in (when calling begin)
Definition HLSStream.h:677
const char * urlStr() override
Provides the url as string.
Definition HLSStream.h:731
void setConnectionClose(bool flag) override
not implemented
Definition HLSStream.h:735
void setClient(Client &clientPar) override
Not implemented: potential future improvement.
Definition HLSStream.h:773
void setCACert(const char *cert) override
Defines the certificate.
Definition HLSStream.h:707
HLSStreamT()=default
Empty constructor.
bool begin() override
Reopens the last url.
Definition HLSStream.h:666
int contentLength() override
Provides the content length of the actual .ts Segment.
Definition HLSStream.h:689
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:762
void setPassword(const char *password) override
Sets the password that will be used for logging in (when calling begin)
Definition HLSStream.h:680
void setPowerSave(bool flag) override
Changes the Wifi to power saving mode.
Definition HLSStream.h:710
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:384
virtual size_t size() override
Returns the maximum capacity of the buffer.
Definition Buffers.h:398
virtual int available() override
provides the number of entries that are available to read
Definition Buffers.h:381
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:214
int contentLength()
Provides the http content lengh.
Definition HLSStream.h:282
void setBufferSize(int size, int count)
Redefines the buffer size.
Definition HLSStream.h:299
bool parseIndexLines()
parse the index file
Definition HLSStream.h:398
void setUrlCount(int count)
Defines the number of urls that are preloaded in the URLLoaderHLS.
Definition HLSStream.h:296
bool parseSegmentLine(StrView &str)
Add all segments to queue.
Definition HLSStream.h:562
bool parseIndexLine(StrView &str)
Determine codec for min bandwidth.
Definition HLSStream.h:431
size_t totalRead()
Povides the number of bytes read.
Definition HLSStream.h:318
const char * contentType()
Provides the content type of the audio data.
Definition HLSStream.h:279
bool parseSegments()
parse the segment url provided by the index
Definition HLSStream.h:477
void reloadSegments()
trigger the reloading of segments if the limit is underflowing
Definition HLSStream.h:378
bool parseSegmentLines()
parse the segments
Definition HLSStream.h:531
void end()
Closes the processing.
Definition HLSStream.h:285
const char * urlStr()
Provides the hls url as string.
Definition HLSStream.h:315
const char * getCodec()
Provides the codec.
Definition HLSStream.h:276
bool parseIndex()
parse the index file and the segments
Definition HLSStream.h:387
static const char * resolveURL(const char *segment, const char *reqURL)
Definition HLSStream.h:347
Definition HLSStream.h:176
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