Skip to content

Instantly share code, notes, and snippets.

@MTDdk
Last active November 12, 2020 01:03
Show Gist options
  • Save MTDdk/a7fc37e9c86e991d3e948ca40dab5a5a to your computer and use it in GitHub Desktop.
Save MTDdk/a7fc37e9c86e991d3e948ca40dab5a5a to your computer and use it in GitHub Desktop.
Continously extract StreamTitle (MP3 metadata) from SHOUTcast / IceCast audio streaming
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
/**
* Uses java.net.http from Java 11
*/
public class IceCastMetaData {
static final int DEFAULT_METADATA_INTERVAL = 8192;
public static void main(String[] args) {
URI streamUri = URI.create("https://radioserver/");
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(streamUri)
.header("Icy-MetaData", "1") // instruct the server, that we want the metadata
.build();
// This could certainly also be done with using Apache HttpClient,
// instead of the built-in Java 11 HttpClient
client.sendAsync(request, BodyHandlers.ofInputStream())
.thenAccept(resp -> {
// Retrieve the headers of the server response
HttpHeaders headers = resp.headers();
// "icy-metaint" = how often the metadata is sent in the stream
// This might not be sent by the server, and then it defaults to DEFAULT_METADATA_INTERVAL
int metaInterval = headers.firstValue("icy-metaint").map(Integer::parseInt).orElse(DEFAULT_METADATA_INTERVAL);
// Get the InputStream of the audio stream
try (InputStream stream = resp.body()) {
while (true) {
// Read a chunk of music data (as we are not using it, just skipping)
stream.skipNBytes(metaInterval);
// Extract the metadata from the chunk
String meta = readMetaData(stream);
// See if we got something useful from this current chunk
if (!meta.isEmpty() && meta.startsWith("StreamTitle")) {
// If we got some metadata, then it will be on the form: "StreamTitle='<song title>';"
// That is, it always starts with "StreamTitle='" and ends with "';"
System.out.println("Now playing :: " + meta.substring("StreamTitle=".length(), meta.indexOf(';')));
}
}
} catch (IOException e) {
e.printStackTrace();
}
})
.join();
}
private static String readMetaData(InputStream stream) throws IOException {
// The next byte after a music chunk indicates the length of the metadata
int length = stream.read();
if (length < 1) return ""; // no new metadata is present (the song is playing)
// Multiply by 16 to get the number of bytes, that holds the data
int metaChunkSize = length * 16;
byte[] metaChunk = stream.readNBytes(metaChunkSize);
return new String(metaChunk, 0, metaChunkSize, StandardCharsets.ISO_8859_1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment