/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.core.audio;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is an AudioStream from an URL. Note that some sinks, like Sonos, can directly handle URL
* based streams, and therefore can/should call getURL() to get an direct reference to the URL.
*
* @author Karel Goderis - Initial contribution and API
* @author Kai Kreuzer - Refactored to not require a source
*
*/
public class URLAudioStream extends org.eclipse.smarthome.core.audio.AudioStream {
private static final Pattern plsStreamPattern = Pattern.compile("^File[0-9]=(.+)$");
private final Logger logger = LoggerFactory.getLogger(URLAudioStream.class);
private AudioFormat audioFormat;
private final InputStream inputStream;
private String url;
private Socket shoutCastSocket;
public URLAudioStream(String url) throws AudioException {
if (url == null) {
throw new IllegalArgumentException("url must not be null!");
}
this.url = url;
this.audioFormat = new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, false, 16, null, null);
this.inputStream = createInputStream();
}
private InputStream createInputStream() throws AudioException {
try {
if (url.toLowerCase().endsWith(".m3u")) {
InputStream is = new URL(url).openStream();
String urls = IOUtils.toString(is);
for (String line : urls.split("\n")) {
if (!line.isEmpty() && !line.startsWith("#")) {
url = line;
break;
}
}
} else if (url.toLowerCase().endsWith(".pls")) {
InputStream is = new URL(url).openStream();
String urls = IOUtils.toString(is);
for (String line : urls.split("\n")) {
if (!line.isEmpty() && line.startsWith("File")) {
Matcher matcher = plsStreamPattern.matcher(line);
if (matcher.find()) {
url = matcher.group(1);
break;
}
}
}
}
URL streamUrl = new URL(url);
URLConnection connection = streamUrl.openConnection();
InputStream is = null;
if (connection.getContentType().equals("unknown/unknown")) {
// Java does not parse non-standard headers used by SHOUTCast
int port = streamUrl.getPort() > 0 ? streamUrl.getPort() : 80;
// Manipulate User-Agent to receive a stream
shoutCastSocket = new Socket(streamUrl.getHost(), port);
OutputStream os = shoutCastSocket.getOutputStream();
String user_agent = "WinampMPEG/5.09";
String req = "GET / HTTP/1.0\r\nuser-agent: " + user_agent
+ "\r\nIcy-MetaData: 1\r\nConnection: keep-alive\r\n\r\n";
os.write(req.getBytes());
is = shoutCastSocket.getInputStream();
} else {
// getInputStream() method is more error-proof than openStream(),
// because openStream() does openConnection().getInputStream(),
// which opens a new connection and does not reuse the old one.
is = connection.getInputStream();
}
return is;
} catch (MalformedURLException e) {
logger.error("URL '{}' is not a valid url : '{}'", url, e.getMessage());
throw new AudioException("URL not valid");
} catch (IOException e) {
logger.error("Cannot set up stream '{}': {}", url, e);
throw new AudioException("IO Error");
}
}
@Override
public AudioFormat getFormat() {
return audioFormat;
}
@Override
public int read() throws IOException {
return inputStream.read();
}
public String getURL() {
return url;
}
@Override
public void close() throws IOException {
super.close();
if (shoutCastSocket != null) {
shoutCastSocket.close();
}
}
@Override
public String toString() {
return url;
}
}