javazoom.spi.mpeg.sampled.file.tag

Class IcyInputStream

Implemented Interfaces:
MP3MetadataParser

public class IcyInputStream
extends BufferedInputStream
implements MP3MetadataParser

An BufferedInputStream that parses Shoutcast's "icy" metadata from the stream. Gets headers at the beginning and if the "icy-metaint" tag is found, it parses and strips in-stream metadata.

The deal with metaint: Icy streams don't try to put tags between MP3 frames the way that ID3 does. Instead, it requires the client to strip metadata from the stream before it hits the decoder. You get an icy-metaint name/val in the beginning of the stream iff you sent "Icy-Metadata" with value "1" in the request headers (SimpleMP3DataSource does this if the "parseStreamMetadata" boolean is true). If this is the case then the value of icy-metaint is the amount of real data between metadata blocks. Each block begins with an int indicating how much metadata there is -- the block is this value times 16 (it can be, and often is, 0).

Originally thought that "icy" implied Icecast, but this is completely wrong -- real Icecast servers, found through www.icecast.net and typified by URLs with a trailing directory (like CalArts School of Music - http://65.165.174.100:8000/som) do not have the "ICY 200 OK" magic string or any of the CRLF-separated headers. Apparently, "icy" means "Shoutcast". Yep, that's weird.

Field Summary

static boolean
DEBUG
protected static String
INLINE_TAG_SEPARATORS
inline tags are delimited by ';', also filter out null bytes
protected int
bytesUntilNextMetadata
how many bytes of real data remain before the next block of metadata.
protected byte[]
crlfBuffer
Buffer for readCRLF line...
protected int
metaint
value of the "metaint" tag, which tells us how many bytes of real data are between the metadata tags.

Constructor Summary

IcyInputStream(InputStream in)
Reads the initial headers of the stream and adds tags appropriatly.
IcyInputStream(InputStream in, String metaIntString)
IcyInputStream constructor for know meta-interval (Icecast 2)

Method Summary

protected void
addTag(IcyTag tag)
adds the tag to the HashMap of tags we have encountered either in-stream or as headers, replacing any previous tag with this name.
void
addTagParseListener(TagParseListener tpl)
Adds a TagParseListener to be notified when this stream parses MP3Tags.
MP3Tag
getTag(String tagName)
Get the named tag from the HashMap of headers and in-line tags.
HashMap
getTagHash()
Returns a HashMap of all headers and in-stream tags parsed so far.
MP3Tag[]
getTags()
Get all tags (headers or in-stream) encountered thus far.
static void
main(args[] )
Quickie unit-test.
protected void
parseInlineIcyTags(byte[] tagBlock)
Parse metadata from an in-stream "block" of bytes, add a tag for each one.
int
read()
Reads and returns a single byte.
int
read(byte[] buf)
trivial return read (buf, 0, buf.length)
int
read(byte[] buf, int offset, int length)
Reads a block of bytes.
protected String
readCRLFLine()
Read everything up to the next CRLF, return it as a String.
protected void
readInitialHeaders()
Assuming we're at the top of the stream, read lines one by one until we hit a completely blank \r\n.
protected void
readMetadata()
Read the next segment of metadata.
void
removeTagParseListener(TagParseListener tpl)
Removes a TagParseListener, so it won't be notified when this stream parses MP3Tags.

Field Details

DEBUG

public static boolean DEBUG

INLINE_TAG_SEPARATORS

protected static final String INLINE_TAG_SEPARATORS
inline tags are delimited by ';', also filter out null bytes

bytesUntilNextMetadata

protected int bytesUntilNextMetadata
how many bytes of real data remain before the next block of metadata. Only meaningful if metaint != -1.

crlfBuffer

protected byte[] crlfBuffer
Buffer for readCRLF line... note this limits lines to 1024 chars (I've read that WinAmp barfs at 128, so this is generous)

metaint

protected int metaint
value of the "metaint" tag, which tells us how many bytes of real data are between the metadata tags. if -1, this stream does not have metadata after the header.

Constructor Details

IcyInputStream

public IcyInputStream(InputStream in)
            throws IOException
Reads the initial headers of the stream and adds tags appropriatly. Gets set up to find, read, and strip blocks of in-line metadata if the icy-metaint header is found.

IcyInputStream

public IcyInputStream(InputStream in,
                      String metaIntString)
            throws IOException
IcyInputStream constructor for know meta-interval (Icecast 2)
Parameters:
in -

Method Details

addTag

protected void addTag(IcyTag tag)
adds the tag to the HashMap of tags we have encountered either in-stream or as headers, replacing any previous tag with this name.

addTagParseListener

public void addTagParseListener(TagParseListener tpl)
Adds a TagParseListener to be notified when this stream parses MP3Tags.
Specified by:
addTagParseListener in interface MP3MetadataParser

getTag

public MP3Tag getTag(String tagName)
Get the named tag from the HashMap of headers and in-line tags. Null if no such tag has been encountered.

getTagHash

public HashMap getTagHash()
Returns a HashMap of all headers and in-stream tags parsed so far.

getTags

public MP3Tag[] getTags()
Get all tags (headers or in-stream) encountered thus far.
Specified by:
getTags in interface MP3MetadataParser

main

public static void main(args[] )
Quickie unit-test.

parseInlineIcyTags

protected void parseInlineIcyTags(byte[] tagBlock)
Parse metadata from an in-stream "block" of bytes, add a tag for each one.

Hilariously, the inline data format is totally different than the top-of-stream header. For example, here's a block I saw on "Final Fantasy Radio":

StreamTitle='Final Fantasy 8 - Nobuo Uematsu - Blue Fields';StreamUrl='';
In other words:
  1. Tags are delimited by semicolons
  2. Keys/values are delimited by equals-signs
  3. Values are wrapped in single-quotes
  4. Key names are in SentenceCase, not lowercase-dashed

read

public int read()
            throws IOException
Reads and returns a single byte. If the next byte is a metadata block, then that block is read, stripped, and parsed before reading and returning the first byte after the metadata block.

read

public int read(byte[] buf)
            throws IOException
trivial return read (buf, 0, buf.length)

read

public int read(byte[] buf,
                int offset,
                int length)
            throws IOException
Reads a block of bytes. If the next byte is known to be a block of metadata, then that is read, parsed, and stripped, and then a block of bytes is read and returned. Otherwise, it may read up to but not into the next metadata block if bytesUntilNextMetadata < length

readCRLFLine

protected String readCRLFLine()
            throws IOException
Read everything up to the next CRLF, return it as a String.

readInitialHeaders

protected void readInitialHeaders()
            throws IOException
Assuming we're at the top of the stream, read lines one by one until we hit a completely blank \r\n. Parse the data as IcyTags.

readMetadata

protected void readMetadata()
            throws IOException
Read the next segment of metadata. The stream must be right on the segment, ie, the next byte to read is the metadata block count. The metadata is parsed and new tags are added with addTag(), which fires events

removeTagParseListener

public void removeTagParseListener(TagParseListener tpl)
Removes a TagParseListener, so it won't be notified when this stream parses MP3Tags.
Specified by:
removeTagParseListener in interface MP3MetadataParser

JavaZOOM 1999-2005