#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:
Denny
2017-01-03 06:41:22 -05:00
parent 42c18341b6
commit 4578e45f60
37 changed files with 1236 additions and 692 deletions

View File

@@ -223,7 +223,8 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
} }
catch(IOException ioe) 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(mProcessing.compareAndSet(false, true))
{ {
if(mInputStream == null || mInputStream.available() <= 0) try
{ {
if(mFinalSilencePadding > 0) if(mInputStream == null || mInputStream.available() <= 0)
{ {
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding)); if(mFinalSilencePadding > 0)
mFinalSilencePadding = 0; {
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
mFinalSilencePadding = 0;
}
nextRecording();
} }
nextRecording(); if(mInputStream != null)
}
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
{ {
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); mProcessing.set(false);
@@ -447,17 +455,18 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
metadataUpdateRequired = false; metadataUpdateRequired = false;
} }
removeRecording(recording);
} }
catch(IOException ioe) catch(IOException ioe)
{ {
mLog.error("Error reading temporary audio stream recording [" + recording.getPath().toString() + mLog.error("Stream [" + getBroadcastConfiguration().getName() + "] error reading temporary audio " +
"] - skipping recording"); "stream recording [" + recording.getPath().toString() + "] - skipping recording - ", ioe);
mInputStream = null; mInputStream = null;
metadataUpdateRequired = false;
} }
removeRecording(recording);
broadcast(new BroadcastEvent(AudioBroadcaster.this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE)); broadcast(new BroadcastEvent(AudioBroadcaster.this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
} }

View File

@@ -53,11 +53,11 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)"; private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
private static final int COLUMN_SERVER_ICON = 0; public static final int COLUMN_SERVER_ICON = 0;
private static final int COLUMN_STREAM_NAME = 1; public static final int COLUMN_STREAM_NAME = 1;
private static final int COLUMN_BROADCASTER_STATUS = 2; public static final int COLUMN_BROADCASTER_STATUS = 2;
private static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3; public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
private static final int COLUMN_BROADCASTER_STREAMED_COUNT = 4; public static final int COLUMN_BROADCASTER_STREAMED_COUNT = 4;
private List<BroadcastConfiguration> mBroadcastConfigurations = new CopyOnWriteArrayList<>(); private List<BroadcastConfiguration> mBroadcastConfigurations = new CopyOnWriteArrayList<>();
@@ -513,15 +513,15 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
if(audioBroadcasterA != null) if(audioBroadcasterA != null)
{ {
return audioBroadcasterA.getBroadcastState().toString(); return audioBroadcasterA.getBroadcastState();
} }
else if(!configuration.isEnabled()) else if(!configuration.isEnabled())
{ {
return "Disabled"; return BroadcastState.DISABLED;
} }
else if(!configuration.isValid()) else if(!configuration.isValid())
{ {
return "Invalid Settings"; return BroadcastState.INVALID_SETTINGS;
} }
case COLUMN_BROADCASTER_QUEUE_SIZE: case COLUMN_BROADCASTER_QUEUE_SIZE:
AudioBroadcaster audioBroadcasterB = mBroadcasterMap.get(configuration.getName()); AudioBroadcaster audioBroadcasterB = mBroadcasterMap.get(configuration.getName());

View File

@@ -33,6 +33,9 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener; 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.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
@@ -77,8 +80,10 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
mBroadcastConfigurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); mBroadcastConfigurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mBroadcastConfigurationTable.getSelectionModel().addListSelectionListener(BroadcastPanel.this); mBroadcastConfigurationTable.getSelectionModel().addListSelectionListener(BroadcastPanel.this);
mBroadcastConfigurationTable.getColumnModel().getColumn(0).setPreferredWidth(1); mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_SERVER_ICON).setPreferredWidth(1);
mBroadcastConfigurationTable.getColumnModel().getColumn(1).setPreferredWidth(25); mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_STREAM_NAME).setPreferredWidth(25);
mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS)
.setCellRenderer(new StatusCellRenderer());
JScrollPane scroller = new JScrollPane(mBroadcastConfigurationTable); 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;
}
}
} }

View File

@@ -35,6 +35,11 @@ public enum BroadcastState
*/ */
CONNECTING("Connecting", false), CONNECTING("Connecting", false),
/**
* Indicates the configuration is disabled
*/
DISABLED("Disabled", true),
/** /**
* Disconnected from the broadcastAudio server * Disconnected from the broadcastAudio server
*/ */
@@ -50,6 +55,11 @@ public enum BroadcastState
*/ */
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true), INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true),
/**
* Invalid configuration settings
*/
INVALID_SETTINGS("Invalid Settings", true),
/** /**
* Remote server max sources has been exceeded * Remote server max sources has been exceeded
*/ */

