diff options
Diffstat (limited to 'songdbj/javazoom/spi/mpeg/sampled/file')
14 files changed, 1895 insertions, 0 deletions
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/IcyListener.java b/songdbj/javazoom/spi/mpeg/sampled/file/IcyListener.java new file mode 100644 index 0000000000..8e49e6e2ef --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/IcyListener.java | |||
@@ -0,0 +1,131 @@ | |||
1 | /* | ||
2 | * IcyListener. | ||
3 | * | ||
4 | * JavaZOOM : mp3spi@javazoom.net | ||
5 | * http://www.javazoom.net | ||
6 | * | ||
7 | *----------------------------------------------------------------------- | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU Library General Public License as published | ||
10 | * by the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU Library General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU Library General Public | ||
19 | * License along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | *---------------------------------------------------------------------- | ||
22 | */ | ||
23 | |||
24 | package javazoom.spi.mpeg.sampled.file; | ||
25 | |||
26 | import javazoom.spi.mpeg.sampled.file.tag.MP3Tag; | ||
27 | import javazoom.spi.mpeg.sampled.file.tag.TagParseEvent; | ||
28 | import javazoom.spi.mpeg.sampled.file.tag.TagParseListener; | ||
29 | |||
30 | /** | ||
31 | * This class (singleton) allow to be notified on shoutcast meta data | ||
32 | * while playing the stream (such as song title). | ||
33 | */ | ||
34 | public class IcyListener implements TagParseListener | ||
35 | { | ||
36 | private static IcyListener instance = null; | ||
37 | private MP3Tag lastTag = null; | ||
38 | private String streamTitle = null; | ||
39 | private String streamUrl = null; | ||
40 | |||
41 | |||
42 | private IcyListener() | ||
43 | { | ||
44 | super(); | ||
45 | } | ||
46 | |||
47 | public static synchronized IcyListener getInstance() | ||
48 | { | ||
49 | if (instance == null) | ||
50 | { | ||
51 | instance = new IcyListener(); | ||
52 | } | ||
53 | return instance; | ||
54 | } | ||
55 | |||
56 | /* (non-Javadoc) | ||
57 | * @see javazoom.spi.mpeg.sampled.file.tag.TagParseListener#tagParsed(javazoom.spi.mpeg.sampled.file.tag.TagParseEvent) | ||
58 | */ | ||
59 | public void tagParsed(TagParseEvent tpe) | ||
60 | { | ||
61 | lastTag = tpe.getTag(); | ||
62 | String name = lastTag.getName(); | ||
63 | if ((name != null) && (name.equalsIgnoreCase("streamtitle"))) | ||
64 | { | ||
65 | streamTitle = (String) lastTag.getValue(); | ||
66 | } | ||
67 | else if ((name != null) && (name.equalsIgnoreCase("streamurl"))) | ||
68 | { | ||
69 | streamUrl = (String) lastTag.getValue(); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | /** | ||
74 | * @return | ||
75 | */ | ||
76 | public MP3Tag getLastTag() | ||
77 | { | ||
78 | return lastTag; | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * @param tag | ||
83 | */ | ||
84 | public void setLastTag(MP3Tag tag) | ||
85 | { | ||
86 | lastTag = tag; | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * @return | ||
91 | */ | ||
92 | public String getStreamTitle() | ||
93 | { | ||
94 | return streamTitle; | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * @return | ||
99 | */ | ||
100 | public String getStreamUrl() | ||
101 | { | ||
102 | return streamUrl; | ||
103 | } | ||
104 | |||
105 | /** | ||
106 | * @param string | ||
107 | */ | ||
108 | public void setStreamTitle(String string) | ||
109 | { | ||
110 | streamTitle = string; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * @param string | ||
115 | */ | ||
116 | public void setStreamUrl(String string) | ||
117 | { | ||
118 | streamUrl = string; | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * Reset all properties. | ||
123 | */ | ||
124 | public void reset() | ||
125 | { | ||
126 | lastTag = null; | ||
127 | streamTitle = null; | ||
128 | streamUrl = null; | ||
129 | } | ||
130 | |||
131 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.java new file mode 100644 index 0000000000..afdc4c5e9c --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.java | |||
@@ -0,0 +1,103 @@ | |||
1 | /* | ||
2 | * MpegAudioFileFormat. | ||
3 | * | ||
4 | * JavaZOOM : mp3spi@javazoom.net | ||
5 | * http://www.javazoom.net | ||
6 | * | ||
7 | *----------------------------------------------------------------------- | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU Library General Public License as published | ||
10 | * by the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU Library General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU Library General Public | ||
19 | * License along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | *---------------------------------------------------------------------- | ||
22 | */ | ||
23 | package javazoom.spi.mpeg.sampled.file; | ||
24 | |||
25 | import java.util.Map; | ||
26 | |||
27 | import javax.sound.sampled.AudioFormat; | ||
28 | |||
29 | import org.tritonus.share.sampled.file.TAudioFileFormat; | ||
30 | |||
31 | /** | ||
32 | * @author JavaZOOM | ||
33 | */ | ||
34 | public class MpegAudioFileFormat extends TAudioFileFormat | ||
35 | { | ||
36 | /** | ||
37 | * Contructor. | ||
38 | * @param type | ||
39 | * @param audioFormat | ||
40 | * @param nLengthInFrames | ||
41 | * @param nLengthInBytes | ||
42 | */ | ||
43 | public MpegAudioFileFormat(Type type, AudioFormat audioFormat, int nLengthInFrames, int nLengthInBytes, Map properties) | ||
44 | { | ||
45 | super(type, audioFormat, nLengthInFrames, nLengthInBytes, properties); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * MP3 audio file format parameters. | ||
50 | * Some parameters might be unavailable. So availability test is required before reading any parameter. | ||
51 | * | ||
52 | * <br>AudioFileFormat parameters. | ||
53 | * <ul> | ||
54 | * <li><b>duration</b> [Long], duration in microseconds. | ||
55 | * <li><b>title</b> [String], Title of the stream. | ||
56 | * <li><b>author</b> [String], Name of the artist of the stream. | ||
57 | * <li><b>album</b> [String], Name of the album of the stream. | ||
58 | * <li><b>date</b> [String], The date (year) of the recording or release of the stream. | ||
59 | * <li><b>copyright</b> [String], Copyright message of the stream. | ||
60 | * <li><b>comment</b> [String], Comment of the stream. | ||
61 | * </ul> | ||
62 | * <br>MP3 parameters. | ||
63 | * <ul> | ||
64 | * <li><b>mp3.version.mpeg</b> [String], mpeg version : 1,2 or 2.5 | ||
65 | * <li><b>mp3.version.layer</b> [String], layer version 1, 2 or 3 | ||
66 | * <li><b>mp3.version.encoding</b> [String], mpeg encoding : MPEG1, MPEG2-LSF, MPEG2.5-LSF | ||
67 | * <li><b>mp3.channels</b> [Integer], number of channels 1 : mono, 2 : stereo. | ||
68 | * <li><b>mp3.frequency.hz</b> [Integer], sampling rate in hz. | ||
69 | * <li><b>mp3.bitrate.nominal.bps</b> [Integer], nominal bitrate in bps. | ||
70 | * <li><b>mp3.length.bytes</b> [Integer], length in bytes. | ||
71 | * <li><b>mp3.length.frames</b> [Integer], length in frames. | ||
72 | * <li><b>mp3.framesize.bytes</b> [Integer], framesize of the first frame. framesize is not constant for VBR streams. | ||
73 | * <li><b>mp3.framerate.fps</b> [Float], framerate in frames per seconds. | ||
74 | * <li><b>mp3.header.pos</b> [Integer], position of first audio header (or ID3v2 size). | ||
75 | * <li><b>mp3.vbr</b> [Boolean], vbr flag. | ||
76 | * <li><b>mp3.vbr.scale</b> [Integer], vbr scale. | ||
77 | * <li><b>mp3.crc</b> [Boolean], crc flag. | ||
78 | * <li><b>mp3.original</b> [Boolean], original flag. | ||
79 | * <li><b>mp3.copyright</b> [Boolean], copyright flag. | ||
80 | * <li><b>mp3.padding</b> [Boolean], padding flag. | ||
81 | * <li><b>mp3.mode</b> [Integer], mode 0:STEREO 1:JOINT_STEREO 2:DUAL_CHANNEL 3:SINGLE_CHANNEL | ||
82 | * <li><b>mp3.id3tag.genre</b> [String], ID3 tag (v1 or v2) genre. | ||
83 | * <li><b>mp3.id3tag.track</b> [String], ID3 tag (v1 or v2) track info. | ||
84 | * <li><b>mp3.id3tag.encoded</b> [String], ID3 tag v2 encoded by info. | ||
85 | * <li><b>mp3.id3tag.composer</b> [String], ID3 tag v2 composer info. | ||
86 | * <li><b>mp3.id3tag.grouping</b> [String], ID3 tag v2 grouping info. | ||
87 | * <li><b>mp3.id3tag.disc</b> [String], ID3 tag v2 track info. | ||
88 | * <li><b>mp3.id3tag.v2</b> [InputStream], ID3v2 frames. | ||
89 | * <li><b>mp3.id3tag.v2.version</b> [String], ID3v2 major version (2=v2.2.0, 3=v2.3.0, 4=v2.4.0). | ||
90 | * <li><b>mp3.shoutcast.metadata.key</b> [String], Shoutcast meta key with matching value. | ||
91 | * <br>For instance : | ||
92 | * <br>mp3.shoutcast.metadata.icy-irc=#shoutcast | ||
93 | * <br>mp3.shoutcast.metadata.icy-metaint=8192 | ||
94 | * <br>mp3.shoutcast.metadata.icy-genre=Trance Techno Dance | ||
95 | * <br>mp3.shoutcast.metadata.icy-url=http://www.di.fm | ||
96 | * <br>and so on ... | ||
97 | * </ul> | ||
98 | */ | ||
99 | public Map properties() | ||
100 | { | ||
101 | return super.properties(); | ||
102 | } | ||
103 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java new file mode 100644 index 0000000000..54440551a1 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java | |||
@@ -0,0 +1,772 @@ | |||
1 | /* | ||
2 | * MpegAudioFileReader. | ||
3 | * | ||
4 | * 12/31/04 : mp3spi.weak system property added to skip controls. | ||
5 | * | ||
6 | * 11/29/04 : ID3v2.2, v2.3 & v2.4 support improved. | ||
7 | * "mp3.id3tag.composer" (TCOM/TCM) added | ||
8 | * "mp3.id3tag.grouping" (TIT1/TT1) added | ||
9 | * "mp3.id3tag.disc" (TPA/TPOS) added | ||
10 | * "mp3.id3tag.encoded" (TEN/TENC) added | ||
11 | * "mp3.id3tag.v2.version" added | ||
12 | * | ||
13 | * 11/28/04 : String encoding bug fix in chopSubstring method. | ||
14 | * | ||
15 | * JavaZOOM : mp3spi@javazoom.net | ||
16 | * http://www.javazoom.net | ||
17 | * | ||
18 | *----------------------------------------------------------------------- | ||
19 | * This program is free software; you can redistribute it and/or modify | ||
20 | * it under the terms of the GNU Library General Public License as published | ||
21 | * by the Free Software Foundation; either version 2 of the License, or | ||
22 | * (at your option) any later version. | ||
23 | * | ||
24 | * This program is distributed in the hope that it will be useful, | ||
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
27 | * GNU Library General Public License for more details. | ||
28 | * | ||
29 | * You should have received a copy of the GNU Library General Public | ||
30 | * License along with this program; if not, write to the Free Software | ||
31 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
32 | *---------------------------------------------------------------------- | ||
33 | */ | ||
34 | |||
35 | package javazoom.spi.mpeg.sampled.file; | ||
36 | |||
37 | |||
38 | import java.io.BufferedInputStream; | ||
39 | import java.io.File; | ||
40 | import java.io.FileInputStream; | ||
41 | import java.io.IOException; | ||
42 | import java.io.InputStream; | ||
43 | import java.io.PushbackInputStream; | ||
44 | import java.io.UnsupportedEncodingException; | ||
45 | import java.net.URL; | ||
46 | import java.net.URLConnection; | ||
47 | import java.security.AccessControlException; | ||
48 | import java.util.HashMap; | ||
49 | |||
50 | import javax.sound.sampled.AudioFileFormat; | ||
51 | import javax.sound.sampled.AudioFormat; | ||
52 | import javax.sound.sampled.AudioInputStream; | ||
53 | import javax.sound.sampled.AudioSystem; | ||
54 | import javax.sound.sampled.UnsupportedAudioFileException; | ||
55 | |||
56 | import javazoom.jl.decoder.Bitstream; | ||
57 | import javazoom.jl.decoder.Header; | ||
58 | import javazoom.spi.mpeg.sampled.file.tag.IcyInputStream; | ||
59 | import javazoom.spi.mpeg.sampled.file.tag.MP3Tag; | ||
60 | |||
61 | import org.tritonus.share.TDebug; | ||
62 | import org.tritonus.share.sampled.file.TAudioFileReader; | ||
63 | |||
64 | /** | ||
65 | * This class implements AudioFileReader for MP3 SPI. | ||
66 | */ | ||
67 | public class MpegAudioFileReader extends TAudioFileReader | ||
68 | { | ||
69 | private final int SYNC = 0xFFE00000; | ||
70 | private String weak = null; | ||
71 | private final AudioFormat.Encoding[][] sm_aEncodings = | ||
72 | { | ||
73 | {MpegEncoding.MPEG2L1, MpegEncoding.MPEG2L2, MpegEncoding.MPEG2L3}, | ||
74 | {MpegEncoding.MPEG1L1, MpegEncoding.MPEG1L2, MpegEncoding.MPEG1L3}, | ||
75 | {MpegEncoding.MPEG2DOT5L1, MpegEncoding.MPEG2DOT5L2, MpegEncoding.MPEG2DOT5L3}, | ||
76 | |||
77 | }; | ||
78 | |||
79 | private static final int INITAL_READ_LENGTH = 64000; | ||
80 | private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1; | ||
81 | |||
82 | private static final String[] id3v1genres = { | ||
83 | "Blues" | ||
84 | , "Classic Rock" | ||
85 | , "Country" | ||
86 | , "Dance" | ||
87 | , "Disco" | ||
88 | , "Funk" | ||
89 | , "Grunge" | ||
90 | , "Hip-Hop" | ||
91 | , "Jazz" | ||
92 | , "Metal" | ||
93 | , "New Age" | ||
94 | , "Oldies" | ||
95 | , "Other" | ||
96 | , "Pop" | ||
97 | , "R&B" | ||
98 | , "Rap" | ||
99 | , "Reggae" | ||
100 | , "Rock" | ||
101 | , "Techno" | ||
102 | , "Industrial" | ||
103 | , "Alternative" | ||
104 | , "Ska" | ||
105 | , "Death Metal" | ||
106 | , "Pranks" | ||
107 | , "Soundtrack" | ||
108 | , "Euro-Techno" | ||
109 | , "Ambient" | ||
110 | , "Trip-Hop" | ||
111 | , "Vocal" | ||
112 | , "Jazz+Funk" | ||
113 | , "Fusion" | ||
114 | , "Trance" | ||
115 | , "Classical" | ||
116 | , "Instrumental" | ||
117 | , "Acid" | ||
118 | , "House" | ||
119 | , "Game" | ||
120 | , "Sound Clip" | ||
121 | , "Gospel" | ||
122 | , "Noise" | ||
123 | , "AlternRock" | ||
124 | , "Bass" | ||
125 | , "Soul" | ||
126 | , "Punk" | ||
127 | , "Space" | ||
128 | , "Meditative" | ||
129 | , "Instrumental Pop" | ||
130 | , "Instrumental Rock" | ||
131 | , "Ethnic" | ||
132 | , "Gothic" | ||
133 | , "Darkwave" | ||
134 | , "Techno-Industrial" | ||
135 | , "Electronic" | ||
136 | , "Pop-Folk" | ||
137 | , "Eurodance" | ||
138 | , "Dream" | ||
139 | , "Southern Rock" | ||
140 | , "Comedy" | ||
141 | , "Cult" | ||
142 | , "Gangsta" | ||
143 | , "Top 40" | ||
144 | , "Christian Rap" | ||
145 | , "Pop/Funk" | ||
146 | , "Jungle" | ||
147 | , "Native American" | ||
148 | , "Cabaret" | ||
149 | , "New Wave" | ||
150 | , "Psychadelic" | ||
151 | , "Rave" | ||
152 | , "Showtunes" | ||
153 | , "Trailer" | ||
154 | , "Lo-Fi" | ||
155 | , "Tribal" | ||
156 | , "Acid Punk" | ||
157 | , "Acid Jazz" | ||
158 | , "Polka" | ||
159 | , "Retro" | ||
160 | , "Musical" | ||
161 | , "Rock & Roll" | ||
162 | , "Hard Rock" | ||
163 | , "Folk" | ||
164 | , "Folk-Rock" | ||
165 | , "National Folk" | ||
166 | , "Swing" | ||
167 | , "Fast Fusion" | ||
168 | , "Bebob" | ||
169 | , "Latin" | ||
170 | , "Revival" | ||
171 | , "Celtic" | ||
172 | , "Bluegrass" | ||
173 | , "Avantgarde" | ||
174 | , "Gothic Rock" | ||
175 | , "Progressive Rock" | ||
176 | , "Psychedelic Rock" | ||
177 | , "Symphonic Rock" | ||
178 | , "Slow Rock" | ||
179 | , "Big Band" | ||
180 | , "Chorus" | ||
181 | , "Easy Listening" | ||
182 | , "Acoustic" | ||
183 | , "Humour" | ||
184 | , "Speech" | ||
185 | , "Chanson" | ||
186 | , "Opera" | ||
187 | , "Chamber Music" | ||
188 | , "Sonata" | ||
189 | , "Symphony" | ||
190 | , "Booty Brass" | ||
191 | , "Primus" | ||
192 | , "Porn Groove" | ||
193 | , "Satire" | ||
194 | , "Slow Jam" | ||
195 | , "Club" | ||
196 | , "Tango" | ||
197 | , "Samba" | ||
198 | , "Folklore" | ||
199 | , "Ballad" | ||
200 | , "Power Ballad" | ||
201 | , "Rhythmic Soul" | ||
202 | , "Freestyle" | ||
203 | , "Duet" | ||
204 | , "Punk Rock" | ||
205 | , "Drum Solo" | ||
206 | , "A Capela" | ||
207 | , "Euro-House" | ||
208 | , "Dance Hall" | ||
209 | , "Goa" | ||
210 | , "Drum & Bass" | ||
211 | , "Club-House" | ||
212 | , "Hardcore" | ||
213 | , "Terror" | ||
214 | , "Indie" | ||
215 | , "BritPop" | ||
216 | , "Negerpunk" | ||
217 | , "Polsk Punk" | ||
218 | , "Beat" | ||
219 | , "Christian Gangsta Rap" | ||
220 | , "Heavy Metal" | ||
221 | , "Black Metal" | ||
222 | , "Crossover" | ||
223 | , "Contemporary Christian" | ||
224 | , "Christian Rock" | ||
225 | , "Merengue" | ||
226 | , "Salsa" | ||
227 | , "Thrash Metal" | ||
228 | , "Anime" | ||
229 | , "JPop" | ||
230 | , "SynthPop" | ||
231 | }; | ||
232 | |||
233 | public MpegAudioFileReader() | ||
234 | { | ||
235 | super(MARK_LIMIT, true); | ||
236 | if (TDebug.TraceAudioFileReader) TDebug.out(">MpegAudioFileReader(1.9.2-FINAL)"); | ||
237 | try | ||
238 | { | ||
239 | weak = System.getProperty("mp3spi.weak"); | ||
240 | } | ||
241 | catch(AccessControlException e) | ||
242 | {} | ||
243 | } | ||
244 | |||
245 | /** | ||
246 | * Returns AudioFileFormat from File. | ||
247 | */ | ||
248 | public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException | ||
249 | { | ||
250 | return super.getAudioFileFormat(file); | ||
251 | } | ||
252 | |||
253 | /** | ||
254 | * Returns AudioFileFormat from URL. | ||
255 | */ | ||
256 | public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException | ||
257 | { | ||
258 | if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioFileFormat(URL): begin"); } | ||
259 | long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; | ||
260 | URLConnection conn = url.openConnection(); | ||
261 | // Tell shoucast server (if any) that SPI support shoutcast stream. | ||
262 | conn.setRequestProperty ("Icy-Metadata", "1"); | ||
263 | InputStream inputStream = conn.getInputStream(); | ||
264 | AudioFileFormat audioFileFormat = null; | ||
265 | try | ||
266 | { | ||
267 | audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes); | ||
268 | } | ||
269 | finally | ||
270 | { | ||
271 | inputStream.close(); | ||
272 | } | ||
273 | if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioFileFormat(URL): end"); } | ||
274 | return audioFileFormat; | ||
275 | } | ||
276 | |||
277 | /** | ||
278 | * Returns AudioFileFormat from inputstream and medialength. | ||
279 | */ | ||
280 | public AudioFileFormat getAudioFileFormat(InputStream inputStream, long mediaLength) throws UnsupportedAudioFileException, IOException | ||
281 | { | ||
282 | if (TDebug.TraceAudioFileReader) TDebug.out(">MpegAudioFileReader.getAudioFileFormat(InputStream inputStream, long mediaLength): begin"); | ||
283 | HashMap aff_properties = new HashMap(); | ||
284 | HashMap af_properties = new HashMap(); | ||
285 | int mLength = (int) mediaLength; | ||
286 | int size = inputStream.available(); | ||
287 | PushbackInputStream pis = new PushbackInputStream(inputStream, MARK_LIMIT); | ||
288 | byte head[] = new byte[12]; | ||
289 | pis.read(head); | ||
290 | if (TDebug.TraceAudioFileReader) | ||
291 | { | ||
292 | TDebug.out("InputStream : "+inputStream + " =>" + new String(head)); | ||
293 | } | ||
294 | /* | ||
295 | * Check for WAV, AU, and AIFF file formats. | ||
296 | * | ||
297 | * Next check for Shoutcast (supported) and OGG (unsupported) streams. | ||
298 | * | ||
299 | * Note -- the check for WAV files will reject Broadcast WAV files. | ||
300 | * This may be incorrect as broadcast WAV files may contain MPEG data. | ||
301 | * Need to investigate. | ||
302 | * | ||
303 | */ | ||
304 | if ((head[0] == 'R') && (head[1] == 'I') && (head[2] == 'F') && (head[3] == 'F') && (head[8] == 'W') && (head[9] == 'A') && (head[10] == 'V') && (head[11] == 'E')) | ||
305 | { | ||
306 | if (TDebug.TraceAudioFileReader) TDebug.out("WAV stream found"); | ||
307 | if (weak == null) throw new UnsupportedAudioFileException("WAV stream found"); | ||
308 | } | ||
309 | else if ((head[0] == '.') && (head[1] == 's') && (head[2] == 'n') && (head[3] == 'd')) | ||
310 | { | ||
311 | if (TDebug.TraceAudioFileReader) TDebug.out("AU stream found"); | ||
312 | if (weak == null) throw new UnsupportedAudioFileException("AU stream found"); | ||
313 | } | ||
314 | else if ((head[0] == 'F') && (head[1] == 'O') && (head[2] == 'R') && (head[3] == 'M') && (head[8] == 'A') && (head[9] == 'I') && (head[10] == 'F') && (head[11] == 'F')) | ||
315 | { | ||
316 | if (TDebug.TraceAudioFileReader) TDebug.out("AIFF stream found"); | ||
317 | if (weak == null) throw new UnsupportedAudioFileException("AIFF stream found"); | ||
318 | } | ||
319 | // Shoutcast stream ? | ||
320 | else if (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y'))) | ||
321 | { | ||
322 | pis.unread(head); | ||
323 | // Load shoutcast meta data. | ||
324 | loadShoutcastInfo(pis, aff_properties); | ||
325 | } | ||
326 | // Ogg stream ? | ||
327 | else if (((head[0] == 'O') | (head[0] == 'o')) && ((head[1] == 'G') | (head[1] == 'g')) && ((head[2] == 'G') | (head[2] == 'g'))) | ||
328 | { | ||
329 | if (TDebug.TraceAudioFileReader) TDebug.out("Ogg stream found"); | ||
330 | if (weak == null) throw new UnsupportedAudioFileException("Ogg stream found"); | ||
331 | } | ||
332 | // No, so pushback. | ||
333 | else | ||
334 | { | ||
335 | pis.unread(head); | ||
336 | } | ||
337 | // MPEG header info. | ||
338 | int nVersion = AudioSystem.NOT_SPECIFIED; | ||
339 | int nLayer = AudioSystem.NOT_SPECIFIED; | ||
340 | int nSFIndex = AudioSystem.NOT_SPECIFIED; | ||
341 | int nMode = AudioSystem.NOT_SPECIFIED; | ||
342 | int FrameSize = AudioSystem.NOT_SPECIFIED; | ||
343 | int nFrameSize = AudioSystem.NOT_SPECIFIED; | ||
344 | int nFrequency = AudioSystem.NOT_SPECIFIED; | ||
345 | int nTotalFrames = AudioSystem.NOT_SPECIFIED; | ||
346 | float FrameRate = AudioSystem.NOT_SPECIFIED; | ||
347 | int BitRate = AudioSystem.NOT_SPECIFIED; | ||
348 | int nChannels = AudioSystem.NOT_SPECIFIED; | ||
349 | int nHeader = AudioSystem.NOT_SPECIFIED; | ||
350 | int nTotalMS = AudioSystem.NOT_SPECIFIED; | ||
351 | boolean nVBR = false; | ||
352 | AudioFormat.Encoding encoding = null; | ||
353 | try | ||
354 | { | ||
355 | Bitstream m_bitstream = new Bitstream(pis); | ||
356 | aff_properties.put("mp3.header.pos",new Integer(m_bitstream.header_pos())); | ||
357 | Header m_header = m_bitstream.readFrame(); | ||
358 | // nVersion = 0 => MPEG2-LSF (Including MPEG2.5), nVersion = 1 => MPEG1 | ||
359 | nVersion = m_header.version(); | ||
360 | if (nVersion == 2) aff_properties.put("mp3.version.mpeg",Float.toString(2.5f)); | ||
361 | else aff_properties.put("mp3.version.mpeg",Integer.toString(2-nVersion)); | ||
362 | // nLayer = 1,2,3 | ||
363 | nLayer = m_header.layer(); | ||
364 | aff_properties.put("mp3.version.layer",Integer.toString(nLayer)); | ||
365 | nSFIndex = m_header.sample_frequency(); | ||
366 | nMode = m_header.mode(); | ||
367 | aff_properties.put("mp3.mode",new Integer(nMode)); | ||
368 | nChannels = nMode == 3 ? 1 : 2; | ||
369 | aff_properties.put("mp3.channels",new Integer(nChannels)); | ||
370 | nVBR = m_header.vbr(); | ||
371 | af_properties.put("vbr",new Boolean(nVBR)); | ||
372 | aff_properties.put("mp3.vbr",new Boolean(nVBR)); | ||
373 | aff_properties.put("mp3.vbr.scale",new Integer(m_header.vbr_scale())); | ||
374 | FrameSize = m_header.calculate_framesize(); | ||
375 | aff_properties.put("mp3.framesize.bytes",new Integer(FrameSize)); | ||
376 | if (FrameSize < 0) throw new UnsupportedAudioFileException("Invalid FrameSize : " + FrameSize); | ||
377 | nFrequency = m_header.frequency(); | ||
378 | aff_properties.put("mp3.frequency.hz",new Integer(nFrequency)); | ||
379 | FrameRate = (float) ((1.0 / (m_header.ms_per_frame())) * 1000.0); | ||
380 | aff_properties.put("mp3.framerate.fps",new Float(FrameRate)); | ||
381 | if (FrameRate < 0) throw new UnsupportedAudioFileException("Invalid FrameRate : " + FrameRate); | ||
382 | if (mLength != AudioSystem.NOT_SPECIFIED) | ||
383 | { | ||
384 | aff_properties.put("mp3.length.bytes",new Integer(mLength)); | ||
385 | nTotalFrames = m_header.max_number_of_frames(mLength); | ||
386 | aff_properties.put("mp3.length.frames",new Integer(nTotalFrames)); | ||
387 | } | ||
388 | BitRate = m_header.bitrate(); | ||
389 | af_properties.put("bitrate",new Integer(BitRate)); | ||
390 | aff_properties.put("mp3.bitrate.nominal.bps",new Integer(BitRate)); | ||
391 | nHeader = m_header.getSyncHeader(); | ||
392 | encoding = sm_aEncodings[nVersion][nLayer - 1]; | ||
393 | aff_properties.put("mp3.version.encoding",encoding.toString()); | ||
394 | if (mLength != AudioSystem.NOT_SPECIFIED) | ||
395 | { | ||
396 | nTotalMS = Math.round(m_header.total_ms(mLength)); | ||
397 | aff_properties.put("duration",new Long((long)nTotalMS*1000L)); | ||
398 | } | ||
399 | aff_properties.put("mp3.copyright",new Boolean(m_header.copyright())); | ||
400 | aff_properties.put("mp3.original",new Boolean(m_header.original())); | ||
401 | aff_properties.put("mp3.crc",new Boolean(m_header.checksums())); | ||
402 | aff_properties.put("mp3.padding",new Boolean(m_header.padding())); | ||
403 | InputStream id3v2 = m_bitstream.getRawID3v2(); | ||
404 | if (id3v2 != null) | ||
405 | { | ||
406 | aff_properties.put("mp3.id3tag.v2",id3v2); | ||
407 | parseID3v2Frames(id3v2,aff_properties); | ||
408 | } | ||
409 | if (TDebug.TraceAudioFileReader) TDebug.out(m_header.toString()); | ||
410 | } | ||
411 | catch (Exception e) | ||
412 | { | ||
413 | if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream:" + e.getMessage()); | ||
414 | throw new UnsupportedAudioFileException("not a MPEG stream:" + e.getMessage()); | ||
415 | } | ||
416 | |||
417 | // Deeper checks ? | ||
418 | int cVersion = (nHeader >> 19) & 0x3; | ||
419 | if (cVersion == 1) | ||
420 | { | ||
421 | if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream: wrong version"); | ||
422 | throw new UnsupportedAudioFileException("not a MPEG stream: wrong version"); | ||
423 | } | ||
424 | |||
425 | int cSFIndex = (nHeader >> 10) & 0x3; | ||
426 | if (cSFIndex == 3) | ||
427 | { | ||
428 | if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream: wrong sampling rate"); | ||
429 | throw new UnsupportedAudioFileException("not a MPEG stream: wrong sampling rate"); | ||
430 | } | ||
431 | |||
432 | // Look up for ID3v1 tag | ||
433 | if ((size == mediaLength) && (mediaLength != AudioSystem.NOT_SPECIFIED)) | ||
434 | { | ||
435 | FileInputStream fis = (FileInputStream) inputStream; | ||
436 | byte[] id3v1 = new byte[128]; | ||
437 | long bytesSkipped = fis.skip(inputStream.available()-id3v1.length); | ||
438 | int read = fis.read(id3v1,0,id3v1.length); | ||
439 | if ((id3v1[0]=='T') && (id3v1[1]=='A') && (id3v1[2]=='G')) | ||
440 | { | ||
441 | parseID3v1Frames(id3v1, aff_properties); | ||
442 | } | ||
443 | } | ||
444 | |||
445 | AudioFormat format = new MpegAudioFormat(encoding, (float) nFrequency, AudioSystem.NOT_SPECIFIED // SampleSizeInBits - The size of a sample | ||
446 | , nChannels // Channels - The number of channels | ||
447 | , -1 // The number of bytes in each frame | ||
448 | , FrameRate // FrameRate - The number of frames played or recorded per second | ||
449 | , true | ||
450 | , af_properties); | ||
451 | return new MpegAudioFileFormat(MpegFileFormatType.MP3, format, nTotalFrames, mLength,aff_properties); | ||
452 | } | ||
453 | |||
454 | /** | ||
455 | * Returns AudioInputStream from file. | ||
456 | */ | ||
457 | public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException | ||
458 | { | ||
459 | if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(File file)"); | ||
460 | InputStream inputStream = new FileInputStream(file); | ||
461 | try | ||
462 | { | ||
463 | return getAudioInputStream(inputStream); | ||
464 | } | ||
465 | catch (UnsupportedAudioFileException e) | ||
466 | { | ||
467 | if (inputStream != null) inputStream.close(); | ||
468 | throw e; | ||
469 | } | ||
470 | catch (IOException e) | ||
471 | { | ||
472 | if (inputStream != null) inputStream.close(); | ||
473 | throw e; | ||
474 | } | ||
475 | } | ||
476 | |||
477 | /** | ||
478 | * Returns AudioInputStream from url. | ||
479 | */ | ||
480 | public AudioInputStream getAudioInputStream(URL url) | ||
481 | throws UnsupportedAudioFileException, IOException | ||
482 | { | ||
483 | if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioInputStream(URL): begin"); } | ||
484 | long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; | ||
485 | URLConnection conn = url.openConnection(); | ||
486 | // Tell shoucast server (if any) that SPI support shoutcast stream. | ||
487 | boolean isShout = false; | ||
488 | int toRead=4; | ||
489 | byte[] head = new byte[toRead]; | ||
490 | |||
491 | conn.setRequestProperty ("Icy-Metadata", "1"); | ||
492 | BufferedInputStream bInputStream = new BufferedInputStream(conn.getInputStream()); | ||
493 | bInputStream.mark(toRead); | ||
494 | int read = bInputStream.read(head,0,toRead); | ||
495 | if ((read>2) && (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y')))) isShout = true; | ||
496 | bInputStream.reset(); | ||
497 | InputStream inputStream = null; | ||
498 | // Is is a shoutcast server ? | ||
499 | if (isShout == true) | ||
500 | { | ||
501 | // Yes | ||
502 | IcyInputStream icyStream = new IcyInputStream(bInputStream); | ||
503 | icyStream.addTagParseListener(IcyListener.getInstance()); | ||
504 | inputStream = icyStream; | ||
505 | } | ||
506 | else | ||
507 | { | ||
508 | // No, is Icecast 2 ? | ||
509 | String metaint = conn.getHeaderField("icy-metaint"); | ||
510 | if (metaint != null) | ||
511 | { | ||
512 | // Yes, it might be icecast 2 mp3 stream. | ||
513 | IcyInputStream icyStream = new IcyInputStream(bInputStream,metaint); | ||
514 | icyStream.addTagParseListener(IcyListener.getInstance()); | ||
515 | inputStream = icyStream; | ||
516 | } | ||
517 | else | ||
518 | { | ||
519 | // No | ||
520 | inputStream = bInputStream; | ||
521 | } | ||
522 | } | ||
523 | AudioInputStream audioInputStream = null; | ||
524 | try | ||
525 | { | ||
526 | audioInputStream = getAudioInputStream(inputStream, lFileLengthInBytes); | ||
527 | } | ||
528 | catch (UnsupportedAudioFileException e) | ||
529 | { | ||
530 | inputStream.close(); | ||
531 | throw e; | ||
532 | } | ||
533 | catch (IOException e) | ||
534 | { | ||
535 | inputStream.close(); | ||
536 | throw e; | ||
537 | } | ||
538 | if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioInputStream(URL): end"); } | ||
539 | return audioInputStream; | ||
540 | } | ||
541 | |||
542 | /** | ||
543 | * Return the AudioInputStream from the given InputStream. | ||
544 | */ | ||
545 | public AudioInputStream getAudioInputStream(InputStream inputStream) throws UnsupportedAudioFileException, IOException | ||
546 | { | ||
547 | if (TDebug.TraceAudioFileReader) TDebug.out("MpegAudioFileReader.getAudioInputStream(InputStream inputStream)"); | ||
548 | if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream); | ||
549 | return super.getAudioInputStream(inputStream); | ||
550 | } | ||
551 | |||
552 | /** | ||
553 | * Parser ID3v1 frames | ||
554 | * @param frames | ||
555 | * @param props | ||
556 | */ | ||
557 | protected void parseID3v1Frames(byte[] frames, HashMap props) | ||
558 | { | ||
559 | if (TDebug.TraceAudioFileReader) TDebug.out("Parsing ID3v1"); | ||
560 | String tag = null; | ||
561 | try | ||
562 | { | ||
563 | tag = new String(frames, 0, frames.length, "ISO-8859-1"); | ||
564 | } | ||
565 | catch (UnsupportedEncodingException e) | ||
566 | { | ||
567 | tag = new String(frames, 0, frames.length); | ||
568 | if (TDebug.TraceAudioFileReader) TDebug.out("Cannot use ISO-8859-1"); | ||
569 | } | ||
570 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v1 frame dump='"+tag+"'"); | ||
571 | int start = 3; | ||
572 | String titlev1 = chopSubstring(tag, start, start += 30); | ||
573 | String titlev2 = (String) props.get("title"); | ||
574 | if (((titlev2==null) || (titlev2.length()==0)) && (titlev1 != null)) props.put("title",titlev1); | ||
575 | String artistv1 = chopSubstring(tag, start, start += 30); | ||
576 | String artistv2 = (String) props.get("author"); | ||
577 | if (((artistv2==null) || (artistv2.length()==0)) && (artistv1 != null)) props.put("author",artistv1); | ||
578 | String albumv1 = chopSubstring(tag, start, start += 30); | ||
579 | String albumv2 = (String) props.get("album"); | ||
580 | if (((albumv2==null) || (albumv2.length()==0)) && (albumv1 != null)) props.put("album",albumv1); | ||
581 | String yearv1 = chopSubstring(tag, start, start += 4); | ||
582 | String yearv2 = (String) props.get("year"); | ||
583 | if (((yearv2==null) || (yearv2.length()==0)) && (yearv1 != null)) props.put("date",yearv1); | ||
584 | String commentv1 = chopSubstring(tag, start, start += 28); | ||
585 | String commentv2 = (String) props.get("comment"); | ||
586 | if (((commentv2==null) || (commentv2.length()==0)) && (commentv1 != null)) props.put("comment",commentv1); | ||
587 | String trackv1 = ""+((int) (frames[126] & 0xff)); | ||
588 | String trackv2 = (String) props.get("mp3.id3tag.track"); | ||
589 | if (((trackv2==null) || (trackv2.length()==0)) && (trackv1 != null)) props.put("mp3.id3tag.track",trackv1); | ||
590 | |||
591 | int genrev1 = (int) (frames[127] & 0xff); | ||
592 | if ((genrev1 >=0) && (genrev1<id3v1genres.length)) | ||
593 | { | ||
594 | String genrev2 = (String) props.get("mp3.id3tag.genre"); | ||
595 | if (((genrev2==null) || (genrev2.length()==0))) props.put("mp3.id3tag.genre",id3v1genres[genrev1]); | ||
596 | } | ||
597 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v1 parsed"); | ||
598 | } | ||
599 | |||
600 | /** | ||
601 | * Extract | ||
602 | * @param s | ||
603 | * @param start | ||
604 | * @param end | ||
605 | * @return | ||
606 | */ | ||
607 | private String chopSubstring(String s, int start, int end) | ||
608 | { | ||
609 | String str = null; | ||
610 | // 11/28/04 - String encoding bug fix. | ||
611 | try | ||
612 | { | ||
613 | str = s.substring(start, end); | ||
614 | int loc = str.indexOf('\0'); | ||
615 | if (loc != -1) str = str.substring(0, loc); | ||
616 | } | ||
617 | catch (StringIndexOutOfBoundsException e) | ||
618 | { | ||
619 | // Skip encoding issues. | ||
620 | if (TDebug.TraceAudioFileReader) TDebug.out("Cannot chopSubString "+e.getMessage()); | ||
621 | } | ||
622 | return str; | ||
623 | } | ||
624 | /** | ||
625 | * Parse ID3v2 frames to add album (TALB), title (TIT2), date (TYER), author (TPE1), copyright (TCOP), comment (COMM) ... | ||
626 | * @param frames | ||
627 | * @param props | ||
628 | */ | ||
629 | protected void parseID3v2Frames(InputStream frames, HashMap props) | ||
630 | { | ||
631 | if (TDebug.TraceAudioFileReader) TDebug.out("Parsing ID3v2"); | ||
632 | byte[] bframes = null; | ||
633 | int size = -1; | ||
634 | try | ||
635 | { | ||
636 | size = frames.available(); | ||
637 | bframes = new byte[size]; | ||
638 | frames.mark(size); | ||
639 | frames.read(bframes); | ||
640 | frames.reset(); | ||
641 | } | ||
642 | catch (IOException e) | ||
643 | { | ||
644 | if (TDebug.TraceAudioFileReader) TDebug.out("Cannot parse ID3v2 :"+e.getMessage()); | ||
645 | } | ||
646 | |||
647 | try | ||
648 | { | ||
649 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 frame dump='"+new String(bframes,0,bframes.length)+"'"); | ||
650 | /* ID3 tags : http://www.unixgods.org/~tilo/ID3/docs/ID3_comparison.html */ | ||
651 | String value = null; | ||
652 | for (int i=0;i<bframes.length-4;i++) | ||
653 | { | ||
654 | String code = new String(bframes,i,4); | ||
655 | String scode = new String(bframes,i,3); | ||
656 | // ID3v2.3 & v2.4 | ||
657 | if ((code.equals("TALB")) || (code.equals("TIT2")) || (code.equals("TYER")) || (code.equals("TPE1")) || (code.equals("TCOP")) || (code.equals("COMM")) || (code.equals("TCON")) || (code.equals("TRCK")) || (code.equals("TPOS")) || (code.equals("TDRC")) || (code.equals("TCOM")) || (code.equals("TIT1")) || (code.equals("TENC"))) | ||
658 | { | ||
659 | i=i+10; | ||
660 | size = (int) (bframes[i-6] << 24) + (bframes[i-5] << 16) + (bframes[i-4] << 8) + (bframes[i-3]); | ||
661 | if (code.equals("COMM")) value = parseText(bframes, i, size, 5); | ||
662 | else value = parseText(bframes,i, size, 1); | ||
663 | if ((value != null) && (value.length()>0)) | ||
664 | { | ||
665 | if (code.equals("TALB")) props.put("album",value); | ||
666 | else if (code.equals("TIT2")) props.put("title",value); | ||
667 | else if (code.equals("TYER")) props.put("date",value); | ||
668 | // ID3v2.4 date fix. | ||
669 | else if (code.equals("TDRC")) props.put("date",value); | ||
670 | else if (code.equals("TPE1")) props.put("author",value); | ||
671 | else if (code.equals("TCOP")) props.put("copyright",value); | ||
672 | else if (code.equals("COMM")) props.put("comment",value); | ||
673 | else if (code.equals("TCON")) props.put("mp3.id3tag.genre",value); | ||
674 | else if (code.equals("TRCK")) props.put("mp3.id3tag.track",value); | ||
675 | else if (code.equals("TPOS")) props.put("mp3.id3tag.disc",value); | ||
676 | else if (code.equals("TCOM")) props.put("mp3.id3tag.composer",value); | ||
677 | else if (code.equals("TIT1")) props.put("mp3.id3tag.grouping",value); | ||
678 | else if (code.equals("TENC")) props.put("mp3.id3tag.encoded",value); | ||
679 | } | ||
680 | i=i+size-1; | ||
681 | } | ||
682 | // ID3v2.2. | ||
683 | else if ((scode.equals("TAL")) || (scode.equals("TT2")) || (scode.equals("TP1")) || (scode.equals("TYE")) || (scode.equals("TRK")) || (scode.equals("TPA")) || (scode.equals("TCR")) || (scode.equals("TCO")) || (scode.equals("TCM")) || (scode.equals("COM")) || (scode.equals("TT1")) || (scode.equals("TEN"))) | ||
684 | { | ||
685 | i=i+6; | ||
686 | size = (int) (0x00000000) + (bframes[i-3] << 16) + (bframes[i-2] << 8) + (bframes[i-1]); | ||
687 | if (scode.equals("COM")) value = parseText(bframes, i, size, 5); | ||
688 | else value = parseText(bframes,i, size, 1); | ||
689 | if ((value != null) && (value.length()>0)) | ||
690 | { | ||
691 | if (scode.equals("TAL")) props.put("album",value); | ||
692 | else if (scode.equals("TT2")) props.put("title",value); | ||
693 | else if (scode.equals("TYE")) props.put("date",value); | ||
694 | else if (scode.equals("TP1")) props.put("author",value); | ||
695 | else if (scode.equals("TCR")) props.put("copyright",value); | ||
696 | else if (scode.equals("COM")) props.put("comment",value); | ||
697 | else if (scode.equals("TCO")) props.put("mp3.id3tag.genre",value); | ||
698 | else if (scode.equals("TRK")) props.put("mp3.id3tag.track",value); | ||
699 | else if (scode.equals("TPA")) props.put("mp3.id3tag.disc",value); | ||
700 | else if (scode.equals("TCM")) props.put("mp3.id3tag.composer",value); | ||
701 | else if (scode.equals("TT1")) props.put("mp3.id3tag.grouping",value); | ||
702 | else if (scode.equals("TEN")) props.put("mp3.id3tag.encoded",value); | ||
703 | } | ||
704 | i=i+size-1; | ||
705 | } | ||
706 | else if (code.startsWith("ID3")) | ||
707 | { | ||
708 | // ID3v2 Header. | ||
709 | int v2version = (int) (bframes[3] & 0xFF); | ||
710 | props.put("mp3.id3tag.v2.version",String.valueOf(v2version)); | ||
711 | i=i+4; | ||
712 | } | ||
713 | } | ||
714 | } | ||
715 | catch (RuntimeException e) | ||
716 | { | ||
717 | // Ignore all parsing errors. | ||
718 | if (TDebug.TraceAudioFileReader) TDebug.out("Cannot parse ID3v2 :"+e.getMessage()); | ||
719 | } | ||
720 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 parsed"); | ||
721 | } | ||
722 | |||
723 | /** | ||
724 | * Parse Text Frames. | ||
725 | * @param bframes | ||
726 | * @param offset | ||
727 | * @param size | ||
728 | * @param skip | ||
729 | * @return | ||
730 | */ | ||
731 | protected String parseText(byte[] bframes, int offset, int size, int skip) | ||
732 | { | ||
733 | String value = null; | ||
734 | try | ||
735 | { | ||
736 | String[] ENC_TYPES = {"ISO-8859-1", "UTF16","UTF-16BE", "UTF-8"}; | ||
737 | value = new String(bframes,offset+skip,size-skip,ENC_TYPES[bframes[offset]]); | ||
738 | value = chopSubstring(value,0,value.length()); | ||
739 | } | ||
740 | catch (UnsupportedEncodingException e) | ||
741 | { | ||
742 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 Encoding error :"+e.getMessage()); | ||
743 | } | ||
744 | return value; | ||
745 | } | ||
746 | |||
747 | /** | ||
748 | * Load shoutcast (ICY) info. | ||
749 | * @param input | ||
750 | * @param props | ||
751 | * @throws IOException | ||
752 | */ | ||
753 | protected void loadShoutcastInfo(InputStream input, HashMap props) throws IOException | ||
754 | { | ||
755 | IcyInputStream icy = new IcyInputStream(new BufferedInputStream(input)); | ||
756 | HashMap metadata = icy.getTagHash(); | ||
757 | MP3Tag titleMP3Tag = icy.getTag("icy-name"); | ||
758 | if (titleMP3Tag != null) props.put("title",((String) titleMP3Tag.getValue()).trim()); | ||
759 | MP3Tag[] meta = icy.getTags(); | ||
760 | if (meta != null) | ||
761 | { | ||
762 | StringBuffer metaStr = new StringBuffer(); | ||
763 | for (int i=0;i<meta.length;i++) | ||
764 | { | ||
765 | String key = meta[i].getName(); | ||
766 | String value = ((String) icy.getTag(key).getValue()).trim(); | ||
767 | props.put("mp3.shoutcast.metadata."+key, value); | ||
768 | } | ||
769 | } | ||
770 | } | ||
771 | |||
772 | } \ No newline at end of file | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFormat.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFormat.java new file mode 100644 index 0000000000..29a66a3d93 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFormat.java | |||
@@ -0,0 +1,67 @@ | |||
1 | /* | ||
2 | * MpegAudioFormat. | ||
3 | * | ||
4 | * JavaZOOM : mp3spi@javazoom.net | ||
5 | * http://www.javazoom.net | ||
6 | * | ||
7 | *----------------------------------------------------------------------- | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU Library General Public License as published | ||
10 | * by the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU Library General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU Library General Public | ||
19 | * License along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | *---------------------------------------------------------------------- | ||
22 | */ | ||
23 | |||
24 | package javazoom.spi.mpeg.sampled.file; | ||
25 | |||
26 | import java.util.Map; | ||
27 | |||
28 | import javax.sound.sampled.AudioFormat; | ||
29 | |||
30 | import org.tritonus.share.sampled.TAudioFormat; | ||
31 | |||
32 | /** | ||
33 | * @author JavaZOOM | ||
34 | */ | ||
35 | public class MpegAudioFormat extends TAudioFormat | ||
36 | { | ||
37 | /** | ||
38 | * Constructor. | ||
39 | * @param encoding | ||
40 | * @param nFrequency | ||
41 | * @param SampleSizeInBits | ||
42 | * @param nChannels | ||
43 | * @param FrameSize | ||
44 | * @param FrameRate | ||
45 | * @param isBigEndian | ||
46 | * @param properties | ||
47 | */ | ||
48 | public MpegAudioFormat(AudioFormat.Encoding encoding, float nFrequency, int SampleSizeInBits, int nChannels, int FrameSize, float FrameRate, boolean isBigEndian, Map properties) | ||
49 | { | ||
50 | super(encoding, nFrequency, SampleSizeInBits, nChannels, FrameSize, FrameRate, isBigEndian, properties); | ||
51 | } | ||
52 | |||
53 | /** | ||
54 | * MP3 audio format parameters. | ||
55 | * Some parameters might be unavailable. So availability test is required before reading any parameter. | ||
56 | * | ||
57 | * <br>AudioFormat parameters. | ||
58 | * <ul> | ||
59 | * <li><b>bitrate</b> [Integer], bitrate in bits per seconds, average bitrate for VBR enabled stream. | ||
60 | * <li><b>vbr</b> [Boolean], VBR flag. | ||
61 | * </ul> | ||
62 | */ | ||
63 | public Map properties() | ||
64 | { | ||
65 | return super.properties(); | ||
66 | } | ||
67 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegEncoding.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegEncoding.java new file mode 100644 index 0000000000..6306d9ec80 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegEncoding.java | |||
@@ -0,0 +1,47 @@ | |||
1 | /* | ||
2 | * MpegEncoding. | ||
3 | * | ||
4 | * JavaZOOM : mp3spi@javazoom.net | ||
5 | * http://www.javazoom.net | ||
6 | * | ||
7 | *----------------------------------------------------------------------- | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU Library General Public License as published | ||
10 | * by the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU Library General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU Library General Public | ||
19 | * License along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | *---------------------------------------------------------------------- | ||
22 | */ | ||
23 | |||
24 | package javazoom.spi.mpeg.sampled.file; | ||
25 | |||
26 | import javax.sound.sampled.AudioFormat; | ||
27 | |||
28 | /** | ||
29 | * Encodings used by the MPEG audio decoder. | ||
30 | */ | ||
31 | public class MpegEncoding extends AudioFormat.Encoding | ||
32 | { | ||
33 | public static final AudioFormat.Encoding MPEG1L1 = new MpegEncoding("MPEG1L1"); | ||
34 | public static final AudioFormat.Encoding MPEG1L2 = new MpegEncoding("MPEG1L2"); | ||
35 | public static final AudioFormat.Encoding MPEG1L3 = new MpegEncoding("MPEG1L3"); | ||
36 | public static final AudioFormat.Encoding MPEG2L1 = new MpegEncoding("MPEG2L1"); | ||
37 | public static final AudioFormat.Encoding MPEG2L2 = new MpegEncoding("MPEG2L2"); | ||
38 | public static final AudioFormat.Encoding MPEG2L3 = new MpegEncoding("MPEG2L3"); | ||
39 | public static final AudioFormat.Encoding MPEG2DOT5L1 = new MpegEncoding("MPEG2DOT5L1"); | ||
40 | public static final AudioFormat.Encoding MPEG2DOT5L2 = new MpegEncoding("MPEG2DOT5L2"); | ||
41 | public static final AudioFormat.Encoding MPEG2DOT5L3 = new MpegEncoding("MPEG2DOT5L3"); | ||
42 | |||
43 | public MpegEncoding(String strName) | ||
44 | { | ||
45 | super(strName); | ||
46 | } | ||
47 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegFileFormatType.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegFileFormatType.java new file mode 100644 index 0000000000..2c59ad8621 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegFileFormatType.java | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * MpegFileFormatType. | ||
3 | * | ||
4 | * JavaZOOM : mp3spi@javazoom.net | ||
5 | * http://www.javazoom.net | ||
6 | * | ||
7 | *----------------------------------------------------------------------- | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU Library General Public License as published | ||
10 | * by the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU Library General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU Library General Public | ||
19 | * License along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | *---------------------------------------------------------------------- | ||
22 | */ | ||
23 | |||
24 | package javazoom.spi.mpeg.sampled.file; | ||
25 | |||
26 | import javax.sound.sampled.AudioFileFormat; | ||
27 | |||
28 | /** | ||
29 | * FileFormatTypes used by the MPEG audio decoder. | ||
30 | */ | ||
31 | public class MpegFileFormatType extends AudioFileFormat.Type | ||
32 | { | ||
33 | public static final AudioFileFormat.Type MPEG = new MpegFileFormatType("MPEG", "mpeg"); | ||
34 | public static final AudioFileFormat.Type MP3 = new MpegFileFormatType("MP3", "mp3"); | ||
35 | |||
36 | public MpegFileFormatType(String strName, String strExtension) | ||
37 | { | ||
38 | super(strName, strExtension); | ||
39 | } | ||
40 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyInputStream.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyInputStream.java new file mode 100644 index 0000000000..22aa4439fe --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyInputStream.java | |||
@@ -0,0 +1,412 @@ | |||
1 | /* | ||
2 | * IcyInputStream. | ||
3 | * | ||
4 | * jicyshout : http://sourceforge.net/projects/jicyshout/ | ||
5 | * | ||
6 | * JavaZOOM : mp3spi@javazoom.net | ||
7 | * http://www.javazoom.net | ||
8 | * | ||
9 | *----------------------------------------------------------------------- | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | *---------------------------------------------------------------------- | ||
24 | */ | ||
25 | |||
26 | package javazoom.spi.mpeg.sampled.file.tag; | ||
27 | |||
28 | import java.io.BufferedInputStream; | ||
29 | import java.io.IOException; | ||
30 | import java.io.InputStream; | ||
31 | import java.net.URL; | ||
32 | import java.net.URLConnection; | ||
33 | import java.util.HashMap; | ||
34 | import java.util.StringTokenizer; | ||
35 | |||
36 | /** An BufferedInputStream that parses Shoutcast's "icy" metadata | ||
37 | from the stream. Gets headers at the beginning and if the | ||
38 | "icy-metaint" tag is found, it parses and strips in-stream | ||
39 | metadata. | ||
40 | <p> | ||
41 | <b>The deal with metaint</b>: Icy streams don't try to put | ||
42 | tags between MP3 frames the way that ID3 does. Instead, it | ||
43 | requires the client to strip metadata from the stream before | ||
44 | it hits the decoder. You get an | ||
45 | <code>icy-metaint</code> name/val in the beginning of the | ||
46 | stream iff you sent "Icy-Metadata" with value "1" in the | ||
47 | request headers (SimpleMP3DataSource does this if the | ||
48 | "parseStreamMetadata" boolean is true). If this is the case | ||
49 | then the value of icy-metaint is the amount of real data | ||
50 | between metadata blocks. Each block begins with an int | ||
51 | indicating how much metadata there is -- the block is this | ||
52 | value times 16 (it can be, and often is, 0). | ||
53 | <p> | ||
54 | Originally thought that "icy" implied Icecast, but this is | ||
55 | completely wrong -- real Icecast servers, found through | ||
56 | www.icecast.net and typified by URLs with a trailing directory | ||
57 | (like CalArts School of Music - http://65.165.174.100:8000/som) | ||
58 | do not have the "ICY 200 OK" magic string or any of the | ||
59 | CRLF-separated headers. Apparently, "icy" means "Shoutcast". | ||
60 | Yep, that's weird. | ||
61 | @author Chris Adamson, invalidname@mac.com | ||
62 | */ | ||
63 | public class IcyInputStream | ||
64 | extends BufferedInputStream | ||
65 | implements MP3MetadataParser { | ||
66 | |||
67 | public static boolean DEBUG = false; | ||
68 | |||
69 | MP3TagParseSupport tagParseSupport; | ||
70 | /** inline tags are delimited by ';', also filter out | ||
71 | null bytes | ||
72 | */ | ||
73 | protected static final String INLINE_TAG_SEPARATORS = ";\u0000"; | ||
74 | /* looks like icy streams start start with | ||
75 | ICY 200 OK\r\n | ||
76 | then the tags are like | ||
77 | icy-notice1:<BR>This stream requires <a href="http://www.winamp.com/">Winamp</a><BR>\r\n | ||
78 | icy-notice2:SHOUTcast Distributed Network Audio Server/win32 v1.8.2<BR>\r\n | ||
79 | icy-name:Core-upt Radio\r\n | ||
80 | icy-genre:Punk Ska Emo\r\n | ||
81 | icy-url:http://www.core-uptrecords.com\r\n | ||
82 | icy-pub:1\r\n | ||
83 | icy-metaint:8192\r\n | ||
84 | icy-br:56\r\n | ||
85 | \r\n (signifies end of headers) | ||
86 | we only get icy-metaint if the http request that created | ||
87 | this stream sent the header "icy-metadata:1" | ||
88 | // | ||
89 | in in-line metadata, we read a byte that tells us how | ||
90 | many 16-byte blocks there are (presumably, we still use | ||
91 | \r\n for the separator... the block is padded out with | ||
92 | 0x00's that we can ignore) | ||
93 | |||
94 | // when server is full/down/etc, we get the following for | ||
95 | // one of the notice lines: | ||
96 | icy-notice2:This server has reached its user limit<BR> | ||
97 | or | ||
98 | icy-notice2:The resource requested is currently unavailable<BR> | ||
99 | */ | ||
100 | /** Tags that have been discovered in the stream. | ||
101 | */ | ||
102 | HashMap tags; | ||
103 | /** Buffer for readCRLF line... note this limits lines to | ||
104 | 1024 chars (I've read that WinAmp barfs at 128, so | ||
105 | this is generous) | ||
106 | */ | ||
107 | protected byte[] crlfBuffer = new byte[1024]; | ||
108 | /** value of the "metaint" tag, which tells us how many bytes | ||
109 | of real data are between the metadata tags. if -1, this stream | ||
110 | does not have metadata after the header. | ||
111 | */ | ||
112 | protected int metaint = -1; | ||
113 | /** how many bytes of real data remain before the next | ||
114 | block of metadata. Only meaningful if metaint != -1. | ||
115 | */ | ||
116 | protected int bytesUntilNextMetadata = -1; | ||
117 | // TODO: comment for constructor | ||
118 | /** Reads the initial headers of the stream and adds | ||
119 | tags appropriatly. Gets set up to find, read, | ||
120 | and strip blocks of in-line metadata if the | ||
121 | <code>icy-metaint</code> header is found. | ||
122 | */ | ||
123 | public IcyInputStream(InputStream in) throws IOException { | ||
124 | super(in); | ||
125 | tags = new HashMap(); | ||
126 | tagParseSupport = new MP3TagParseSupport(); | ||
127 | // read the initial tags here, including the metaint | ||
128 | // and set the counter for how far we read until | ||
129 | // the next metadata block (if any). | ||
130 | readInitialHeaders(); | ||
131 | IcyTag metaIntTag = (IcyTag) getTag("icy-metaint"); | ||
132 | if (DEBUG) System.out.println("METATAG:"+metaIntTag); | ||
133 | if (metaIntTag != null) { | ||
134 | String metaIntString = metaIntTag.getValueAsString(); | ||
135 | try { | ||
136 | metaint = Integer.parseInt(metaIntString.trim()); | ||
137 | if (DEBUG) System.out.println("METAINT:"+metaint); | ||
138 | bytesUntilNextMetadata = metaint; | ||
139 | } | ||
140 | catch (NumberFormatException nfe) { | ||
141 | } | ||
142 | } | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * IcyInputStream constructor for know meta-interval (Icecast 2) | ||
147 | * @param in | ||
148 | * @param metaint | ||
149 | * @throws IOException | ||
150 | */ | ||
151 | public IcyInputStream(InputStream in, String metaIntString) throws IOException { | ||
152 | super(in); | ||
153 | tags = new HashMap(); | ||
154 | tagParseSupport = new MP3TagParseSupport(); | ||
155 | try | ||
156 | { | ||
157 | metaint = Integer.parseInt(metaIntString.trim()); | ||
158 | if (DEBUG) System.out.println("METAINT:"+metaint); | ||
159 | bytesUntilNextMetadata = metaint; | ||
160 | } | ||
161 | catch (NumberFormatException nfe) { | ||
162 | } | ||
163 | } | ||
164 | |||
165 | /** Assuming we're at the top of the stream, read lines one | ||
166 | by one until we hit a completely blank \r\n. Parse the | ||
167 | data as IcyTags. | ||
168 | */ | ||
169 | protected void readInitialHeaders() throws IOException { | ||
170 | String line = null; | ||
171 | while (!((line = readCRLFLine()).equals(""))) { | ||
172 | int colonIndex = line.indexOf(':'); | ||
173 | // does it have a ':' separator | ||
174 | if (colonIndex == -1) | ||
175 | continue; | ||
176 | IcyTag tag = | ||
177 | new IcyTag( | ||
178 | line.substring(0, colonIndex), | ||
179 | line.substring(colonIndex + 1)); | ||
180 | //System.out.println(tag); | ||
181 | addTag(tag); | ||
182 | } | ||
183 | } | ||
184 | /** Read everything up to the next CRLF, return it as | ||
185 | a String. | ||
186 | */ | ||
187 | protected String readCRLFLine() throws IOException { | ||
188 | int i = 0; | ||
189 | for (; i < crlfBuffer.length; i++) { | ||
190 | byte aByte = (byte) read(); | ||
191 | if (aByte == '\r') { | ||
192 | // possible end of line | ||
193 | byte anotherByte = (byte) read(); | ||
194 | i++; // since we read again | ||
195 | if (anotherByte == '\n') { | ||
196 | break; // break out of while | ||
197 | } | ||
198 | else { | ||
199 | // oops, not end of line - put these in array | ||
200 | crlfBuffer[i - 1] = aByte; | ||
201 | crlfBuffer[i] = anotherByte; | ||
202 | } | ||
203 | } | ||
204 | else { | ||
205 | // if not \r | ||
206 | crlfBuffer[i] = aByte; | ||
207 | } | ||
208 | } // for | ||
209 | // get the string from the byte[]. i is 1 too high because of | ||
210 | // read-ahead in crlf block | ||
211 | return new String(crlfBuffer, 0, i - 1); | ||
212 | } | ||
213 | /** Reads and returns a single byte. | ||
214 | If the next byte is a metadata block, then that | ||
215 | block is read, stripped, and parsed before reading | ||
216 | and returning the first byte after the metadata block. | ||
217 | */ | ||
218 | public int read() throws IOException { | ||
219 | if (bytesUntilNextMetadata > 0) { | ||
220 | bytesUntilNextMetadata--; | ||
221 | return super.read(); | ||
222 | } | ||
223 | else if (bytesUntilNextMetadata == 0) { | ||
224 | // we need to read next metadata block | ||
225 | readMetadata(); | ||
226 | bytesUntilNextMetadata = metaint - 1; | ||
227 | // -1 because we read byte on next line | ||
228 | return super.read(); | ||
229 | } | ||
230 | else { | ||
231 | // no metadata in this stream | ||
232 | return super.read(); | ||
233 | } | ||
234 | } | ||
235 | /** Reads a block of bytes. If the next byte is known | ||
236 | to be a block of metadata, then that is read, parsed, | ||
237 | and stripped, and then a block of bytes is read and | ||
238 | returned. | ||
239 | Otherwise, it may read up to but | ||
240 | not into the next metadata block if | ||
241 | <code>bytesUntilNextMetadata < length</code> | ||
242 | */ | ||
243 | public int read(byte[] buf, int offset, int length) throws IOException { | ||
244 | // if not on metadata, do the usual read so long as we | ||
245 | // don't read past metadata | ||
246 | if (bytesUntilNextMetadata > 0) { | ||
247 | int adjLength = Math.min(length, bytesUntilNextMetadata); | ||
248 | int got = super.read(buf, offset, adjLength); | ||
249 | bytesUntilNextMetadata -= got; | ||
250 | return got; | ||
251 | } | ||
252 | else if (bytesUntilNextMetadata == 0) { | ||
253 | // read/parse the metadata | ||
254 | readMetadata(); | ||
255 | // now as above, except that we reset | ||
256 | // bytesUntilNextMetadata differently | ||
257 | |||
258 | //int adjLength = Math.min(length, bytesUntilNextMetadata); | ||
259 | //int got = super.read(buf, offset, adjLength); | ||
260 | //bytesUntilNextMetadata = metaint - got; | ||
261 | |||
262 | // Chop Fix - JavaZOOM (3 lines above seem buggy) | ||
263 | bytesUntilNextMetadata = metaint; | ||
264 | int adjLength = Math.min(length, bytesUntilNextMetadata); | ||
265 | int got = super.read(buf, offset, adjLength); | ||
266 | bytesUntilNextMetadata -= got; | ||
267 | // End fix - JavaZOOM | ||
268 | |||
269 | |||
270 | return got; | ||
271 | } | ||
272 | else { | ||
273 | // not even reading metadata | ||
274 | return super.read(buf, offset, length); | ||
275 | } | ||
276 | } | ||
277 | /** trivial <code>return read (buf, 0, buf.length)</code> | ||
278 | */ | ||
279 | public int read(byte[] buf) throws IOException { | ||
280 | return read(buf, 0, buf.length); | ||
281 | } | ||
282 | /** Read the next segment of metadata. The stream <b>must</b> | ||
283 | be right on the segment, ie, the next byte to read is | ||
284 | the metadata block count. The metadata is parsed and | ||
285 | new tags are added with addTag(), which fires events | ||
286 | */ | ||
287 | protected void readMetadata() throws IOException { | ||
288 | int blockCount = super.read(); | ||
289 | if (DEBUG) System.out.println("BLOCKCOUNT:"+blockCount); | ||
290 | // System.out.println ("blocks to read: " + blockCount); | ||
291 | int byteCount = (blockCount * 16); // 16 bytes per block | ||
292 | if (byteCount < 0) | ||
293 | return; // WTF?! | ||
294 | byte[] metadataBlock = new byte[byteCount]; | ||
295 | int index = 0; | ||
296 | // build an array of this metadata | ||
297 | while (byteCount > 0) { | ||
298 | int bytesRead = super.read(metadataBlock, index, byteCount); | ||
299 | index += bytesRead; | ||
300 | byteCount -= bytesRead; | ||
301 | } | ||
302 | // now parse it | ||
303 | if (blockCount > 0) | ||
304 | parseInlineIcyTags(metadataBlock); | ||
305 | } // readMetadata | ||
306 | /** Parse metadata from an in-stream "block" of bytes, add | ||
307 | a tag for each one. | ||
308 | <p> | ||
309 | Hilariously, the inline data format is totally different | ||
310 | than the top-of-stream header. For example, here's a | ||
311 | block I saw on "Final Fantasy Radio": | ||
312 | <pre> | ||
313 | StreamTitle='Final Fantasy 8 - Nobuo Uematsu - Blue Fields';StreamUrl=''; | ||
314 | </pre> | ||
315 | In other words: | ||
316 | <ol> | ||
317 | <li>Tags are delimited by semicolons | ||
318 | <li>Keys/values are delimited by equals-signs | ||
319 | <li>Values are wrapped in single-quotes | ||
320 | <li>Key names are in SentenceCase, not lowercase-dashed | ||
321 | </ol> | ||
322 | */ | ||
323 | protected void parseInlineIcyTags(byte[] tagBlock) { | ||
324 | String blockString = new String(tagBlock); | ||
325 | if (DEBUG) System.out.println("BLOCKSTR:"+blockString); | ||
326 | StringTokenizer izer = | ||
327 | new StringTokenizer(blockString, INLINE_TAG_SEPARATORS); | ||
328 | int i = 0; | ||
329 | while (izer.hasMoreTokens()) { | ||
330 | String tagString = izer.nextToken(); | ||
331 | int separatorIdx = tagString.indexOf('='); | ||
332 | if (separatorIdx == -1) | ||
333 | continue; // bogus tagString if no '=' | ||
334 | // try to strip single-quotes around value, if present | ||
335 | int valueStartIdx = | ||
336 | (tagString.charAt(separatorIdx + 1) == '\'') | ||
337 | ? separatorIdx + 2 | ||
338 | : separatorIdx + 1; | ||
339 | int valueEndIdx = | ||
340 | (tagString.charAt(tagString.length() - 1)) == '\'' | ||
341 | ? tagString.length() - 1 | ||
342 | : tagString.length(); | ||
343 | String name = tagString.substring(0, separatorIdx); | ||
344 | String value = tagString.substring(valueStartIdx, valueEndIdx); | ||
345 | // System.out.println (i++ + " " + name + ":" + value); | ||
346 | IcyTag tag = new IcyTag(name, value); | ||
347 | addTag(tag); | ||
348 | } | ||
349 | } | ||
350 | /** adds the tag to the HashMap of tags we have encountered | ||
351 | either in-stream or as headers, replacing any previous | ||
352 | tag with this name. | ||
353 | */ | ||
354 | protected void addTag(IcyTag tag) { | ||
355 | tags.put(tag.getName(), tag); | ||
356 | // fire this as an event too | ||
357 | tagParseSupport.fireTagParsed(this, tag); | ||
358 | } | ||
359 | /** Get the named tag from the HashMap of headers and | ||
360 | in-line tags. Null if no such tag has been encountered. | ||
361 | */ | ||
362 | public MP3Tag getTag(String tagName) { | ||
363 | return (MP3Tag) tags.get(tagName); | ||
364 | } | ||
365 | /** Get all tags (headers or in-stream) encountered thus far. | ||
366 | */ | ||
367 | public MP3Tag[] getTags() { | ||
368 | return (MP3Tag[]) tags.values().toArray(new MP3Tag[0]); | ||
369 | } | ||
370 | /** Returns a HashMap of all headers and in-stream tags | ||
371 | parsed so far. | ||
372 | */ | ||
373 | public HashMap getTagHash() { | ||
374 | return tags; | ||
375 | } | ||
376 | /** Adds a TagParseListener to be notified when this stream | ||
377 | parses MP3Tags. | ||
378 | */ | ||
379 | public void addTagParseListener(TagParseListener tpl) { | ||
380 | tagParseSupport.addTagParseListener(tpl); | ||
381 | } | ||
382 | /** Removes a TagParseListener, so it won't be notified when | ||
383 | this stream parses MP3Tags. | ||
384 | */ | ||
385 | public void removeTagParseListener(TagParseListener tpl) { | ||
386 | tagParseSupport.removeTagParseListener(tpl); | ||
387 | } | ||
388 | /** Quickie unit-test. | ||
389 | */ | ||
390 | public static void main(String args[]) { | ||
391 | byte[] chow = new byte[200]; | ||
392 | if (args.length != 1) { | ||
393 | //System.out.println("Usage: IcyInputStream <url>"); | ||
394 | return; | ||
395 | } | ||
396 | try { | ||
397 | URL url = new URL(args[0]); | ||
398 | URLConnection conn = url.openConnection(); | ||
399 | conn.setRequestProperty("Icy-Metadata", "1"); | ||
400 | IcyInputStream icy = | ||
401 | new IcyInputStream( | ||
402 | new BufferedInputStream(conn.getInputStream())); | ||
403 | while (icy.available() > -1) { | ||
404 | // icy.read(); | ||
405 | icy.read(chow, 0, chow.length); | ||
406 | } | ||
407 | } | ||
408 | catch (Exception e) { | ||
409 | e.printStackTrace(); | ||
410 | } | ||
411 | } | ||
412 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyTag.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyTag.java new file mode 100644 index 0000000000..bbe70f1f3c --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyTag.java | |||
@@ -0,0 +1,42 @@ | |||
1 | /* | ||
2 | * IcyTag. | ||
3 | * | ||
4 | * jicyshout : http://sourceforge.net/projects/jicyshout/ | ||
5 | * | ||
6 | * JavaZOOM : mp3spi@javazoom.net | ||
7 | * http://www.javazoom.net | ||
8 | * | ||
9 | *----------------------------------------------------------------------- | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | *---------------------------------------------------------------------- | ||
24 | */ | ||
25 | |||
26 | package javazoom.spi.mpeg.sampled.file.tag; | ||
27 | /** | ||
28 | * A tag parsed from an icecast tag. | ||
29 | */ | ||
30 | public class IcyTag extends MP3Tag implements StringableTag { | ||
31 | /** Create a new tag, from the parsed name and (String) value. | ||
32 | It looks like all Icecast tags are Strings (safe to assume | ||
33 | this going forward?) | ||
34 | */ | ||
35 | public IcyTag(String name, String stringValue) { | ||
36 | super(name, stringValue); | ||
37 | } | ||
38 | // so far as I know, all Icecast tags are strings | ||
39 | public String getValueAsString() { | ||
40 | return (String) getValue(); | ||
41 | } | ||
42 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3MetadataParser.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3MetadataParser.java new file mode 100644 index 0000000000..81511064b8 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3MetadataParser.java | |||
@@ -0,0 +1,50 @@ | |||
1 | /* | ||
2 | * MP3MetadataParser. | ||
3 | * | ||
4 | * jicyshout : http://sourceforge.net/projects/jicyshout/ | ||
5 | * | ||
6 | * JavaZOOM : mp3spi@javazoom.net | ||
7 | * http://www.javazoom.net | ||
8 | * | ||
9 | *----------------------------------------------------------------------- | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | *---------------------------------------------------------------------- | ||
24 | */ | ||
25 | |||
26 | package javazoom.spi.mpeg.sampled.file.tag; | ||
27 | |||
28 | /** An object that fires off TagParseEvents as they are parsed | ||
29 | from a stream, ServerSocket, or other metadata source | ||
30 | */ | ||
31 | public interface MP3MetadataParser { | ||
32 | /** Adds a TagParseListener to be notified when this object | ||
33 | parses MP3Tags. | ||
34 | */ | ||
35 | public void addTagParseListener(TagParseListener tpl); | ||
36 | /** Removes a TagParseListener, so it won't be notified when | ||
37 | this object parses MP3Tags. | ||
38 | */ | ||
39 | public void removeTagParseListener(TagParseListener tpl); | ||
40 | /** Get all tags (headers or in-stream) encountered thusfar. | ||
41 | This is included in this otherwise Listener-like scheme | ||
42 | because most standards are a mix of start-of-stream | ||
43 | metadata tags (like the http headers or the stuff at the | ||
44 | top of an ice stream) and inline data. Implementations should | ||
45 | hang onto all tags they parse and provide them with this | ||
46 | call. Callers should first use this call to get initial | ||
47 | tags, then subscribe for events as the stream continues. | ||
48 | */ | ||
49 | public MP3Tag[] getTags(); | ||
50 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3Tag.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3Tag.java new file mode 100644 index 0000000000..b545356240 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3Tag.java | |||
@@ -0,0 +1,52 @@ | |||
1 | /* | ||
2 | * MP3Tag. | ||
3 | * | ||
4 | * jicyshout : http://sourceforge.net/projects/jicyshout/ | ||
5 | * | ||
6 | * JavaZOOM : mp3spi@javazoom.net | ||
7 | * http://www.javazoom.net | ||
8 | * | ||
9 | *----------------------------------------------------------------------- | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | *---------------------------------------------------------------------- | ||
24 | */ | ||
25 | |||
26 | package javazoom.spi.mpeg.sampled.file.tag; | ||
27 | |||
28 | /** An individual piece of mp3 metadata, as a name/value pair. | ||
29 | Abstract just so that subclasses will indicate their | ||
30 | tagging scheme (Icy, ID3, etc.). | ||
31 | */ | ||
32 | public abstract class MP3Tag extends Object { | ||
33 | protected String name; | ||
34 | protected Object value; | ||
35 | public MP3Tag(String name, Object value) { | ||
36 | this.name = name; | ||
37 | this.value = value; | ||
38 | } | ||
39 | public String getName() { | ||
40 | return name; | ||
41 | } | ||
42 | public Object getValue() { | ||
43 | return value; | ||
44 | } | ||
45 | public String toString() { | ||
46 | return getClass().getName() | ||
47 | + " -- " | ||
48 | + getName() | ||
49 | + ":" | ||
50 | + getValue().toString(); | ||
51 | } | ||
52 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3TagParseSupport.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3TagParseSupport.java new file mode 100644 index 0000000000..1ab6525512 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3TagParseSupport.java | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * MP3TagParseSupport. | ||
3 | * | ||
4 | * jicyshout : http://sourceforge.net/projects/jicyshout/ | ||
5 | * | ||
6 | * JavaZOOM : mp3spi@javazoom.net | ||
7 | * http://www.javazoom.net | ||
8 | * | ||
9 | *----------------------------------------------------------------------- | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | *---------------------------------------------------------------------- | ||
24 | */ | ||
25 | |||
26 | package javazoom.spi.mpeg.sampled.file.tag; | ||
27 | |||
28 | import java.util.ArrayList; | ||
29 | /** | ||
30 | */ | ||
31 | public class MP3TagParseSupport extends Object { | ||
32 | ArrayList tagParseListeners; | ||
33 | /** trivial constructor, sets up listeners list. | ||
34 | */ | ||
35 | public MP3TagParseSupport() { | ||
36 | super(); | ||
37 | tagParseListeners = new ArrayList(); | ||
38 | } | ||
39 | /** Adds a TagParseListener to be notified when a stream | ||
40 | parses MP3Tags. | ||
41 | */ | ||
42 | public void addTagParseListener(TagParseListener tpl) { | ||
43 | tagParseListeners.add(tpl); | ||
44 | } | ||
45 | /** Removes a TagParseListener, so it won't be notified when | ||
46 | a stream parses MP3Tags. | ||
47 | */ | ||
48 | public void removeTagParseListener(TagParseListener tpl) { | ||
49 | tagParseListeners.add(tpl); | ||
50 | } | ||
51 | /** Fires the given event to all registered listeners | ||
52 | */ | ||
53 | public void fireTagParseEvent(TagParseEvent tpe) { | ||
54 | for (int i = 0; i < tagParseListeners.size(); i++) { | ||
55 | TagParseListener l = (TagParseListener) tagParseListeners.get(i); | ||
56 | l.tagParsed(tpe); | ||
57 | } | ||
58 | } | ||
59 | public void fireTagParsed(Object source, MP3Tag tag) { | ||
60 | fireTagParseEvent(new TagParseEvent(source, tag)); | ||
61 | } | ||
62 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/StringableTag.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/StringableTag.java new file mode 100644 index 0000000000..685c5207f9 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/StringableTag.java | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | * StringableTag. | ||
3 | * | ||
4 | * jicyshout : http://sourceforge.net/projects/jicyshout/ | ||
5 | * | ||
6 | * JavaZOOM : mp3spi@javazoom.net | ||
7 | * http://www.javazoom.net | ||
8 | * | ||
9 | *----------------------------------------------------------------------- | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | *---------------------------------------------------------------------- | ||
24 | */ | ||
25 | |||
26 | package javazoom.spi.mpeg.sampled.file.tag; | ||
27 | |||
28 | /** Indicates that the value of a tag is a string, and | ||
29 | provides a getValueAsString() method to get it. | ||
30 | Appropriate for tags like artist, title, etc. | ||
31 | */ | ||
32 | public interface StringableTag { | ||
33 | /** Return the value of this tag as a string. | ||
34 | */ | ||
35 | public String getValueAsString(); | ||
36 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseEvent.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseEvent.java new file mode 100644 index 0000000000..97e9ec1e19 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseEvent.java | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | * TagParseEvent. | ||
3 | * | ||
4 | * jicyshout : http://sourceforge.net/projects/jicyshout/ | ||
5 | * | ||
6 | * JavaZOOM : mp3spi@javazoom.net | ||
7 | * http://www.javazoom.net | ||
8 | * | ||
9 | *----------------------------------------------------------------------- | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | *---------------------------------------------------------------------- | ||
24 | */ | ||
25 | |||
26 | package javazoom.spi.mpeg.sampled.file.tag; | ||
27 | |||
28 | import java.util.EventObject; | ||
29 | /** Event to indicate that an MP3 tag was received through | ||
30 | some means (parsed in stream, received via UDP, whatever) | ||
31 | and converted into an MP3Tag object. | ||
32 | */ | ||
33 | public class TagParseEvent extends EventObject { | ||
34 | protected MP3Tag tag; | ||
35 | public TagParseEvent(Object source, MP3Tag tag) { | ||
36 | super(source); | ||
37 | this.tag = tag; | ||
38 | } | ||
39 | /** Get the tag that was parsed. | ||
40 | */ | ||
41 | public MP3Tag getTag() { | ||
42 | return tag; | ||
43 | } | ||
44 | } | ||
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseListener.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseListener.java new file mode 100644 index 0000000000..a630827297 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseListener.java | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * TagParseListener. | ||
3 | * | ||
4 | * jicyshout : http://sourceforge.net/projects/jicyshout/ | ||
5 | * | ||
6 | * JavaZOOM : mp3spi@javazoom.net | ||
7 | * http://www.javazoom.net | ||
8 | * | ||
9 | *----------------------------------------------------------------------- | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | *---------------------------------------------------------------------- | ||
24 | */ | ||
25 | |||
26 | package javazoom.spi.mpeg.sampled.file.tag; | ||
27 | |||
28 | import java.util.EventListener; | ||
29 | /** EventListener to be implemented by objects that want to | ||
30 | get callbacks when MP3 tags are received. | ||
31 | */ | ||
32 | public interface TagParseListener extends EventListener { | ||
33 | /** Called when a tag is found (parsed from the stream, | ||
34 | received via UDP, etc.) | ||
35 | */ | ||
36 | public void tagParsed(TagParseEvent tpe); | ||
37 | } | ||