Class IcyInputStream
- java.lang.Object
-
- java.io.InputStream
-
- java.io.FilterInputStream
-
- java.io.BufferedInputStream
-
- javazoom.spi.mpeg.sampled.file.tag.IcyInputStream
-
- All Implemented Interfaces:
java.io.Closeable
,java.lang.AutoCloseable
,MP3MetadataParser
public class IcyInputStream extends java.io.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
Fields Modifier and Type Field Description protected int
bytesUntilNextMetadata
how many bytes of real data remain before the next block of metadata.protected byte[]
crlfBuffer
Buffer for readCRLF line...static boolean
DEBUG
protected static java.lang.String
INLINE_TAG_SEPARATORS
inline tags are delimited by ';', also filter out null bytesprotected int
metaint
value of the "metaint" tag, which tells us how many bytes of real data are between the metadata tags.
-
Constructor Summary
Constructors Constructor Description IcyInputStream(java.io.InputStream in)
Reads the initial headers of the stream and adds tags appropriatly.IcyInputStream(java.io.InputStream in, java.lang.String metaIntString)
IcyInputStream constructor for know meta-interval (Icecast 2)
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description 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(java.lang.String tagName)
Get the named tag from the HashMap of headers and in-line tags.java.util.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(java.lang.String[] 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)
trivialreturn read (buf, 0, buf.length)
int
read(byte[] buf, int offset, int length)
Reads a block of bytes.protected java.lang.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.-
Methods inherited from class java.io.BufferedInputStream
available, close, mark, markSupported, reset, skip
-
-
-
-
Field Detail
-
DEBUG
public static boolean DEBUG
-
INLINE_TAG_SEPARATORS
protected static final java.lang.String INLINE_TAG_SEPARATORS
inline tags are delimited by ';', also filter out null bytes- See Also:
- Constant Field Values
-
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.
-
bytesUntilNextMetadata
protected int bytesUntilNextMetadata
how many bytes of real data remain before the next block of metadata. Only meaningful if metaint != -1.
-
-
Constructor Detail
-
IcyInputStream
public IcyInputStream(java.io.InputStream in) throws java.io.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 theicy-metaint
header is found.- Throws:
java.io.IOException
-
IcyInputStream
public IcyInputStream(java.io.InputStream in, java.lang.String metaIntString) throws java.io.IOException
IcyInputStream constructor for know meta-interval (Icecast 2)- Parameters:
in
-metaint
-- Throws:
java.io.IOException
-
-
Method Detail
-
readInitialHeaders
protected void readInitialHeaders() throws java.io.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.- Throws:
java.io.IOException
-
readCRLFLine
protected java.lang.String readCRLFLine() throws java.io.IOException
Read everything up to the next CRLF, return it as a String.- Throws:
java.io.IOException
-
read
public int read() throws java.io.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.- Overrides:
read
in classjava.io.BufferedInputStream
- Throws:
java.io.IOException
-
read
public int read(byte[] buf, int offset, int length) throws java.io.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 ifbytesUntilNextMetadata < length
- Overrides:
read
in classjava.io.BufferedInputStream
- Throws:
java.io.IOException
-
read
public int read(byte[] buf) throws java.io.IOException
trivialreturn read (buf, 0, buf.length)
- Overrides:
read
in classjava.io.FilterInputStream
- Throws:
java.io.IOException
-
readMetadata
protected void readMetadata() throws java.io.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- Throws:
java.io.IOException
-
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:- Tags are delimited by semicolons
- Keys/values are delimited by equals-signs
- Values are wrapped in single-quotes
- Key names are in SentenceCase, not lowercase-dashed
-
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.
-
getTag
public MP3Tag getTag(java.lang.String tagName)
Get the named tag from the HashMap of headers and in-line tags. Null if no such tag has been encountered.
-
getTags
public MP3Tag[] getTags()
Get all tags (headers or in-stream) encountered thus far.- Specified by:
getTags
in interfaceMP3MetadataParser
-
getTagHash
public java.util.HashMap getTagHash()
Returns a HashMap of all headers and in-stream tags parsed so far.
-
addTagParseListener
public void addTagParseListener(TagParseListener tpl)
Adds a TagParseListener to be notified when this stream parses MP3Tags.- Specified by:
addTagParseListener
in interfaceMP3MetadataParser
-
removeTagParseListener
public void removeTagParseListener(TagParseListener tpl)
Removes a TagParseListener, so it won't be notified when this stream parses MP3Tags.- Specified by:
removeTagParseListener
in interfaceMP3MetadataParser
-
main
public static void main(java.lang.String[] args)
Quickie unit-test.
-
-