javazoom.spi.mpeg.sampled.file.tag
Class IcyInputStream

java.lang.Object
  extended by java.io.InputStream
      extended by java.io.FilterInputStream
          extended by java.io.BufferedInputStream
              extended by javazoom.spi.mpeg.sampled.file.tag.IcyInputStream
All Implemented Interfaces:
java.io.Closeable, 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
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 bytes
protected  int metaint
          value of the "metaint" tag, which tells us how many bytes of real data are between the metadata tags.
 
Fields inherited from class java.io.BufferedInputStream
buf, count, marklimit, markpos, pos
 
Fields inherited from class java.io.FilterInputStream
in
 
Constructor Summary
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
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)
          trivial return 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
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

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 the icy-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 class java.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 if bytesUntilNextMetadata < length

Overrides:
read in class java.io.BufferedInputStream
Throws:
java.io.IOException

read

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

Overrides:
read in class java.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:
  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


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 interface MP3MetadataParser

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 interface MP3MetadataParser

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

main

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



JavaZOOM 1999-2005