Permalink
Browse files

Add metadta callback for Icy streams

Allow a UI to get a notification when a StreamTitle or other metadata
comes into the stream. Callback function would normally just update a
flag and let the next global event loop handle updating the display.
  • Loading branch information...
earlephilhower committed Dec 4, 2017
1 parent 8c6bb54 commit 3226d6ec0a5d8faf31eff06ee8612508ac6a7005
Showing with 95 additions and 22 deletions.
  1. +1 −0 keywords.txt
  2. +86 −10 src/AudioFileSourceICYStream.cpp
  3. +4 −8 src/AudioFileSourceICYStream.h
  4. +4 −4 src/AudioFileSourceID3.cpp
View
@@ -3,6 +3,7 @@ AudioFileSourceSPIFFS KEYWORD1
AudioFileSourceFastROMFS KEYWORD1
AudioFileSourcePROGMEM KEYWORD1
AudioFileSourceHTTPStream KEYWORD1
AudioFileSourceICYStream KEYWORD1
AudioFileSourceID3 KEYWORD1
AudioFileSourceSD KEYWORD1
AudioFileSourceBuffer KEYWORD1
@@ -25,11 +25,13 @@ AudioFileSourceICYStream::AudioFileSourceICYStream()
pos = 0;
reconnectTries = 0;
saveURL = NULL;
cb = NULL;
}
AudioFileSourceICYStream::AudioFileSourceICYStream(const char *url)
{
saveURL = NULL;
cb = NULL;
reconnectTries = 0;
open(url);
}
@@ -50,13 +52,11 @@ bool AudioFileSourceICYStream::open(const char *url)
if (http.hasHeader(hdr[0])) {
String ret = http.header(hdr[0]);
icyMetaInt = ret.toInt();
Serial.printf("ICY metaint = %d\n", icyMetaInt);
} else {
icyMetaInt = 0;
}
icyByteCount = 0;
size = http.getSize();
Serial.printf("Stream size: %d\n", size);
free(saveURL);
saveURL = strdup(url);
return true;
@@ -67,6 +67,52 @@ AudioFileSourceICYStream::~AudioFileSourceICYStream()
http.end();
}
class ICYMDReader {
public:
ICYMDReader(WiFiClient *str, uint16_t bytes) {
stream = str;
avail = bytes;
ptr = sizeof(buff)+1; // Cause read next time
}
~ICYMDReader() {
// Get rid of any remaining bytes in the MD block
char xxx[16];
while (avail > 16) {
stream->readBytes(xxx, 16);
avail -= 16;
}
stream->readBytes(xxx, avail);
}
int read(uint8_t *dest, int len) {
int ret = 0;
while ((len>0) && (avail>0)) {
// Always copy from bounce buffer first
while ((ptr < sizeof(buff)) && (len>0) && (avail>0)) {
*(dest++) = buff[ptr++];
avail--;
ret++;
len--;
}
// refill the bounce buffer
if ((avail>0) && (len>0)) {
ptr = 0;
int toRead = (sizeof(buff)>avail)? avail : sizeof(buff);
int read = stream->readBytes(buff, toRead);
if (read != toRead) return 0; // Error, short read!
}
}
return ret;
}
bool eof() { return (avail==0); }
private:
WiFiClient *stream;
uint16_t avail;
uint8_t ptr;
uint8_t buff[16];
};
uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool nonBlock)
{
retry:
@@ -126,16 +172,46 @@ uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool n
uint8_t mdSize;
int mdret = stream->readBytes(&mdSize, 1);
if ((mdret == 1) && (mdSize > 0)) {
char mdBuff[17];
mdBuff[16] = 0;
Serial.print("ICY MD: ");
for (int i=0; i<mdSize; i++) {
mdret = stream->readBytes(reinterpret_cast<uint8_t*>(mdBuff), 16);
if (mdret == 16) {
Serial.printf("%s", mdBuff);
ICYMDReader md(stream, mdSize * 16);
// Break out (potentially multiple) NAME='xxxx'
char type[32];
char value[64];
while (!md.eof()) {
memset(type, 0, sizeof(type));
memset(value, 0, sizeof(value));
uint8_t c;
char *p = type;
for (size_t i=0; i<sizeof(type)-1; i++) {
int ret = md.read(&c, 1);
if (!ret) break;
if (c == '=') break;
*(p++) = c;
}
if (c != '=') {
// MD type was too long, read remainder and throw away
while ((c != '=') && !md.eof()) md.read(&c, 1);
}
md.read(&c, 1);
if (c=='\'') {
// Got start of string value, read that, too
p = value;
for (size_t i=0; i<sizeof(value)-1; i++) {
int ret = md.read(&c, 1);
if (!ret) break;
if (c == '\'') break;
if ((c=='\n') || (c=='\r') || (c==' ') || (c=='\t') || (c==0)) continue; // Throw away any whitespace
*(p++) = c;
}
if (c != '\'') {
// MD data was too long, read and throw away rest
while ((c != '\'') && !md.eof()) md.read(&c, 1);
}
if (cb) cb(type, value);
do {
ret = md.read(&c, 1);
} while ((c !=';') && (c!=0) && !md.eof());
}
}
Serial.println("\n---end---");
}
icyByteCount = 0;
}
@@ -34,19 +34,15 @@ class AudioFileSourceICYStream : public AudioFileSourceHTTPStream
virtual ~AudioFileSourceICYStream() override;
virtual bool open(const char *url) override;
// virtual uint32_t read(void *data, uint32_t len) override;
// virtual uint32_t readNonBlock(void *data, uint32_t len) override;
// virtual bool seek(int32_t pos, int dir) override;
// virtual bool close() override;
// virtual bool isOpen() override;
// virtual uint32_t getSize() override;
// virtual uint32_t getPos() override;
// bool SetReconnect(bool val) { reconnect = val; return true; }
typedef void (*callbackFn)(const char *type, const char *value);
void setCallback(callbackFn f) { cb = f; }
private:
virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock) override;
int icyMetaInt;
int icyByteCount;
callbackFn cb;
};
@@ -180,13 +180,13 @@ uint32_t AudioFileSourceID3::read(void *data, uint32_t len)
id3.getByte();
}
if (frameid[0]=='T' && frameid[1]=='A' && frameid[2]=='L' && frameid[3] == 'B') {
cb("Album", id3.getByte()==1, framesize-1, &id3);
if (cb) cb("Album", id3.getByte()==1, framesize-1, &id3);
} else if (frameid[0]=='T' && frameid[1]=='I' && frameid[2]=='T' && frameid[3] == '2') {
cb("Title", id3.getByte()==1, framesize-1, &id3);
if (cb) cb("Title", id3.getByte()==1, framesize-1, &id3);
} else if (frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='E' && frameid[3] == '1') {
cb("Performer", id3.getByte()==1, framesize-1, &id3);
if (cb) cb("Performer", id3.getByte()==1, framesize-1, &id3);
} else if (frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && frameid[3] == 'R') {
cb("Year", id3.getByte()==1, framesize-1, &id3);
if (cb) cb("Year", id3.getByte()==1, framesize-1, &id3);
} else {
for (int j=0; j<framesize; j++)
id3.getByte();

0 comments on commit 3226d6e

Please sign in to comment.