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:"));