View File

@@ -30,18 +30,20 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class StreamManager implements Listener<AudioPacket> public class StreamManager implements Listener<AudioPacket>
{ {
private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class); private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class);
private static final long MAXIMUM_RECORDER_LIFESPAN_MILLIS = 30000; //30 seconds private static final long MAXIMUM_RECORDER_LIFESPAN_MILLIS = 30000; //30 seconds
private static AtomicInteger sNextRecordingNumber = new AtomicInteger();
private ThreadPoolManager mThreadPoolManager; private ThreadPoolManager mThreadPoolManager;
private AudioBroadcaster mAudioBroadcaster; private AudioBroadcaster mAudioBroadcaster;
private Path mTempDirectory; private Path mTempDirectory;
@@ -197,6 +199,7 @@ public class StreamManager implements Listener<AudioPacket>
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX); sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
sb.append(sNextRecordingNumber.incrementAndGet()).append("_");
sb.append( TimeStamp.getLongTimeStamp( "_" ) ); sb.append( TimeStamp.getLongTimeStamp( "_" ) );
sb.append( mAudioBroadcaster.getBroadcastConfiguration().getBroadcastFormat().getFileExtension() ); 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) .filter(entry -> entry.getValue().getTimeRecordingStart() + MAXIMUM_RECORDER_LIFESPAN_MILLIS < now)
.forEach(entry -> .forEach(entry ->
{ {
mLog.debug("Maximum recording time limit reached - removing recorder: " +
entry.getValue().getPath().toString());
removeRecorder(entry.getKey()); removeRecorder(entry.getKey());
}); });
} }

View File

@@ -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);
}
}

View File

