Skip to content

Commit 627a6a2

Browse files
committed
prepare for http range bytes
1 parent 6506165 commit 627a6a2

File tree

3 files changed

+214
-41
lines changed

3 files changed

+214
-41
lines changed

src/Audio.cpp

Lines changed: 149 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
audio.cpp
44
55
Created on: Oct 28.2018 */char audioI2SVers[] ="\
6-
Version 3.4.0g ";
7-
/* Updated on: Jul 24.2025
6+
Version 3.4.0h ";
7+
/* Updated on: Jul 26.2025
88
99
Author: Wolle (schreibfaul1)
1010
Audio library for ESP32, ESP32-S3 or ESP32-P4
@@ -851,8 +851,8 @@ bool Audio::httpRange(const char* host, uint32_t range){
851851
if(startsWith(host, "https")) m_f_ssl = true;
852852
else m_f_ssl = false;
853853

854-
if(m_f_ssl) h_host.append(host + 8);
855-
else h_host.append(host + 7);
854+
if(m_f_ssl) h_host.assign(host + 8);
855+
else h_host.assign(host + 7);
856856

857857
int16_t pos_slash; // position of "/" in hostname
858858
int16_t pos_colon; // position of ":" in hostname
@@ -890,10 +890,10 @@ bool Audio::httpRange(const char* host, uint32_t range){
890890
ltoa(range, ch_range, 10);
891891
AUDIO_INFO("skip to position: %li", (long int)range);
892892
strcat(rqh, "GET ");
893-
strcat(rqh, extension.get());
893+
strcat(rqh, extension.c_get());
894894
strcat(rqh, " HTTP/1.1\r\n");
895895
strcat(rqh, "Host: ");
896-
strcat(rqh, hostwoext.get());
896+
strcat(rqh, hostwoext.c_get());
897897
strcat(rqh, "\r\n");
898898
strcat(rqh, "Icy-MetaData:1\r\n");
899899
strcat(rqh, "Icy-MetaData:2\r\n");
@@ -906,16 +906,18 @@ bool Audio::httpRange(const char* host, uint32_t range){
906906
strcat(rqh, "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36\r\n");
907907
strcat(rqh, "Connection: keep-alive\r\n\r\n");
908908

909-
_client->stop();
910909
if(m_f_ssl) { _client = static_cast<WiFiClient*>(&clientsecure); if(m_f_ssl && port == 80) port = 443;}
911910
else { _client = static_cast<WiFiClient*>(&client); }
912-
AUDIO_INFO("The host has disconnected, reconnecting");
911+
// AUDIO_INFO("The host has disconnected, reconnecting");
912+
913913
if(!_client->connect(hostwoext.get(), port)) {
914914
AUDIO_LOG_ERROR("connection lost %s", c_host.c_get());
915915
stopSong();
916916
return false;
917917
}
918+
;
918919
_client->print(rqh);
920+
919921
if(extension.ends_with_icase(".mp3")) m_expectedCodec = CODEC_MP3;
920922
if(extension.ends_with_icase(".aac")) m_expectedCodec = CODEC_AAC;
921923
if(extension.ends_with_icase(".wav")) m_expectedCodec = CODEC_WAV;
@@ -926,11 +928,9 @@ bool Audio::httpRange(const char* host, uint32_t range){
926928
if(extension.contains(".m3u8")) m_expectedPlsFmt = FORMAT_M3U8;
927929
if(extension.ends_with_icase(".pls")) m_expectedPlsFmt = FORMAT_PLS;
928930

929-
m_dataMode = HTTP_RESPONSE_HEADER; // Handle header
931+
m_dataMode = HTTP_RANGE_HEADER; // Handle header
930932
m_streamType = ST_WEBFILE;
931-
m_contentlength = 0;
932933
m_f_chunked = false;
933-
934934
return true;
935935
}
936936
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1916,8 +1916,7 @@ int Audio::read_ID3_Header(uint8_t* data, size_t len) {
19161916

19171917
if( // any lyrics embedded in file, passing it to external function
19181918
startsWith(m_ID3Hdr.tag, "SYLT") || startsWith(m_ID3Hdr.tag, "USLT")) {
1919-
if(m_dataMode == AUDIO_LOCALFILE) {
1920-
1919+
if(m_dataMode == AUDIO_LOCALFILE || (m_streamType == ST_WEBFILE && m_f_acceptRanges)) {
19211920
ps_ptr<char> tmp;
19221921
ps_ptr<char> content_descriptor;
19231922
ps_ptr<char> syltBuff;
@@ -1930,13 +1929,36 @@ int Audio::read_ID3_Header(uint8_t* data, size_t len) {
19301929
m_ID3Hdr.SYLT.size = m_ID3Hdr.framesize;
19311930

19321931
syltBuff.alloc(m_ID3Hdr.SYLT.size, "syltBuff");
1933-
uint32_t pos = m_audiofile.position();
1934-
m_audiofile.seek(m_ID3Hdr.SYLT.pos);
1935-
uint16_t bytesWritten = 0;
1936-
while(bytesWritten < m_ID3Hdr.SYLT.size){
1937-
bytesWritten += m_audiofile.read((uint8_t*)syltBuff.get() + bytesWritten, m_ID3Hdr.SYLT.size);
1932+
1933+
if(m_streamType == ST_WEBFILE && m_f_acceptRanges){
1934+
uint32_t pos = m_pwf.byteCounter;
1935+
// log_w("m_audiofile.position() %i, m_ID3Hdr.SYLT.pos %i", pos, m_ID3Hdr.SYLT.pos);
1936+
bool res;
1937+
res = httpRange(m_currentHost.c_get(), m_ID3Hdr.SYLT.pos);
1938+
if(!res) AUDIO_LOG_ERROR("http range request was not successful");
1939+
res = parseHttpRangeHeader();
1940+
if(!res) AUDIO_LOG_ERROR("http range response was not successful");
1941+
uint16_t bytesWritten = 0;
1942+
while(bytesWritten < m_ID3Hdr.SYLT.size){
1943+
bytesWritten += _client->read((uint8_t*)syltBuff.get() + bytesWritten, m_ID3Hdr.SYLT.size);
1944+
}
1945+
res = httpRange(m_currentHost.c_get(), pos);
1946+
if(!res) AUDIO_LOG_ERROR("http range request was not successful");
1947+
res = parseHttpRangeHeader();
1948+
if(!res) AUDIO_LOG_ERROR("http range response was not successful");
19381949
}
1939-
m_audiofile.seek(pos);
1950+
if(m_dataMode == AUDIO_LOCALFILE){
1951+
uint32_t pos = m_audiofile.position();
1952+
// log_w("m_audiofile.position() %i, m_ID3Hdr.SYLT.pos %i", pos, m_ID3Hdr.SYLT.pos);
1953+
m_audiofile.seek(m_ID3Hdr.SYLT.pos);
1954+
uint16_t bytesWritten = 0;
1955+
while(bytesWritten < m_ID3Hdr.SYLT.size){
1956+
bytesWritten += m_audiofile.read((uint8_t*)syltBuff.get() + bytesWritten, m_ID3Hdr.SYLT.size);
1957+
}
1958+
m_audiofile.seek(pos);
1959+
}
1960+
syltBuff.hex_dump(10);
1961+
19401962
m_ID3Hdr.SYLT.text_encoding = syltBuff[0]; // 0=ISO-8859-1, 1=UTF-16, 2=UTF-16BE, 3=UTF-8
19411963
if(m_ID3Hdr.SYLT.text_encoding == 1) isBigEndian = false;
19421964
if(m_ID3Hdr.SYLT.text_encoding > 3){AUDIO_LOG_ERROR("unknown text encoding: %i", m_ID3Hdr.SYLT.text_encoding), m_ID3Hdr.SYLT.text_encoding = 0;}
@@ -1988,8 +2010,6 @@ int Audio::read_ID3_Header(uint8_t* data, size_t len) {
19882010
return 0;
19892011
}
19902012

1991-
1992-
19932013
if(m_ID3Hdr.framesize == 0) return 0;
19942014

19952015
size_t fs = m_ID3Hdr.framesize; // fs = size of the frame data field as read from header
@@ -4063,21 +4083,21 @@ bool Audio::parseHttpResponseHeader() { // this is the response to a GET / reque
40634083
if(m_dataMode != HTTP_RESPONSE_HEADER) return false;
40644084
if(!m_currentHost.valid()) {AUDIO_LOG_ERROR("m_currentHost is empty"); return false;}
40654085

4066-
m_phrh.ctime = millis();
4067-
m_phrh.timeout = 4500; // ms
4086+
m_phreh.ctime = millis();
4087+
m_phreh.timeout = 4500; // ms
40684088

40694089
if(_client->available() == 0) {
4070-
if(!m_phrh.f_time) {
4071-
m_phrh.stime = millis();
4072-
m_phrh.f_time = true;
4090+
if(!m_phreh.f_time) {
4091+
m_phreh.stime = millis();
4092+
m_phreh.f_time = true;
40734093
}
4074-
if((millis() - m_phrh.stime) > m_phrh.timeout) {
4094+
if((millis() - m_phreh.stime) > m_phreh.timeout) {
40754095
AUDIO_LOG_ERROR("timeout");
4076-
m_phrh.f_time = false;
4096+
m_phreh.f_time = false;
40774097
return false;
40784098
}
40794099
}
4080-
m_phrh.f_time = false;
4100+
m_phreh.f_time = false;
40814101

40824102
ps_ptr<char>rhl;
40834103
rhl.alloc(1024, "rhl"); // responseHeaderline
@@ -4086,7 +4106,7 @@ bool Audio::parseHttpResponseHeader() { // this is the response to a GET / reque
40864106

40874107
while(true) { // outer while
40884108
uint16_t pos = 0;
4089-
if((millis() - m_phrh.ctime) > m_phrh.timeout) {
4109+
if((millis() - m_phreh.ctime) > m_phreh.timeout) {
40904110
AUDIO_LOG_ERROR("timeout");
40914111
m_f_timeout = true;
40924112
goto exit;
@@ -4235,7 +4255,6 @@ bool Audio::parseHttpResponseHeader() { // this is the response to a GET / reque
42354255
const char* c_cl = (rhl.get() + 15);
42364256
int32_t i_cl = atoi(c_cl);
42374257
m_contentlength = i_cl;
4238-
m_streamType = ST_WEBFILE; // Stream comes from a fileserver
42394258
// AUDIO_INFO("content-length: %lu", (long unsigned int)m_contentlength);
42404259
}
42414260

@@ -4258,13 +4277,13 @@ bool Audio::parseHttpResponseHeader() { // this is the response to a GET / reque
42584277
}
42594278
}
42604279

4261-
else if(rhl.starts_with("accept-ranges:")) {
4280+
else if(rhl.starts_with_icase("accept-ranges:")) {
42624281
if(rhl.ends_with_icase("bytes")) m_f_acceptRanges = true;
4263-
// log_w("%s", rhl);
4282+
AUDIO_LOG_WARN("%s", rhl.c_get());
42644283
}
42654284

4266-
else if(rhl.starts_with("content-range:")) {
4267-
// log_w("%s", rhl);
4285+
else if(rhl.starts_with_icase("content-range:")) {
4286+
AUDIO_LOG_WARN("%s", rhl.c_get());
42684287
}
42694288

42704289
else if(rhl.starts_with_icase("icy-url:")) {
@@ -4312,6 +4331,101 @@ bool Audio::parseHttpResponseHeader() { // this is the response to a GET / reque
43124331
return true;
43134332
}
43144333
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4334+
bool Audio::parseHttpRangeHeader() { // this is the response to a Range request
4335+
4336+
ps_ptr<char>rhl;
4337+
rhl.alloc(1024, "rhl"); // responseHeaderline
4338+
bool ct_seen = false;
4339+
4340+
if(m_dataMode != HTTP_RANGE_HEADER){
4341+
AUDIO_LOG_ERROR("wrong datamode %i", m_dataMode);
4342+
goto exit;
4343+
}
4344+
4345+
m_phrah.ctime = millis();
4346+
m_phrah.timeout = 4500; // ms
4347+
4348+
if(_client->available() == 0) {
4349+
if(!m_phrah.f_time) {
4350+
m_phrah.stime = millis();
4351+
m_phrah.f_time = true;
4352+
}
4353+
if((millis() - m_phrah.stime) > m_phrah.timeout) {
4354+
AUDIO_LOG_ERROR("timeout");
4355+
m_phrah.f_time = false;
4356+
return false;
4357+
}
4358+
}
4359+
m_phrah.f_time = false;
4360+
4361+
rhl.clear();
4362+
4363+
while(true) { // outer while
4364+
uint16_t pos = 0;
4365+
if((millis() - m_phrah.ctime) > m_phrah.timeout) {
4366+
AUDIO_LOG_ERROR("timeout");
4367+
m_f_timeout = true;
4368+
goto exit;
4369+
}
4370+
while(_client->available()) {
4371+
uint8_t b = _client->read();
4372+
if(b == '\n') {
4373+
if(!pos) { // empty line received, is the last line of this responseHeader
4374+
goto lastToDo;
4375+
}
4376+
break;
4377+
}
4378+
if(b == '\r') rhl[pos] = 0;
4379+
if(b < 0x20) continue;
4380+
rhl[pos] = b;
4381+
pos++;
4382+
if(pos == 1023) {
4383+
pos = 1022;
4384+
continue;
4385+
}
4386+
if(pos == 1022) {
4387+
rhl[pos] = '\0';
4388+
log_w("responseHeaderline overflow");
4389+
}
4390+
} // inner while
4391+
if(!pos) {
4392+
vTaskDelay(5);
4393+
continue;
4394+
}
4395+
4396+
if(rhl.starts_with_icase("HTTP/")) { // HTTP status error code
4397+
char statusCode[5];
4398+
statusCode[0] = rhl[9];
4399+
statusCode[1] = rhl[10];
4400+
statusCode[2] = rhl[11];
4401+
statusCode[3] = '\0';
4402+
int sc = atoi(statusCode);
4403+
if(sc > 310) { // e.g. HTTP/1.1 301 Moved Permanently
4404+
if(audio_showstreamtitle) audio_showstreamtitle(rhl.get());
4405+
goto exit;
4406+
}
4407+
}
4408+
if(rhl.starts_with_icase("Server:")) {
4409+
AUDIO_INFO("%s", rhl.c_get());
4410+
}
4411+
if(rhl.starts_with_icase("Content-Length:")) {
4412+
// AUDIO_INFO("%s", rhl.c_get());
4413+
}
4414+
if(rhl.starts_with_icase("Content-Range:")) {
4415+
AUDIO_INFO("%s", rhl.c_get());
4416+
}
4417+
if(rhl.starts_with_icase("Content-Type:")) {
4418+
// AUDIO_INFO("%s", rhl.c_get());
4419+
}
4420+
}
4421+
exit:
4422+
return false;
4423+
lastToDo:
4424+
m_dataMode = AUDIO_DATA;
4425+
m_streamType = ST_WEBFILE;
4426+
return true;
4427+
}
4428+
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
43154429
bool Audio::initializeDecoder(uint8_t codec) {
43164430
uint32_t gfH = 0;
43174431
uint32_t hWM = 0;

src/Audio.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,13 +353,21 @@ class Audio{
353353
} rflh_t;
354354
rflh_t m_rflh;
355355

356-
typedef struct _phrh{ // used in parseHttpResponseHeader
356+
typedef struct _phreh{ // used in parseHttpResponseHeader
357357
uint32_t ctime;
358358
uint32_t timeout;
359359
uint32_t stime;
360360
bool f_time = false;
361-
} phrh_t;
362-
phrh_t m_phrh;
361+
} phreh_t;
362+
phreh_t m_phreh;
363+
364+
typedef struct _phrah{ // used in parseHttpRangeHeader
365+
uint32_t ctime;
366+
uint32_t timeout;
367+
uint32_t stime;
368+
bool f_time = false;
369+
} phrah_t;
370+
phrah_t m_phrah;
363371

364372
typedef struct _sdet{ // used in streamDetection
365373
uint32_t tmr_slow = 0;
@@ -470,6 +478,7 @@ class Audio{
470478
void showstreamtitle(char* ml);
471479
bool parseContentType(char* ct);
472480
bool parseHttpResponseHeader();
481+
bool parseHttpRangeHeader();
473482
bool initializeDecoder(uint8_t codec);
474483
esp_err_t I2Sstart();
475484
esp_err_t I2Sstop();
@@ -752,7 +761,7 @@ class Audio{
752761
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
753762
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
754763
enum : int { FORMAT_NONE = 0, FORMAT_M3U = 1, FORMAT_PLS = 2, FORMAT_ASX = 3, FORMAT_M3U8 = 4}; // playlist formats
755-
enum : int { AUDIO_NONE, HTTP_RESPONSE_HEADER, AUDIO_DATA, AUDIO_LOCALFILE,
764+
enum : int { AUDIO_NONE, HTTP_RESPONSE_HEADER, HTTP_RANGE_HEADER, AUDIO_DATA, AUDIO_LOCALFILE,
756765
AUDIO_PLAYLISTINIT, AUDIO_PLAYLISTHEADER, AUDIO_PLAYLISTDATA};
757766
enum : int { FLAC_BEGIN = 0, FLAC_MAGIC = 1, FLAC_MBH =2, FLAC_SINFO = 3, FLAC_PADDING = 4, FLAC_APP = 5,
758767
FLAC_SEEK = 6, FLAC_VORBIS = 7, FLAC_CUESHEET = 8, FLAC_PICTURE = 9, FLAC_OKAY = 100};

0 commit comments

Comments
 (0)