Skip to content

Commit e1f8892

Browse files
authored
Merge pull request #1116 from schreibfaul1/httpRange
prepare for Http range: bytes
2 parents 7a4e08e + c49c3df commit e1f8892

File tree

3 files changed

+157
-178
lines changed

3 files changed

+157
-178
lines changed

src/Audio.cpp

Lines changed: 153 additions & 174 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.0h ";
7-
/* Updated on: Jul 26.2025
6+
Version 3.4.0i ";
7+
/* Updated on: Jul 28.2025
88
99
Author: Wolle (schreibfaul1)
1010
Audio library for ESP32, ESP32-S3 or ESP32-P4
@@ -791,15 +791,15 @@ bool Audio::httpPrint(const char* host) {
791791
rqh.append("\r\n");
792792
rqh.append("Icy-MetaData:1\r\n");
793793
rqh.append("Icy-MetaData:2\r\n");
794-
rqh.append("Accept: */*\r\n");
794+
rqh.append("Accept:*/*\r\n");
795795
rqh.append("User-Agent: VLC/3.0.21 LibVLC/3.0.21 AppleWebKit/537.36 (KHTML, like Gecko)\r\n");
796796
rqh.append("Accept-Encoding: identity;q=1,*;q=0\r\n");
797797
rqh.append("Connection: keep-alive\r\n\r\n");
798798

799799
AUDIO_INFO("next URL: \"%s\"", c_host.get());
800800