@@ -18,7 +18,9 @@
******************************************************************************/ ******************************************************************************/
package audio.broadcast.shoutcast.v1; package audio.broadcast.shoutcast.v1;
import audio.broadcast.AudioBroadcaster;
import audio.broadcast.BroadcastState; import audio.broadcast.BroadcastState;
import audio.broadcast.IBroadcastMetadataUpdater;
import controller.ThreadPoolManager; import controller.ThreadPoolManager;
import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.IoBuffer;
@@ -37,13 +39,14 @@ import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; 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 final static Logger mLog = LoggerFactory.getLogger( ShoutcastV1AudioBroadcaster.class );
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
private NioSocketConnector mSocketConnector; private NioSocketConnector mSocketConnector;
private IoSession mStreamingSession = null; private IoSession mStreamingSession = null;
private IBroadcastMetadataUpdater mMetadataUpdater;
private long mLastConnectionAttempt = 0; private long mLastConnectionAttempt = 0;
private AtomicBoolean mConnecting = new AtomicBoolean(); private AtomicBoolean mConnecting = new AtomicBoolean();
@@ -71,6 +74,16 @@ public class ShoutcastV1AudioBroadcaster extends ShoutcastAudioBroadcaster
return (ShoutcastV1Configuration)getBroadcastConfiguration(); return (ShoutcastV1Configuration)getBroadcastConfiguration();
} }
@Override
protected IBroadcastMetadataUpdater getMetadataUpdater()
{
if(mMetadataUpdater == null)
{
mMetadataUpdater = new ShoutcastV1BroadcastMetadataUpdater(getThreadPoolManager(), getConfiguration());
}
return mMetadataUpdater;
}
/** /**
* Broadcasts the audio frame or sequence * Broadcasts the audio frame or sequence

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* sdrtrunk * 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 * 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 * 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/> * 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.IBroadcastMetadataUpdater;
import audio.broadcast.icecast.IcecastHeader;
import audio.broadcast.shoutcast.v1.ShoutcastMetadata;
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import audio.metadata.AudioMetadata; import audio.metadata.AudioMetadata;
import audio.metadata.Metadata; import audio.metadata.Metadata;
import audio.metadata.MetadataType; import audio.metadata.MetadataType;
@@ -29,9 +26,6 @@ import controller.ThreadPoolManager;
import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession; 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.HttpClientCodec;
import org.apache.mina.http.HttpRequestImpl; import org.apache.mina.http.HttpRequestImpl;
import org.apache.mina.http.api.HttpMethod; 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.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.ConnectException; import java.net.ConnectException;
@@ -53,9 +46,9 @@ import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; 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 final static String UTF8 = "UTF-8";
private ThreadPoolManager mThreadPoolManager; 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 * 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. * 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; mThreadPoolManager = threadPoolManager;
mShoutcastV1Configuration = shoutcastV1Configuration; mShoutcastV1Configuration = shoutcastV1Configuration;
@@ -89,7 +82,7 @@ public class ShoutcastMetadataUpdater
mSocketConnector = new NioSocketConnector(); mSocketConnector = new NioSocketConnector();
// mSocketConnector.getFilterChain().addLast("logger", // mSocketConnector.getFilterChain().addLast("logger",
// new LoggingFilter(ShoutcastMetadataUpdater.class)); // new LoggingFilter(ShoutcastV1BroadcastMetadataUpdater.class));
mSocketConnector.getFilterChain().addLast("http_client_codec", new HttpClientCodec()); mSocketConnector.getFilterChain().addLast("http_client_codec", new HttpClientCodec());
@@ -257,7 +250,6 @@ public class ShoutcastMetadataUpdater
sb.append("&song=").append(URLEncoder.encode(song, UTF8)); sb.append("&song=").append(URLEncoder.encode(song, UTF8));
Map<String,String> headers = new HashMap<>(); 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", HttpRequestImpl request = new HttpRequestImpl(HttpVersion.HTTP_1_0, HttpMethod.GET, "/admin.cgi",
sb.toString(), headers); sb.toString(), headers);

View File

@@ -239,7 +239,6 @@ public class ShoutcastV1ConfigurationEditor extends BroadcastConfigurationEditor
updateConfigurationName(config, mName.getText()); updateConfigurationName(config, mName.getText());
config.setHost(mServer.getText()); config.setHost(mServer.getText());
config.setPort(getPort()); config.setPort(getPort());
config.setDescription(mDescription.getText());
config.setPassword(mPassword.getText()); config.setPassword(mPassword.getText());
config.setDescription(mDescription.getText()); config.setDescription(mDescription.getText());
config.setGenre(mGenre.getText()); config.setGenre(mGenre.getText());

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* sdrtrunk * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -18,120 +18,199 @@
******************************************************************************/ ******************************************************************************/
package audio.broadcast.shoutcast.v2; package audio.broadcast.shoutcast.v2;
import audio.AudioPacket;
import audio.broadcast.AudioBroadcaster; import audio.broadcast.AudioBroadcaster;
import audio.broadcast.BroadcastFactory;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastState; import audio.broadcast.BroadcastState;
import audio.broadcast.shoutcast.v2.message.AuthenticateBroadcast; import audio.broadcast.IBroadcastMetadataUpdater;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyGenre; import audio.broadcast.shoutcast.v2.ultravox.AuthenticateBroadcast;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyName; import audio.broadcast.shoutcast.v2.ultravox.CacheableXMLMetadata;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyPublic; import audio.broadcast.shoutcast.v2.ultravox.ConfigureIcyName;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyURL; import audio.broadcast.shoutcast.v2.ultravox.ConfigureIcyPublic;
import audio.broadcast.shoutcast.v2.message.MP3Audio; import audio.broadcast.shoutcast.v2.ultravox.MP3Audio;
import audio.broadcast.shoutcast.v2.message.NegotiateMaxPayloadSize; import audio.broadcast.shoutcast.v2.ultravox.NegotiateMaxPayloadSize;
import audio.broadcast.shoutcast.v2.message.RequestCipher; import audio.broadcast.shoutcast.v2.ultravox.RequestCipher;
import audio.broadcast.shoutcast.v2.message.SetupBroadcast; import audio.broadcast.shoutcast.v2.ultravox.SetupBroadcast;
import audio.broadcast.shoutcast.v2.message.Standby; import audio.broadcast.shoutcast.v2.ultravox.Standby;
import audio.broadcast.shoutcast.v2.message.UltravoxMessage; import audio.broadcast.shoutcast.v2.ultravox.StreamMimeType;
import audio.broadcast.shoutcast.v2.message.UltravoxMessageFactory; import audio.broadcast.shoutcast.v2.ultravox.TerminateBroadcast;
import audio.broadcast.shoutcast.v2.message.UltravoxMessageType; import audio.broadcast.shoutcast.v2.ultravox.UltravoxMessage;
import audio.broadcast.shoutcast.v2.message.UltravoxMetadata; import audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageFactory;
import audio.broadcast.shoutcast.v2.message.XMLMetadata; 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.AudioMetadata;
import audio.metadata.Metadata;
import audio.metadata.MetadataType;
import controller.ThreadPoolManager; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import properties.SystemProperties; import properties.SystemProperties;
import record.wave.AudioPacketMonoWaveReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.ConnectException;
import java.net.SocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2AudioBroadcaster.class );
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
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 int mMaxPayloadSize = 16377; 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. * Shoutcast 2.x (Ultravox 2.1) broadcaster. This broadcaster is compatible with Shoutcast 2.x and newer versions
* @param configuration details for shoutcast version 2 * 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) public ShoutcastV2AudioBroadcaster(ThreadPoolManager threadPoolManager, ShoutcastV2Configuration configuration)
{ {
super(threadPoolManager, configuration); super(threadPoolManager, configuration);
} }
/** public ShoutcastV2Configuration getConfiguration()
* Shoutcast V2 Configuration information
*/
private ShoutcastV2Configuration getShoutcastConfiguration()
{ {
return (ShoutcastV2Configuration)getBroadcastConfiguration(); 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 @Override
protected void broadcastAudio(byte[] audio) protected void broadcastAudio(byte[] audio)
{ {
//If we're connected, send the audio, otherwise discard it //Dispatch any queued metadata messages
if(connect()) UltravoxMessage metadataMessage = mMetadataMessageQueue.poll();
{
List<UltravoxMessage> audioMessages = getAudioMessages(audio);
try while(metadataMessage != null)
{ {
for(UltravoxMessage audioMessage: audioMessages) mStreamingSession.write(metadataMessage);
{ metadataMessage = mMetadataMessageQueue.poll();
send(audioMessage, NO_RESPONSE_EXPECTED); }
}
} //Dispatch audio message
catch(IOException e) if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
{ {
mLog.error("Error while dispatching audio", e); MP3Audio mp3Audio = new MP3Audio();
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR); mp3Audio.setPayload(audio);
return;
} 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 * @return true if the audio handler can stream audio
*/ */
private boolean connect() 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(); 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() public void disconnect()
{ {
// TerminateBroadcast terminate = (TerminateBroadcast)UltravoxMessageFactory if(connected())
// .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)
{ {
try if(mStreamingSession != null)
{ {
Thread.sleep(50); mStreamingSession.write(new TerminateBroadcast());
} mStreamingSession.closeNow();
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);
} }
} }
return null;
} }
/** /**
* Loads the encoded audio byte array into a list of ultravox messages ready for transmission * Encodes the list of metadata in one or more Ultravox CacheableXMLMetadata messages according to the maximum negotiated
* @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
* payload size for the current connection. Each entry in the list of metadata strings should be an xml encoded * payload size for the current connection. Each entry in the list of metadata strings should be an xml encoded
* value: <tag>value</tag> * value: <tag>value</tag>
* *
* See UltravoxMetadata.TAG.asXML(String value) * See UltravoxMetadata.TAG.asXML(String value)
* *
* @param xmlMetadata list of xml encoded values * @param metadata containing audio metadata tags and attributes
* @return a sequence of XMLMetadata messages sufficient to carry the complete set of metadata * @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(); 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(); byte[] xml = sb.toString().getBytes();
int pointer = 0; int pointer = 0;
@@ -257,6 +309,8 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster
} }
List<UltravoxMessage> messages = new ArrayList<>();
while(pointer < xml.length && messageCounter <= messageCount) while(pointer < xml.length && messageCounter <= messageCount)
{ {
int payloadSize = Math.min(mMaxPayloadSize - 6, xml.length - pointer); 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); System.arraycopy(xml, pointer, payload, 6, payloadSize);
XMLMetadata message = (XMLMetadata)UltravoxMessageFactory.getMessage(UltravoxMessageType.XML_METADATA); CacheableXMLMetadata message = new CacheableXMLMetadata();
message.setPayload(payload); message.setPayload(payload);
messages.add(message); 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 * IO Handler for managing Shoutcast V2 connection and credentials
* following a successful connection, attempts to reestablish a connection on a set interval
*/ */
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(); @Override
public void sessionClosed(IoSession session) throws Exception
if (mSocket.isConnected()) {
//If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState())
{ {
RequestCipher requestCipher = (RequestCipher) UltravoxMessageFactory setBroadcastState(BroadcastState.DISCONNECTED);
.getMessage(UltravoxMessageType.REQUEST_CIPHER); }
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) AuthenticateBroadcast authenticateBroadcast = new AuthenticateBroadcast();
{
RequestCipher cipherResponse = (RequestCipher) response;
AuthenticateBroadcast authenticateBroadcast = (AuthenticateBroadcast) UltravoxMessageFactory authenticateBroadcast.setCredentials(cipherKey, getConfiguration().getStreamID(),
.getMessage(UltravoxMessageType.AUTHENTICATE_BROADCAST); getConfiguration().getUserID(), getConfiguration().getPassword());
authenticateBroadcast.setCredentials(cipherResponse.getCipher(),
getShoutcastConfiguration().getStreamID(), getShoutcastConfiguration().getUserID(),
getShoutcastConfiguration().getPassword());
response = send(authenticateBroadcast, GET_RESPONSE);
if (response.isErrorResponse()) session.write(authenticateBroadcast);
break;
case AUTHENTICATE_BROADCAST:
if(ultravoxMessage.isErrorResponse())
{ {
setBroadcastState(BroadcastState.INVALID_CREDENTIALS); String errorMessage = ultravoxMessage.getErrorMessage();
return;
}
UltravoxMessage streamMimeType = UltravoxMessageFactory if(errorMessage.startsWith(AuthenticateBroadcast.STREAM_ID_ERROR))
.getMessage(UltravoxMessageType.STREAM_MIME_TYPE); {
streamMimeType.setPayload(getShoutcastConfiguration().getBroadcastFormat().getValue()); setBroadcastState(BroadcastState.INVALID_MOUNT_POINT);
response = send(streamMimeType, GET_RESPONSE); }
else
if (response.isErrorResponse()) {
{ mLog.error("Invalid Credentials - response: " + ultravoxMessage.getPayload());
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT); setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
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);
} }
else else
{ {
setBroadcastState(BroadcastState.DISCONNECTED); StreamMimeType streamMimeType = new StreamMimeType();
mLog.error("Error message after configuration and sending standby:" + streamMimeType.setFormat(getConfiguration().getBroadcastFormat());
(response.getError() != null ? response.getError() : "no error message")); session.write(streamMimeType);
} }
break;
metadata.add(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION case STREAM_MIME_TYPE:
.asXML(SystemProperties.getInstance().getApplicationName())); if(ultravoxMessage.isErrorResponse())
mLog.debug("Fetching metadata messages from metadata...");
List<UltravoxMessage> messages = getMetadataMessages(metadata);
for (UltravoxMessage message : messages)
{ {
mLog.debug("Sending metadata message"); mLog.error("Unsupported Audio Format:" + getConfiguration().getBroadcastFormat().toString() +
send(message, GET_RESPONSE); " - " + ultravoxMessage.getErrorMessage());
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
} }
else
{
SetupBroadcast setupBroadcast = new SetupBroadcast();
mLog.debug("We're connected!!!"); //Use the same value for average and minimum bit rates
} setupBroadcast.setBitRate(getConfiguration().getBitRate(), getConfiguration().getBitRate());
}
catch (IOException e) session.write(setupBroadcast);
{ }
setBroadcastState(BroadcastState.ERROR); break;
mLog.error("Error while creating connection to server", e); 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); 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;
} }
} }

