From 333be3ce0c4f49a8b5ce447b80bb4491f0f7001d Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Fri, 24 May 2013 23:46:03 +0200 Subject: [PATCH] Add streaming audio source. --- .../sonitus/data/source/StreamSource.java | 145 +++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/main/java/net/pterodactylus/sonitus/data/source/StreamSource.java diff --git a/src/main/java/net/pterodactylus/sonitus/data/source/StreamSource.java b/src/main/java/net/pterodactylus/sonitus/data/source/StreamSource.java new file mode 100644 index 0000000..d9df054 --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/data/source/StreamSource.java @@ -0,0 +1,145 @@ +/* + * Sonitus - StreamSource.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.data.source; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; + +import net.pterodactylus.sonitus.data.ContentMetadata; +import net.pterodactylus.sonitus.data.FormatMetadata; +import net.pterodactylus.sonitus.data.Metadata; +import net.pterodactylus.sonitus.data.Source; +import net.pterodactylus.sonitus.io.MetadataStream; + +import com.google.common.base.Optional; +import com.google.common.collect.Maps; +import com.google.common.primitives.Ints; + +/** + * {@link Source} implementation that can download an audio stream from a + * streaming server. + *

+ * Currently only “audio/mpeg” (aka MP3) streams are supported. + * + * @author David ‘Bombe’ Roden + */ +public class StreamSource implements Source { + + /** The URL of the stream. */ + private final String streamUrl; + + /** The metadata stream. */ + private final MetadataStream metadataStream; + + /** The current metadata. */ + private Metadata metadata; + + /** + * Creates a new stream source. This will also connect to the server and parse + * the response header for vital information (sampling frequency, number of + * channels, etc.). + * + * @param streamUrl + * The URL of the stream + * @throws IOException + * if an I/O error occurs + */ + public StreamSource(String streamUrl) throws IOException { + this.streamUrl = streamUrl; + URL url = new URL(streamUrl); + + /* set up connection. */ + URLConnection urlConnection = url.openConnection(); + if (!(urlConnection instanceof HttpURLConnection)) { + throw new IllegalArgumentException("Not an HTTP URL!"); + } + HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection; + httpUrlConnection.setRequestProperty("ICY-Metadata", "1"); + + /* connect. */ + httpUrlConnection.connect(); + + /* check content type. */ + String contentType = httpUrlConnection.getContentType(); + if (!contentType.startsWith("audio/mpeg")) { + throw new IllegalArgumentException("Not an MP3 stream!"); + } + + /* get ice-audio-info header. */ + String iceAudioInfo = httpUrlConnection.getHeaderField("ICE-Audio-Info"); + if (iceAudioInfo == null) { + throw new IllegalArgumentException("No ICE Audio Info!"); + } + + /* parse ice-audio-info header. */ + String[] audioInfos = iceAudioInfo.split(";"); + Map audioParameters = Maps.newHashMap(); + for (String audioInfo : audioInfos) { + String key = audioInfo.substring(0, audioInfo.indexOf('=')).toLowerCase(); + int value = Ints.tryParse(audioInfo.substring(audioInfo.indexOf('=') + 1)); + audioParameters.put(key, value); + } + + /* check metadata interval. */ + String metadataIntervalHeader = httpUrlConnection.getHeaderField("ICY-MetaInt"); + if (metadataIntervalHeader == null) { + throw new IllegalArgumentException("No Metadata Interval header!"); + } + Integer metadataInterval = Ints.tryParse(metadataIntervalHeader); + if (metadataInterval == null) { + throw new IllegalArgumentException(String.format("Invalid Metadata Interval header: %s", metadataIntervalHeader)); + } + + metadata = new Metadata(new FormatMetadata(audioParameters.get("ice-channels"), audioParameters.get("ice-samplerate"), "MP3"), new ContentMetadata()); + metadataStream = new MetadataStream(new BufferedInputStream(httpUrlConnection.getInputStream()), metadataInterval); + } + + // + // SOURCE METHODS + // + + @Override + public Metadata metadata() { + Optional streamMetadata = metadataStream.getContentMetadata(); + if (!streamMetadata.isPresent()) { + return metadata; + } + return metadata = metadata.title(streamMetadata.get().title()); + } + + @Override + public byte[] get(int bufferSize) throws IOException { + byte[] buffer = new byte[bufferSize]; + metadataStream.read(buffer); + return buffer; + } + + // + // OBJECT METHODS + // + + @Override + public String toString() { + return String.format("StreamSource(%s,%s)", streamUrl, metadata); + } + +} -- 2.7.4