801801
if(f_equal == false){
802-
_client->stop();
802+
if(_client->connected()) _client->stop();
803803
}
804804
if(!_client->connected() ) {
805805
if(m_f_ssl) { _client = static_cast<WiFiClient*>(&clientsecure); if(m_f_ssl && port == 80) port = 443;}
@@ -836,101 +836,79 @@ bool Audio::httpPrint(const char* host) {
836836
return true;
837837
}
838838
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
839-
bool Audio::httpRange(const char* host, uint32_t range){
839+
bool Audio::httpRange(uint32_t seek, uint32_t length){
840840

841841
if(!m_f_running) return false;
842-
if(host == NULL) {
843-
AUDIO_INFO("Hostaddress is empty");
844-
stopSong();
845-
return false;
846-
}
847-
ps_ptr<char>c_host;
848-
c_host.copy_from(host);
849-
ps_ptr<char> h_host; // copy of host without http:// or https://
850-
851-
if(startsWith(host, "https")) m_f_ssl = true;
852-
else m_f_ssl = false;
853-
854-
if(m_f_ssl) h_host.assign(host + 8);
855-
else h_host.assign(host + 7);
856-
857-
int16_t pos_slash; // position of "/" in hostname
858-
int16_t pos_colon; // position of ":" in hostname
859-
int16_t pos_ampersand; // position of "&" in hostname
860-
uint16_t port = 80; // port number
861-
862-
// In the URL there may be an extension, like noisefm.ru:8000/play.m3u&t=.m3u
863-
pos_slash = h_host.index_of( "/", 0);
864-
pos_colon = h_host.index_of( ":", 0);
865-
if(isalpha(h_host[pos_colon + 1])) pos_colon = -1; // no portnumber follows
866-
pos_ampersand = h_host.index_of( "&", 0);
867-
868-
ps_ptr<char> hostwoext; // "skonto.ls.lv:8002" in "skonto.ls.lv:8002/mp3"
869-
ps_ptr<char> extension; // "/mp3" in "skonto.ls.lv:8002/mp3"
870-
871-
if(pos_slash > 1) {
872-
hostwoext.alloc(pos_slash + 1, "hostwoext");
873-
memcpy(hostwoext.get(), h_host.get(), pos_slash);
874-
hostwoext[pos_slash] = '\0';
875-
extension = urlencode(h_host.get() + pos_slash, true);
876-
}
877-
else { // url has no extension
878-
hostwoext.append(h_host.get());
879-
extension.append("/");
880-
}
881-
882-
if((pos_colon >= 0) && ((pos_ampersand == -1) || (pos_ampersand > pos_colon))) {
883-
port = atoi(h_host.get() + pos_colon + 1); // Get portnumber as integer
884-
hostwoext[pos_colon] = '\0'; // Host without portnumber
885-
}
886-
887-
char rqh[strlen(h_host.get()) + strlen(host) + 300]; // http request header
888-
rqh[0] = '\0';
889-
char ch_range[12];
890-
ltoa(range, ch_range, 10);
891-
AUDIO_INFO("skip to position: %li", (long int)range);
892-
strcat(rqh, "GET ");
893-
strcat(rqh, extension.c_get());
894-
strcat(rqh, " HTTP/1.1\r\n");
895-
strcat(rqh, "Host: ");
896-
strcat(rqh, hostwoext.c_get());
897-
strcat(rqh, "\r\n");
898-
strcat(rqh, "Icy-MetaData:1\r\n");
899-
strcat(rqh, "Icy-MetaData:2\r\n");
900-
strcat(rqh, "Range: bytes=");
901-
strcat(rqh, (const char*)ch_range);
902-
strcat(rqh, "-\r\n");
903-
strcat(rqh, "Referer: ");
904-
strcat(rqh, host);
905-
strcat(rqh, "\r\n");
906-
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");
907-
strcat(rqh, "Connection: keep-alive\r\n\r\n");
908842

843+
uint16_t port = 0; // port number
844+
ps_ptr<char> c_host; // copy of host
845+
ps_ptr<char> hwoe; // host without extension
846+
ps_ptr<char> extension; // extension
847+
ps_ptr<char> query_string; // parameter
848+
ps_ptr<char> path; // extension + '?' + parameter
849+
ps_ptr<char> rqh; // request header
850+
ps_ptr<char> cur_hwoe; // m_currenthost without extension
851+
ps_ptr<char> range; // e.g. "Range: bytes=124-"
852+
853+
c_host.clone_from(m_currentHost);
854+
c_host.trim();
855+
auto dismantledHost = dismantle_host(c_host.get());
856+
857+
// https://edge.live.mp3.mdn.newmedia.nacamar.net:8000/ps-charivariwb/livestream.mp3;?user=ps-charivariwb;&pwd=ps-charivariwb-------
858+
// | | | | |
859+
// | | | | | (query string)
860+
// ssl?| |<-----host without extension-------->|port|<----- --extension----------->|<-first parameter->|<-second parameter->.......
861+
// |
862+
// |<-----------------------------path------------------------------------------->
863+
864+
m_f_ssl = dismantledHost.ssl;
865+
port = dismantledHost.port;
866+
if(dismantledHost.hwoe.valid()) hwoe.clone_from(dismantledHost.hwoe);
867+
if(dismantledHost.extension.valid()) extension.clone_from(dismantledHost.extension);
868+
if(dismantledHost.query_string.valid()) query_string.clone_from(dismantledHost.query_string);
869+
870+
if(extension.valid()) path.assign(extension.get());
871+
if(query_string.valid()){path.append("?"); path.append(query_string.get());}
872+
if(!hwoe.valid()) hwoe.assign("");
873+
if(!extension.valid()) extension.assign("");
874+
if(!path.valid()) path.assign("");
875+
876+
path = urlencode(path.get(), true);
877+
878+
if(!m_currentHost.valid()) m_currentHost.assign("");
879+
auto dismantledLastHost = dismantle_host(m_currentHost.get());
880+
cur_hwoe.clone_from(dismantledLastHost.hwoe);
881+
882+
if(length == UINT32_MAX) range.assignf("Range: bytes=%li-\r\n",seek);
883+
else range.assignf("Range: bytes=%li-%li\r\n",seek, length);
884+
885+
rqh.assignf("GET /%s HTTP/1.1\r\n", path.get());
886+
rqh.appendf("Host: %s\r\n", hwoe.get());
887+
rqh.append("Accept: */*\r\n");
888+
rqh.append("Accept-Encoding: identity;q=1,*;q=0\r\n");
889+
rqh.append("Cache-Control: no-cache\r\n");
890+
rqh.append("Connection: keep-alive\r\n");
891+
rqh.appendf(range.c_get());
892+
rqh.appendf("Referer: %s\r\n", m_currentHost.c_get());
893+
rqh.append("Sec-GPC: 1\r\n");
894+
rqh.append("User-Agent: VLC/3.0.21 LibVLC/3.0.21 AppleWebKit/537.36 (KHTML, like Gecko)\r\n\r\n");
895+
896+
if(_client->connected()) {_client->stop();}
909897
if(m_f_ssl) { _client = static_cast<WiFiClient*>(&clientsecure); if(m_f_ssl && port == 80) port = 443;}
910898
else { _client = static_cast<WiFiClient*>(&client); }
911-
// AUDIO_INFO("The host has disconnected, reconnecting");
912899

913-
if(!_client->connect(hostwoext.get(), port)) {
900+
if(!_client->connect(hwoe.get(), port)) {
914901
AUDIO_LOG_ERROR("connection lost %s", c_host.c_get());
915902
stopSong();
916903
return false;
917904
}
918-
;
919-
_client->print(rqh);
920905

921-
if(extension.ends_with_icase(".mp3")) m_expectedCodec = CODEC_MP3;
922-
if(extension.ends_with_icase(".aac")) m_expectedCodec = CODEC_AAC;
923-
if(extension.ends_with_icase(".wav")) m_expectedCodec = CODEC_WAV;
924-
if(extension.ends_with_icase(".m4a")) m_expectedCodec = CODEC_M4A;
925-
if(extension.ends_with_icase(".flac")) m_expectedCodec = CODEC_FLAC;
926-
if(extension.ends_with_icase(".asx")) m_expectedPlsFmt = FORMAT_ASX;
927-
if(extension.ends_with_icase(".m3u")) m_expectedPlsFmt = FORMAT_M3U;
928-
if(extension.contains(".m3u8")) m_expectedPlsFmt = FORMAT_M3U8;
929-
if(extension.ends_with_icase(".pls")) m_expectedPlsFmt = FORMAT_PLS;
906+
// AUDIO_LOG_INFO("rqh \n%s", rqh.get());
930907

931-
m_dataMode = HTTP_RANGE_HEADER; // Handle header
908+
_client->print(rqh.c_get());
909+
910+
m_dataMode = HTTP_RANGE_HEADER;
932911
m_streamType = ST_WEBFILE;
933-
m_f_chunked = false;
934912
return true;
935913
}
936914
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1914,93 +1892,8 @@ int Audio::read_ID3_Header(uint8_t* data, size_t len) {
19141892
return 0;
19151893
}
19161894

1917-
if( // any lyrics embedded in file, passing it to external function
1918-
startsWith(m_ID3Hdr.tag, "SYLT") || startsWith(m_ID3Hdr.tag, "USLT")) {
1919-
if(m_dataMode == AUDIO_LOCALFILE || (m_streamType == ST_WEBFILE && m_f_acceptRanges)) {
1920-
ps_ptr<char> tmp;
1921-
ps_ptr<char> content_descriptor;
1922-
ps_ptr<char> syltBuff;
1923-
bool isBigEndian = true;
1924-
size_t len = 0;
1925-
int idx = 0;
1926-
1927-
m_ID3Hdr.SYLT.seen = true;
1928-
m_ID3Hdr.SYLT.pos = m_ID3Hdr.id3Size - m_ID3Hdr.remainingHeaderBytes;
1929-
m_ID3Hdr.SYLT.size = m_ID3Hdr.framesize;
1930-
1931-
syltBuff.alloc(m_ID3Hdr.SYLT.size, "syltBuff");
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");
1949-
}
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-
1962-
m_ID3Hdr.SYLT.text_encoding = syltBuff[0]; // 0=ISO-8859-1, 1=UTF-16, 2=UTF-16BE, 3=UTF-8
1963-
if(m_ID3Hdr.SYLT.text_encoding == 1) isBigEndian = false;
1964-
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;}
1965-
char encodingTab [4][12] = {"ISO-8859-1", "UTF-16", "UTF-16BE", "UTF-8"};
1966-
memcpy(m_ID3Hdr.SYLT.lang, syltBuff.get() + 1, 3); m_ID3Hdr.SYLT.lang[3] = '\0';
1967-
AUDIO_INFO("Lyrics: text_encoding: %s, language: %s, size %i", encodingTab[m_ID3Hdr.SYLT.text_encoding], m_ID3Hdr.SYLT.lang, m_ID3Hdr.SYLT.size);
1968-
m_ID3Hdr.SYLT.time_stamp_format = syltBuff[4];
1969-
m_ID3Hdr.SYLT.content_type = syltBuff[5];
1970-
1971-
idx = 6;
1972-
1973-
if(m_ID3Hdr.SYLT.text_encoding == 0 || m_ID3Hdr.SYLT.text_encoding == 3){ // utf-8
1974-
len = content_descriptor.copy_from((const char*)(syltBuff.get() + idx), "content_descriptor");
1975-
}
1976-
else{ // utf-16
1977-
len = content_descriptor.copy_from_utf16((const uint8_t*)(syltBuff.get() + idx), isBigEndian, "content_descriptor");
1978-
}
1979-
if(len > 2) AUDIO_INFO("Lyrics: content_descriptor: %s", content_descriptor.c_get());
1980-
idx += len;
1981-
1982-
while (idx < m_ID3Hdr.SYLT.size) {
1983-
// UTF-16LE, UTF-16BE
1984-
if (m_ID3Hdr.SYLT.text_encoding == 1 || m_ID3Hdr.SYLT.text_encoding == 2) {
1985-
idx += tmp.copy_from_utf16((const uint8_t*)(syltBuff.get() + idx), isBigEndian, "sylt-text");
1986-
} else {
1987-
// ISO-8859-1 / UTF-8
1988-
idx += tmp.copy_from((const char*)syltBuff.get() + idx, "sylt-text");
1989-
}
1990-
if (tmp.starts_with("\n")) tmp.remove_before(1);
1991-
m_syltLines.push_back(std::move(tmp));
1992-
1993-
if (idx + 4 > m_ID3Hdr.SYLT.size) break; // no more 4 bytes?
1994-
1995-
uint32_t timestamp = bigEndian((uint8_t*)syltBuff.get() + idx, 4);
1996-
m_syltTimeStamp.push_back(timestamp);
1997-
1998-
idx += 4;
1999-
}
2000-
for(int i = 0; i < m_syltLines.size(); i++){
2001-
AUDIO_INFO("%07i ms, %s", m_syltTimeStamp[i], m_syltLines[i].c_get());
2002-
}
2003-
}
1895+
if(startsWith(m_ID3Hdr.tag, "SYLT") || startsWith(m_ID3Hdr.tag, "USLT")) { // any lyrics embedded in file, passing it to external function
1896+
m_controlCounter = 7;
20041897
return 0;
20051898
}
20061899

@@ -2086,6 +1979,92 @@ int Audio::read_ID3_Header(uint8_t* data, size_t len) {
20861979
}
20871980
return fs;
20881981
}
1982+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1983+
if(m_controlCounter == 7) { // SYLT
1984+
m_controlCounter = 5;
1985+
if(m_dataMode == AUDIO_LOCALFILE || (m_streamType == ST_WEBFILE && m_f_acceptRanges)) {
1986+
ps_ptr<char> tmp;
1987+
ps_ptr<char> content_descriptor;
1988+
ps_ptr<char> syltBuff;
1989+
bool isBigEndian = true;
1990+
size_t len = 0;
1991+
int idx = 0;
1992+
m_ID3Hdr.SYLT.seen = true;
1993+
m_ID3Hdr.SYLT.pos = m_ID3Hdr.id3Size - m_ID3Hdr.remainingHeaderBytes;
1994+
m_ID3Hdr.SYLT.size = m_ID3Hdr.framesize;
1995+
syltBuff.alloc(m_ID3Hdr.SYLT.size + 1, "syltBuff");
1996+
if(m_streamType == ST_WEBFILE && m_f_acceptRanges){
1997+
uint32_t pos = m_pwf.byteCounter;
1998+
// log_w("m_audiofile.position() %i, m_ID3Hdr.SYLT.pos %i", pos, m_ID3Hdr.SYLT.pos);
1999+
bool res;
2000+
res = httpRange(m_ID3Hdr.SYLT.pos, m_ID3Hdr.SYLT.pos + m_ID3Hdr.SYLT.size);
2001+
if(res == false){AUDIO_LOG_ERROR("http range request was not successful"); return 0;}
2002+
res = parseHttpRangeHeader();
2003+
if(res == false){AUDIO_LOG_ERROR("http range response was not successful"); return 0;}
2004+
uint16_t bytesWritten = 0;
2005+
while(bytesWritten < m_ID3Hdr.SYLT.size){
2006+
bytesWritten += _client->read((uint8_t*)syltBuff.get() + bytesWritten, m_ID3Hdr.SYLT.size);
2007+
}
2008+
res = httpRange(pos);
2009+
if(!res){AUDIO_LOG_ERROR("http range request was not successful"); return 0;}
2010+
res = parseHttpRangeHeader();
2011+
if(!res){AUDIO_LOG_ERROR("http range response was not successful"); return 0;}
2012+
// return 0;
2013+
}
2014+
if(m_dataMode == AUDIO_LOCALFILE){
2015+
uint32_t pos = m_audiofile.position();
2016+
// log_w("m_audiofile.position() %i, m_ID3Hdr.SYLT.pos %i", pos, m_ID3Hdr.SYLT.pos);
2017+
m_audiofile.seek(m_ID3Hdr.SYLT.pos);
2018+
uint16_t bytesWritten = 0;
2019+
while(bytesWritten < m_ID3Hdr.SYLT.size){
2020+
bytesWritten += m_audiofile.read((uint8_t*)syltBuff.get() + bytesWritten, m_ID3Hdr.SYLT.size);
2021+
}
2022+
m_audiofile.seek(pos);
2023+
}
2024+
// syltBuff.hex_dump(10);
2025+
m_ID3Hdr.SYLT.text_encoding = syltBuff[0]; // 0=ISO-8859-1, 1=UTF-16, 2=UTF-16BE, 3=UTF-8
2026+
if(m_ID3Hdr.SYLT.text_encoding == 1) isBigEndian = false;
2027+
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;}
2028+
char encodingTab [4][12] = {"ISO-8859-1", "UTF-16", "UTF-16BE", "UTF-8"};
2029+
memcpy(m_ID3Hdr.SYLT.lang, syltBuff.get() + 1, 3); m_ID3Hdr.SYLT.lang[3] = '\0';
2030+
AUDIO_INFO("Lyrics: text_encoding: %s, language: %s, size %i", encodingTab[m_ID3Hdr.SYLT.text_encoding], m_ID3Hdr.SYLT.lang, m_ID3Hdr.SYLT.size);
2031+
m_ID3Hdr.SYLT.time_stamp_format = syltBuff[4];
2032+
m_ID3Hdr.SYLT.content_type = syltBuff[5];
2033+
idx = 6;
2034+
if(m_ID3Hdr.SYLT.text_encoding == 0 || m_ID3Hdr.SYLT.text_encoding == 3){ // utf-8
2035+
len = content_descriptor.copy_from((const char*)(syltBuff.get() + idx), "content_descriptor");
2036+
}
2037+
else{ // utf-16
2038+
len = content_descriptor.copy_from_utf16((const uint8_t*)(syltBuff.get() + idx), isBigEndian, "content_descriptor");
2039+
}
2040+
if(len > 2) AUDIO_INFO("Lyrics: content_descriptor: %s", content_descriptor.c_get());
2041+
idx += len;
2042+
while (idx < m_ID3Hdr.SYLT.size) {
2043+
// UTF-16LE, UTF-16BE
2044+
if (m_ID3Hdr.SYLT.text_encoding == 1 || m_ID3Hdr.SYLT.text_encoding == 2) {
2045+
idx += tmp.copy_from_utf16((const uint8_t*)(syltBuff.get() + idx), isBigEndian, "sylt-text");
2046+
} else {
2047+
// ISO-8859-1 / UTF-8
2048+
idx += tmp.copy_from((const char*)syltBuff.get() + idx, "sylt-text");
2049+
}
2050+
if (tmp.starts_with("\n")) tmp.remove_before(1);
2051+
m_syltLines.push_back(std::move(tmp));
2052+
if (idx + 4 > m_ID3Hdr.SYLT.size) break; // no more 4 bytes?
2053+
uint32_t timestamp = bigEndian((uint8_t*)syltBuff.get() + idx, 4);
2054+
m_syltTimeStamp.push_back(timestamp);
2055+
idx += 4;
2056+
}
2057+
for(int i = 0; i < m_syltLines.size(); i++){
2058+
AUDIO_INFO("%07i ms, %s", m_syltTimeStamp[i], m_syltLines[i].c_get());
2059+
}
2060+
}
2061+
return 0;
2062+
}
2063+
2064+
2065+
2066+
2067+
20892068
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
20902069

20912070
// --- section V2.2 only , greater Vers above ----
@@ -4392,7 +4371,7 @@ bool Audio::parseHttpRangeHeader() { // this is the response to a Range request
43924371
vTaskDelay(5);
43934372
continue;
43944373
}
4395-
4374+
// AUDIO_LOG_WARN("rh %s", rhl.c_get());
43964375
if(rhl.starts_with_icase("HTTP/")) { // HTTP status error code
43974376
char statusCode[5];
43984377
statusCode[0] = rhl[9];

src/Audio.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ class Audio{
438438
void setDefaults(); // free buffers and set defaults
439439
void initInBuff();
440440
bool httpPrint(const char* host);
441-
bool httpRange(const char* host, uint32_t range);
441+
bool httpRange(uint32_t range, uint32_t length = UINT32_MAX);
442442
void processLocalFile();
443443
void processWebStream();
444444
void processWebFile();

0 commit comments

Comments
 (0)