View File

@@ -22,12 +22,13 @@ import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat; import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastServerType; import audio.broadcast.BroadcastServerType;
import javax.xml.bind.annotation.XmlAttribute;
public class ShoutcastV2Configuration extends BroadcastConfiguration public class ShoutcastV2Configuration extends BroadcastConfiguration
{ {
private int mStreamID; private int mStreamID;
private String mUserID; private String mUserID;
private int mBitRate = 16; private int mBitRate = 16;
private String mStreamName;
private String mGenre; private String mGenre;
private String mURL; private String mURL;
private boolean mPublic = true; private boolean mPublic = true;
@@ -59,7 +60,6 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
copy.setStreamID(getStreamID()); copy.setStreamID(getStreamID());
copy.setUserID(getUserID()); copy.setUserID(getUserID());
copy.setBitRate(getBitRate()); copy.setBitRate(getBitRate());
copy.setStreamName(getStreamName());
copy.setGenre(getGenre()); copy.setGenre(getGenre());
copy.setURL(getURL()); copy.setURL(getURL());
copy.setPublic(isPublic()); copy.setPublic(isPublic());
@@ -77,6 +77,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
/** /**
* Stream ID * Stream ID
*/ */
@XmlAttribute(name = "stream_id")
public int getStreamID() public int getStreamID()
{ {
return mStreamID; return mStreamID;
@@ -96,6 +97,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
/** /**
* User ID * User ID
*/ */
@XmlAttribute(name = "user_id")
public String getUserID() public String getUserID()
{ {
return mUserID; return mUserID;
@@ -113,6 +115,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
/** /**
* Bit rate in kilobits per second * Bit rate in kilobits per second
*/ */
@XmlAttribute(name = "bitrate")
public int getBitRate() public int getBitRate()
{ {
return mBitRate; return mBitRate;
@@ -127,26 +130,10 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
mBitRate = bitRate; mBitRate = bitRate;
} }
/**
* Stream name
*/
public String getStreamName()
{
return mStreamName;
}
/**
* Sets the stream name
* @param name
*/
public void setStreamName(String name)
{
mStreamName = name;
}
/** /**
* Stream genre * Stream genre
*/ */
@XmlAttribute(name = "genre")
public String getGenre() public String getGenre()
{ {
return mGenre; return mGenre;
@@ -160,9 +147,16 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
mGenre = genre; mGenre = genre;
} }
public boolean hasGenre()
{
return mGenre != null && !mGenre.isEmpty();
}
/** /**
* Public visibility of the broadcastAudio * Public visibility of the broadcastAudio
*/ */
@XmlAttribute(name = "public")
public boolean isPublic() public boolean isPublic()
{ {
return mPublic; return mPublic;
@@ -180,6 +174,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
/** /**
* URL associated with the broadcastAudio where users can find additional details. * URL associated with the broadcastAudio where users can find additional details.
*/ */
@XmlAttribute(name = "url")
public String getURL() public String getURL()
{ {
return mURL; return mURL;
@@ -193,4 +188,18 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
{ {
mURL = url; 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();
}
} }

View File

@@ -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;
}
}

View File

@@ -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());
// }
}

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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; import util.XTEA;
@@ -24,12 +24,12 @@ import java.util.Arrays;
public class AuthenticateBroadcast extends UltravoxMessage public class AuthenticateBroadcast extends UltravoxMessage
{ {
public static final String STREAM_ID_ERROR = "Stream ID Error";
/** /**
* Client request to server to send authentication credentials * Client request to server to send authentication credentials
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/ */
AuthenticateBroadcast() public AuthenticateBroadcast()
{ {
super(UltravoxMessageType.AUTHENTICATE_BROADCAST); super(UltravoxMessageType.AUTHENTICATE_BROADCAST);
} }
@@ -60,7 +60,7 @@ public class AuthenticateBroadcast extends UltravoxMessage
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(SHOUTCAST_VERSION); sb.append(ULTRAVOX_VERSION);
sb.append(":"); sb.append(":");
sb.append(streamID); sb.append(streamID);
sb.append(":"); sb.append(":");

View File

@@ -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);
}
}

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class ConfigureIcyGenre extends UltravoxMessage
{ {
/** /**
* Client request to server to configure the ICY-GENRE metadata * 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); super(UltravoxMessageType.CONFIGURE_ICY_GENRE);
} }

View File

@@ -16,20 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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;
import java.util.Arrays;
public class ConfigureIcyName extends UltravoxMessage public class ConfigureIcyName extends UltravoxMessage
{ {
/** /**
* Client request to server to configure the ICY-NAME metadata * 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); super(UltravoxMessageType.CONFIGURE_ICY_NAME);
} }

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class ConfigureIcyPublic extends UltravoxMessage
{ {
/** /**
* Client request to server to configure the ICY-PUBLIC metadata * 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); super(UltravoxMessageType.CONFIGURE_ICY_PUBLIC);
} }

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class ConfigureIcyURL extends UltravoxMessage
{ {
/** /**
* Client request to server to configure the ICY-URL metadata * 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); super(UltravoxMessageType.CONFIGURE_ICY_URL);
} }

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class FlushCachedMetadata extends UltravoxMessage
{ {
/** /**
* Client request to server to flush any currently cached stream metadata * 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); super(UltravoxMessageType.FLUSH_CACHED_METADATA);
} }

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class MP3Audio extends UltravoxMessage
{ {
@@ -25,7 +25,7 @@ public class MP3Audio extends UltravoxMessage
* *
* Package private constructor. Use the UltravoxMessageFactory for this constructor. * Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/ */
MP3Audio() public MP3Audio()
{ {
super(UltravoxMessageType.MP3_DATA); super(UltravoxMessageType.MP3_DATA);
} }

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class NegotiateBufferSize extends UltravoxMessage
{ {
/** /**
* Client request to server to negotiate the server's buffer size * 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); 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 * @return server negotiated buffer size, or zero if this is not a server response ultravox, or this is an error
* server response message, or the server response message buffer size cannot be determined from the message payload. * server response ultravox, or the server response ultravox buffer size cannot be determined from the ultravox payload.
*/ */
public int getBufferSize() public int getBufferSize()
{ {

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class NegotiateMaxPayloadSize extends UltravoxMessage
{ {
/** /**
* Client request to server to negotiate maximum payload size * 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); 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 desiredMaximum payload size (no larger than 16377)
* @param minimum acceptable 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 * @return server negotiated maximum payload size, or zero if this is not a server response ultravox, or this is an
* error server response message, or the server response message maximum payload size cannot be determined from the * error server response ultravox, or the server response ultravox maximum payload size cannot be determined from the
* message payload. * ultravox payload.
*/ */
public int getMaximumPayloadSize() public int getMaximumPayloadSize()
{ {

View File

@@ -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);
}
}

View File

@@ -16,19 +16,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class RequestCipher extends UltravoxMessage
{ {
/** /**
* Client request to server for encryption key * Client request to server for encryption key
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/ */
RequestCipher() public RequestCipher()
{ {
super(UltravoxMessageType.REQUEST_CIPHER); super(UltravoxMessageType.REQUEST_CIPHER);
setPayload(SHOUTCAST_VERSION); setPayload(ULTRAVOX_VERSION);
} }
/** /**

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class SetupBroadcast extends UltravoxMessage
{ {
/** /**
* Client request to server to specify the stream bit rate * 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); super(UltravoxMessageType.SETUP_BROADCAST);
} }

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class Standby extends UltravoxMessage
{ {
/** /**
* Client request to server to notify that configuration is complete, prepare for streaming * 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); super(UltravoxMessageType.STANDBY);
} }

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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; import audio.broadcast.BroadcastFormat;
@@ -24,10 +24,8 @@ public class StreamMimeType extends UltravoxMessage
{ {
/** /**
* Client request to server to set stream MIME type * 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); super(UltravoxMessageType.STREAM_MIME_TYPE);
} }

View File

@@ -16,16 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public class TerminateBroadcast extends UltravoxMessage
{ {
/** /**
* Client request to server to terminate the broadcastAudio * Client request to server to terminate the broadcastAudio
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/ */
TerminateBroadcast() public TerminateBroadcast()
{ {
super(UltravoxMessageType.TERMINATE_BROADCAST); super(UltravoxMessageType.TERMINATE_BROADCAST);
} }

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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; import bits.BinaryMessage;
@@ -24,7 +24,7 @@ import java.util.Arrays;
public abstract class UltravoxMessage 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_PREFIX = "ACK";
public static final String VALID_RESPONSE_PAYLOAD_PREFIX = "ACK:"; public static final String VALID_RESPONSE_PAYLOAD_PREFIX = "ACK:";
public static final String ERROR_RESPONSE_PREFIX = "NAK:"; public static final String ERROR_RESPONSE_PREFIX = "NAK:";
@@ -40,7 +40,7 @@ public abstract class UltravoxMessage
private BinaryMessage mMessage; private BinaryMessage mMessage;
/** /**
* Constructs a message from the byte array * Constructs a ultravox from the byte array
*/ */
public UltravoxMessage(byte[] data) 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) public UltravoxMessage(UltravoxMessageType ultravoxMessageType)
{ {
@@ -71,7 +71,7 @@ public abstract class UltravoxMessage
/** /**
* Message byte array * Message byte array
* *
* @return message bytes * @return ultravox bytes
*/ */
public byte[] getMessage() public byte[] getMessage()
{ {
@@ -80,11 +80,11 @@ public abstract class UltravoxMessage
public String toString() 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() private void setSync()
{ {
@@ -92,7 +92,7 @@ public abstract class UltravoxMessage
} }
/** /**
* Ultravox message class * Ultravox ultravox class
*/ */
public UltravoxMessageClass getMessageClass() public UltravoxMessageClass getMessageClass()
{ {
@@ -100,7 +100,7 @@ public abstract class UltravoxMessage
} }
/** /**
* Ultravox message type * Ultravox ultravox type
* @return * @return
*/ */
public UltravoxMessageType getMessageType() public UltravoxMessageType getMessageType()
@@ -109,8 +109,8 @@ public abstract class UltravoxMessage
} }
/** /**
* Sets the message bits according to the message class and type * Sets the ultravox bits according to the ultravox class and type
* @param messageType of ultravox message * @param messageType of ultravox ultravox
*/ */
private void setMessageType(UltravoxMessageType messageType) 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() 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() 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 * @param priority 0 - 7
*/ */
public void setSendQueuePriority(int priority) 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) public void setPayload(String payload)
{ {
@@ -211,6 +211,9 @@ public abstract class UltravoxMessage
mMessage.setByte(payloadPointer, (byte)0x00); mMessage.setByte(payloadPointer, (byte)0x00);
} }
/**
* Indicates if the response is a valid (ie non-error) response
*/
public boolean isValidResponse() public boolean isValidResponse()
{ {
String payload = getPayload(); String payload = getPayload();
@@ -218,6 +221,9 @@ public abstract class UltravoxMessage
return payload != null && payload.startsWith(VALID_RESPONSE_PREFIX); return payload != null && payload.startsWith(VALID_RESPONSE_PREFIX);
} }
/**
* Indicates if the response message contains an error message.
*/
public boolean isErrorResponse() public boolean isErrorResponse()
{ {
String payload = getPayload(); String payload = getPayload();
@@ -225,7 +231,10 @@ public abstract class UltravoxMessage
return payload != null && payload.startsWith(ERROR_RESPONSE_PREFIX); 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(); String payload = getPayload();

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public enum UltravoxMessageClass
{ {

View File

@@ -16,12 +16,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 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) public static UltravoxMessage getMessage(UltravoxMessageType type)
{ {
@@ -29,6 +29,8 @@ public class UltravoxMessageFactory
{ {
case AUTHENTICATE_BROADCAST: case AUTHENTICATE_BROADCAST:
return new AuthenticateBroadcast(); return new AuthenticateBroadcast();
case CACHEABLE_XML_METADATA:
return new CacheableXMLMetadata();
case CONFIGURE_ICY_GENRE: case CONFIGURE_ICY_GENRE:
return new ConfigureIcyGenre(); return new ConfigureIcyGenre();
case CONFIGURE_ICY_NAME: case CONFIGURE_ICY_NAME:
@@ -45,6 +47,8 @@ public class UltravoxMessageFactory
return new NegotiateBufferSize(); return new NegotiateBufferSize();
case NEGOTIATE_MAX_PAYLOAD_SIZE: case NEGOTIATE_MAX_PAYLOAD_SIZE:
return new NegotiateMaxPayloadSize(); return new NegotiateMaxPayloadSize();
case PASS_THROUGH_XML_METADATA:
return new PassThroughXMLMetadata();
case REQUEST_CIPHER: case REQUEST_CIPHER:
return new RequestCipher(); return new RequestCipher();
case SETUP_BROADCAST: case SETUP_BROADCAST:
@@ -55,19 +59,17 @@ public class UltravoxMessageFactory
return new StreamMimeType(); return new StreamMimeType();
case TERMINATE_BROADCAST: case TERMINATE_BROADCAST:
return new TerminateBroadcast(); return new TerminateBroadcast();
case XML_METADATA:
return new XMLMetadata();
default: default:
return null; 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). * @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) public static UltravoxMessage getMessage(byte[] bytes)
{ {
@@ -77,6 +79,8 @@ public class UltravoxMessageFactory
{ {
case AUTHENTICATE_BROADCAST: case AUTHENTICATE_BROADCAST:
return new AuthenticateBroadcast(bytes); return new AuthenticateBroadcast(bytes);
case CACHEABLE_XML_METADATA:
return new CacheableXMLMetadata(bytes);
case CONFIGURE_ICY_GENRE: case CONFIGURE_ICY_GENRE:
return new ConfigureIcyGenre(bytes); return new ConfigureIcyGenre(bytes);
case CONFIGURE_ICY_NAME: case CONFIGURE_ICY_NAME:
@@ -93,6 +97,8 @@ public class UltravoxMessageFactory
return new NegotiateBufferSize(bytes); return new NegotiateBufferSize(bytes);
case NEGOTIATE_MAX_PAYLOAD_SIZE: case NEGOTIATE_MAX_PAYLOAD_SIZE:
return new NegotiateMaxPayloadSize(bytes); return new NegotiateMaxPayloadSize(bytes);
case PASS_THROUGH_XML_METADATA:
return new PassThroughXMLMetadata(bytes);
case REQUEST_CIPHER: case REQUEST_CIPHER:
return new RequestCipher(bytes); return new RequestCipher(bytes);
case SETUP_BROADCAST: case SETUP_BROADCAST:
@@ -103,8 +109,6 @@ public class UltravoxMessageFactory
return new StreamMimeType(bytes); return new StreamMimeType(bytes);
case TERMINATE_BROADCAST: case TERMINATE_BROADCAST:
return new TerminateBroadcast(bytes); return new TerminateBroadcast(bytes);
case XML_METADATA:
return new XMLMetadata(bytes);
case UNKNOWN: case UNKNOWN:
default: default:
return null; 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 * @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) private static UltravoxMessageType getMessageType(byte[] data)
{ {

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public enum UltravoxMessageType
{ {
@@ -33,7 +33,8 @@ public enum UltravoxMessageType
CONFIGURE_ICY_GENRE(0x1101), CONFIGURE_ICY_GENRE(0x1101),
CONFIGURE_ICY_URL(0x1102), CONFIGURE_ICY_URL(0x1102),
CONFIGURE_ICY_PUBLIC(0x1103), CONFIGURE_ICY_PUBLIC(0x1103),
XML_METADATA(0x3902), CACHEABLE_XML_METADATA(0x3902),
PASS_THROUGH_XML_METADATA(0x5902),
MP3_DATA(0x7000), MP3_DATA(0x7000),
UNKNOWN(0x0000); UNKNOWN(0x0000);
@@ -80,7 +81,9 @@ public enum UltravoxMessageType
case 0x1103: case 0x1103:
return CONFIGURE_ICY_PUBLIC; return CONFIGURE_ICY_PUBLIC;
case 0x3902: case 0x3902:
return XML_METADATA; return CACHEABLE_XML_METADATA;
case 0x5902:
return PASS_THROUGH_XML_METADATA;
case 0x7000: case 0x7000:
return MP3_DATA; return MP3_DATA;
default: default:

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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 public enum UltravoxMetadata
{ {

View File

@@ -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;
}
}