diff --git a/src/audio/broadcast/icecast/IcecastConfiguration.java b/src/audio/broadcast/icecast/IcecastConfiguration.java
new file mode 100644
index 00000000..e70f6075
--- /dev/null
+++ b/src/audio/broadcast/icecast/IcecastConfiguration.java
@@ -0,0 +1,292 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * 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 audio.broadcast.icecast;
+
+import audio.broadcast.BroadcastConfiguration;
+import audio.broadcast.BroadcastFormat;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlTransient;
+import java.util.Base64;
+
+public abstract class IcecastConfiguration extends BroadcastConfiguration
+{
+ protected String mUserName = "source";
+ private String mMountPoint = "/stream";
+ private String mDescription;
+ private String mGenre;
+ private boolean mPublic;
+ private int mBitRate = 16;
+ private int mChannels = 1;
+ private int mSampleRate = 8000;
+ private String mURL;
+
+ public IcecastConfiguration(BroadcastFormat format)
+ {
+ super(format);
+ }
+
+ public IcecastConfiguration()
+ {
+ }
+
+ /**
+ * Base64 encoded version of the username and password with prepended 'Basic ' tag.
+ */
+ @XmlTransient
+ public String getBase64EncodedCredentials()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ if(hasUserName())
+ {
+ sb.append(getUserName()).append(":").append(getPassword());
+ }
+ else
+ {
+ sb.append(getPassword());
+ }
+
+ String base64 = Base64.getEncoder().encodeToString(sb.toString().getBytes());
+
+ return "Basic " + base64;
+ }
+
+ @XmlAttribute( name="user_name" )
+ public String getUserName()
+ {
+ return mUserName;
+ }
+
+ public void setUserName(String userName)
+ {
+ mUserName = userName;
+ }
+
+ public boolean hasUserName()
+ {
+ return mUserName != null;
+ }
+
+ /**
+ * Mount point or path to the stream
+ * @return mount point
+ */
+ @XmlAttribute( name="mount_point" )
+ public String getMountPoint()
+ {
+ return mMountPoint;
+ }
+
+ /**
+ * Sets the mount point (path) for the stream
+ * @param mountPoint
+ */
+ public void setMountPoint(String mountPoint)
+ {
+ if(mountPoint != null && !mountPoint.isEmpty())
+ {
+ if(mountPoint.startsWith("/"))
+ {
+ mMountPoint = mountPoint;
+ }
+ else
+ {
+ mMountPoint = "/" + mountPoint;
+ }
+ }
+ }
+
+ public boolean hasMountPoint()
+ {
+ return mMountPoint != null;
+ }
+
+ @XmlAttribute( name="description" )
+ public String getDescription()
+ {
+ return mDescription;
+ }
+
+ public void setDescription(String description)
+ {
+ mDescription = description;
+ }
+
+ public boolean hasDescription()
+ {
+ return mDescription != null;
+ }
+
+ /**
+ * Stream genre
+ */
+ @XmlAttribute( name="genre" )
+ public String getGenre()
+ {
+ return mGenre;
+ }
+
+ /**
+ * Stream genre
+ * @param genre
+ */
+ public void setGenre(String genre)
+ {
+ mGenre = genre;
+ }
+
+ public boolean hasGenre()
+ {
+ return mGenre != null;
+ }
+
+ /**
+ * Public visibility of the broadcastAudio
+ */
+ @XmlAttribute( name="public" )
+ public boolean isPublic()
+ {
+ return mPublic;
+ }
+
+ /**
+ * Sets public visibility of the broadcastAudio
+ * @param isPublic indicates if the broadcastAudio should be visible to the public
+ */
+ public void setPublic(boolean isPublic)
+ {
+ mPublic = isPublic;
+ }
+
+ /**
+ * Number of audio channels in the broadcastAudio
+ */
+ @XmlAttribute( name="channels" )
+ public int getChannels()
+ {
+ return mChannels;
+ }
+
+ /**
+ * Sets the number of audio channels in the broadcastAudio
+ */
+ public void setChannels(int channels)
+ {
+ mChannels = channels;
+ }
+
+ public boolean hasChannels()
+ {
+ return mChannels > 0;
+ }
+
+ /**
+ * Bit rate in bits per second
+ */
+ @XmlAttribute( name="bitrate" )
+ public int getBitRate()
+ {
+ return mBitRate;
+ }
+
+ /**
+ * Bit rate
+ * @param bitRate in samples per second
+ */
+ public void setBitRate(int bitRate)
+ {
+ mBitRate = bitRate;
+ }
+
+ public boolean hasBitRate()
+ {
+ return mBitRate > 0;
+ }
+
+ @XmlAttribute( name="sample_rate" )
+ public int getSampleRate()
+ {
+ return mSampleRate;
+ }
+
+ public void setSampleRate(int sampleRate)
+ {
+ mSampleRate = sampleRate;
+ }
+
+ public boolean hasSampleRate()
+ {
+ return mSampleRate > 0;
+ }
+
+ /**
+ * URL associated with the broadcastAudio where users can find additional details.
+ */
+ @XmlAttribute( name="url" )
+ public String getURL()
+ {
+ return mURL;
+ }
+
+ /**
+ * URL associated with the broadcastAudio where users can find additional details.
+ * @param url
+ */
+ public void setURL(String url)
+ {
+ mURL = url;
+ }
+
+ public boolean hasURL()
+ {
+ return mURL != null;
+ }
+
+ @Override
+ public boolean isValid()
+ {
+ if(!super.isValid())
+ {
+ return false;
+ }
+ else if(getUserName() == null)
+ {
+ return false;
+ }
+ else if(getMountPoint() == null)
+ {
+ return false;
+ }
+ else if(getChannels() != 1)
+ {
+ return false;
+ }
+ else if(getSampleRate() <= 0)
+ {
+ return false;
+ }
+ else if(getBitRate() <= 0)
+ {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/audio/broadcast/icecast/IcecastHTTPConfiguration.java b/src/audio/broadcast/icecast/IcecastHTTPConfiguration.java
index a84b6322..ee0be59b 100644
--- a/src/audio/broadcast/icecast/IcecastHTTPConfiguration.java
+++ b/src/audio/broadcast/icecast/IcecastHTTPConfiguration.java
@@ -24,18 +24,8 @@ import audio.broadcast.BroadcastServerType;
import java.util.Base64;
-public class IcecastHTTPConfiguration extends BroadcastConfiguration
+public class IcecastHTTPConfiguration extends IcecastConfiguration
{
- private String mUserName = "source";
- private String mMountPoint = "stream";
- private String mDescription;
- private String mGenre;
- private boolean mPublic;
- private int mBitRate = 16; //kHz
- private int mChannels = 1;
- private int mSampleRate = 8000;
- private String mURL;
-
public IcecastHTTPConfiguration()
{
//no-arg JAXB constructor
@@ -84,234 +74,5 @@ public class IcecastHTTPConfiguration extends BroadcastConfiguration
return BroadcastServerType.ICECAST_HTTP;
}
- /**
- * Base64 encoded version of the username and password with prepended 'Basic ' tag.
- */
- public String getEncodedCredentials()
- {
- StringBuilder sb = new StringBuilder();
- if(hasUserName())
- {
- sb.append(getUserName()).append(":").append(getPassword());
- }
- else
- {
- sb.append(getPassword());
- }
-
- String base64 = Base64.getEncoder().encodeToString(sb.toString().getBytes());
-
- return "Basic " + base64;
- }
-
- /**
- * User name. Default is 'source'.
- */
- public String getUserName()
- {
- return mUserName;
- }
-
- /**
- * Sets the user name.
- */
- public void setUserName(String userName)
- {
- mUserName = userName;
- }
-
- /**
- * Indicates if this configuration has a user name
- */
- public boolean hasUserName()
- {
- return mUserName != null;
- }
-
- /**
- * Mount point or path to the stream
- * @return mount point
- */
- public String getMountPoint()
- {
- return mMountPoint;
- }
-
- /**
- * Sets the mount point (path) for the stream
- * @param mountPoint
- */
- public void setMountPoint(String mountPoint)
- {
- mMountPoint = mountPoint;
- }
-
- /**
- * Indicates if this configuration has a mount point
- */
- public boolean hasMountPoint()
- {
- return mMountPoint != null;
- }
-
-
- /**
- * Stream description
- */
- public String getDescription()
- {
- return mDescription;
- }
-
- /**
- * Sets the stream description
- */
- public void setDescription(String description)
- {
- mDescription = description;
- }
-
- /**
- * Indicates if this configuration contains a stream description
- */
- public boolean hasDescription()
- {
- return mDescription != null;
- }
-
- /**
- * Stream genre
- */
- public String getGenre()
- {
- return mGenre;
- }
-
- /**
- * Sets the stream genre
- */
- public void setGenre(String genre)
- {
- mGenre = genre;
- }
-
- /**
- * Indicates if this configuration contains a genre
- */
- public boolean hasGenre()
- {
- return mGenre != null;
- }
-
- /**
- * Public visibility of the broadcastAudio
- */
- public boolean isPublic()
- {
- return mPublic;
- }
-
- /**
- * Sets public visibility of the broadcastAudio
- * @param isPublic indicates if the broadcastAudio should be visible to the public
- */
- public void setPublic(boolean isPublic)
- {
- mPublic = isPublic;
- }
-
- /**
- * Number of audio channels in the broadcastAudio
- */
- public int getChannels()
- {
- return mChannels;
- }
-
- /**
- * Sets the number of audio channels in the broadcastAudio
- */
- public void setChannels(int channels)
- {
- mChannels = channels;
- }
-
- public boolean hasChannels()
- {
- return mChannels > 0;
- }
-
- /**
- * Bit rate in bits per second
- */
- public int getBitRate()
- {
- return mBitRate;
- }
-
- /**
- * Bit rate
- * @param bitRate in samples per second
- */
- public void setBitRate(int bitRate)
- {
- mBitRate = bitRate;
- }
-
- /**
- * Indicates if this configuration has a bit rate value
- */
- public boolean hasBitRate()
- {
- return mBitRate > 0;
- }
-
- /**
- * Sample rate of the input PCM audio samples. Default is 8 kHz
- */
- public int getSampleRate()
- {
- return mSampleRate;
- }
-
- /**
- * Sets the input PCM audio sample rate
- */
- public void setSampleRate(int sampleRate)
- {
- mSampleRate = sampleRate;
- }
-
- /**
- * Indicates if this configuration contains an audio sample rate
- */
- public boolean hasSampleRate()
- {
- return mSampleRate > 0;
- }
-
- /**
- * URL associated with the broadcastAudio where users can find additional details.
- */
- public String getURL()
- {
- return mURL;
- }
-
- /**
- * URL associated with the broadcastAudio where users can find additional details.
- */
- public void setURL(String url)
- {
- mURL = url;
- }
-
- /**
- * Indicates if this configuration contains a stream URL
- */
- public boolean hasURL()
- {
- return mURL != null;
- }
}
diff --git a/src/audio/broadcast/icecast/IcecastHTTPConfigurationEditor.java b/src/audio/broadcast/icecast/IcecastHTTPConfigurationEditor.java
index d6025aa3..2352e46b 100644
--- a/src/audio/broadcast/icecast/IcecastHTTPConfigurationEditor.java
+++ b/src/audio/broadcast/icecast/IcecastHTTPConfigurationEditor.java
@@ -98,7 +98,7 @@ public class IcecastHTTPConfigurationEditor extends BroadcastConfigurationEditor
add(new JLabel("Mount:"));
mMountPoint = new JTextField();
mMountPoint.getDocument().addDocumentListener(this);
- mMountPoint.setToolTipText("Mount point (e.g. \\12345)");
+ mMountPoint.setToolTipText("Mount point (e.g. /12345)");
add(mMountPoint);
add(new JLabel("User Name:"));
diff --git a/src/audio/broadcast/icecast/IcecastMetadataUpdater.java b/src/audio/broadcast/icecast/IcecastMetadataUpdater.java
new file mode 100644
index 00000000..768588b7
--- /dev/null
+++ b/src/audio/broadcast/icecast/IcecastMetadataUpdater.java
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * 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 audio.broadcast.icecast;
+
+import controller.ThreadPoolManager;
+import org.apache.mina.core.future.ConnectFuture;
+import org.apache.mina.core.service.IoHandlerAdapter;
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.http.HttpClientCodec;
+import org.apache.mina.http.HttpRequestImpl;
+import org.apache.mina.http.api.HttpMethod;
+import org.apache.mina.http.api.HttpRequest;
+import org.apache.mina.http.api.HttpVersion;
+import org.apache.mina.transport.socket.nio.NioSocketConnector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import properties.SystemProperties;
+
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class IcecastMetadataUpdater
+{
+ private final static Logger mLog = LoggerFactory.getLogger(IcecastMetadataUpdater.class);
+ private final static String UTF8 = "UTF-8";
+
+ private ThreadPoolManager mThreadPoolManager;
+ private IcecastConfiguration mIcecastConfiguration;
+
+ private NioSocketConnector mSocketConnector;
+ private AtomicBoolean mUpdating = new AtomicBoolean();
+ private Queue mMetadataQueue = new LinkedTransferQueue<>();
+ private Map mHTTPHeaders;
+ private boolean mStackTraceLoggingSuppressed;
+
+ /**
+ * Icecast song metadata updater. Each metadata update is processed in the order received and the dispatch of
+ * the HTTP update request is processed in a separate thread (runnable) to avoid delaying streaming of this
+ * broadcaster. When multiple metadata updates are received prior to completion of the current ongoing update
+ * sequence, those updates will be queued and processed in the order received.
+ */
+ public IcecastMetadataUpdater(ThreadPoolManager threadPoolManager, IcecastConfiguration icecastConfiguration)
+ {
+ mThreadPoolManager = threadPoolManager;
+ mIcecastConfiguration = icecastConfiguration;
+ }
+
+ /**
+ * Initializes the network socket and HTTP headers map
+ */
+ private void init()
+ {
+ if (mSocketConnector == null)
+ {
+ mSocketConnector = new NioSocketConnector();
+// mSocketConnector.getFilterChain().addLast("logger", new LoggingFilter(IcecastMetadataUpdater.class));
+ mSocketConnector.getFilterChain().addLast("http_client_codec", new HttpClientCodec());
+
+ //Each metadata update session is single-use, so we'll shut it down upon success or failure
+ mSocketConnector.setHandler(new IoHandlerAdapter()
+ {
+ @Override
+ public void exceptionCaught(IoSession session, Throwable cause) throws Exception
+ {
+// mLog.debug("Session " + session.getId() + " metadata update complete - ERROR received");
+ session.closeNow();
+ }
+
+ @Override
+ public void messageReceived(IoSession session, Object message) throws Exception
+ {
+// mLog.debug("Session " + session.getId() + " metadata update complete - response received");
+ session.closeNow();
+ }
+ });
+
+ mHTTPHeaders = new HashMap<>();
+ mHTTPHeaders.put(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
+ mHTTPHeaders.put(IcecastHeader.AUTHORIZATION.getValue(), mIcecastConfiguration.getBase64EncodedCredentials());
+ }
+ }
+
+ /**
+ * Sends a song metadata update to the remote server. Additional metadata updates received while the updater
+ * is in the process of sending an existing metadata update will be queued and processed in received order.
+ *
+ * Note: due to thread timing, there is a slight chance that a concurrent update request will not be processed
+ * and will remain in the update queue until the next metadata update is requested. However, this is a design
+ * trade-off to avoid having a scheduled runnable repeatedly processing the update queue.
+ */
+ public void update(String metadata)
+ {
+ if (metadata != null)
+ {
+ mMetadataQueue.offer(metadata);
+
+ if (mUpdating.compareAndSet(false, true))
+ {
+ String metadataUpdate = mMetadataQueue.poll();
+
+ while (metadataUpdate != null)
+ {
+ //Ensure we're setup for network communications (only happens once)
+ init();
+
+ HttpRequest updateRequest = createUpdateRequest(metadataUpdate);
+
+ if (updateRequest != null)
+ {
+ mThreadPoolManager.scheduleOnce(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ ConnectFuture connectFuture = mSocketConnector
+ .connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
+ mIcecastConfiguration.getPort()));
+ connectFuture.awaitUninterruptibly();
+ IoSession session = connectFuture.getSession();
+
+ if (session != null)
+ {
+ session.write(updateRequest);
+ }
+ }
+ catch (Exception e)
+ {
+ Throwable throwableCause = e.getCause();
+
+ if (throwableCause instanceof ConnectException)
+ {
+ //Do nothing, the server is unavailable
+ }
+ else
+ {
+ if (!mStackTraceLoggingSuppressed)
+ {
+ mLog.error("Error sending metadata update. Future errors will " +
+ "be suppressed", e);
+
+ mStackTraceLoggingSuppressed = true;
+ }
+ }
+ }
+ }
+ }, 0l, TimeUnit.SECONDS);
+ }
+
+ //Fetch next metadata update to send
+ metadataUpdate = mMetadataQueue.poll();
+ }
+
+ mUpdating.set(false);
+ }
+ }
+ }
+
+ /**
+ * Creates an HTTP GET request to update the metadata on the remote server
+ */
+ private HttpRequest createUpdateRequest(String metadata)
+ {
+ try
+ {
+ String songEncoded = URLEncoder.encode(metadata, UTF8);
+ StringBuilder sb = new StringBuilder();
+ sb.append("mode=updinfo");
+ sb.append("&mount=").append(mIcecastConfiguration.getMountPoint());
+ sb.append("&charset=UTF%2d8");
+ sb.append("&song=").append(songEncoded);
+
+ return new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/admin/metadata",
+ sb.toString(), mHTTPHeaders);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ //This should never happen
+ mLog.error("UTF-8 encoding is not supported - can't update song metadata");
+ }
+
+ return null;
+ }
+}
diff --git a/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
index d4e91b4b..3800a08b 100644
--- a/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
+++ b/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
@@ -31,11 +31,6 @@ import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
-import org.apache.mina.http.HttpClientCodec;
-import org.apache.mina.http.HttpRequestImpl;
-import org.apache.mina.http.api.HttpMethod;
-import org.apache.mina.http.api.HttpRequest;
-import org.apache.mina.http.api.HttpVersion;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.asynchttpclient.DefaultAsyncHttpClient;
import org.slf4j.Logger;
@@ -43,22 +38,15 @@ import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
-import java.net.URLEncoder;
import java.nio.charset.Charset;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class IcecastTCPAudioBroadcaster extends AudioBroadcaster
{
private final static Logger mLog = LoggerFactory.getLogger( IcecastTCPAudioBroadcaster.class );
- private final static String UTF8 = "UTF-8";
private final static String TERMINATOR = "\r\n";
private final static String SEPARATOR = ":";
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
@@ -171,7 +159,7 @@ public class IcecastTCPAudioBroadcaster extends AudioBroadcaster
if(mIcecastMetadataUpdater == null)
{
- mIcecastMetadataUpdater = new IcecastMetadataUpdater();
+ mIcecastMetadataUpdater = new IcecastMetadataUpdater(getThreadPoolManager(), getConfiguration());
}
mIcecastMetadataUpdater.update(sb.toString());
@@ -358,7 +346,7 @@ public class IcecastTCPAudioBroadcaster extends AudioBroadcaster
sb.append("SOURCE ").append(getConfiguration().getMountPoint());
sb.append(" HTTP/1.0").append(TERMINATOR);
- sb.append("Authorization: ").append(getConfiguration().getEncodedCredentials()).append(TERMINATOR);
+ sb.append("Authorization: ").append(getConfiguration().getBase64EncodedCredentials()).append(TERMINATOR);
sb.append(IcecastHeader.USER_AGENT.getValue()).append(SEPARATOR)
.append(SystemProperties.getInstance().getApplicationName()).append(TERMINATOR);
sb.append(IcecastHeader.CONTENT_TYPE.getValue()).append(SEPARATOR)
@@ -459,162 +447,4 @@ public class IcecastTCPAudioBroadcaster extends AudioBroadcaster
super.inputClosed(session);
}
}
-
- public class IcecastMetadataUpdater
- {
- private NioSocketConnector mSocketConnector;
- private AtomicBoolean mUpdating = new AtomicBoolean();
- private Queue mMetadataQueue = new LinkedTransferQueue<>();
- private Map mHTTPHeaders;
- private boolean mStackTraceLoggingSuppressed;
-
- /**
- * Icecast song metadata updater. Each metadata update is processed in the order received and the dispatch of
- * the HTTP update request is processed in a separate thread (runnable) to avoid delaying streaming of this
- * broadcaster. When multiple metadata updates are received prior to completion of the current ongoing update
- * sequence, those updates will be queued and processed in the order received.
- *
- */
- public IcecastMetadataUpdater()
- {
- }
-
- /**
- * Initializes the network socket and HTTP headers map
- */
- private void init()
- {
- if(mSocketConnector == null)
- {
- mSocketConnector = new NioSocketConnector();
-// mSocketConnector.getFilterChain().addLast("logger", new LoggingFilter(IcecastMetadataUpdater.class));
- mSocketConnector.getFilterChain().addLast("http_client_codec", new HttpClientCodec());
-
- //Each metadata update session is single-use, so we'll shut it down upon success or failure
- mSocketConnector.setHandler(new IoHandlerAdapter()
- {
- @Override
- public void exceptionCaught(IoSession session, Throwable cause) throws Exception
- {
-// mLog.debug("Session " + session.getId() + " metadata update complete - ERROR received");
- session.closeNow();
- }
-
- @Override
- public void messageReceived(IoSession session, Object message) throws Exception
- {
-// mLog.debug("Session " + session.getId() + " metadata update complete - response received");
- session.closeNow();
- }
- });
-
- mHTTPHeaders = new HashMap<>();
- mHTTPHeaders.put(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
- mHTTPHeaders.put(IcecastHeader.AUTHORIZATION.getValue(), getConfiguration().getEncodedCredentials());
- }
- }
-
- /**
- * Sends a song metadata update to the remote server. Additional metadata updates received while the updater
- * is in the process of sending an existing metadata update will be queued and processed in received order.
- *
- * Note: due to thread timing, there is a slight chance that a concurrent update request will not be processed
- * and will remain in the update queue until the next metadata update is requested. However, this is a design
- * trade-off to avoid having a scheduled runnable repeatedly processing the update queue.
- */
- public void update(String metadata)
- {
- if(metadata != null)
- {
- mMetadataQueue.offer(metadata);
-
- if(mUpdating.compareAndSet(false, true))
- {
- String metadataUpdate = mMetadataQueue.poll();
-
- while(metadataUpdate != null)
- {
- //Ensure we're setup for network communications (only happens once)
- init();
-
- HttpRequest updateRequest = createUpdateRequest(metadataUpdate);
-
- if(updateRequest != null)
- {
- getThreadPoolManager().scheduleOnce(new Runnable()
- {
- @Override
- public void run()
- {
- try
- {
- ConnectFuture connectFuture = mSocketConnector
- .connect(new InetSocketAddress(getConfiguration().getHost(),
- getConfiguration().getPort()));
- connectFuture.awaitUninterruptibly();
- IoSession session = connectFuture.getSession();
-
- if(session != null)
- {
- session.write(updateRequest);
- }
- }
- catch(Exception e)
- {
- Throwable throwableCause = e.getCause();
-
- if(throwableCause instanceof ConnectException)
- {
- //Do nothing, the server is unavailable
- }
- else
- {
- if(!mStackTraceLoggingSuppressed)
- {
- mLog.error("Error sending metadata update. Future errors will " +
- "be suppressed", e);
-
- mStackTraceLoggingSuppressed = true;
- }
- }
- }
- }
- }, 0l, TimeUnit.SECONDS);
- }
-
- //Fetch next metadata update to send
- metadataUpdate = mMetadataQueue.poll();
- }
-
- mUpdating.set(false);
- }
- }
- }
-
- /**
- * Creates an HTTP GET request to update the metadata on the remote server
- */
- private HttpRequest createUpdateRequest(String metadata)
- {
- try
- {
- String songEncoded = URLEncoder.encode(metadata, UTF8);
- StringBuilder sb = new StringBuilder();
- sb.append("mode=updinfo");
- sb.append("&mount=").append(getConfiguration().getMountPoint());
- sb.append("&charset=UTF%2d8");
- sb.append("&song=").append(songEncoded);
-
- return new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/admin/metadata",
- sb.toString(), mHTTPHeaders);
- }
- catch(UnsupportedEncodingException e)
- {
- //This should never happen
- mLog.error("UTF-8 encoding is not supported - can't update song metadata");
- }
-
- return null;
- }
- }
}
diff --git a/src/audio/broadcast/icecast/IcecastTCPConfiguration.java b/src/audio/broadcast/icecast/IcecastTCPConfiguration.java
index 1b5b3951..413f0a32 100644
--- a/src/audio/broadcast/icecast/IcecastTCPConfiguration.java
+++ b/src/audio/broadcast/icecast/IcecastTCPConfiguration.java
@@ -25,40 +25,25 @@ import audio.broadcast.broadcastify.BroadcastifyConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
-import javax.xml.bind.annotation.XmlTransient;
-import java.util.Base64;
@XmlSeeAlso( {BroadcastifyConfiguration.class } )
-public class IcecastTCPConfiguration extends BroadcastConfiguration
+public class IcecastTCPConfiguration extends IcecastConfiguration
{
private final static Logger mLog = LoggerFactory.getLogger( IcecastTCPConfiguration.class );
- private String mUserName;
- private String mMountPoint;
- private String mDescription;
- private String mGenre;
- private boolean mPublic;
- private int mBitRate = 16;
- private int mChannels = 1;
- private int mSampleRate = 8000;
- private String mURL;
-
public IcecastTCPConfiguration()
{
- setUserName("source");
- //No-arg constructor for JAXB
+ this(BroadcastFormat.MP3);
}
/**
* Icecast 2.3.x and 2.4.x compatible configuration
- * @param format
+ * @param format of audio
*/
public IcecastTCPConfiguration(BroadcastFormat format)
{
super(format);
- setUserName("source");
}
@Override
@@ -90,249 +75,4 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
{
return BroadcastServerType.ICECAST_TCP;
}
-
- /**
- * Base64 encoded version of the username and password with prepended 'Basic ' tag.
- */
- @XmlTransient
- public String getEncodedCredentials()
- {
- StringBuilder sb = new StringBuilder();
-
- if(hasUserName())
- {
- sb.append(getUserName()).append(":").append(getPassword());
- }
- else
- {
- sb.append(getPassword());
- }
-
- String base64 = Base64.getEncoder().encodeToString(sb.toString().getBytes());
-
- return "Basic " + base64;
- }
-
- @XmlAttribute( name="user_name" )
- public String getUserName()
- {
- return mUserName;
- }
-
- public void setUserName(String userName)
- {
- mUserName = userName;
- }
-
- public boolean hasUserName()
- {
- return mUserName != null;
- }
-
- /**
- * Mount point or path to the stream
- * @return mount point
- */
- @XmlAttribute( name="mount_point" )
- public String getMountPoint()
- {
- return mMountPoint;
- }
-
- /**
- * Sets the mount point (path) for the stream
- * @param mountPoint
- */
- public void setMountPoint(String mountPoint)
- {
- if(mountPoint != null && !mountPoint.isEmpty())
- {
- if(mountPoint.startsWith("/"))
- {
- mMountPoint = mountPoint;
- }
- else
- {
- mMountPoint = "/" + mountPoint;
- }
- }
- }
-
- public boolean hasMountPoint()
- {
- return mMountPoint != null;
- }
-
-
- @XmlAttribute( name="description" )
- public String getDescription()
- {
- return mDescription;
- }
-
- public void setDescription(String description)
- {
- mDescription = description;
- }
-
- public boolean hasDescription()
- {
- return mDescription != null;
- }
-
- /**
- * Stream genre
- */
- @XmlAttribute( name="genre" )
- public String getGenre()
- {
- return mGenre;
- }
-
- /**
- * Stream genre
- * @param genre
- */
- public void setGenre(String genre)
- {
- mGenre = genre;
- }
-
- public boolean hasGenre()
- {
- return mGenre != null;
- }
-
- /**
- * Public visibility of the broadcastAudio
- */
- @XmlAttribute( name="public" )
- public boolean isPublic()
- {
- return mPublic;
- }
-
- /**
- * Sets public visibility of the broadcastAudio
- * @param isPublic indicates if the broadcastAudio should be visible to the public
- */
- public void setPublic(boolean isPublic)
- {
- mPublic = isPublic;
- }
-
- /**
- * Number of audio channels in the broadcastAudio
- */
- @XmlAttribute( name="channels" )
- public int getChannels()
- {
- return mChannels;
- }
-
- /**
- * Sets the number of audio channels in the broadcastAudio
- */
- public void setChannels(int channels)
- {
- mChannels = channels;
- }
-
- public boolean hasChannels()
- {
- return mChannels > 0;
- }
-
- /**
- * Bit rate in bits per second
- */
- @XmlAttribute( name="bitrate" )
- public int getBitRate()
- {
- return mBitRate;
- }
-
- /**
- * Bit rate
- * @param bitRate in samples per second
- */
- public void setBitRate(int bitRate)
- {
- mBitRate = bitRate;
- }
-
- public boolean hasBitRate()
- {
- return mBitRate > 0;
- }
-
- @XmlAttribute( name="sample_rate" )
- public int getSampleRate()
- {
- return mSampleRate;
- }
-
- public void setSampleRate(int sampleRate)
- {
- mSampleRate = sampleRate;
- }
-
- public boolean hasSampleRate()
- {
- return mSampleRate > 0;
- }
-
- /**
- * URL associated with the broadcastAudio where users can find additional details.
- */
- @XmlAttribute( name="url" )
- public String getURL()
- {
- return mURL;
- }
-
- /**
- * URL associated with the broadcastAudio where users can find additional details.
- * @param url
- */
- public void setURL(String url)
- {
- mURL = url;
- }
-
- public boolean hasURL()
- {
- return mURL != null;
- }
-
- @Override
- public boolean isValid()
- {
- if(!super.isValid())
- {
- return false;
- }
- else if(mUserName == null)
- {
- return false;
- }
- else if(mMountPoint == null)
- {
- return false;
- }
- else if(mChannels != 1)
- {
- return false;
- }
- else if(mSampleRate <= 0)
- {
- return false;
- }
- else if(mBitRate <= 0)
- {
- return false;
- }
-
- return true;
- }
}
diff --git a/src/audio/broadcast/icecast/IcecastTCPConfigurationEditor.java b/src/audio/broadcast/icecast/IcecastTCPConfigurationEditor.java
index 69b887a4..34dc16f7 100644
--- a/src/audio/broadcast/icecast/IcecastTCPConfigurationEditor.java
+++ b/src/audio/broadcast/icecast/IcecastTCPConfigurationEditor.java
@@ -99,7 +99,7 @@ public class IcecastTCPConfigurationEditor extends BroadcastConfigurationEditor
add(new JLabel("Mount:"));
mMountPoint = new JTextField();
mMountPoint.getDocument().addDocumentListener(this);
- mMountPoint.setToolTipText("Mount point (e.g. \\12345)");
+ mMountPoint.setToolTipText("Mount point (e.g. /12345)");
add(mMountPoint);
add(new JLabel("Password:"));