Reputation: 75
I'm developing a radio app with multiple radios, the stream is playing fine. But I'm struggling to show artist and music playing at the moment.
This is the class I'm using to get metadata from shoutcast stream:
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IcyStreamMeta<Message> {
protected URL streamUrl;
private Map<String, String> metadata;
private boolean isError;
public IcyStreamMeta(URL streamUrl) {
setStreamUrl(streamUrl);
isError = false;
}
/**
* Get artist using stream's title
*
* @return String
* @throws IOException
*/
public String getArtist() throws IOException {
Map<String, String> data = getMetadata();
if (!data.containsKey("StreamTitle"))
return "";
String streamTitle = data.get("StreamTitle");
String title = streamTitle.substring(0, streamTitle.indexOf("-"));
return title.trim();
}
/**
* Get title using stream's title
*
* @return String
* @throws IOException
*/
public String getTitle() throws IOException {
Map<String, String> data = getMetadata();
if (!data.containsKey("StreamTitle"))
return "";
String streamTitle = data.get("StreamTitle");
String artist = streamTitle.substring(streamTitle.indexOf("-")+1);
return artist.trim();
}
public Map<String, String> getMetadata() throws IOException {
if (metadata == null) {
refreshMeta();
}
return metadata;
}
public void refreshMeta() throws IOException {
retreiveMetadata();
}
private void retreiveMetadata() throws IOException {
URLConnection con = streamUrl.openConnection();
con.setRequestProperty("Icy-MetaData", "1");
con.setRequestProperty("Connection", "close");
con.setRequestProperty("Accept", null);
con.connect();
int metaDataOffset = 0;
Map<String, List<String>> headers = con.getHeaderFields();
InputStream stream = con.getInputStream();
if (headers.containsKey("icy-metaint")) {
// Headers are sent via HTTP
metaDataOffset = Integer.parseInt(headers.get("icy-metaint").get(0));
} else {
// Headers are sent within a stream
StringBuilder strHeaders = new StringBuilder();
char c;
while ((c = (char)stream.read()) != -1) {
strHeaders.append(c);
if (strHeaders.length() > 5 && (strHeaders.substring((strHeaders.length() - 4), strHeaders.length()).equals("\r\n\r\n"))) {
// end of headers
break;
}
}
// Match headers to get metadata offset within a stream
Pattern p = Pattern.compile("\\r\\n(icy-metaint):\\s*(.*)\\r\\n");
Matcher m = p.matcher(strHeaders.toString());
if (m.find()) {
metaDataOffset = Integer.parseInt(m.group(2));
}
}
// In case no data was sent
if (metaDataOffset == 0) {
isError = true;
return;
}
// Read metadata
int b;
int count = 0;
int metaDataLength = 4080; // 4080 is the max length
boolean inData = false;
StringBuilder metaData = new StringBuilder();
// Stream position should be either at the beginning or right after headers
while ((b = stream.read()) != -1) {
count++;
// Length of the metadata
if (count == metaDataOffset + 1) {
metaDataLength = b * 16;
}
if (count > metaDataOffset + 1 && count < (metaDataOffset + metaDataLength)) {
inData = true;
} else {
inData = false;
}
if (inData) {
if (b != 0) {
metaData.append((char)b);
}
}
if (count > (metaDataOffset + metaDataLength)) {
break;
}
}
// Set the data
metadata = IcyStreamMeta.parseMetadata(metaData.toString());
// Close
stream.close();
}
public boolean isError() {
return isError;
}
public URL getStreamUrl() {
return streamUrl;
}
public void setStreamUrl(URL streamUrl) {
this.metadata = null;
this.streamUrl = streamUrl;
this.isError = false;
}
public static Map<String, String> parseMetadata(String metaString) {
Map<String, String> metadata = new HashMap();
String[] metaParts = metaString.split(";");
Pattern p = Pattern.compile("^([a-zA-Z]+)=\\'([^\\']*)\\'$");
Matcher m;
for (int i = 0; i < metaParts.length; i++) {
m = p.matcher(metaParts[i]);
if (m.find()) {
metadata.put(m.group(1), m.group(2));
}
}
return metadata;
}
}
And the method on MainActivity to get the metadata every 10 seconds
private void getMeta()
{
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
try {
IcyStreamMeta icy = new IcyStreamMeta(new URL(RadiophonyService.getRadioURL()));
final String data = icy.getArtist() + " - " + icy.getTitle();
final TextView meta = (TextView) findViewById(R.id.now_playing);
runOnUiThread(new Runnable() {
public void run() {
meta.setText(data);
}
});
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}, 0, 10000);
}
Initially, when I select one station, it plays but does not show metadata and when I select another station the app crashes with this runtime error:
E/AndroidRuntime: FATAL EXCEPTION: Timer-0 Process: com.example.app, PID: 23597
java.lang.StringIndexOutOfBoundsException: length=0; regionStart=0; regionLength=-1 at java.lang.String.startEndAndLength(String.java:504) at java.lang.String.substring(String.java:1333) at com.example.app.utilities.IcyStreamMeta.getArtist(IcyStreamMeta.java:41) at com.example.app.activities.MainActivity$8.run(MainActivity.java:306) at java.util.Timer$TimerImpl.run(Timer.java:284)
I'm stuck at this for days, and I tried other solutions but nothing works!
Upvotes: 3
Views: 1448
Reputation: 30985
Looks like this line
String title = streamTitle.substring(0, streamTitle.indexOf("-"));
is the culprit.
If streamTitle
does not have a dash in its title, indexOf()
will return a -1, and substring()
is choking because you can't have an end index less than the start index.
Maybe you need something like this:
int pos = streamTitle.indexOf("-");
String title = (pos == -1) ? streamTitle : streamTitle.substring(0, pos);
Upvotes: 2