Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to separate metadata and track from a shoutcast stream without making separate requests

I have made a radio app which works perfectly fine. I'm able to play the radio stream and fetch the metadata too. The streaming service is from shoutcast.

The only problem is that, I'm adding the URL as the data source to the media player and then fetching the title and artist every 5 seconds.

Is there any way, I can just make one HTTP request and then split the audio and the metadata and then send it to the media player?

Code for fetching Metadata.

private void retreiveMetadata() throws IOException {

    int metaDataOffset = 0;

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
        .addHeader("Icy-MetaData", "1")
        .addHeader("Connection", "close")
        .addHeader("Accept", "")
        .url(streamUrl)
        .build();

    request.headers("");


    Response response = client.newCall(request).execute();
    InputStream stream = response.body().byteStream();

    //Map<String, List<String>> headers = response..getHeaderFields();

    if (!response.headers("icy-metaint").equals("")) {
        // Headers are sent via HTTP

        String icyMetaInt = response.headers("icy-metaint").toString();
        icyMetaInt = icyMetaInt.replace("[", "");
        icyMetaInt = icyMetaInt.replace("]", "");

        metaDataOffset = Integer.parseInt(icyMetaInt);
    } 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 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((String)m.group(1), (String)m.group(2));
        }
    }

    return metadata;
}

And passing the url to the datasource of the media player

String url = "http://68.68.109.106:8356/";
mp = new MediaPlayer();
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {

    mp.setDataSource(url);
    mp.prepare();

} catch (IllegalArgumentException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
    // TODO Auto-generated catch block
    Log.e(TAG, "SecurityException");
    Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
} catch (IllegalStateException e) {
    // TODO Auto-generated catch block
    Log.e(TAG, "IllegalStateException");
    Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
    // TODO Auto-generated catch block
    Log.e(TAG, "IOException");
    Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
}
like image 473
Searock Avatar asked May 31 '16 19:05

Searock


1 Answers

The task you try to achieve is not something that is easy to do. (Tho it is not impossible.) The metadata of the stream is only "downloaded" at the start of the stream, so changing it afterwards would have no effect reading the metainformations "cached" from the stream. To read the new properties you have to restart the stream, this will fetch the new headers etc. (But it could cause a break in the stream so it is not really recommended.)

In sound technology it is common to use watermarking. It is a process where you enrich your sound with some kind of data in the not (quality) destructing way. (Usage on YouTube.) Although it is hard to do, there are some ways to hide your information in the stream. I recommend to read this to achieve the goal you want to reach.

Because your hardware is a mobile phone and not all of the Android devices are strong enough you should consider some new HTTP requests. The audio processing is not a cheap thing speaking in terms of CPU, memory etc. There is some other options to consider if you do so. The five second polling is not the best way to get the information, because you could display false information to the user and false information is worse than nothing. I recommend a server side push based on mqtt. Here is a pretty nice example of the usage. A solution based on this method instead of polling is using less traffic and is more accurate.

like image 54
Hash Avatar answered Oct 03 '22 15:10

Hash