mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#30 - adds streaming status color indicators to the streaming panel table.
Resolves issue with temporary streaming file name collisions by adding a 1-up number to each generated temporary recording file.
This commit is contained in:
@@ -223,7 +223,8 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error deleting temporary internet recording file: " + recording.getPath().toString());
|
||||
mLog.error("Stream [" + getBroadcastConfiguration().getName() + "] - error deleting temporary internet " +
|
||||
"recording file: " + recording.getPath().toString() + " - " + ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,44 +355,51 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
|
||||
{
|
||||
if(mProcessing.compareAndSet(false, true))
|
||||
{
|
||||
if(mInputStream == null || mInputStream.available() <= 0)
|
||||
try
|
||||
{
|
||||
if(mFinalSilencePadding > 0)
|
||||
if(mInputStream == null || mInputStream.available() <= 0)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
|
||||
mFinalSilencePadding = 0;
|
||||
if(mFinalSilencePadding > 0)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
|
||||
mFinalSilencePadding = 0;
|
||||
}
|
||||
|
||||
nextRecording();
|
||||
}
|
||||
|
||||
nextRecording();
|
||||
}
|
||||
|
||||
if(mInputStream != null)
|
||||
{
|
||||
//We need to stream at 13.888 fps (144 byte frame) to achieve 2000 Bps or 16 kbps
|
||||
mBytesStreamedRequired += 2000; //2000 bytes per second for 16 kbps data rate
|
||||
int bytesToStream = mBytesStreamedRequired - mBytesStreamedActual;
|
||||
|
||||
//Trim length to whole-frame intervals (144 byte frame)
|
||||
bytesToStream -= (bytesToStream % 144);
|
||||
|
||||
int length = Math.min(bytesToStream, mInputStream.available());
|
||||
|
||||
byte[] audio = new byte[length];
|
||||
|
||||
try
|
||||
if(mInputStream != null)
|
||||
{
|
||||
mBytesStreamedActual += mInputStream.read(audio);
|
||||
//We need to stream at 13.888 fps (144 byte frame) to achieve 2000 Bps or 16 kbps
|
||||
mBytesStreamedRequired += 2000; //2000 bytes per second for 16 kbps data rate
|
||||
int bytesToStream = mBytesStreamedRequired - mBytesStreamedActual;
|
||||
|
||||
broadcastAudio(audio);
|
||||
//Trim length to whole-frame intervals (144 byte frame)
|
||||
bytesToStream -= (bytesToStream % 144);
|
||||
|
||||
int length = Math.min(bytesToStream, mInputStream.available());
|
||||
|
||||
byte[] audio = new byte[length];
|
||||
|
||||
try
|
||||
{
|
||||
mBytesStreamedActual += mInputStream.read(audio);
|
||||
|
||||
broadcastAudio(audio);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error reading from in-memory audio recording input stream", ioe);
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
else
|
||||
{
|
||||
mLog.error("Error reading from in-memory audio recording input stream", ioe);
|
||||
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch(Exception e)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
|
||||
mLog.error("Error while processing audio streaming queue", e);
|
||||
}
|
||||
|
||||
mProcessing.set(false);
|
||||
@@ -447,17 +455,18 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
|
||||
|
||||
metadataUpdateRequired = false;
|
||||
}
|
||||
|
||||
removeRecording(recording);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error reading temporary audio stream recording [" + recording.getPath().toString() +
|
||||
"] - skipping recording");
|
||||
mLog.error("Stream [" + getBroadcastConfiguration().getName() + "] error reading temporary audio " +
|
||||
"stream recording [" + recording.getPath().toString() + "] - skipping recording - ", ioe);
|
||||
|
||||
mInputStream = null;
|
||||
metadataUpdateRequired = false;
|
||||
}
|
||||
|
||||
removeRecording(recording);
|
||||
|
||||
broadcast(new BroadcastEvent(AudioBroadcaster.this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||
}
|
||||
|
||||
|
||||
@@ -53,11 +53,11 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
|
||||
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
||||
|
||||
private static final int COLUMN_SERVER_ICON = 0;
|
||||
private static final int COLUMN_STREAM_NAME = 1;
|
||||
private static final int COLUMN_BROADCASTER_STATUS = 2;
|
||||
private static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
||||
private static final int COLUMN_BROADCASTER_STREAMED_COUNT = 4;
|
||||
public static final int COLUMN_SERVER_ICON = 0;
|
||||
public static final int COLUMN_STREAM_NAME = 1;
|
||||
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
||||
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
||||
public static final int COLUMN_BROADCASTER_STREAMED_COUNT = 4;
|
||||
|
||||
private List<BroadcastConfiguration> mBroadcastConfigurations = new CopyOnWriteArrayList<>();
|
||||
|
||||
@@ -513,15 +513,15 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
|
||||
if(audioBroadcasterA != null)
|
||||
{
|
||||
return audioBroadcasterA.getBroadcastState().toString();
|
||||
return audioBroadcasterA.getBroadcastState();
|
||||
}
|
||||
else if(!configuration.isEnabled())
|
||||
{
|
||||
return "Disabled";
|
||||
return BroadcastState.DISABLED;
|
||||
}
|
||||
else if(!configuration.isValid())
|
||||
{
|
||||
return "Invalid Settings";
|
||||
return BroadcastState.INVALID_SETTINGS;
|
||||
}
|
||||
case COLUMN_BROADCASTER_QUEUE_SIZE:
|
||||
AudioBroadcaster audioBroadcasterB = mBroadcasterMap.get(configuration.getName());
|
||||
|
||||
@@ -33,6 +33,9 @@ import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.event.TableColumnModelEvent;
|
||||
import javax.swing.event.TableColumnModelListener;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
@@ -77,8 +80,10 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
|
||||
mBroadcastConfigurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
mBroadcastConfigurationTable.getSelectionModel().addListSelectionListener(BroadcastPanel.this);
|
||||
|
||||
mBroadcastConfigurationTable.getColumnModel().getColumn(0).setPreferredWidth(1);
|
||||
mBroadcastConfigurationTable.getColumnModel().getColumn(1).setPreferredWidth(25);
|
||||
mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_SERVER_ICON).setPreferredWidth(1);
|
||||
mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_STREAM_NAME).setPreferredWidth(25);
|
||||
mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS)
|
||||
.setCellRenderer(new StatusCellRenderer());
|
||||
|
||||
JScrollPane scroller = new JScrollPane(mBroadcastConfigurationTable);
|
||||
|
||||
@@ -261,4 +266,67 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom cell renderer for the broadcast state column.
|
||||
*/
|
||||
public class StatusCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
public StatusCellRenderer()
|
||||
{
|
||||
setOpaque(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
|
||||
boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if(isSelected)
|
||||
{
|
||||
setBackground(table.getSelectionBackground());
|
||||
setForeground(table.getSelectionForeground());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(value instanceof BroadcastState)
|
||||
{
|
||||
BroadcastState state = (BroadcastState)value;
|
||||
|
||||
if(state == BroadcastState.CONNECTED)
|
||||
{
|
||||
setBackground(Color.GREEN);
|
||||
setForeground(table.getForeground());
|
||||
}
|
||||
else if(state == BroadcastState.DISABLED)
|
||||
{
|
||||
setBackground(table.getBackground());
|
||||
setForeground(Color.LIGHT_GRAY);
|
||||
}
|
||||
else if(state == BroadcastState.INVALID_SETTINGS)
|
||||
{
|
||||
setBackground(Color.YELLOW);
|
||||
setForeground(table.getForeground());
|
||||
}
|
||||
else if(state.isErrorState())
|
||||
{
|
||||
setBackground(Color.RED);
|
||||
setForeground(table.getForeground());
|
||||
}
|
||||
else
|
||||
{
|
||||
setBackground(table.getBackground());
|
||||
setForeground(table.getForeground());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setForeground(table.getForeground());
|
||||
setBackground(table.getBackground());
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ public enum BroadcastState
|
||||
*/
|
||||
CONNECTING("Connecting", false),
|
||||
|
||||
/**
|
||||
* Indicates the configuration is disabled
|
||||
*/
|
||||
DISABLED("Disabled", true),
|
||||
|
||||
/**
|
||||
* Disconnected from the broadcastAudio server
|
||||
*/
|
||||
@@ -50,6 +55,11 @@ public enum BroadcastState
|
||||
*/
|
||||
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true),
|
||||
|
||||
/**
|
||||
* Invalid configuration settings
|
||||
*/
|
||||
INVALID_SETTINGS("Invalid Settings", true),
|
||||
|
||||
/**
|
||||
* Remote server max sources has been exceeded
|
||||
*/
|
||||
|
||||
@@ -30,18 +30,20 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class StreamManager implements Listener<AudioPacket>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class);
|
||||
private static final long MAXIMUM_RECORDER_LIFESPAN_MILLIS = 30000; //30 seconds
|
||||
|
||||
private static AtomicInteger sNextRecordingNumber = new AtomicInteger();
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private AudioBroadcaster mAudioBroadcaster;
|
||||
private Path mTempDirectory;
|
||||
@@ -197,6 +199,7 @@ public class StreamManager implements Listener<AudioPacket>
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
|
||||
sb.append(sNextRecordingNumber.incrementAndGet()).append("_");
|
||||
sb.append( TimeStamp.getLongTimeStamp( "_" ) );
|
||||
sb.append( mAudioBroadcaster.getBroadcastConfiguration().getBroadcastFormat().getFileExtension() );
|
||||
|
||||
@@ -221,6 +224,9 @@ public class StreamManager implements Listener<AudioPacket>
|
||||
.filter(entry -> entry.getValue().getTimeRecordingStart() + MAXIMUM_RECORDER_LIFESPAN_MILLIS < now)
|
||||
.forEach(entry ->
|
||||
{
|
||||
mLog.debug("Maximum recording time limit reached - removing recorder: " +
|
||||
entry.getValue().getPath().toString());
|
||||
|
||||
removeRecorder(entry.getKey());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v1;
|
||||
|
||||
import audio.broadcast.AudioBroadcaster;
|
||||
import audio.broadcast.BroadcastConfiguration;
|
||||
import audio.broadcast.shoutcast.ShoutcastMetadataUpdater;
|
||||
import audio.metadata.AudioMetadata;
|
||||
import controller.ThreadPoolManager;
|
||||
|
||||
public abstract class ShoutcastAudioBroadcaster extends AudioBroadcaster
|
||||
{
|
||||
private ShoutcastMetadataUpdater mMetadataUpdater;
|
||||
|
||||
public ShoutcastAudioBroadcaster(ThreadPoolManager threadPoolManager, BroadcastConfiguration broadcastConfiguration)
|
||||
{
|
||||
super(threadPoolManager, broadcastConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void broadcastMetadata(AudioMetadata metadata)
|
||||
{
|
||||
if(mMetadataUpdater == null)
|
||||
{
|
||||
mMetadataUpdater = new ShoutcastMetadataUpdater(getThreadPoolManager(),
|
||||
(ShoutcastV1Configuration)getBroadcastConfiguration());
|
||||
}
|
||||
|
||||
mMetadataUpdater.update(metadata);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v1;
|
||||
|
||||
import audio.broadcast.AudioBroadcaster;
|
||||
import audio.broadcast.BroadcastState;
|
||||
import audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import controller.ThreadPoolManager;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
@@ -37,13 +39,14 @@ import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV1AudioBroadcaster extends ShoutcastAudioBroadcaster
|
||||
public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV1AudioBroadcaster.class );
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
|
||||
|
||||
private NioSocketConnector mSocketConnector;
|
||||
private IoSession mStreamingSession = null;
|
||||
private IBroadcastMetadataUpdater mMetadataUpdater;
|
||||
|
||||
private long mLastConnectionAttempt = 0;
|
||||
private AtomicBoolean mConnecting = new AtomicBoolean();
|
||||
@@ -71,6 +74,16 @@ public class ShoutcastV1AudioBroadcaster extends ShoutcastAudioBroadcaster
|
||||
return (ShoutcastV1Configuration)getBroadcastConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IBroadcastMetadataUpdater getMetadataUpdater()
|
||||
{
|
||||
if(mMetadataUpdater == null)
|
||||
{
|
||||
mMetadataUpdater = new ShoutcastV1BroadcastMetadataUpdater(getThreadPoolManager(), getConfiguration());
|
||||
}
|
||||
|
||||
return mMetadataUpdater;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the audio frame or sequence
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
* Copyright (C) 2014-2017 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
|
||||
@@ -16,12 +16,9 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast;
|
||||
package audio.broadcast.shoutcast.v1;
|
||||
|
||||
import audio.broadcast.icecast.IcecastConfiguration;
|
||||
import audio.broadcast.icecast.IcecastHeader;
|
||||
import audio.broadcast.shoutcast.v1.ShoutcastMetadata;
|
||||
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
import audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import audio.metadata.AudioMetadata;
|
||||
import audio.metadata.Metadata;
|
||||
import audio.metadata.MetadataType;
|
||||
@@ -29,9 +26,6 @@ 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.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;
|
||||
@@ -40,7 +34,6 @@ 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;
|
||||
@@ -53,9 +46,9 @@ import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastMetadataUpdater
|
||||
public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastMetadataUpdater.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1BroadcastMetadataUpdater.class);
|
||||
private final static String UTF8 = "UTF-8";
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
@@ -73,7 +66,7 @@ public class ShoutcastMetadataUpdater
|
||||
* 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 ShoutcastMetadataUpdater(ThreadPoolManager threadPoolManager, ShoutcastV1Configuration shoutcastV1Configuration)
|
||||
public ShoutcastV1BroadcastMetadataUpdater(ThreadPoolManager threadPoolManager, ShoutcastV1Configuration shoutcastV1Configuration)
|
||||
{
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
mShoutcastV1Configuration = shoutcastV1Configuration;
|
||||
@@ -89,7 +82,7 @@ public class ShoutcastMetadataUpdater
|
||||
mSocketConnector = new NioSocketConnector();
|
||||
|
||||
// mSocketConnector.getFilterChain().addLast("logger",
|
||||
// new LoggingFilter(ShoutcastMetadataUpdater.class));
|
||||
// new LoggingFilter(ShoutcastV1BroadcastMetadataUpdater.class));
|
||||
|
||||
mSocketConnector.getFilterChain().addLast("http_client_codec", new HttpClientCodec());
|
||||
|
||||
@@ -257,7 +250,6 @@ public class ShoutcastMetadataUpdater
|
||||
sb.append("&song=").append(URLEncoder.encode(song, UTF8));
|
||||
|
||||
Map<String,String> headers = new HashMap<>();
|
||||
// headers.put("User-Agent", SystemProperties.getInstance().getApplicationName() + " (Mozilla compatible)");
|
||||
|
||||
HttpRequestImpl request = new HttpRequestImpl(HttpVersion.HTTP_1_0, HttpMethod.GET, "/admin.cgi",
|
||||
sb.toString(), headers);
|
||||
@@ -239,7 +239,6 @@ public class ShoutcastV1ConfigurationEditor extends BroadcastConfigurationEditor
|
||||
updateConfigurationName(config, mName.getText());
|
||||
config.setHost(mServer.getText());
|
||||
config.setPort(getPort());
|
||||
config.setDescription(mDescription.getText());
|
||||
config.setPassword(mPassword.getText());
|
||||
config.setDescription(mDescription.getText());
|
||||
config.setGenre(mGenre.getText());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
* Copyright (C) 2014-2017 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
|
||||
@@ -18,120 +18,199 @@
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2;
|
||||
|
||||
import audio.AudioPacket;
|
||||
import audio.broadcast.AudioBroadcaster;
|
||||
import audio.broadcast.BroadcastFactory;
|
||||
import audio.broadcast.BroadcastFormat;
|
||||
import audio.broadcast.BroadcastState;
|
||||
import audio.broadcast.shoutcast.v2.message.AuthenticateBroadcast;
|
||||
import audio.broadcast.shoutcast.v2.message.ConfigureIcyGenre;
|
||||
import audio.broadcast.shoutcast.v2.message.ConfigureIcyName;
|
||||
import audio.broadcast.shoutcast.v2.message.ConfigureIcyPublic;
|
||||
import audio.broadcast.shoutcast.v2.message.ConfigureIcyURL;
|
||||
import audio.broadcast.shoutcast.v2.message.MP3Audio;
|
||||
import audio.broadcast.shoutcast.v2.message.NegotiateMaxPayloadSize;
|
||||
import audio.broadcast.shoutcast.v2.message.RequestCipher;
|
||||
import audio.broadcast.shoutcast.v2.message.SetupBroadcast;
|
||||
import audio.broadcast.shoutcast.v2.message.Standby;
|
||||
import audio.broadcast.shoutcast.v2.message.UltravoxMessage;
|
||||
import audio.broadcast.shoutcast.v2.message.UltravoxMessageFactory;
|
||||
import audio.broadcast.shoutcast.v2.message.UltravoxMessageType;
|
||||
import audio.broadcast.shoutcast.v2.message.UltravoxMetadata;
|
||||
import audio.broadcast.shoutcast.v2.message.XMLMetadata;
|
||||
import audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.AuthenticateBroadcast;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.CacheableXMLMetadata;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.ConfigureIcyName;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.ConfigureIcyPublic;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.MP3Audio;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.NegotiateMaxPayloadSize;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.RequestCipher;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.SetupBroadcast;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.Standby;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.StreamMimeType;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.TerminateBroadcast;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.UltravoxMessage;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageFactory;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageType;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.UltravoxMetadata;
|
||||
import audio.broadcast.shoutcast.v2.ultravox.UltravoxProtocolFactory;
|
||||
import audio.metadata.AudioMetadata;
|
||||
import audio.metadata.Metadata;
|
||||
import audio.metadata.MetadataType;
|
||||
import controller.ThreadPoolManager;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
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.filter.codec.ProtocolCodecFilter;
|
||||
import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import properties.SystemProperties;
|
||||
import record.wave.AudioPacketMonoWaveReader;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster
|
||||
public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBroadcastMetadataUpdater
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2AudioBroadcaster.class );
|
||||
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
|
||||
private static final boolean GET_RESPONSE = true;
|
||||
private static final boolean NO_RESPONSE_EXPECTED = false;
|
||||
private long mLastConnectionAttempt = 0;
|
||||
|
||||
private Socket mSocket;
|
||||
private DataOutputStream mOutputStream;
|
||||
private DataInputStream mInputStream;
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
|
||||
private int mMaxPayloadSize = 16377;
|
||||
private List<AudioPacket> mPacketsToBroadcast = new ArrayList<>();
|
||||
|
||||
private NioSocketConnector mSocketConnector;
|
||||
private IoSession mStreamingSession = null;
|
||||
|
||||
private long mLastConnectionAttempt = 0;
|
||||
private AtomicBoolean mConnecting = new AtomicBoolean();
|
||||
private LinkedTransferQueue<UltravoxMessage> mMetadataMessageQueue = new LinkedTransferQueue<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Shoutcast Version 2 broadcaster.
|
||||
* @param configuration details for shoutcast version 2
|
||||
* Shoutcast 2.x (Ultravox 2.1) broadcaster. This broadcaster is compatible with Shoutcast 2.x and newer versions
|
||||
* of the server software.
|
||||
*
|
||||
* Note: use @see ShoutcastV1AudioBroadcaster for Shoutcast version 1.x and older.
|
||||
*
|
||||
* This broadcaster uses the Apache Mina library for the streaming socket connection. The
|
||||
* ShoutcastV2IOHandler manages all interaction with the Shoutcast server and manages the overall broadcast state.
|
||||
*
|
||||
* @param threadPoolManager for thread pool access
|
||||
* @param configuration for the Shoutcast V2 stream
|
||||
*/
|
||||
public ShoutcastV2AudioBroadcaster(ThreadPoolManager threadPoolManager, ShoutcastV2Configuration configuration)
|
||||
{
|
||||
super(threadPoolManager, configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shoutcast V2 Configuration information
|
||||
*/
|
||||
private ShoutcastV2Configuration getShoutcastConfiguration()
|
||||
public ShoutcastV2Configuration getConfiguration()
|
||||
{
|
||||
return (ShoutcastV2Configuration)getBroadcastConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void broadcastMetadata(AudioMetadata metadata)
|
||||
{
|
||||
mLog.debug("Request to broadcast audio metadata to shoutcast v2 server - needs coding");
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast audio
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
{
|
||||
//If we're connected, send the audio, otherwise discard it
|
||||
if(connect())
|
||||
{
|
||||
List<UltravoxMessage> audioMessages = getAudioMessages(audio);
|
||||
//Dispatch any queued metadata messages
|
||||
UltravoxMessage metadataMessage = mMetadataMessageQueue.poll();
|
||||
|
||||
try
|
||||
{
|
||||
for(UltravoxMessage audioMessage: audioMessages)
|
||||
{
|
||||
send(audioMessage, NO_RESPONSE_EXPECTED);
|
||||
}
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error while dispatching audio", e);
|
||||
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||
return;
|
||||
}
|
||||
while(metadataMessage != null)
|
||||
{
|
||||
mStreamingSession.write(metadataMessage);
|
||||
metadataMessage = mMetadataMessageQueue.poll();
|
||||
}
|
||||
|
||||
//Dispatch audio message
|
||||
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
|
||||
{
|
||||
MP3Audio mp3Audio = new MP3Audio();
|
||||
mp3Audio.setPayload(audio);
|
||||
|
||||
mStreamingSession.write(mp3Audio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(AudioMetadata metadata)
|
||||
{
|
||||
List<UltravoxMessage> metadataMessages = getMetadataMessages(metadata);
|
||||
|
||||
if(!metadataMessages.isEmpty())
|
||||
{
|
||||
mMetadataMessageQueue.addAll(metadataMessages);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IBroadcastMetadataUpdater getMetadataUpdater()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the audio handler is currently connected to the remote server and capable of streaming audio.
|
||||
* (Re)Connects the broadcaster to the remote server if it currently is disconnected and indicates if the broadcaster
|
||||
* is currently connected to the remote server following any connection attempts.
|
||||
*
|
||||
* Attempts to connect via this method when the broadcast state indicates an error condition will be ignored.
|
||||
*
|
||||
* This method will attempt to establish a connection or reconnect to the streaming server.
|
||||
* @return true if the audio handler can stream audio
|
||||
*/
|
||||
private boolean connect()
|
||||
{
|
||||
if(!connected())
|
||||
if(!connected() && canConnect() &&
|
||||
(mLastConnectionAttempt + RECONNECT_INTERVAL_MILLISECONDS < System.currentTimeMillis()) &&
|
||||
mConnecting.compareAndSet(false, true))
|
||||
{
|
||||
createConnection();
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
if(mSocketConnector == null)
|
||||
{
|
||||
mSocketConnector = new NioSocketConnector();
|
||||
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
||||
|
||||
// mSocketConnector.getFilterChain().addLast("logger",
|
||||
// new LoggingFilter(ShoutcastV2AudioBroadcaster.class));
|
||||
|
||||
mSocketConnector.getFilterChain().addLast("codec",
|
||||
new ProtocolCodecFilter(new UltravoxProtocolFactory()));
|
||||
|
||||
mSocketConnector.setHandler(new ShoutcastV2IOHandler());
|
||||
}
|
||||
|
||||
mStreamingSession = null;
|
||||
|
||||
Runnable runnable = new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTING);
|
||||
|
||||
try
|
||||
{
|
||||
ConnectFuture future = mSocketConnector
|
||||
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
|
||||
getBroadcastConfiguration().getPort()));
|
||||
future.awaitUninterruptibly();
|
||||
mStreamingSession = future.getSession();
|
||||
}
|
||||
catch(RuntimeIoException rie)
|
||||
{
|
||||
Throwable throwableCause = rie.getCause();
|
||||
|
||||
if(throwableCause instanceof ConnectException)
|
||||
{
|
||||
setBroadcastState(BroadcastState.NO_SERVER);
|
||||
}
|
||||
else if(throwableCause != null)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
mLog.error("Failed to connect", rie);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
mLog.error("Failed to connect - no exception is available");
|
||||
}
|
||||
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getThreadPoolManager().scheduleOnce(runnable, 0l, TimeUnit.SECONDS);
|
||||
|
||||
}
|
||||
|
||||
return connected();
|
||||
@@ -139,112 +218,85 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect from the remote server
|
||||
* Disconnect from the remote broadcast server and cleanup input/output streams and socket connection
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
// TerminateBroadcast terminate = (TerminateBroadcast)UltravoxMessageFactory
|
||||
// .getMessage(UltravoxMessageType.TERMINATE_BROADCAST);
|
||||
// response = send(terminate, out, in);
|
||||
// mLog.debug("Terminate Response:" + (response.getPayload() != null ? response.getPayload() : ""));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the message. When get response is true, reads from the input stream and converts the response to a
|
||||
* message that is returned.
|
||||
*
|
||||
* @param message to send to the shoutcast/ultravox server
|
||||
* @param getResponse true if the sent message will generate a response
|
||||
* @return response message when getResponse is set to true
|
||||
* @throws IOException if there is an issue while communicating with the server
|
||||
*/
|
||||
private UltravoxMessage send(UltravoxMessage message, boolean getResponse) throws IOException
|
||||
{
|
||||
byte[] bytes = message.getMessage();
|
||||
|
||||
mOutputStream.write(bytes);
|
||||
|
||||
if(getResponse)
|
||||
if(connected())
|
||||
{
|
||||
try
|
||||
if(mStreamingSession != null)
|
||||
{
|
||||
Thread.sleep(50);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
int available = mInputStream.available();
|
||||
|
||||
if(available > 0)
|
||||
{
|
||||
byte[] buffer = new byte[available];
|
||||
int read = mInputStream.read(buffer);
|
||||
|
||||
return UltravoxMessageFactory.getMessage(buffer);
|
||||
mStreamingSession.write(new TerminateBroadcast());
|
||||
mStreamingSession.closeNow();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the encoded audio byte array into a list of ultravox messages ready for transmission
|
||||
* @param audio to embed as message payload
|
||||
* @return list of messages containing the audio payload
|
||||
*/
|
||||
private List<UltravoxMessage> getAudioMessages(byte[] audio)
|
||||
{
|
||||
List<UltravoxMessage> messages = new ArrayList<>();
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
while(pointer < audio.length)
|
||||
{
|
||||
int payloadSize = Math.min(mMaxPayloadSize, audio.length - pointer);
|
||||
|
||||
byte[] payload = new byte[payloadSize];
|
||||
|
||||
System.arraycopy(audio, pointer, payload, 0, payloadSize);
|
||||
|
||||
MP3Audio mp3Audio = (MP3Audio)UltravoxMessageFactory.getMessage(UltravoxMessageType.MP3_DATA);
|
||||
mp3Audio.setPayload(payload);
|
||||
messages.add(mp3Audio);
|
||||
|
||||
pointer += payloadSize;
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the list of metadata in one or more Ultravox XMLMetadata messages according to the maximum negotiated
|
||||
* Encodes the list of metadata in one or more Ultravox CacheableXMLMetadata messages according to the maximum negotiated
|
||||
* payload size for the current connection. Each entry in the list of metadata strings should be an xml encoded
|
||||
* value: <tag>value</tag>
|
||||
*
|
||||
* See UltravoxMetadata.TAG.asXML(String value)
|
||||
*
|
||||
* @param xmlMetadata list of xml encoded values
|
||||
* @return a sequence of XMLMetadata messages sufficient to carry the complete set of metadata
|
||||
* @param metadata containing audio metadata tags and attributes
|
||||
* @return a sequence of CacheableXMLMetadata messages sufficient to carry the complete set of metadata
|
||||
*/
|
||||
private List<UltravoxMessage> getMetadataMessages(List<String> xmlMetadata)
|
||||
private List<UltravoxMessage> getMetadataMessages(AudioMetadata metadata)
|
||||
{
|
||||
List<UltravoxMessage> messages = new ArrayList<>();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<metadata>\n");
|
||||
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?><metadata>");
|
||||
|
||||
for(String metadata: xmlMetadata)
|
||||
sb.append(UltravoxMetadata.ALBUM_TITLE.asXML(getConfiguration().getName()));
|
||||
|
||||
sb.append(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION.asXML(SystemProperties.getInstance().getApplicationName()));
|
||||
|
||||
if(getConfiguration().hasGenre())
|
||||
{
|
||||
sb.append(metadata);
|
||||
sb.append(UltravoxMetadata.GENRE.asXML(getConfiguration().getGenre()));
|
||||
}
|
||||
|
||||
sb.append("\n</metadata>");
|
||||
if(getConfiguration().hasURL())
|
||||
{
|
||||
sb.append(UltravoxMetadata.URL.asXML(getConfiguration().getURL()));
|
||||
}
|
||||
|
||||
if(metadata != null)
|
||||
{
|
||||
Metadata to = metadata.getMetadata(MetadataType.TO);
|
||||
if(to != null)
|
||||
{
|
||||
if(to.hasAlias())
|
||||
{
|
||||
sb.append(UltravoxMetadata.TITLE_2.asXML(to.getAlias().toString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(UltravoxMetadata.TITLE_2.asXML(to.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
Metadata from = metadata.getMetadata(MetadataType.FROM);
|
||||
if(from != null)
|
||||
{
|
||||
if(from.hasAlias())
|
||||
{
|
||||
sb.append(UltravoxMetadata.TITLE_3.asXML("From: " + from.getAlias().toString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(UltravoxMetadata.TITLE_3.asXML("From: " + from.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(UltravoxMetadata.TITLE_2.asXML("Scanning ..."));
|
||||
}
|
||||
|
||||
sb.append("</metadata>");
|
||||
|
||||
mLog.debug("Metadata:" + sb.toString());
|
||||
byte[] xml = sb.toString().getBytes();
|
||||
|
||||
int pointer = 0;
|
||||
@@ -257,6 +309,8 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster
|
||||
}
|
||||
|
||||
|
||||
List<UltravoxMessage> messages = new ArrayList<>();
|
||||
|
||||
while(pointer < xml.length && messageCounter <= messageCount)
|
||||
{
|
||||
int payloadSize = Math.min(mMaxPayloadSize - 6, xml.length - pointer);
|
||||
@@ -269,7 +323,7 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster
|
||||
|
||||
System.arraycopy(xml, pointer, payload, 6, payloadSize);
|
||||
|
||||
XMLMetadata message = (XMLMetadata)UltravoxMessageFactory.getMessage(UltravoxMessageType.XML_METADATA);
|
||||
CacheableXMLMetadata message = new CacheableXMLMetadata();
|
||||
message.setPayload(payload);
|
||||
messages.add(message);
|
||||
|
||||
@@ -281,235 +335,194 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster
|
||||
|
||||
|
||||
/**
|
||||
* Creates a connnection to the remote server using the shoutcast configuration information. Once disconnected
|
||||
* following a successful connection, attempts to reestablish a connection on a set interval
|
||||
* IO Handler for managing Shoutcast V2 connection and credentials
|
||||
*/
|
||||
private void createConnection()
|
||||
public class ShoutcastV2IOHandler extends IoHandlerAdapter
|
||||
{
|
||||
if(!connected() && System.currentTimeMillis() - mLastConnectionAttempt >= RECONNECT_INTERVAL_MILLISECONDS)
|
||||
/**
|
||||
* Sends stream configuration and user credentials upon connecting to remote server
|
||||
*/
|
||||
@Override
|
||||
public void sessionOpened(IoSession session) throws Exception
|
||||
{
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
session.write(UltravoxMessageFactory.getMessage(UltravoxMessageType.REQUEST_CIPHER));
|
||||
}
|
||||
|
||||
createSocket();
|
||||
|
||||
if (mSocket.isConnected())
|
||||
@Override
|
||||
public void sessionClosed(IoSession session) throws Exception
|
||||
{
|
||||
//If there is already an error state, don't override it. Otherwise, set state to disconnected
|
||||
if(!getBroadcastState().isErrorState())
|
||||
{
|
||||
RequestCipher requestCipher = (RequestCipher) UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.REQUEST_CIPHER);
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
}
|
||||
|
||||
try
|
||||
mSocketConnector.dispose();
|
||||
mStreamingSession = null;
|
||||
mSocketConnector = null;
|
||||
|
||||
mConnecting.set(false);
|
||||
super.sessionClosed(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(IoSession session, Throwable cause) throws Exception
|
||||
{
|
||||
if(cause instanceof IOException && ((IOException)cause).getMessage().startsWith("Connection reset"))
|
||||
{
|
||||
mLog.info("Streaming connection reset by remote server - reestablishing connection");
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Broadcast error", cause);
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
disconnect();
|
||||
}
|
||||
|
||||
mConnecting.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(IoSession session, Object object) throws Exception
|
||||
{
|
||||
if(object instanceof UltravoxMessage)
|
||||
{
|
||||
UltravoxMessage ultravoxMessage = (UltravoxMessage)object;
|
||||
|
||||
switch (ultravoxMessage.getMessageType())
|
||||
{
|
||||
UltravoxMessage response = send(requestCipher, GET_RESPONSE);
|
||||
case REQUEST_CIPHER:
|
||||
String cipherKey = ((RequestCipher)ultravoxMessage).getCipher();
|
||||
|
||||
if (response instanceof RequestCipher)
|
||||
{
|
||||
RequestCipher cipherResponse = (RequestCipher) response;
|
||||
AuthenticateBroadcast authenticateBroadcast = new AuthenticateBroadcast();
|
||||
|
||||
AuthenticateBroadcast authenticateBroadcast = (AuthenticateBroadcast) UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.AUTHENTICATE_BROADCAST);
|
||||
authenticateBroadcast.setCredentials(cipherResponse.getCipher(),
|
||||
getShoutcastConfiguration().getStreamID(), getShoutcastConfiguration().getUserID(),
|
||||
getShoutcastConfiguration().getPassword());
|
||||
response = send(authenticateBroadcast, GET_RESPONSE);
|
||||
authenticateBroadcast.setCredentials(cipherKey, getConfiguration().getStreamID(),
|
||||
getConfiguration().getUserID(), getConfiguration().getPassword());
|
||||
|
||||
if (response.isErrorResponse())
|
||||
session.write(authenticateBroadcast);
|
||||
break;
|
||||
case AUTHENTICATE_BROADCAST:
|
||||
if(ultravoxMessage.isErrorResponse())
|
||||
{
|
||||
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
|
||||
return;
|
||||
}
|
||||
String errorMessage = ultravoxMessage.getErrorMessage();
|
||||
|
||||
UltravoxMessage streamMimeType = UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.STREAM_MIME_TYPE);
|
||||
streamMimeType.setPayload(getShoutcastConfiguration().getBroadcastFormat().getValue());
|
||||
response = send(streamMimeType, GET_RESPONSE);
|
||||
|
||||
if (response.isErrorResponse())
|
||||
{
|
||||
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
|
||||
mLog.error("Audio format [" + getShoutcastConfiguration().getBroadcastFormat() + "] not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
SetupBroadcast setupBroadcast = (SetupBroadcast) UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.SETUP_BROADCAST);
|
||||
setupBroadcast.setBitRate(getShoutcastConfiguration().getBitRate(),
|
||||
getShoutcastConfiguration().getBitRate());
|
||||
response = send(setupBroadcast, GET_RESPONSE);
|
||||
|
||||
if (response.isErrorResponse())
|
||||
{
|
||||
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
|
||||
mLog.error("Audio bit rate [" + getShoutcastConfiguration().getBitRate() + "] not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
NegotiateMaxPayloadSize negotiateMaxPayloadSize = (NegotiateMaxPayloadSize) UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.NEGOTIATE_MAX_PAYLOAD_SIZE);
|
||||
negotiateMaxPayloadSize.setMaximumPayloadSize(mMaxPayloadSize, 14000);
|
||||
response = send(negotiateMaxPayloadSize, GET_RESPONSE);
|
||||
mMaxPayloadSize = ((NegotiateMaxPayloadSize) response).getMaximumPayloadSize();
|
||||
|
||||
List<String> metadata = new ArrayList<>();
|
||||
|
||||
if (getShoutcastConfiguration().getStreamName() != null)
|
||||
{
|
||||
metadata.add(UltravoxMetadata.TITLE_1.asXML(getShoutcastConfiguration().getStreamName()));
|
||||
|
||||
ConfigureIcyName icyName = (ConfigureIcyName) UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.CONFIGURE_ICY_NAME);
|
||||
icyName.setName(getShoutcastConfiguration().getStreamName());
|
||||
response = send(icyName, NO_RESPONSE_EXPECTED);
|
||||
}
|
||||
|
||||
if (getShoutcastConfiguration().getGenre() != null)
|
||||
{
|
||||
metadata.add(UltravoxMetadata.GENRE.asXML(getShoutcastConfiguration().getGenre()));
|
||||
|
||||
ConfigureIcyGenre icyGenre = (ConfigureIcyGenre) UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.CONFIGURE_ICY_GENRE);
|
||||
icyGenre.setGenre(getShoutcastConfiguration().getGenre());
|
||||
response = send(icyGenre, NO_RESPONSE_EXPECTED);
|
||||
}
|
||||
|
||||
if (getShoutcastConfiguration().getURL() != null)
|
||||
{
|
||||
ConfigureIcyURL icyURL = (ConfigureIcyURL) UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.CONFIGURE_ICY_URL);
|
||||
icyURL.setURL(getShoutcastConfiguration().getURL());
|
||||
response = send(icyURL, NO_RESPONSE_EXPECTED);
|
||||
}
|
||||
|
||||
ConfigureIcyPublic icyPublic = (ConfigureIcyPublic) UltravoxMessageFactory
|
||||
.getMessage(UltravoxMessageType.CONFIGURE_ICY_PUBLIC);
|
||||
icyPublic.setPublic(getShoutcastConfiguration().isPublic());
|
||||
response = send(icyPublic, NO_RESPONSE_EXPECTED);
|
||||
|
||||
Standby standby = (Standby) UltravoxMessageFactory.getMessage(UltravoxMessageType.STANDBY);
|
||||
response = send(standby, GET_RESPONSE);
|
||||
|
||||
if (response.isValidResponse())
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
if(errorMessage.startsWith(AuthenticateBroadcast.STREAM_ID_ERROR))
|
||||
{
|
||||
setBroadcastState(BroadcastState.INVALID_MOUNT_POINT);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Invalid Credentials - response: " + ultravoxMessage.getPayload());
|
||||
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
mLog.error("Error message after configuration and sending standby:" +
|
||||
(response.getError() != null ? response.getError() : "no error message"));
|
||||
StreamMimeType streamMimeType = new StreamMimeType();
|
||||
streamMimeType.setFormat(getConfiguration().getBroadcastFormat());
|
||||
session.write(streamMimeType);
|
||||
}
|
||||
|
||||
metadata.add(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION
|
||||
.asXML(SystemProperties.getInstance().getApplicationName()));
|
||||
|
||||
mLog.debug("Fetching metadata messages from metadata...");
|
||||
List<UltravoxMessage> messages = getMetadataMessages(metadata);
|
||||
|
||||
for (UltravoxMessage message : messages)
|
||||
break;
|
||||
case STREAM_MIME_TYPE:
|
||||
if(ultravoxMessage.isErrorResponse())
|
||||
{
|
||||
mLog.debug("Sending metadata message");
|
||||
send(message, GET_RESPONSE);
|
||||
mLog.error("Unsupported Audio Format:" + getConfiguration().getBroadcastFormat().toString() +
|
||||
" - " + ultravoxMessage.getErrorMessage());
|
||||
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupBroadcast setupBroadcast = new SetupBroadcast();
|
||||
|
||||
mLog.debug("We're connected!!!");
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
mLog.error("Error while creating connection to server", e);
|
||||
//Use the same value for average and minimum bit rates
|
||||
setupBroadcast.setBitRate(getConfiguration().getBitRate(), getConfiguration().getBitRate());
|
||||
|
||||
session.write(setupBroadcast);
|
||||
}
|
||||
break;
|
||||
case SETUP_BROADCAST:
|
||||
if(ultravoxMessage.isErrorResponse())
|
||||
{
|
||||
mLog.error("Unsupported Audio Bit Rate:" + getConfiguration().getBitRate() +
|
||||
" - " + ultravoxMessage.getErrorMessage());
|
||||
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
|
||||
}
|
||||
else
|
||||
{
|
||||
NegotiateMaxPayloadSize negotiateMaxPayloadSize = new NegotiateMaxPayloadSize();
|
||||
|
||||
negotiateMaxPayloadSize.setMaximumPayloadSize(16377, 4192);
|
||||
|
||||
session.write(negotiateMaxPayloadSize);
|
||||
}
|
||||
break;
|
||||
case NEGOTIATE_MAX_PAYLOAD_SIZE:
|
||||
if(ultravoxMessage.isErrorResponse())
|
||||
{
|
||||
mLog.error("Unsupported maximum payload size (18 min - 36 kbps max) - " +
|
||||
ultravoxMessage.getErrorMessage());
|
||||
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
|
||||
}
|
||||
else
|
||||
{
|
||||
mMaxPayloadSize = ((NegotiateMaxPayloadSize)ultravoxMessage).getMaximumPayloadSize();
|
||||
|
||||
ConfigureIcyPublic configureIcyPublic = new ConfigureIcyPublic();
|
||||
configureIcyPublic.setPublic(getConfiguration().isPublic());
|
||||
session.write(configureIcyPublic);
|
||||
}
|
||||
break;
|
||||
case CONFIGURE_ICY_PUBLIC:
|
||||
if(ultravoxMessage.isErrorResponse())
|
||||
{
|
||||
mLog.error("Error setting shoutcast stream as public - " + ultravoxMessage.getErrorMessage());
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigureIcyName configureIcyName = new ConfigureIcyName();
|
||||
configureIcyName.setName(getConfiguration().getName());
|
||||
session.write(configureIcyName);
|
||||
}
|
||||
break;
|
||||
case CONFIGURE_ICY_NAME:
|
||||
if(ultravoxMessage.isErrorResponse())
|
||||
{
|
||||
mLog.error("Error setting shoutcast stream name - " + ultravoxMessage.getErrorMessage());
|
||||
setBroadcastState(BroadcastState.CONFIGURATION_ERROR);
|
||||
}
|
||||
else
|
||||
{
|
||||
session.write(new Standby());
|
||||
}
|
||||
break;
|
||||
case STANDBY:
|
||||
if(ultravoxMessage.isErrorResponse())
|
||||
{
|
||||
mLog.error("Error following stream configuration and standby - " +
|
||||
ultravoxMessage.getErrorMessage());
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
mLog.error("Unrecognized ultravox message:" + ultravoxMessage.getMessageType() +
|
||||
" payload:" + ultravoxMessage.getPayload());
|
||||
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Unrecognized message received from shoutcast v2 server:" + object.toString());
|
||||
|
||||
/**
|
||||
* Creates a socket connection to the remote server and sets the state to CONNECTING.
|
||||
*/
|
||||
private void createSocket()
|
||||
{
|
||||
if(mSocket == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mSocket = new Socket(getShoutcastConfiguration().getHost(),
|
||||
getShoutcastConfiguration().getPort());
|
||||
mOutputStream = new DataOutputStream(mSocket.getOutputStream());
|
||||
mInputStream = new DataInputStream(mSocket.getInputStream());
|
||||
}
|
||||
catch(UnknownHostException uhe)
|
||||
{
|
||||
setBroadcastState(BroadcastState.NO_SERVER);
|
||||
mLog.error("Unknown host or port. Unable to create connection to streaming server host[" +
|
||||
getShoutcastConfiguration().getHost() + "] and port[" +
|
||||
getShoutcastConfiguration().getPort() + "] - will reattempt connection periodically");
|
||||
return;
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
|
||||
mLog.error("Error connecting to streaming server host[" +
|
||||
getShoutcastConfiguration().getHost() + "] and port[" +
|
||||
getShoutcastConfiguration().getPort() + "]", ioe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(mSocket.isConnected())
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTING);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
SocketAddress address = getShoutcastConfiguration().getAddress();
|
||||
mSocket.connect(address);
|
||||
setBroadcastState(BroadcastState.CONNECTING);
|
||||
}
|
||||
catch(UnknownHostException uhe)
|
||||
{
|
||||
setBroadcastState(BroadcastState.NO_SERVER);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
|
||||
mLog.error("Error connecting to streaming server host[" +
|
||||
getShoutcastConfiguration().getHost() + "] and port[" +
|
||||
getShoutcastConfiguration().getPort() + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static UltravoxMessage send(UltravoxMessage message,
|
||||
DataOutputStream out,
|
||||
DataInputStream in,
|
||||
int maxPayloadSize) throws IOException
|
||||
{
|
||||
byte[] bytes = message.getMessage();
|
||||
|
||||
out.write(bytes);
|
||||
|
||||
try
|
||||
{
|
||||
Thread.sleep(50);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
int available = in.available();
|
||||
|
||||
if(available > 0)
|
||||
{
|
||||
byte[] buffer = new byte[available];
|
||||
int read = in.read(buffer);
|
||||
|
||||
return UltravoxMessageFactory.getMessage(buffer);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,13 @@ import audio.broadcast.BroadcastConfiguration;
|
||||
import audio.broadcast.BroadcastFormat;
|
||||
import audio.broadcast.BroadcastServerType;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
|
||||
public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
{
|
||||
private int mStreamID;
|
||||
private String mUserID;
|
||||
private int mBitRate = 16;
|
||||
private String mStreamName;
|
||||
private String mGenre;
|
||||
private String mURL;
|
||||
private boolean mPublic = true;
|
||||
@@ -59,7 +60,6 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
copy.setStreamID(getStreamID());
|
||||
copy.setUserID(getUserID());
|
||||
copy.setBitRate(getBitRate());
|
||||
copy.setStreamName(getStreamName());
|
||||
copy.setGenre(getGenre());
|
||||
copy.setURL(getURL());
|
||||
copy.setPublic(isPublic());
|
||||
@@ -77,6 +77,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
/**
|
||||
* Stream ID
|
||||
*/
|
||||
@XmlAttribute(name = "stream_id")
|
||||
public int getStreamID()
|
||||
{
|
||||
return mStreamID;
|
||||
@@ -96,6 +97,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
/**
|
||||
* User ID
|
||||
*/
|
||||
@XmlAttribute(name = "user_id")
|
||||
public String getUserID()
|
||||
{
|
||||
return mUserID;
|
||||
@@ -113,6 +115,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
/**
|
||||
* Bit rate in kilobits per second
|
||||
*/
|
||||
@XmlAttribute(name = "bitrate")
|
||||
public int getBitRate()
|
||||
{
|
||||
return mBitRate;
|
||||
@@ -127,26 +130,10 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
mBitRate = bitRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream name
|
||||
*/
|
||||
public String getStreamName()
|
||||
{
|
||||
return mStreamName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stream name
|
||||
* @param name
|
||||
*/
|
||||
public void setStreamName(String name)
|
||||
{
|
||||
mStreamName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream genre
|
||||
*/
|
||||
@XmlAttribute(name = "genre")
|
||||
public String getGenre()
|
||||
{
|
||||
return mGenre;
|
||||
@@ -160,9 +147,16 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
mGenre = genre;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasGenre()
|
||||
{
|
||||
return mGenre != null && !mGenre.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public visibility of the broadcastAudio
|
||||
*/
|
||||
@XmlAttribute(name = "public")
|
||||
public boolean isPublic()
|
||||
{
|
||||
return mPublic;
|
||||
@@ -180,6 +174,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
/**
|
||||
* URL associated with the broadcastAudio where users can find additional details.
|
||||
*/
|
||||
@XmlAttribute(name = "url")
|
||||
public String getURL()
|
||||
{
|
||||
return mURL;
|
||||
@@ -193,4 +188,18 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
{
|
||||
mURL = url;
|
||||
}
|
||||
|
||||
public boolean hasURL()
|
||||
{
|
||||
return mURL != null && !mURL.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return super.isValid() && hasName() &&
|
||||
getUserID() != null && !getUserID().isEmpty() &&
|
||||
1 <= getStreamID() &&
|
||||
getPassword() != null && !getPassword().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2;
|
||||
|
||||
import alias.AliasModel;
|
||||
import audio.broadcast.BroadcastConfiguration;
|
||||
import audio.broadcast.BroadcastConfigurationEditor;
|
||||
import audio.broadcast.BroadcastEvent;
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import audio.broadcast.BroadcastServerType;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import settings.SettingsManager;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class ShoutcastV2ConfigurationEditor extends BroadcastConfigurationEditor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2ConfigurationEditor.class );
|
||||
|
||||
private static final int ONE_MINUTE_MS = 60000;
|
||||
|
||||
private JTextField mName;
|
||||
private JTextField mServer;
|
||||
private JTextField mPort;
|
||||
private JTextField mStreamID;
|
||||
private JTextField mUserID;
|
||||
private JTextField mPassword;
|
||||
private JTextField mGenre;
|
||||
private JCheckBox mPublic;
|
||||
private JSlider mDelay;
|
||||
private JLabel mDelayValue;
|
||||
private JCheckBox mEnabled;
|
||||
private JButton mSaveButton;
|
||||
private JButton mResetButton;
|
||||
|
||||
public ShoutcastV2ConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel,
|
||||
SettingsManager settingsManager)
|
||||
{
|
||||
super(broadcastModel, aliasModel, settingsManager);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[align right][grow,fill]",
|
||||
"[][][][][][][][][][][][][][grow,fill]" ) );
|
||||
setPreferredSize(new Dimension(150,400));
|
||||
|
||||
JLabel channelLabel = new JLabel("Shoutcast (v2) Stream");
|
||||
|
||||
ImageIcon icon = mSettingsManager.getImageIcon(BroadcastServerType.SHOUTCAST_V2.getIconName(), 25);
|
||||
channelLabel.setIcon(icon);
|
||||
|
||||
add(channelLabel, "span, align center");
|
||||
|
||||
add(new JLabel("Name:"));
|
||||
mName = new JTextField();
|
||||
mName.getDocument().addDocumentListener(this);
|
||||
mName.setToolTipText("Unique name for this stream");
|
||||
add(mName);
|
||||
|
||||
add(new JLabel("Server:"));
|
||||
mServer = new JTextField();
|
||||
mServer.getDocument().addDocumentListener(this);
|
||||
mServer.setToolTipText("Server address (e.g. audio1.shoutcast.com or localhost)");
|
||||
add(mServer);
|
||||
|
||||
add(new JLabel("Port:"));
|
||||
mPort = new JTextField();
|
||||
mPort.getDocument().addDocumentListener(this);
|
||||
mPort.setToolTipText("Server port number (e.g. 80)");
|
||||
add(mPort);
|
||||
|
||||
add(new JLabel("Stream ID:"));
|
||||
mStreamID = new JTextField();
|
||||
mStreamID.getDocument().addDocumentListener(this);
|
||||
mStreamID.setToolTipText("Stream Identifier)");
|
||||
add(mStreamID);
|
||||
|
||||
add(new JLabel("User ID:"));
|
||||
mUserID = new JTextField();
|
||||
mUserID.getDocument().addDocumentListener(this);
|
||||
mUserID.setToolTipText("User ID/Name");
|
||||
add(mUserID);
|
||||
|
||||
add(new JLabel("Password:"));
|
||||
mPassword = new JTextField();
|
||||
mPassword.getDocument().addDocumentListener(this);
|
||||
mPassword.setToolTipText("Password for the stream");
|
||||
add(mPassword);
|
||||
|
||||
add(new JLabel("Genre:"));
|
||||
mGenre = new JTextField();
|
||||
mGenre.getDocument().addDocumentListener(this);
|
||||
mGenre.setToolTipText("Genre (e.g. public safety)");
|
||||
add(mGenre);
|
||||
|
||||
add(new JLabel("Public:"));
|
||||
mPublic = new JCheckBox();
|
||||
mPublic.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
mPublic.setToolTipText("Designate stream as public (checked) or private");
|
||||
add(mPublic);
|
||||
|
||||
add(new JLabel());
|
||||
mDelayValue = new JLabel("0 Minutes");
|
||||
add(mDelayValue);
|
||||
|
||||
add(new JLabel("Delay:"));
|
||||
mDelay = new JSlider(0,60,0);
|
||||
mDelay.setMajorTickSpacing(10);
|
||||
mDelay.setMinorTickSpacing(5);
|
||||
mDelay.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
mDelayValue.setText(mDelay.getValue() + " Minutes");
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
mDelay.setToolTipText("Audio broadcast delay in minutes");
|
||||
add(mDelay);
|
||||
|
||||
add(new JLabel("Enabled:")); //Empty
|
||||
mEnabled = new JCheckBox();
|
||||
mEnabled.setToolTipText("Enable (checked) or disable this stream");
|
||||
mEnabled.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
add(mEnabled);
|
||||
|
||||
mSaveButton = new JButton("Save");
|
||||
mSaveButton.setEnabled(false);
|
||||
mSaveButton.setToolTipText("Click to save configuration information");
|
||||
mSaveButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
save();
|
||||
}
|
||||
});
|
||||
add(mSaveButton);
|
||||
|
||||
mResetButton = new JButton("Reset");
|
||||
mResetButton.setEnabled(false);
|
||||
mResetButton.setToolTipText("Click to reset any changes you've made since the last save");
|
||||
mResetButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(false);
|
||||
setItem(getItem());
|
||||
}
|
||||
});
|
||||
add(mResetButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModified(boolean modified)
|
||||
{
|
||||
super.setModified(modified);
|
||||
|
||||
mSaveButton.setEnabled(modified);
|
||||
mResetButton.setEnabled(modified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(BroadcastConfiguration item)
|
||||
{
|
||||
super.setItem(item);
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
ShoutcastV2Configuration config = (ShoutcastV2Configuration)getItem();
|
||||
|
||||
mName.setText(config.getName());
|
||||
mServer.setText(config.getHost());
|
||||
mPort.setText(config.getPort() != 0 ? String.valueOf(config.getPort()) : null);
|
||||
mStreamID.setText(String.valueOf(config.getStreamID()));
|
||||
mUserID.setText(config.getUserID());
|
||||
mPassword.setText(config.getPassword());
|
||||
mGenre.setText(config.getGenre());
|
||||
mPublic.setSelected(config.isPublic());
|
||||
mDelay.setValue((int)(config.getDelay() / ONE_MINUTE_MS));
|
||||
mEnabled.setSelected(config.isEnabled());
|
||||
}
|
||||
else
|
||||
{
|
||||
mName.setText(null);
|
||||
mServer.setText(null);
|
||||
mPort.setText(null);
|
||||
mStreamID.setText(null);
|
||||
mUserID.setText(null);
|
||||
mPassword.setText(null);
|
||||
mGenre.setText(null);
|
||||
mPublic.setSelected(true);
|
||||
mDelay.setValue(0);
|
||||
mEnabled.setSelected(false);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
ShoutcastV2Configuration config = (ShoutcastV2Configuration)getItem();
|
||||
|
||||
if(validateConfiguration())
|
||||
{
|
||||
updateConfigurationName(config, mName.getText());
|
||||
config.setHost(mServer.getText());
|
||||
config.setPort(getPort());
|
||||
config.setUserID(mUserID.getText());
|
||||
config.setPassword(mPassword.getText());
|
||||
config.setStreamID(getStreamID());
|
||||
config.setGenre(mGenre.getText());
|
||||
config.setPublic(mPublic.isSelected());
|
||||
config.setDelay(mDelay.getValue() * ONE_MINUTE_MS);
|
||||
config.setEnabled(mEnabled.isSelected());
|
||||
|
||||
setModified(false);
|
||||
|
||||
mBroadcastModel.process(new BroadcastEvent(getItem(), BroadcastEvent.Event.CONFIGURATION_CHANGE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the user specified values in the controls prior to saving them to the configuration
|
||||
*/
|
||||
private boolean validateConfiguration()
|
||||
{
|
||||
String name = mName.getText();
|
||||
|
||||
//A unique stream name is required
|
||||
if(!mBroadcastModel.isUniqueName(name, getItem()))
|
||||
{
|
||||
String message = (name == null || name.isEmpty()) ?
|
||||
"Please specify a stream name." : "Stream name " + name +
|
||||
"is already in use.\nPlease choose another name.";
|
||||
|
||||
JOptionPane.showMessageDialog(ShoutcastV2ConfigurationEditor.this,
|
||||
message, "Invalid Stream Name", JOptionPane.ERROR_MESSAGE);
|
||||
|
||||
mName.requestFocus();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Server address is required
|
||||
if(!validateTextField(mServer, "Invalid Server Address", "Please specify a server address."))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Port is required
|
||||
if(!validateIntegerTextField(mPort, "Invalid Port Number", "Please specify a port number (1 <> 65535)", 1, 65535))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Stream ID is required
|
||||
if(!validateIntegerTextField(mStreamID, "Invalid Stream ID", "Please specify a stream ID of 1 or higher", 1, Integer.MAX_VALUE))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//User ID is required
|
||||
if(!validateTextField(mUserID, "Invalid User ID", "Please specify a user ID/name."))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Password is required
|
||||
if(!validateTextField(mPassword, "Invalid Password", "Please specify a password."))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
|
||||
*/
|
||||
private int getPort()
|
||||
{
|
||||
String port = mPort.getText();
|
||||
|
||||
if(port != null && !port.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(port);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Do nothing, we couldn't parse the number value
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
|
||||
*/
|
||||
private int getStreamID()
|
||||
{
|
||||
String streamID = mStreamID.getText();
|
||||
|
||||
if(streamID != null && !streamID.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(streamID);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Do nothing, we couldn't parse the number value
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
|
||||
public class XMLMetadata extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to send cacheable XML metadata
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
XMLMetadata()
|
||||
{
|
||||
super(UltravoxMessageType.XML_METADATA);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Server response to client request
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*
|
||||
* @param data bytes received from the server
|
||||
*/
|
||||
XMLMetadata(byte[] data)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Adds custom metadata (TXXX) tagged value to the metadata set. The metadata value is identified by the
|
||||
// * id parameter.
|
||||
// *
|
||||
// * @param id tag to identify the type of metadata
|
||||
// * @param value for the id tag
|
||||
// */
|
||||
// public void addCustomMetadata(String id, String value)
|
||||
// {
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// sb.append("<").append(UltravoxMetadata.CUSTOM_TEXT_FIELD.getXMLTag());
|
||||
// sb.append(" id=\"").append(id).append("\">");
|
||||
// sb.append(value);
|
||||
// sb.append("</").append(UltravoxMetadata.CUSTOM_TEXT_FIELD.getXMLTag()).append(">");
|
||||
//
|
||||
// mMetadata.add(sb.toString());
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Adds the binary object data to the metadata set.
|
||||
// * @param id tag to identify the binary object
|
||||
// * @param mime type
|
||||
// * @param filename associated with the data
|
||||
// * @param base64Data base-64 encoded binary data to embed in the metadata tag
|
||||
// */
|
||||
// public void addBinaryObject(String id, String mime, String filename, String base64Data)
|
||||
// {
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// sb.append("<").append(UltravoxMetadata.GENERAL_BINARY_OBJECT.getXMLTag());
|
||||
// sb.append(" mime=\"").append(mime).append("\"");
|
||||
// sb.append(" filename=\"").append(filename).append("\"");
|
||||
// sb.append(" id=\"").append(id).append("\"");
|
||||
// sb.append(">");
|
||||
// sb.append(base64Data);
|
||||
// sb.append("</").append(UltravoxMetadata.GENERAL_BINARY_OBJECT.getXMLTag()).append(">");
|
||||
//
|
||||
// mMetadata.add(sb.toString());
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Adds a recording time (TDRC) tagged metadata timestamp
|
||||
// *
|
||||
// * @param timestamp for the recording time
|
||||
// */
|
||||
// public void addRecordingTime(long timestamp)
|
||||
// {
|
||||
// Calendar calendar = new GregorianCalendar();
|
||||
// calendar.setTimeInMillis(timestamp);
|
||||
//
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// sb.append("<TDRC>\n");
|
||||
// sb.append("<YEAR>").append(calendar.get(Calendar.YEAR)).append("</YEAR>\n");
|
||||
// sb.append("<MONTH>").append(calendar.get(Calendar.MONTH)).append("</MONTH>\n");
|
||||
// sb.append("<DAY>").append(calendar.get(Calendar.DAY_OF_MONTH)).append("</DAY>\n");
|
||||
// sb.append("<HOUR>").append(calendar.get(Calendar.HOUR_OF_DAY)).append("</HOUR>\n");
|
||||
// sb.append("<MINUTE>").append(calendar.get(Calendar.MINUTE)).append("</MINUTE>\n");
|
||||
// sb.append("<ZONE>").append(calendar.get(Calendar.ZONE_OFFSET)).append("</ZONE>\n");
|
||||
// sb.append("</TDRC>\n");
|
||||
//
|
||||
// mMetadata.add(sb.toString());
|
||||
// }
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import util.XTEA;
|
||||
|
||||
@@ -24,12 +24,12 @@ import java.util.Arrays;
|
||||
|
||||
public class AuthenticateBroadcast extends UltravoxMessage
|
||||
{
|
||||
public static final String STREAM_ID_ERROR = "Stream ID Error";
|
||||
|
||||
/**
|
||||
* Client request to server to send authentication credentials
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
AuthenticateBroadcast()
|
||||
public AuthenticateBroadcast()
|
||||
{
|
||||
super(UltravoxMessageType.AUTHENTICATE_BROADCAST);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class AuthenticateBroadcast extends UltravoxMessage
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(SHOUTCAST_VERSION);
|
||||
sb.append(ULTRAVOX_VERSION);
|
||||
sb.append(":");
|
||||
sb.append(streamID);
|
||||
sb.append(":");
|
||||
@@ -0,0 +1,42 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class CacheableXMLMetadata extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to send cacheable XML metadata
|
||||
*/
|
||||
public CacheableXMLMetadata()
|
||||
{
|
||||
super(UltravoxMessageType.CACHEABLE_XML_METADATA);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Server response to client request
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*
|
||||
* @param data bytes received from the server
|
||||
*/
|
||||
CacheableXMLMetadata(byte[] data)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class ConfigureIcyGenre extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to configure the ICY-GENRE metadata
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
ConfigureIcyGenre()
|
||||
public ConfigureIcyGenre()
|
||||
{
|
||||
super(UltravoxMessageType.CONFIGURE_ICY_GENRE);
|
||||
}
|
||||
@@ -16,20 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
|
||||
import util.XTEA;
|
||||
|
||||
import java.util.Arrays;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class ConfigureIcyName extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to configure the ICY-NAME metadata
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
ConfigureIcyName()
|
||||
public ConfigureIcyName()
|
||||
{
|
||||
super(UltravoxMessageType.CONFIGURE_ICY_NAME);
|
||||
}
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class ConfigureIcyPublic extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to configure the ICY-PUBLIC metadata
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
ConfigureIcyPublic()
|
||||
public ConfigureIcyPublic()
|
||||
{
|
||||
super(UltravoxMessageType.CONFIGURE_ICY_PUBLIC);
|
||||
}
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class ConfigureIcyURL extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to configure the ICY-URL metadata
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
ConfigureIcyURL()
|
||||
public ConfigureIcyURL()
|
||||
{
|
||||
super(UltravoxMessageType.CONFIGURE_ICY_URL);
|
||||
}
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class FlushCachedMetadata extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to flush any currently cached stream metadata
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
FlushCachedMetadata()
|
||||
public FlushCachedMetadata()
|
||||
{
|
||||
super(UltravoxMessageType.FLUSH_CACHED_METADATA);
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class MP3Audio extends UltravoxMessage
|
||||
{
|
||||
@@ -25,7 +25,7 @@ public class MP3Audio extends UltravoxMessage
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
MP3Audio()
|
||||
public MP3Audio()
|
||||
{
|
||||
super(UltravoxMessageType.MP3_DATA);
|
||||
}
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class NegotiateBufferSize extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to negotiate the server's buffer size
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
NegotiateBufferSize()
|
||||
public NegotiateBufferSize()
|
||||
{
|
||||
super(UltravoxMessageType.NEGOTIATE_BUFFER_SIZE);
|
||||
}
|
||||
@@ -55,10 +53,10 @@ public class NegotiateBufferSize extends UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiated buffer size from a server response message.
|
||||
* Negotiated buffer size from a server response ultravox.
|
||||
*
|
||||
* @return server negotiated buffer size, or zero if this is not a server response message, or this is an error
|
||||
* server response message, or the server response message buffer size cannot be determined from the message payload.
|
||||
* @return server negotiated buffer size, or zero if this is not a server response ultravox, or this is an error
|
||||
* server response ultravox, or the server response ultravox buffer size cannot be determined from the ultravox payload.
|
||||
*/
|
||||
public int getBufferSize()
|
||||
{
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class NegotiateMaxPayloadSize extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to negotiate maximum payload size
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
NegotiateMaxPayloadSize()
|
||||
public NegotiateMaxPayloadSize()
|
||||
{
|
||||
super(UltravoxMessageType.NEGOTIATE_MAX_PAYLOAD_SIZE);
|
||||
}
|
||||
@@ -42,7 +40,7 @@ public class NegotiateMaxPayloadSize extends UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum payload size.
|
||||
* Sets the maximum payload size in bytes
|
||||
*
|
||||
* @param desiredMaximum payload size (no larger than 16377)
|
||||
* @param minimum acceptable payload size (no larger than 16377)
|
||||
@@ -59,11 +57,11 @@ public class NegotiateMaxPayloadSize extends UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiated maximum payload size from a server response message.
|
||||
* Negotiated maximum payload size from a server response ultravox.
|
||||
*
|
||||
* @return server negotiated maximum payload size, or zero if this is not a server response message, or this is an
|
||||
* error server response message, or the server response message maximum payload size cannot be determined from the
|
||||
* message payload.
|
||||
* @return server negotiated maximum payload size, or zero if this is not a server response ultravox, or this is an
|
||||
* error server response ultravox, or the server response ultravox maximum payload size cannot be determined from the
|
||||
* ultravox payload.
|
||||
*/
|
||||
public int getMaximumPayloadSize()
|
||||
{
|
||||
@@ -0,0 +1,42 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class PassThroughXMLMetadata extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to send pass-through XML metadata
|
||||
*/
|
||||
public PassThroughXMLMetadata()
|
||||
{
|
||||
super(UltravoxMessageType.PASS_THROUGH_XML_METADATA);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Server response to client request
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*
|
||||
* @param data bytes received from the server
|
||||
*/
|
||||
PassThroughXMLMetadata(byte[] data)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
}
|
||||
@@ -16,19 +16,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class RequestCipher extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server for encryption key
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
RequestCipher()
|
||||
public RequestCipher()
|
||||
{
|
||||
super(UltravoxMessageType.REQUEST_CIPHER);
|
||||
setPayload(SHOUTCAST_VERSION);
|
||||
setPayload(ULTRAVOX_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class SetupBroadcast extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to specify the stream bit rate
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
SetupBroadcast()
|
||||
public SetupBroadcast()
|
||||
{
|
||||
super(UltravoxMessageType.SETUP_BROADCAST);
|
||||
}
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class Standby extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to notify that configuration is complete, prepare for streaming
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
Standby()
|
||||
public Standby()
|
||||
{
|
||||
super(UltravoxMessageType.STANDBY);
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import audio.broadcast.BroadcastFormat;
|
||||
|
||||
@@ -24,10 +24,8 @@ public class StreamMimeType extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to set stream MIME type
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
StreamMimeType()
|
||||
public StreamMimeType()
|
||||
{
|
||||
super(UltravoxMessageType.STREAM_MIME_TYPE);
|
||||
}
|
||||
@@ -16,16 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class TerminateBroadcast extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
* Client request to server to terminate the broadcastAudio
|
||||
*
|
||||
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
|
||||
*/
|
||||
TerminateBroadcast()
|
||||
public TerminateBroadcast()
|
||||
{
|
||||
super(UltravoxMessageType.TERMINATE_BROADCAST);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.ProtocolDecoderAdapter;
|
||||
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UltravoxDecoder extends ProtocolDecoderAdapter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(UltravoxDecoder.class);
|
||||
|
||||
@Override
|
||||
public void decode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput out) throws Exception
|
||||
{
|
||||
byte[] message = new byte[ioBuffer.limit()];
|
||||
|
||||
ioBuffer.get(message);
|
||||
|
||||
UltravoxMessage ultravoxMessage = UltravoxMessageFactory.getMessage(message);
|
||||
|
||||
if(ultravoxMessage != null)
|
||||
{
|
||||
out.write(ultravoxMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
out.write(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
|
||||
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
|
||||
|
||||
public class UltravoxEncoder extends ProtocolEncoderAdapter
|
||||
{
|
||||
@Override
|
||||
public void encode(IoSession ioSession, Object message, ProtocolEncoderOutput out) throws Exception
|
||||
{
|
||||
if(message instanceof UltravoxMessage)
|
||||
{
|
||||
byte[] data = ((UltravoxMessage)message).getMessage();
|
||||
|
||||
IoBuffer buf = IoBuffer.allocate(data.length).setAutoExpand(false);
|
||||
buf.put(data);
|
||||
buf.flip();
|
||||
out.write(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import bits.BinaryMessage;
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.Arrays;
|
||||
|
||||
public abstract class UltravoxMessage
|
||||
{
|
||||
public static final String SHOUTCAST_VERSION = "2.1";
|
||||
public static final String ULTRAVOX_VERSION = "2.1";
|
||||
public static final String VALID_RESPONSE_PREFIX = "ACK";
|
||||
public static final String VALID_RESPONSE_PAYLOAD_PREFIX = "ACK:";
|
||||
public static final String ERROR_RESPONSE_PREFIX = "NAK:";
|
||||
@@ -40,7 +40,7 @@ public abstract class UltravoxMessage
|
||||
private BinaryMessage mMessage;
|
||||
|
||||
/**
|
||||
* Constructs a message from the byte array
|
||||
* Constructs a ultravox from the byte array
|
||||
*/
|
||||
public UltravoxMessage(byte[] data)
|
||||
{
|
||||
@@ -56,9 +56,9 @@ public abstract class UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty message of the specified length
|
||||
* Constructs an empty ultravox of the specified length
|
||||
*
|
||||
* @param ultravoxMessageType type of message
|
||||
* @param ultravoxMessageType type of ultravox
|
||||
*/
|
||||
public UltravoxMessage(UltravoxMessageType ultravoxMessageType)
|
||||
{
|
||||
@@ -71,7 +71,7 @@ public abstract class UltravoxMessage
|
||||
/**
|
||||
* Message byte array
|
||||
*
|
||||
* @return message bytes
|
||||
* @return ultravox bytes
|
||||
*/
|
||||
public byte[] getMessage()
|
||||
{
|
||||
@@ -80,11 +80,11 @@ public abstract class UltravoxMessage
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mMessage.toString();
|
||||
return getMessageType().name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message sync bits to the predefined sync pattern: 01011010 (0x5A)
|
||||
* Sets the ultravox sync bits to the predefined sync pattern: 01011010 (0x5A)
|
||||
*/
|
||||
private void setSync()
|
||||
{
|
||||
@@ -92,7 +92,7 @@ public abstract class UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Ultravox message class
|
||||
* Ultravox ultravox class
|
||||
*/
|
||||
public UltravoxMessageClass getMessageClass()
|
||||
{
|
||||
@@ -100,7 +100,7 @@ public abstract class UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Ultravox message type
|
||||
* Ultravox ultravox type
|
||||
* @return
|
||||
*/
|
||||
public UltravoxMessageType getMessageType()
|
||||
@@ -109,8 +109,8 @@ public abstract class UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message bits according to the message class and type
|
||||
* @param messageType of ultravox message
|
||||
* Sets the ultravox bits according to the ultravox class and type
|
||||
* @param messageType of ultravox ultravox
|
||||
*/
|
||||
private void setMessageType(UltravoxMessageType messageType)
|
||||
{
|
||||
@@ -118,7 +118,7 @@ public abstract class UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message has the required delivery flag set
|
||||
* Indicates if this ultravox has the required delivery flag set
|
||||
*/
|
||||
public boolean isRequiredDelivery()
|
||||
{
|
||||
@@ -141,7 +141,7 @@ public abstract class UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Send queue priority (0 - 7) for this message
|
||||
* Send queue priority (0 - 7) for this ultravox
|
||||
*/
|
||||
public int getSendQueuePriority()
|
||||
{
|
||||
@@ -149,7 +149,7 @@ public abstract class UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets send queue priority for this message
|
||||
* Sets send queue priority for this ultravox
|
||||
* @param priority 0 - 7
|
||||
*/
|
||||
public void setSendQueuePriority(int priority)
|
||||
@@ -186,7 +186,7 @@ public abstract class UltravoxMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message payload, updates the message length field, and appends a trailing 0x00 byte after the payload.
|
||||
* Sets the ultravox payload, updates the ultravox length field, and appends a trailing 0x00 byte after the payload.
|
||||
*/
|
||||
public void setPayload(String payload)
|
||||
{
|
||||
@@ -211,6 +211,9 @@ public abstract class UltravoxMessage
|
||||
mMessage.setByte(payloadPointer, (byte)0x00);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the response is a valid (ie non-error) response
|
||||
*/
|
||||
public boolean isValidResponse()
|
||||
{
|
||||
String payload = getPayload();
|
||||
@@ -218,6 +221,9 @@ public abstract class UltravoxMessage
|
||||
return payload != null && payload.startsWith(VALID_RESPONSE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the response message contains an error message.
|
||||
*/
|
||||
public boolean isErrorResponse()
|
||||
{
|
||||
String payload = getPayload();
|
||||
@@ -225,7 +231,10 @@ public abstract class UltravoxMessage
|
||||
return payload != null && payload.startsWith(ERROR_RESPONSE_PREFIX);
|
||||
}
|
||||
|
||||
public String getError()
|
||||
/**
|
||||
* Textual message available when the error response flag is set.
|
||||
*/
|
||||
public String getErrorMessage()
|
||||
{
|
||||
String payload = getPayload();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public enum UltravoxMessageClass
|
||||
{
|
||||
@@ -16,12 +16,12 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class UltravoxMessageFactory
|
||||
{
|
||||
/**
|
||||
* Creates an empty message of the specified message type for sending to the server
|
||||
* Creates an empty ultravox of the specified ultravox type for sending to the server
|
||||
*/
|
||||
public static UltravoxMessage getMessage(UltravoxMessageType type)
|
||||
{
|
||||
@@ -29,6 +29,8 @@ public class UltravoxMessageFactory
|
||||
{
|
||||
case AUTHENTICATE_BROADCAST:
|
||||
return new AuthenticateBroadcast();
|
||||
case CACHEABLE_XML_METADATA:
|
||||
return new CacheableXMLMetadata();
|
||||
case CONFIGURE_ICY_GENRE:
|
||||
return new ConfigureIcyGenre();
|
||||
case CONFIGURE_ICY_NAME:
|
||||
@@ -45,6 +47,8 @@ public class UltravoxMessageFactory
|
||||
return new NegotiateBufferSize();
|
||||
case NEGOTIATE_MAX_PAYLOAD_SIZE:
|
||||
return new NegotiateMaxPayloadSize();
|
||||
case PASS_THROUGH_XML_METADATA:
|
||||
return new PassThroughXMLMetadata();
|
||||
case REQUEST_CIPHER:
|
||||
return new RequestCipher();
|
||||
case SETUP_BROADCAST:
|
||||
@@ -55,19 +59,17 @@ public class UltravoxMessageFactory
|
||||
return new StreamMimeType();
|
||||
case TERMINATE_BROADCAST:
|
||||
return new TerminateBroadcast();
|
||||
case XML_METADATA:
|
||||
return new XMLMetadata();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ultravox message from the server response bytes.
|
||||
* Creates an ultravox ultravox from the server response bytes.
|
||||
*
|
||||
* @param bytes in network order (big-endian).
|
||||
*
|
||||
* @return constructed message or null if the message type cannot be determined or is unrecognized
|
||||
* @return constructed ultravox or null if the ultravox type cannot be determined or is unrecognized
|
||||
*/
|
||||
public static UltravoxMessage getMessage(byte[] bytes)
|
||||
{
|
||||
@@ -77,6 +79,8 @@ public class UltravoxMessageFactory
|
||||
{
|
||||
case AUTHENTICATE_BROADCAST:
|
||||
return new AuthenticateBroadcast(bytes);
|
||||
case CACHEABLE_XML_METADATA:
|
||||
return new CacheableXMLMetadata(bytes);
|
||||
case CONFIGURE_ICY_GENRE:
|
||||
return new ConfigureIcyGenre(bytes);
|
||||
case CONFIGURE_ICY_NAME:
|
||||
@@ -93,6 +97,8 @@ public class UltravoxMessageFactory
|
||||
return new NegotiateBufferSize(bytes);
|
||||
case NEGOTIATE_MAX_PAYLOAD_SIZE:
|
||||
return new NegotiateMaxPayloadSize(bytes);
|
||||
case PASS_THROUGH_XML_METADATA:
|
||||
return new PassThroughXMLMetadata(bytes);
|
||||
case REQUEST_CIPHER:
|
||||
return new RequestCipher(bytes);
|
||||
case SETUP_BROADCAST:
|
||||
@@ -103,8 +109,6 @@ public class UltravoxMessageFactory
|
||||
return new StreamMimeType(bytes);
|
||||
case TERMINATE_BROADCAST:
|
||||
return new TerminateBroadcast(bytes);
|
||||
case XML_METADATA:
|
||||
return new XMLMetadata(bytes);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
return null;
|
||||
@@ -112,10 +116,10 @@ public class UltravoxMessageFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the message type from the server response byte array
|
||||
* Identifies the ultravox type from the server response byte array
|
||||
*
|
||||
* @param data in little-endian format
|
||||
* @return message type or UNKNOWN if the message type cannot be determined.
|
||||
* @return ultravox type or UNKNOWN if the ultravox type cannot be determined.
|
||||
*/
|
||||
private static UltravoxMessageType getMessageType(byte[] data)
|
||||
{
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public enum UltravoxMessageType
|
||||
{
|
||||
@@ -33,7 +33,8 @@ public enum UltravoxMessageType
|
||||
CONFIGURE_ICY_GENRE(0x1101),
|
||||
CONFIGURE_ICY_URL(0x1102),
|
||||
CONFIGURE_ICY_PUBLIC(0x1103),
|
||||
XML_METADATA(0x3902),
|
||||
CACHEABLE_XML_METADATA(0x3902),
|
||||
PASS_THROUGH_XML_METADATA(0x5902),
|
||||
MP3_DATA(0x7000),
|
||||
UNKNOWN(0x0000);
|
||||
|
||||
@@ -80,7 +81,9 @@ public enum UltravoxMessageType
|
||||
case 0x1103:
|
||||
return CONFIGURE_ICY_PUBLIC;
|
||||
case 0x3902:
|
||||
return XML_METADATA;
|
||||
return CACHEABLE_XML_METADATA;
|
||||
case 0x5902:
|
||||
return PASS_THROUGH_XML_METADATA;
|
||||
case 0x7000:
|
||||
return MP3_DATA;
|
||||
default:
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.message;
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public enum UltravoxMetadata
|
||||
{
|
||||
@@ -0,0 +1,52 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.ProtocolCodecFactory;
|
||||
import org.apache.mina.filter.codec.ProtocolDecoder;
|
||||
import org.apache.mina.filter.codec.ProtocolEncoder;
|
||||
|
||||
public class UltravoxProtocolFactory implements ProtocolCodecFactory
|
||||
{
|
||||
private ProtocolEncoder mEncoder;
|
||||
private ProtocolDecoder mDecoder;
|
||||
|
||||
@Override
|
||||
public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception
|
||||
{
|
||||
if(mEncoder == null)
|
||||
{
|
||||
mEncoder = new UltravoxEncoder();
|
||||
}
|
||||
|
||||
return mEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolDecoder getDecoder(IoSession ioSession) throws Exception
|
||||
{
|
||||
if(mDecoder == null)
|
||||
{
|
||||
mDecoder = new UltravoxDecoder();
|
||||
}
|
||||
|
||||
return mDecoder;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user