Wendell Krohling
Wendell Krohling

Reputation: 75

Get metadata from shoutcast stream

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

Answers (1)

kris larson
kris larson

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

Related Questions