diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml
index 569c2a0d..dc5ec847 100644
--- a/.idea/dictionaries/denny.xml
+++ b/.idea/dictionaries/denny.xml
@@ -9,6 +9,7 @@
ident
polyphase
samplerate
+ sdrtrunk
shoutcast
streamable
talkgroup
diff --git a/src/alias/AliasList.java b/src/alias/AliasList.java
index 5401d44e..8b5a6ff8 100644
--- a/src/alias/AliasList.java
+++ b/src/alias/AliasList.java
@@ -212,6 +212,7 @@ public class AliasList implements Listener
mTalkgroup.put(tgid, alias);
}
break;
+ case BROADCAST_CHANNEL:
case NON_RECORDABLE:
case PRIORITY:
//We don't maintain lookups for these items
diff --git a/src/alias/id/broadcast/BroadcastChannel.java b/src/alias/id/broadcast/BroadcastChannel.java
index e952f997..f0baf630 100644
--- a/src/alias/id/broadcast/BroadcastChannel.java
+++ b/src/alias/id/broadcast/BroadcastChannel.java
@@ -23,16 +23,42 @@ import alias.id.AliasIDType;
import javax.xml.bind.annotation.XmlAttribute;
-public class BroadcastChannel extends AliasID
+public class BroadcastChannel extends AliasID implements Comparable
{
private String mChannelName;
public BroadcastChannel()
{
+ //JAXB Constructor
+ }
+
+ @Override
+ public int compareTo(BroadcastChannel other)
+ {
+ if(mChannelName != null && other.getChannelName() != null)
+ {
+ return mChannelName.compareTo(other.getChannelName());
+ }
+ else if(mChannelName != null)
+ {
+ return -1;
+ }
+ else
+ {
+ return 1;
+ }
}
/**
- * Name of the broadcast channel configuration
+ * Creates a named broadcast channel
+ */
+ public BroadcastChannel(String channelName)
+ {
+ mChannelName = channelName;
+ }
+
+ /**
+ * Name of the broadcastAudio channel configuration
*/
@XmlAttribute(name="channel")
public String getChannelName()
@@ -41,7 +67,7 @@ public class BroadcastChannel extends AliasID
}
/**
- * Sets the name of the broadcast channel configuration
+ * Sets the name of the broadcastAudio channel configuration
*/
public void setChannelName(String channel)
{
diff --git a/src/alias/id/broadcast/BroadcastChannelEditor.java b/src/alias/id/broadcast/BroadcastChannelEditor.java
index dd54922c..f8d07810 100644
--- a/src/alias/id/broadcast/BroadcastChannelEditor.java
+++ b/src/alias/id/broadcast/BroadcastChannelEditor.java
@@ -19,12 +19,15 @@
package alias.id.broadcast;
import alias.id.AliasID;
+import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastModel;
import gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
@@ -40,16 +43,14 @@ public class BroadcastChannelEditor extends DocumentListenerEditor
+ "streaming server";
private JComboBox mBroadcastConfigurations;
+ private BroadcastModel mBroadcastModel;
public BroadcastChannelEditor(AliasID aliasID, BroadcastModel broadcastModel)
{
- List channelNames = broadcastModel.getBroadcastConfigurationNames();
-
- mBroadcastConfigurations = new JComboBox(channelNames.toArray(new String[channelNames.size()]));
-
- setItem( aliasID );
+ mBroadcastModel = broadcastModel;
initGUI();
+ setItem( aliasID );
}
private void initGUI()
@@ -58,15 +59,20 @@ public class BroadcastChannelEditor extends DocumentListenerEditor
add( new JLabel( "Broadcast Channel" ), "grow" );
+ List channelNames = mBroadcastModel.getBroadcastConfigurationNames();
+
+ mBroadcastConfigurations = new JComboBox(channelNames.toArray(new String[channelNames.size()]));
+ mBroadcastConfigurations.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ setModified(true);
+ }
+ });
+
add(mBroadcastConfigurations, "grow");
- BroadcastChannel channel = (BroadcastChannel)getItem();
-
- if(channel.isValid())
- {
- mBroadcastConfigurations.setSelectedItem(channel.getChannelName());
- }
-
JLabel help = new JLabel( "Help ..." );
help.setForeground( Color.BLUE.brighter() );
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
@@ -82,10 +88,42 @@ public class BroadcastChannelEditor extends DocumentListenerEditor
add( help, "align left" );
}
-
+
+ @Override
+ public void setItem(AliasID item)
+ {
+ super.setItem(item);
+
+ if(item instanceof BroadcastChannel)
+ {
+ BroadcastChannel channel = (BroadcastChannel)item;
+
+ if(channel.isValid())
+ {
+ mBroadcastConfigurations.setSelectedItem(channel.getChannelName());
+ }
+ }
+
+ setModified(false);
+ }
+
@Override
public void save()
{
- ((BroadcastChannel)getItem()).setChannelName((String)mBroadcastConfigurations.getSelectedItem());
+ String channel = (String)mBroadcastConfigurations.getSelectedItem();
+
+ BroadcastConfiguration selectedConfiguration = mBroadcastModel.getBroadcastConfiguration(channel);
+
+
+ if(selectedConfiguration != null)
+ {
+ ((BroadcastChannel)getItem()).setChannelName(selectedConfiguration.getName());
+ }
+ else
+ {
+ ((BroadcastChannel)getItem()).setChannelName(null);
+ }
}
+
+
}
diff --git a/src/audio/broadcast/AudioBroadcastManager.java b/src/audio/broadcast/AudioBroadcastManager.java
deleted file mode 100644
index c0598a13..00000000
--- a/src/audio/broadcast/AudioBroadcastManager.java
+++ /dev/null
@@ -1,39 +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
- *
- ******************************************************************************/
-package audio.broadcast;
-
-import audio.AudioPacket;
-import controller.ThreadPoolManager;
-import sample.Listener;
-
-public class AudioBroadcastManager implements Listener
-{
- private ThreadPoolManager mThreadPoolManager;
-
- public AudioBroadcastManager(ThreadPoolManager threadPoolManager)
- {
- mThreadPoolManager = threadPoolManager;
- }
-
- @Override
- public void receive(AudioPacket audioPacket)
- {
-
- }
-}
diff --git a/src/audio/broadcast/AudioBroadcaster.java b/src/audio/broadcast/AudioBroadcaster.java
new file mode 100644
index 00000000..676f8c4e
--- /dev/null
+++ b/src/audio/broadcast/AudioBroadcaster.java
@@ -0,0 +1,425 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast;
+
+import audio.AudioPacket;
+import audio.IAudioPacketListener;
+import audio.metadata.AudioMetadata;
+import controller.ThreadPoolManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import properties.SystemProperties;
+import sample.Listener;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public abstract class AudioBroadcaster implements IAudioPacketListener, Listener
+{
+ private final static Logger mLog = LoggerFactory.getLogger( AudioBroadcaster.class );
+ public static final int PROCESSOR_RUN_INTERVAL_MS = 1000;
+
+ private ThreadPoolManager mThreadPoolManager;
+ private ScheduledFuture mScheduledTask;
+
+ private StreamManager mStreamManager;
+ private RecordingQueueProcessor mRecordingQueueProcessor = new RecordingQueueProcessor();
+ private ArrayBlockingQueue mAudioRecordingQueue;
+ private byte[] mSilenceFrame;
+ private int mStreamedAudioCount = 0;
+
+ private Listener mBroadcastEventListener;
+ private BroadcastState mBroadcastState = BroadcastState.READY;
+
+ private BroadcastConfiguration mBroadcastConfiguration;
+
+ private boolean mEnabled = true;
+
+ /**
+ * AudioBroadcaster for sending audio to a remote broadcastAudio server.
+ */
+ public AudioBroadcaster(ThreadPoolManager threadPoolManager, BroadcastConfiguration broadcastConfiguration)
+ {
+ mThreadPoolManager = threadPoolManager;
+ mBroadcastConfiguration = broadcastConfiguration;
+
+ //Recording queue - bounded at 10 queued recordings maximum - new stream recordings will be deleted once full
+ mAudioRecordingQueue = new ArrayBlockingQueue<>(10);
+
+ //Create a 1 second silence frame - from 1200 millis of silence
+ mSilenceFrame = BroadcastFactory.getSilenceFrame(getBroadcastConfiguration().getBroadcastFormat(), 1200);
+
+ mStreamManager = new StreamManager(threadPoolManager, this, broadcastConfiguration.getDelay(),
+ SystemProperties.getInstance().getApplicationFolder(BroadcastModel.TEMPORARY_STREAM_DIRECTORY));
+
+ mStreamManager.start();
+
+ startProcessor();
+ }
+
+ /**
+ * Recording backlog awaiting streaming
+ */
+ public int getQueueSize()
+ {
+ return mAudioRecordingQueue.size();
+ }
+
+ /**
+ * Number of audio recordings streamed to remote server
+ */
+ public int getStreamedAudioCount()
+ {
+ return mStreamedAudioCount;
+ }
+
+ /**
+ * Queues the streamable audio recording for broadcastAudio. The bounded queue is managed by removing older recordings
+ * at the head of the queue, once full, to make room for new recordings. This ensures that once the broadcaster
+ * (re)starts broadcasting or catches up with the queue, that it is broadcasting the most current audio recordings.
+ *
+ * @param recording to queue for broadcasting
+ */
+ public void receive(StreamableAudioRecording recording)
+ {
+ boolean success = false;
+
+ try
+ {
+ success = mAudioRecordingQueue.add(recording);
+ }
+ catch(IllegalStateException ise)
+ {
+ //The queue is full - delete the oldest recording at the head of the queue to make room for a new one
+ removeRecording(mAudioRecordingQueue.poll());
+ }
+
+ if(!success)
+ {
+ try
+ {
+ success = mAudioRecordingQueue.add(recording);
+ }
+ catch(IllegalStateException ise)
+ {
+ //Do nothing on second attempt
+ }
+ }
+
+ //If we can't queue the recording after two attempts, remove the recording
+ if(!success)
+ {
+ removeRecording(recording);
+ }
+ else
+ {
+ broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
+ }
+ }
+
+ /**
+ * Removes the temporary recording file from disk.
+ *
+ * @param recording to remove
+ */
+ private void removeRecording(StreamableAudioRecording recording)
+ {
+ try
+ {
+ Files.delete(recording.getPath());
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Error deleting temporary internet recording file: " + recording.getPath().toString());
+ }
+ }
+
+ /**
+ * Broadcast configuration used by this broadcaster
+ */
+ public BroadcastConfiguration getBroadcastConfiguration()
+ {
+ return mBroadcastConfiguration;
+ }
+
+ /**
+ * IAudioPacketListener interface method
+ */
+ @Override
+ public Listener getAudioPacketListener()
+ {
+ return this;
+ }
+
+ /**
+ * Broadcasts the audio binary data.
+ */
+ protected abstract void broadcastAudio(byte[] audio);
+
+ /**
+ * Broadcasts the next song's audio metadata prior to streaming the next song.
+ * @param metadata
+ */
+ protected abstract void broadcastMetadata(AudioMetadata metadata);
+
+ /**
+ * Disconnects the broadcaster and halts all broadcasting.
+ */
+ public void stop()
+ {
+ mEnabled = false;
+ mStreamManager.stop();
+ stopProcessor();
+ }
+
+ /**
+ * Primary audio packet input method. Audio packets are queued and then processed by the underlying broadcastAudio
+ * handler for broadcastAudio to the remote server.
+ */
+ public void receive(AudioPacket packet)
+ {
+ if(mEnabled)
+ {
+ mStreamManager.receive(packet);
+ }
+ }
+
+ /**
+ * Starts the audio queue processor. The audio packet queue is serviced every 100 milliseconds. Subsequent calls
+ * to this method once running are ignored.
+ */
+ private void startProcessor()
+ {
+ if(mEnabled && mScheduledTask == null)
+ {
+ if(mThreadPoolManager != null)
+ {
+ mScheduledTask = mThreadPoolManager.scheduleFixedRate(ThreadPoolManager.ThreadType.AUDIO_PROCESSING,
+ mRecordingQueueProcessor, PROCESSOR_RUN_INTERVAL_MS, TimeUnit.MILLISECONDS );
+ }
+ }
+ }
+
+ /**
+ * Stops the audio queue processor
+ */
+ private void stopProcessor()
+ {
+ if(mThreadPoolManager != null && mScheduledTask != null)
+ {
+ mThreadPoolManager.cancel(mScheduledTask);
+ }
+ }
+
+ /**
+ * Pauses or unpauses the broadcastAudio
+ */
+ public void setPaused(boolean paused)
+ {
+ if(!getBroadcastState().isErrorState())
+ {
+ if(paused)
+ {
+ setBroadcastState(BroadcastState.PAUSED);
+ }
+ else
+ {
+ setBroadcastState(BroadcastState.READY);
+ }
+ }
+ }
+
+ /**
+ * Registers the listener to receive broadcastAudio state changes
+ */
+ public void setListener(Listener listener)
+ {
+ mBroadcastEventListener = listener;
+ }
+
+ /**
+ * Removes the listener from receiving broadcastAudio state changes
+ */
+ public void removeListener()
+ {
+ mBroadcastEventListener = null;
+ }
+
+ /**
+ * Broadcasts the event to any registered listener
+ */
+ public void broadcast(BroadcastEvent event)
+ {
+ if(mBroadcastEventListener != null)
+ {
+ mBroadcastEventListener.receive(event);
+ }
+ }
+
+ /**
+ * Sets the state of the broadcastAudio connection
+ */
+ protected void setBroadcastState(BroadcastState state)
+ {
+ if(mBroadcastState != state)
+ {
+ mLog.debug("Changing State to: " + state);
+ mBroadcastState = state;
+
+ broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
+ }
+ }
+
+ /**
+ * Current state of the broadcastAudio connection
+ */
+ public BroadcastState getBroadcastState()
+ {
+ return mBroadcastState;
+ }
+
+ public boolean connected()
+ {
+ return getBroadcastState() == BroadcastState.CONNECTED;
+ }
+
+ /**
+ * Indicates if this broadcaster can connect and is not currently in an error state or a connected state.
+ */
+ public boolean canConnect()
+ {
+ BroadcastState state = getBroadcastState();
+
+ return state != BroadcastState.CONNECTED && !state.isErrorState();
+ }
+
+
+ /**
+ * Audio recording queue processor. Fetches recordings from the queue and chunks the recording byte content
+ * to subclass implementations for broadcast in the appropriate manner.
+ */
+ public class RecordingQueueProcessor implements Runnable
+ {
+ private AtomicBoolean mProcessing = new AtomicBoolean();
+ private ByteArrayInputStream mInputStream;
+ private int mChunkSize;
+
+ @Override
+ public void run()
+ {
+ if(mProcessing.compareAndSet(false, true))
+ {
+ if(mInputStream == null || mInputStream.available() <= 0)
+ {
+ nextRecording();
+ }
+
+ if(mInputStream != null)
+ {
+ int length = Math.min(mChunkSize, mInputStream.available());
+
+ byte[] audio = new byte[length];
+
+ try
+ {
+ mInputStream.read(audio);
+
+ broadcastAudio(audio);
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Error reading from in-memory audio recording input stream", ioe);
+ }
+ }
+ else
+ {
+ broadcastAudio(mSilenceFrame);
+ }
+
+ mProcessing.set(false);
+ }
+ }
+
+ /**
+ * Loads the next recording for broadcast
+ */
+ private void nextRecording()
+ {
+ if(mInputStream != null)
+ {
+ mStreamedAudioCount++;
+ broadcast(new BroadcastEvent(AudioBroadcaster.this,
+ BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
+ }
+
+ mInputStream = null;
+
+ StreamableAudioRecording recording = mAudioRecordingQueue.poll();
+
+ if(recording != null)
+ {
+ try
+ {
+ byte[] audio = Files.readAllBytes(recording.getPath());
+
+ if(audio != null && audio.length > 0)
+ {
+ mInputStream = new ByteArrayInputStream(audio);
+
+ int wholeIntervalChunks = (int)(recording.getRecordingLength() / PROCESSOR_RUN_INTERVAL_MS);
+
+ //Check for divide by zero situation
+ if(wholeIntervalChunks == 0)
+ {
+ wholeIntervalChunks = 1;
+ }
+
+ mChunkSize = (int)(mInputStream.available() / wholeIntervalChunks) + 1;
+
+ broadcastMetadata(recording.getAudioMetadata());
+ }
+ else
+ {
+ broadcastMetadata(null);
+ }
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Error reading temporary audio stream recording [" + recording.getPath().toString() +
+ "] - skipping recording");
+
+ mInputStream = null;
+ }
+
+ removeRecording(recording);
+
+ broadcast(new BroadcastEvent(AudioBroadcaster.this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
+ }
+ else
+ {
+ broadcastMetadata(null);
+ }
+ }
+ }
+}
diff --git a/src/audio/broadcast/BroadcastConfiguration.java b/src/audio/broadcast/BroadcastConfiguration.java
index 90b806d7..07161912 100644
--- a/src/audio/broadcast/BroadcastConfiguration.java
+++ b/src/audio/broadcast/BroadcastConfiguration.java
@@ -24,6 +24,7 @@ import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlTransient;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -31,17 +32,24 @@ import java.net.SocketAddress;
@XmlRootElement(name = "stream")
public abstract class BroadcastConfiguration
{
- private BroadcastFormat mBroadcastFormat;
+ private BroadcastFormat mBroadcastFormat = BroadcastFormat.MP3;
private String mName;
private String mHost;
private int mPort;
private String mPassword;
+ private long mDelay;
+ private boolean mEnabled = true;
public BroadcastConfiguration()
{
//No-arg constructor required for JAXB
}
+ /**
+ * Creates a copy/clone of the configuration
+ */
+ public abstract BroadcastConfiguration copyOf();
+
/**
* Broadcast audio streaming configuration. Describes the streaming server and configuration details.
*/
@@ -57,7 +65,7 @@ public abstract class BroadcastConfiguration
public abstract BroadcastServerType getBroadcastServerType();
/**
- * Name identifying this broadcast configuration.
+ * Name identifying this broadcastAudio configuration.
*/
@XmlAttribute(name = "name")
public String getName()
@@ -120,7 +128,7 @@ public abstract class BroadcastConfiguration
}
/**
- * Port number of the streaming server to where the broadcast will be directed.
+ * Port number of the streaming server to where the broadcastAudio will be directed.
*
* @param port
*/
@@ -152,7 +160,7 @@ public abstract class BroadcastConfiguration
}
/**
- * Sets the password used to authenticate the broadcast broadcast user to the streaming server
+ * Sets the password used to authenticate the broadcastAudio broadcastAudio user to the streaming server
*
* @param password
*/
@@ -170,11 +178,95 @@ public abstract class BroadcastConfiguration
}
/**
- * Audio broadcast content type (e.g. mp3)
+ * Audio broadcastAudio content type (e.g. mp3)
*/
- @XmlElement(name = "format")
+ @XmlAttribute(name = "format")
public BroadcastFormat getBroadcastFormat()
{
return mBroadcastFormat;
}
+
+ public void setBroadcastFormat(BroadcastFormat format)
+ {
+ mBroadcastFormat = format;
+ }
+
+ /**
+ * Audio broadcastAudio delay from recording start until the audio file is broadcastAudio to the server.
+ */
+ @XmlAttribute(name = "delay")
+ public long getDelay()
+ {
+ return mDelay;
+ }
+
+ /**
+ * User specified delay in milli-seconds from audio call start until when the audio is broadcastAudio to the server.
+ * @param delay in milliseconds
+ */
+ public void setDelay(long delay)
+ {
+ mDelay = delay;
+ }
+
+ /**
+ * Indicates if this broadcaster is enable, meaning that it will automatically connect on startup.
+ */
+ @XmlAttribute(name = "enabled")
+ public boolean isEnabled()
+ {
+ return mEnabled;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ mEnabled = enabled;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if (!(o instanceof BroadcastConfiguration))
+ {
+ return false;
+ }
+
+ BroadcastConfiguration that = (BroadcastConfiguration) o;
+
+ if (getBroadcastFormat() != that.getBroadcastFormat())
+ {
+ return false;
+ }
+
+ if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null)
+ {
+ return false;
+ }
+
+ return getBroadcastServerType() == that.getBroadcastServerType();
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = getBroadcastFormat().hashCode();
+ result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+ result = 31 * result + getBroadcastServerType().hashCode();
+ return result;
+ }
+
+ /**
+ * Indicates if this configuration is valid. A minimal check will ensure that it contains at least a hostname and
+ * port number.
+ */
+ @XmlTransient
+ public boolean isValid()
+ {
+ return mHost != null && mPort > 0;
+ }
}
diff --git a/src/audio/broadcast/BroadcastConfigurationEvent.java b/src/audio/broadcast/BroadcastEvent.java
similarity index 60%
rename from src/audio/broadcast/BroadcastConfigurationEvent.java
rename to src/audio/broadcast/BroadcastEvent.java
index 02b37be2..6a48bd5e 100644
--- a/src/audio/broadcast/BroadcastConfigurationEvent.java
+++ b/src/audio/broadcast/BroadcastEvent.java
@@ -18,9 +18,10 @@
******************************************************************************/
package audio.broadcast;
-public class BroadcastConfigurationEvent
+public class BroadcastEvent
{
private BroadcastConfiguration mBroadcastConfiguration;
+ private AudioBroadcaster mAudioBroadcaster;
private Event mEvent;
/**
@@ -28,17 +29,44 @@ public class BroadcastConfigurationEvent
* @param configuration - alias that changed
* @param event - change event
*/
- public BroadcastConfigurationEvent(BroadcastConfiguration configuration, Event event )
+ public BroadcastEvent(BroadcastConfiguration configuration, Event event )
{
mBroadcastConfiguration = configuration;
mEvent = event;
}
-
+
+ public BroadcastEvent(AudioBroadcaster audioBroadcaster, Event event )
+ {
+ mAudioBroadcaster = audioBroadcaster;
+ mEvent = event;
+ }
+
public BroadcastConfiguration getBroadcastConfiguration()
{
return mBroadcastConfiguration;
}
-
+
+ public AudioBroadcaster getAudioBroadcaster()
+ {
+ return mAudioBroadcaster;
+ }
+
+ /**
+ * Indicates if this event pertains to an audio broadcaster
+ */
+ public boolean isAudioBroadcasterEvent()
+ {
+ return mAudioBroadcaster != null;
+ }
+
+ /**
+ * Indicates if this event pertains to a broadcast configuration
+ */
+ public boolean isBroadcastConfigurationEvent()
+ {
+ return mBroadcastConfiguration != null;
+ }
+
public Event getEvent()
{
return mEvent;
@@ -49,8 +77,14 @@ public class BroadcastConfigurationEvent
*/
public enum Event
{
- ADD,
- CHANGE,
- DELETE;
+ BROADCASTER_ADD,
+ BROADCASTER_QUEUE_CHANGE,
+ BROADCASTER_STATE_CHANGE,
+ BROADCASTER_STREAMED_COUNT_CHANGE,
+ BROADCASTER_DELETE,
+
+ CONFIGURATION_ADD,
+ CONFIGURATION_CHANGE,
+ CONFIGURATION_DELETE;
}
}
diff --git a/src/audio/broadcast/BroadcastFactory.java b/src/audio/broadcast/BroadcastFactory.java
index b2c47c90..740c4574 100644
--- a/src/audio/broadcast/BroadcastFactory.java
+++ b/src/audio/broadcast/BroadcastFactory.java
@@ -18,27 +18,32 @@
******************************************************************************/
package audio.broadcast;
+import audio.AudioPacket;
import audio.broadcast.broadcastify.BroadcastifyConfiguration;
import audio.broadcast.broadcastify.BroadcastifyConfigurationEditor;
+import audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
import audio.broadcast.icecast.IcecastHTTPConfiguration;
-import audio.broadcast.icecast.IcecastHTTPBroadcaster;
-import audio.broadcast.icecast.IcecastTCPBroadcaster;
+import audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
import audio.broadcast.icecast.IcecastTCPConfiguration;
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
-import audio.broadcast.shoutcast.v1.ShoutcastV1Broadcaster;
+import audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcaster;
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
-import audio.broadcast.shoutcast.v2.ShoutcastV2Broadcaster;
+import audio.broadcast.shoutcast.v2.ShoutcastV2AudioBroadcaster;
import audio.convert.IAudioConverter;
import audio.convert.MP3AudioConverter;
import controller.ThreadPoolManager;
import gui.editor.Editor;
import gui.editor.EmptyEditor;
+import org.asynchttpclient.DefaultAsyncHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.AudioRecorder;
import record.mp3.MP3Recorder;
+import settings.SettingsManager;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
public class BroadcastFactory
{
@@ -54,34 +59,35 @@ public class BroadcastFactory
* @param configuration describing the server and audio types
* @return configured broadcaster or null
*/
- public static Broadcaster getBroadcaster(ThreadPoolManager threadPoolManager,
- BroadcastConfiguration configuration)
+ public static AudioBroadcaster getBroadcaster(DefaultAsyncHttpClient defaultAsyncHttpClient,
+ ThreadPoolManager threadPoolManager,
+ BroadcastConfiguration configuration)
{
- IAudioConverter converter = getAudioConverter(configuration);
-
- if(converter != null)
+ if(configuration != null)
{
- switch(configuration.getBroadcastServerType())
+ IAudioConverter converter = getAudioConverter(configuration);
+
+ if(converter != null)
{
- case BROADCASTIFY:
- return new IcecastTCPBroadcaster(threadPoolManager,
- (BroadcastifyConfiguration)configuration, converter);
- case ICECAST_HTTP:
- return new IcecastHTTPBroadcaster(threadPoolManager,
- (IcecastHTTPConfiguration)configuration, converter);
- case ICECAST_TCP:
- return new IcecastTCPBroadcaster(threadPoolManager,
- (IcecastTCPConfiguration)configuration, converter);
- case SHOUTCAST_V1:
- return new ShoutcastV1Broadcaster(threadPoolManager,
- (ShoutcastV1Configuration)configuration, converter);
- case SHOUTCAST_V2:
- return new ShoutcastV2Broadcaster(threadPoolManager,
- (ShoutcastV2Configuration)configuration, converter);
- case UNKNOWN:
- default:
- mLog.info("Unrecognized broadcast configuration: " + configuration.getBroadcastFormat().name());
- break;
+ switch(configuration.getBroadcastServerType())
+ {
+ case BROADCASTIFY:
+ return new IcecastTCPAudioBroadcaster(defaultAsyncHttpClient, threadPoolManager,
+ (BroadcastifyConfiguration)configuration);
+ case ICECAST_TCP:
+ return new IcecastTCPAudioBroadcaster(defaultAsyncHttpClient, threadPoolManager,
+ (IcecastTCPConfiguration)configuration);
+ case ICECAST_HTTP:
+ return new IcecastHTTPAudioBroadcaster(threadPoolManager, (IcecastHTTPConfiguration)configuration);
+ case SHOUTCAST_V1:
+ return new ShoutcastV1AudioBroadcaster(threadPoolManager, (ShoutcastV1Configuration)configuration);
+ case SHOUTCAST_V2:
+ return new ShoutcastV2AudioBroadcaster(threadPoolManager, (ShoutcastV2Configuration)configuration);
+ case UNKNOWN:
+ default:
+ mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
+ break;
+ }
}
}
@@ -100,14 +106,14 @@ public class BroadcastFactory
case MP3:
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
default:
- mLog.info("Unrecognized broadcast format: " + configuration.getBroadcastFormat().name());
+ mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name());
}
return null;
}
/**
- * Creates a broadcast configuration for the specified server type and format
+ * Creates a broadcastAudio configuration for the specified server type and format
*
* @param serverType for the configuration
* @param format for the output audio format
@@ -129,7 +135,7 @@ public class BroadcastFactory
return new ShoutcastV2Configuration(format);
case UNKNOWN:
default:
- mLog.info("Unrecognized broadcast server type: " + serverType.name());
+ mLog.info("Unrecognized broadcastAudio server type: " + serverType.name());
break;
}
@@ -137,20 +143,21 @@ public class BroadcastFactory
}
/**
- * Constructs an editor for the specified broadcast configuration
+ * Constructs an editor for the specified broadcastAudio configuration
* @param configuration to modify or view
- * @param broadcastModel model for broadcast configurations
- * @return an editor for the specified broadcast configuration
+ * @param broadcastModel model for broadcastAudio configurations
+ * @return an editor for the specified broadcastAudio configuration
*/
public static Editor getEditor(BroadcastConfiguration configuration,
- BroadcastModel broadcastModel)
+ BroadcastModel broadcastModel,
+ SettingsManager settingsManager)
{
Editor editor;
switch (configuration.getBroadcastServerType())
{
case BROADCASTIFY:
- editor = new BroadcastifyConfigurationEditor(broadcastModel);
+ editor = new BroadcastifyConfigurationEditor(broadcastModel, settingsManager);
break;
default:
editor = new EmptyEditor();
@@ -163,7 +170,7 @@ public class BroadcastFactory
}
/**
- * Creates an audio recorder for the specified broadcast format using the specified path output file name
+ * Creates an audio recorder for the specified broadcastAudio format using the specified path output file name
*/
public static AudioRecorder getAudioRecorder(Path path, BroadcastFormat broadcastFormat)
{
@@ -172,8 +179,28 @@ public class BroadcastFactory
case MP3:
return new MP3Recorder(path);
default:
- mLog.debug("Unrecognized broadcast format [" + broadcastFormat + "] cannot create audio recorder");
+ mLog.debug("Unrecognized broadcastAudio format [" + broadcastFormat + "] cannot create audio recorder");
return null;
}
}
+
+ public static byte[] getSilenceFrame(BroadcastFormat format, long duration)
+ {
+ int length = (int)(duration * 8); //8000 Hz sample rate
+ float[] silence = new float[length];
+ AudioPacket silencePacket = new AudioPacket(silence, null);
+ List silencePackets = new ArrayList<>();
+ silencePackets.add(silencePacket);
+
+ switch(format)
+ {
+ case MP3:
+ MP3AudioConverter converter = new MP3AudioConverter(MP3Recorder.MP3_BIT_RATE,
+ MP3Recorder.CONSTANT_BIT_RATE);
+
+ return converter.convert(silencePackets);
+ default:
+ throw new IllegalArgumentException("Unrecognized broadcast format: " + format.name());
+ }
+ }
}
diff --git a/src/audio/broadcast/BroadcastFormat.java b/src/audio/broadcast/BroadcastFormat.java
index 5699085f..134423bd 100644
--- a/src/audio/broadcast/BroadcastFormat.java
+++ b/src/audio/broadcast/BroadcastFormat.java
@@ -28,6 +28,7 @@ public enum BroadcastFormat
private BroadcastFormat(String value, String fileExtension)
{
mValue = value;
+ mFileExtension = fileExtension;
}
public String getValue()
diff --git a/src/audio/broadcast/BroadcastModel.java b/src/audio/broadcast/BroadcastModel.java
index e16d2ed7..5f65d208 100644
--- a/src/audio/broadcast/BroadcastModel.java
+++ b/src/audio/broadcast/BroadcastModel.java
@@ -18,28 +18,70 @@
******************************************************************************/
package audio.broadcast;
+import alias.id.broadcast.BroadcastChannel;
+import audio.AudioPacket;
+import controller.ThreadPoolManager;
+import org.asynchttpclient.DefaultAsyncHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import properties.SystemProperties;
+import sample.Broadcaster;
import sample.Listener;
+import settings.SettingsManager;
+import javax.swing.*;
import javax.swing.table.AbstractTableModel;
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
-public class BroadcastModel extends AbstractTableModel
+public class BroadcastModel extends AbstractTableModel implements Listener
{
- private static final int COLUMN_CHANNEL_NAME = 0;
- private static final int COLUMN_SERVER_TYPE = 1;
- private static final int COLUMN_SERVER_ADDRESS = 2;
+ private final static Logger mLog = LoggerFactory.getLogger( BroadcastModel.class );
+
+ public static final String TEMPORARY_STREAM_DIRECTORY = "streaming";
+ public static final String TEMPORARY_STREAM_FILE_SUFFIX = "temporary_streaming_file_";
+
+ private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
+
+ private static final int COLUMN_SERVER_ICON = 0;
+ private static final int COLUMN_STREAM_NAME = 1;
+ private static final int COLUMN_BROADCASTER_STATUS = 2;
+ private static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
+ private static final int COLUMN_BROADCASTER_STREAMED_COUNT = 4;
private List mBroadcastConfigurations = new CopyOnWriteArrayList<>();
- private sample.Broadcaster mBroadcastEventBroadcaster = new sample.Broadcaster<>();
- public BroadcastModel()
+ private Map mBroadcastConfigurationMap = new HashMap<>();
+ private Map mBroadcasterMap = new HashMap<>();
+ private DefaultAsyncHttpClient mDefaultAsyncHttpClient;
+ private ThreadPoolManager mThreadPoolManager;
+ private SettingsManager mSettingsManager;
+
+ private Broadcaster mBroadcastEventBroadcaster = new Broadcaster<>();
+
+ /**
+ * Model for managing Broadcast configurations and any associated broadcaster instances.
+ */
+ public BroadcastModel(ThreadPoolManager threadPoolManager, SettingsManager settingsManager)
{
+ mThreadPoolManager = threadPoolManager;
+ mSettingsManager = settingsManager;
+
+ removeOrphanedTemporaryRecordings();
}
/**
- * List of broadcast configuration names
+ * List of broadcastAudio configuration names
*/
public List getBroadcastConfigurationNames()
{
@@ -54,7 +96,7 @@ public class BroadcastModel extends AbstractTableModel
}
/**
- * Current list of broadcast configurations
+ * Current list of broadcastAudio configurations
*/
public List getBroadcastConfigurations()
{
@@ -62,7 +104,7 @@ public class BroadcastModel extends AbstractTableModel
}
/**
- * Adds the list of broadcast configurations to this model
+ * Adds the list of broadcastAudio configurations to this model
*/
public void addBroadcastConfigurations(List configurations)
{
@@ -73,58 +115,278 @@ public class BroadcastModel extends AbstractTableModel
}
/**
- * Adds the broadcast configuration to this model
+ * Adds the broadcastAudio configuration to this model
*/
public void addBroadcastConfiguration(BroadcastConfiguration configuration)
{
if(configuration != null)
{
- mBroadcastConfigurations.add(configuration);
+ ensureUniqueName(configuration);
- int index = mBroadcastConfigurations.size() - 1;
+ if(!mBroadcastConfigurations.contains(configuration))
+ {
+ mBroadcastConfigurations.add(configuration);
- fireTableRowsInserted( index, index );
+ int index = mBroadcastConfigurations.size() - 1;
- broadcast( new BroadcastConfigurationEvent( configuration, BroadcastConfigurationEvent.Event.ADD ) );
- }
- }
+ fireTableRowsInserted( index, index );
- public void removeBroadcastConfiguration(BroadcastConfiguration configuration)
- {
- if(configuration != null && mBroadcastConfigurations.contains(configuration))
- {
- int index = mBroadcastConfigurations.indexOf(configuration);
+ mBroadcastConfigurationMap.put(configuration.getName(), configuration);
- mBroadcastConfigurations.remove(configuration);
-
- fireTableRowsDeleted( index, index );
-
- broadcast( new BroadcastConfigurationEvent( configuration, BroadcastConfigurationEvent.Event.DELETE ) );
+ process( new BroadcastEvent( configuration, BroadcastEvent.Event.CONFIGURATION_ADD) );
+ }
}
}
/**
- * Registers the listener to receive broadcast configuration events
+ * Clones the configuration and adds it this model with a unique configuration name
*/
- public void addListener(Listener listener)
+ public BroadcastConfiguration cloneBroadcastConfiguration(BroadcastConfiguration configuration)
+ {
+ if(configuration != null)
+ {
+ BroadcastConfiguration clone = configuration.copyOf();
+ addBroadcastConfiguration(clone);
+ return clone;
+ }
+
+ return null;
+ }
+
+ /**
+ * Updates the configuration's name so that it is unique among all other broadcast configurations
+ * @param configuration
+ */
+ private void ensureUniqueName(BroadcastConfiguration configuration)
+ {
+ if(configuration.getName() == null || configuration.getName().isEmpty())
+ {
+ configuration.setName("New Configuration");
+ }
+
+ while(!isUniqueName(configuration.getName(), configuration))
+ {
+ String currentName = configuration.getName();
+
+ if(currentName.matches(UNIQUE_NAME_REGEX))
+ {
+ int currentVersion = 1;
+
+ StringBuilder sb = new StringBuilder();
+ Matcher m = Pattern.compile(UNIQUE_NAME_REGEX).matcher(currentName);
+
+ if(m.find())
+ {
+ String version = m.group(2);
+
+ try
+ {
+ currentVersion = Integer.parseInt(version);
+ }
+ catch(Exception e)
+ {
+ //Couldn't parse the version number -- keep incrementing until we find a winner
+ }
+
+ currentVersion++;
+
+ sb.append(m.group(1)).append("(").append(currentVersion).append(")");
+ }
+ else
+ {
+ sb.append(configuration.getName()).append("(").append(currentVersion).append(")");
+ }
+
+ configuration.setName(sb.toString());
+ }
+ else
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(configuration.getName()).append("(2)");
+ configuration.setName(sb.toString());
+ }
+ }
+
+ }
+
+ /**
+ * Indicates if the name is unique among all of the broadcast configurations. Checks each of the configurations
+ * in this model for a name collision. Assumes that the name argument will be assigned to the configuration
+ * argument if the name is unique, therefore, the name will not be checked against the configuration argument for
+ * a collision.
+ *
+ * @param name to check for uniqueness
+ * @param configuration to ignore when checking the name for uniqueness
+ * @return true if the name is not null and unique among all configurations managed by this model
+ */
+ public boolean isUniqueName(String name, BroadcastConfiguration configuration)
+ {
+ if(name == null || name.isEmpty())
+ {
+ return false;
+ }
+
+ for(BroadcastConfiguration configurationToCompare: mBroadcastConfigurations)
+ {
+ if(configurationToCompare != configuration && configurationToCompare.getName().equals(name))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public void removeBroadcastConfiguration(BroadcastConfiguration broadcastConfiguration)
+ {
+ if(broadcastConfiguration != null && mBroadcastConfigurations.contains(broadcastConfiguration))
+ {
+ int index = mBroadcastConfigurations.indexOf(broadcastConfiguration);
+
+ mBroadcastConfigurations.remove(broadcastConfiguration);
+
+ mBroadcastConfigurationMap.remove(broadcastConfiguration.getName());
+
+ broadcast( new BroadcastEvent( broadcastConfiguration, BroadcastEvent.Event.CONFIGURATION_DELETE) );
+
+ fireTableRowsDeleted( index, index );
+ }
+ }
+
+ /**
+ * Returns the broadcaster associated with the stream name or null if there is no broadcaster setup for the name.
+ */
+ public AudioBroadcaster getBroadcaster(String streamName)
+ {
+ return mBroadcasterMap.get(streamName);
+ }
+
+ @Override
+ public void receive(AudioPacket audioPacket)
+ {
+ if (audioPacket.hasAudioMetadata() && audioPacket.getAudioMetadata().isStreamable())
+ {
+ for (BroadcastChannel broadcastChannel : audioPacket.getAudioMetadata().getBroadcastChannels())
+ {
+ String channelName = broadcastChannel.getChannelName();
+
+ if (channelName != null)
+ {
+ AudioBroadcaster audioBroadcaster = getBroadcaster(channelName);
+
+ if (audioBroadcaster != null)
+ {
+ audioBroadcaster.receive(audioPacket);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a new broadcaster for the broadcast configuration and adds it to the model
+ */
+ private void createBroadcaster(BroadcastConfiguration broadcastConfiguration)
+ {
+ if (broadcastConfiguration != null &&
+ broadcastConfiguration.isEnabled() &&
+ broadcastConfiguration.isValid() &&
+ !mBroadcasterMap.containsKey(broadcastConfiguration.getName()))
+ {
+ AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(getDefaultAsyncHttpClient(),
+ mThreadPoolManager, broadcastConfiguration);
+
+ if (audioBroadcaster != null)
+ {
+ mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getName(), audioBroadcaster);
+ audioBroadcaster.setListener(new Listener()
+ {
+ @Override
+ public void receive(BroadcastEvent broadcastEvent)
+ {
+ process(broadcastEvent);
+ }
+ });
+
+ int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
+
+ if(index >= 0)
+ {
+ fireTableRowsUpdated(index, index);
+ }
+
+ broadcast(new BroadcastEvent(audioBroadcaster, BroadcastEvent.Event.BROADCASTER_ADD));
+ }
+ }
+ }
+
+ /**
+ * Shut down a broadcaster created from the configuration and remove it from this model
+ */
+ private void deleteBroadcaster(String name)
+ {
+ if(name != null && mBroadcasterMap.containsKey(name))
+ {
+ AudioBroadcaster audioBroadcaster = mBroadcasterMap.remove(name);
+
+ if(audioBroadcaster != null)
+ {
+ audioBroadcaster.stop();
+ audioBroadcaster.removeListener();
+
+ int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
+
+ if(index >= 0)
+ {
+ fireTableRowsUpdated(index, index);
+ }
+
+ broadcast(new BroadcastEvent(audioBroadcaster, BroadcastEvent.Event.BROADCASTER_DELETE));
+ }
+ }
+ }
+
+ private DefaultAsyncHttpClient getDefaultAsyncHttpClient()
+ {
+ if (mDefaultAsyncHttpClient == null)
+ {
+ mDefaultAsyncHttpClient = new DefaultAsyncHttpClient();
+ }
+
+ return mDefaultAsyncHttpClient;
+ }
+
+
+ /**
+ * Returns the broadcast configuration identified by the stream name
+ */
+ public BroadcastConfiguration getBroadcastConfiguration(String streamName)
+ {
+ return mBroadcastConfigurationMap.get(streamName);
+ }
+
+ /**
+ * Registers the listener to receive broadcastAudio configuration events
+ */
+ public void addListener(Listener listener)
{
mBroadcastEventBroadcaster.addListener(listener);
}
/**
- * Removes the listener from receiving broadcast configuration events
+ * Removes the listener from receiving broadcastAudio configuration events
*/
- public void removeListener(Listener listener)
+ public void removeListener(Listener listener)
{
mBroadcastEventBroadcaster.removeListener(listener);
}
/**
- * Broadcasts the broadcast configuration change event
+ * Broadcasts the broadcastAudio configuration change event
*/
- public void broadcast( BroadcastConfigurationEvent event )
+ public void broadcast( BroadcastEvent event )
{
- if( event.getEvent() == BroadcastConfigurationEvent.Event.CHANGE )
+ if( event.getEvent() == BroadcastEvent.Event.CONFIGURATION_CHANGE)
{
int index = mBroadcastConfigurations.indexOf( event.getBroadcastConfiguration() );
@@ -134,6 +396,88 @@ public class BroadcastModel extends AbstractTableModel
mBroadcastEventBroadcaster.broadcast( event );
}
+ /**
+ * Process a broadcast event from one of the broadcasters managed by this model
+ */
+ public void process(BroadcastEvent broadcastEvent)
+ {
+ if(broadcastEvent.isBroadcastConfigurationEvent())
+ {
+ switch(broadcastEvent.getEvent())
+ {
+ case CONFIGURATION_ADD:
+ createBroadcaster(broadcastEvent.getBroadcastConfiguration());
+ break;
+ case CONFIGURATION_CHANGE:
+ //Delete and recreate the broadcaster for any broadcast configuration changes
+ BroadcastConfiguration broadcastConfiguration = broadcastEvent.getBroadcastConfiguration();
+ cleanupMapAssociations(broadcastConfiguration);
+ createBroadcaster(broadcastConfiguration);
+ break;
+ case CONFIGURATION_DELETE:
+ deleteBroadcaster(broadcastEvent.getBroadcastConfiguration().getName());
+ break;
+ }
+ }
+ else if(broadcastEvent.isAudioBroadcasterEvent())
+ {
+ int row = mBroadcastConfigurations.indexOf(broadcastEvent.getAudioBroadcaster().getBroadcastConfiguration());
+
+ switch(broadcastEvent.getEvent())
+ {
+ case BROADCASTER_QUEUE_CHANGE:
+ if(row >= 0)
+ {
+ fireTableCellUpdated(row, COLUMN_BROADCASTER_QUEUE_SIZE);
+ }
+ break;
+ case BROADCASTER_STATE_CHANGE:
+ if(row >= 0)
+ {
+ fireTableCellUpdated(row, COLUMN_BROADCASTER_STATUS);
+ }
+ break;
+ case BROADCASTER_STREAMED_COUNT_CHANGE:
+ if(row >= 0)
+ {
+ fireTableCellUpdated(row, COLUMN_BROADCASTER_STREAMED_COUNT);
+ }
+ break;
+ }
+ }
+
+ //Rebroadcast the event to any listeners of this model
+ broadcast(broadcastEvent);
+ }
+
+ /**
+ * When the name of a broadcast configuration changes, remove the old map associations so that they can be replaced
+ * with the new map associations. Remove any broadcaster that is associated with the old configuration name.
+ *
+ * @param broadcastConfiguration
+ */
+ private void cleanupMapAssociations(BroadcastConfiguration broadcastConfiguration)
+ {
+ String oldName = null;
+
+ for(Map.Entry entry: mBroadcastConfigurationMap.entrySet())
+ {
+ if(entry.getValue() == broadcastConfiguration)
+ {
+ oldName = entry.getKey();
+ continue;
+ }
+ }
+
+ if(oldName != null)
+ {
+ deleteBroadcaster(oldName);
+ mBroadcastConfigurationMap.remove(oldName);
+ }
+
+ mBroadcastConfigurationMap.put(broadcastConfiguration.getName(), broadcastConfiguration);
+ }
+
@Override
public int getRowCount()
{
@@ -143,7 +487,7 @@ public class BroadcastModel extends AbstractTableModel
@Override
public int getColumnCount()
{
- return 3;
+ return 5;
}
@Override
@@ -157,12 +501,47 @@ public class BroadcastModel extends AbstractTableModel
{
switch(columnIndex)
{
- case COLUMN_CHANNEL_NAME:
+ case COLUMN_SERVER_ICON:
+ String iconName = configuration.getBroadcastServerType().getIconName();
+
+ if(iconName != null && mSettingsManager != null)
+ {
+ return mSettingsManager.getImageIcon(iconName, 14);
+ }
+ break;
+ case COLUMN_STREAM_NAME:
return configuration.getName();
- case COLUMN_SERVER_TYPE:
- return configuration.getBroadcastServerType().toString();
- case COLUMN_SERVER_ADDRESS:
- return configuration.getHost();
+ case COLUMN_BROADCASTER_STATUS:
+ AudioBroadcaster audioBroadcasterA = mBroadcasterMap.get(configuration.getName());
+
+ if(audioBroadcasterA != null)
+ {
+ return audioBroadcasterA.getBroadcastState().toString();
+ }
+ else if(!configuration.isEnabled())
+ {
+ return "Disabled";
+ }
+ else if(!configuration.isValid())
+ {
+ return "Invalid Settings";
+ }
+ case COLUMN_BROADCASTER_QUEUE_SIZE:
+ AudioBroadcaster audioBroadcasterB = mBroadcasterMap.get(configuration.getName());
+
+ if(audioBroadcasterB != null)
+ {
+ return audioBroadcasterB.getQueueSize();
+ }
+ break;
+ case COLUMN_BROADCASTER_STREAMED_COUNT:
+ AudioBroadcaster audioBroadcasterC = mBroadcasterMap.get(configuration.getName());
+
+ if(audioBroadcasterC != null)
+ {
+ return audioBroadcasterC.getStreamedAudioCount();
+ }
+ break;
default:
break;
}
@@ -172,6 +551,17 @@ public class BroadcastModel extends AbstractTableModel
return null;
}
+ @Override
+ public Class> getColumnClass(int columnIndex)
+ {
+ if(columnIndex == COLUMN_SERVER_ICON)
+ {
+ return ImageIcon.class;
+ }
+
+ return String.class;
+ }
+
/**
* Broadcast configuration at the specified model row
*/
@@ -193,14 +583,60 @@ public class BroadcastModel extends AbstractTableModel
{
switch(column)
{
- case COLUMN_CHANNEL_NAME:
- return "Channel";
- case COLUMN_SERVER_TYPE:
- return "Server";
- case COLUMN_SERVER_ADDRESS:
- return "Address";
+ case COLUMN_SERVER_ICON:
+ return null;
+ case COLUMN_STREAM_NAME:
+ return "Name";
+ case COLUMN_BROADCASTER_STATUS:
+ return "Status";
+ case COLUMN_BROADCASTER_QUEUE_SIZE:
+ return "Queued";
+ case COLUMN_BROADCASTER_STREAMED_COUNT:
+ return "Streamed";
}
return null;
}
+
+ /**
+ * Removes any temporary stream recordings left-over from the previous application run.
+ *
+ * This should only be invoked on startup.
+ */
+ private void removeOrphanedTemporaryRecordings()
+ {
+ Path path = SystemProperties.getInstance().getApplicationFolder(BroadcastModel.TEMPORARY_STREAM_DIRECTORY);
+
+ if(path != null && Files.isDirectory(path))
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(TEMPORARY_STREAM_FILE_SUFFIX);
+ sb.append("*.*");
+
+ try(DirectoryStream directoryStream = Files.newDirectoryStream(path, sb.toString()))
+ {
+ directoryStream.forEach(new Consumer()
+ {
+ @Override
+ public void accept(Path path)
+ {
+ mLog.info("Deleting orphaned temporary stream recording: " + path.toString());
+
+ try
+ {
+ Files.delete(path);
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Couldn't delete orphaned temporary recording: " + path.toString(), ioe);
+ }
+ }
+ });
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Error discovering orphaned temporary stream recording files", ioe);
+ }
+ }
+ }
}
diff --git a/src/audio/broadcast/BroadcastPanel.java b/src/audio/broadcast/BroadcastPanel.java
index 44c0bd7d..479be12d 100644
--- a/src/audio/broadcast/BroadcastPanel.java
+++ b/src/audio/broadcast/BroadcastPanel.java
@@ -18,15 +18,13 @@
******************************************************************************/
package audio.broadcast;
-import alias.Alias;
-import alias.AliasFactory;
-import alias.id.AliasID;
-import alias.id.AliasIDType;
-import alias.id.AliasIdentifierEditor;
import com.jidesoft.swing.JideSplitPane;
import gui.editor.Editor;
import gui.editor.EmptyEditor;
import net.miginfocom.swing.MigLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import settings.SettingsManager;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
@@ -38,22 +36,27 @@ import java.awt.event.MouseEvent;
public class BroadcastPanel extends JPanel implements ActionListener, ListSelectionListener
{
- private static final String NEW_ALIAS = "New";
- private static final String COPY_ALIAS = "Copy";
- private static final String DELETE_ALIAS = "Delete";
+ private final static Logger mLog = LoggerFactory.getLogger( BroadcastPanel.class );
+
+ private static final String NEW_BROADCAST_CONFIGURATION = "New";
+ private static final String COPY_BROADCAST_CONFIGURATION = "Copy";
+ private static final String DELETE_BROADCAST_CONFIGURATION = "Delete";
private BroadcastModel mBroadcastModel;
+ private SettingsManager mSettingsManager;
+
private JTable mBroadcastConfigurationTable;
private JideSplitPane mSplitPane;
private Editor mEmptyEditor = new EmptyEditor<>("Configuration:");
private Editor mEditor;
- private JButton mNewButton = new JButton(NEW_ALIAS);
- private JButton mCopyButton = new JButton(COPY_ALIAS);
- private JButton mDeleteButton = new JButton(DELETE_ALIAS);
+ private JButton mNewButton = new JButton(NEW_BROADCAST_CONFIGURATION);
+ private JButton mCopyButton = new JButton(COPY_BROADCAST_CONFIGURATION);
+ private JButton mDeleteButton = new JButton(DELETE_BROADCAST_CONFIGURATION);
- public BroadcastPanel(BroadcastModel broadcastModel)
+ public BroadcastPanel(BroadcastModel broadcastModel, SettingsManager settingManager)
{
mBroadcastModel = broadcastModel;
+ mSettingsManager = settingManager;
mEditor = mEmptyEditor;
init();
@@ -68,6 +71,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
mBroadcastConfigurationTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
mBroadcastConfigurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mBroadcastConfigurationTable.getSelectionModel().addListSelectionListener(BroadcastPanel.this);
+ mBroadcastConfigurationTable.getColumnModel().getColumn(0).setWidth(15);
JScrollPane scroller = new JScrollPane(mBroadcastConfigurationTable);
@@ -75,7 +79,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
buttonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
mNewButton = new JButton("New ...");
- mNewButton.setToolTipText("Create a new broadcast configuration");
+ mNewButton.setToolTipText("Create a new broadcastAudio configuration");
mNewButton.addMouseListener(new MouseAdapter()
{
@Override
@@ -121,18 +125,62 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
@Override
public void actionPerformed(ActionEvent e)
{
+ switch(e.getActionCommand())
+ {
+ case COPY_BROADCAST_CONFIGURATION:
+ BroadcastConfiguration copySelected = getSelectedBroadcastConfiguration();
+ mBroadcastModel.cloneBroadcastConfiguration(copySelected);
+ break;
+ case DELETE_BROADCAST_CONFIGURATION:
+ BroadcastConfiguration deleteSelected = getSelectedBroadcastConfiguration();
+
+ if(deleteSelected != null)
+ {
+ int choice = JOptionPane.showConfirmDialog(BroadcastPanel.this, "Do you want to delete broadcast\n" +
+ " configuration " + deleteSelected.getName() + "?", "Delete Broadcast Configuration?",
+ JOptionPane.YES_NO_OPTION);
+
+ if(choice == JOptionPane.YES_OPTION)
+ {
+ mBroadcastModel.removeBroadcastConfiguration(deleteSelected);
+ }
+ }
+ break;
+ }
}
+ /**
+ * Returns the selected configuration
+ */
+ private BroadcastConfiguration getSelectedBroadcastConfiguration()
+ {
+ int viewRow = mBroadcastConfigurationTable.getSelectedRow();
+
+ if(viewRow >= 0)
+ {
+ int modelRow = mBroadcastConfigurationTable.convertRowIndexToModel(viewRow);
+
+ if(modelRow >= 0)
+ {
+ return mBroadcastModel.getConfigurationAt(modelRow);
+ }
+ }
+
+ return null;
+ }
+
+
@Override
public void valueChanged(ListSelectionEvent e)
{
if (!e.getValueIsAdjusting())
{
- int row = mBroadcastConfigurationTable.getSelectedRow();
- int modelRow = mBroadcastConfigurationTable.convertRowIndexToModel(row);
+ BroadcastConfiguration selectedConfiguration = getSelectedBroadcastConfiguration();
- BroadcastConfiguration selectedConfiguration = mBroadcastModel.getConfigurationAt(modelRow);
+ //Enable buttons accordingly
+ mCopyButton.setEnabled(selectedConfiguration != null);
+ mDeleteButton.setEnabled(selectedConfiguration != null);
if (mEditor.isModified())
{
@@ -153,7 +201,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
if (selectedConfiguration != null)
{
- mEditor = BroadcastFactory.getEditor(selectedConfiguration, mBroadcastModel);
+ mEditor = BroadcastFactory.getEditor(selectedConfiguration, mBroadcastModel, mSettingsManager);
}
else
{
@@ -186,6 +234,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
final BroadcastConfiguration configuration =
BroadcastFactory.getConfiguration(mBroadcastServerType, BroadcastFormat.MP3);
+
mBroadcastModel.addBroadcastConfiguration(configuration);
int modelRow = mBroadcastModel.getRowForConfiguration(configuration);
diff --git a/src/audio/broadcast/BroadcastServerType.java b/src/audio/broadcast/BroadcastServerType.java
index 5da3869c..e49ea45b 100644
--- a/src/audio/broadcast/BroadcastServerType.java
+++ b/src/audio/broadcast/BroadcastServerType.java
@@ -20,36 +20,29 @@ package audio.broadcast;
public enum BroadcastServerType
{
- /**
- * Broadcastify is currently using Icecast Server 2.3.2
- */
- BROADCASTIFY("Broadcastify"),
-
- /**
- * Icecast Server 2.4.x and later
- */
- ICECAST_HTTP("Icecast 2.4+"),
-
- /**
- * Icecast Server 2.3.x
- */
- ICECAST_TCP("Icecast 2.3"),
-
- SHOUTCAST_V1("Shoutcast 1.x"),
-
- SHOUTCAST_V2("Shoutcast 2.x"),
-
- UNKNOWN("Unknown");
+ BROADCASTIFY("Broadcastify", "images/broadcastify.png"), //Icecast Server 2.3.2
+ ICECAST_HTTP("Icecast 2.4+", null),
+ ICECAST_TCP("Icecast 2.3", null),
+ SHOUTCAST_V1("Shoutcast 1.x", null),
+ SHOUTCAST_V2("Shoutcast 2.x", null),
+ UNKNOWN("Unknown", null);
private String mLabel;
+ private String mIconName;
- BroadcastServerType(String label)
+ BroadcastServerType(String label, String iconName)
{
mLabel = label;
+ mIconName = iconName;
}
public String toString()
{
return mLabel;
}
+
+ public String getIconName()
+ {
+ return mIconName;
+ }
}
diff --git a/src/audio/broadcast/BroadcastState.java b/src/audio/broadcast/BroadcastState.java
index 47a6bd82..c15d3bbd 100644
--- a/src/audio/broadcast/BroadcastState.java
+++ b/src/audio/broadcast/BroadcastState.java
@@ -26,7 +26,7 @@ public enum BroadcastState
BROADCAST_ERROR("Broadcast Error", false),
/**
- * Connected to the broadcast server and capable of streaming
+ * Connected to the broadcastAudio server and capable of streaming
*/
CONNECTED("Connected", false),
@@ -36,7 +36,7 @@ public enum BroadcastState
CONNECTING("Connecting", false),
/**
- * Disconnected from the broadcast server
+ * Disconnected from the broadcastAudio server
*/
DISCONNECTED("Disconnected", true),
diff --git a/src/audio/broadcast/Broadcaster.java b/src/audio/broadcast/Broadcaster.java
deleted file mode 100644
index 427f7bac..00000000
--- a/src/audio/broadcast/Broadcaster.java
+++ /dev/null
@@ -1,242 +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
- *
- ******************************************************************************/
-package audio.broadcast;
-
-import audio.AudioPacket;
-import audio.IAudioPacketListener;
-import audio.convert.IAudioConverter;
-import controller.ThreadPoolManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import sample.Listener;
-
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.LinkedTransferQueue;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public abstract class Broadcaster implements IAudioPacketListener, Listener
-{
- private final static Logger mLog = LoggerFactory.getLogger( Broadcaster.class );
-
- private ThreadPoolManager mThreadPoolManager;
- private ScheduledFuture mScheduledTask;
-
- private AudioQueueProcessor mAudioQueueProcessor;
- protected LinkedTransferQueue mAudioQueue = new LinkedTransferQueue<>();
-
- private List> mBroadcastStateListeners = new CopyOnWriteArrayList<>();
- private BroadcastState mBroadcastState = BroadcastState.READY;
-
- private BroadcastConfiguration mBroadcastConfiguration;
- private IAudioConverter mAudioConverter;
-
- private boolean mEnabled = true;
-
- /**
- * Broadcaster for sending audio to a remote broadcast server.
- */
- public Broadcaster(ThreadPoolManager threadPoolManager,
- BroadcastConfiguration broadcastConfiguration,
- IAudioConverter audioConverter)
- {
- mThreadPoolManager = threadPoolManager;
- mBroadcastConfiguration = broadcastConfiguration;
- mAudioConverter = audioConverter;
- mAudioQueueProcessor = new AudioQueueProcessor();
- }
-
- /**
- * Broadcast configuration used by this broadcaster
- */
- public BroadcastConfiguration getBroadcastConfiguration()
- {
- return mBroadcastConfiguration;
- }
-
- /**
- * IAudioPacketListener interface method
- */
- @Override
- public Listener getAudioPacketListener()
- {
- return this;
- }
-
- /**
- * Audio converter used by this broadcaster to convert PCM audio packets to the desired output format.
- */
- protected IAudioConverter getAudioConverter()
- {
- return mAudioConverter;
- }
-
- /**
- * Commands the broadcaster to broadcast any queued audio packets. This method is invoked by a scheduled thread
- * pool task to periodically command the sub-class to broadcast queued audio packets.
- */
- protected abstract void broadcast();
-
- /**
- * Disconnects the broadcaster and halts all broadcasting.
- */
- public void stop()
- {
- mEnabled = false;
- stopProcessor();
- }
-
- /**
- * Primary audio packet input method. Audio packets are queued and then processed by the underlying broadcast
- * handler for broadcast to the remote server.
- */
- public void receive(AudioPacket packet)
- {
- if(mEnabled)
- {
- startProcessor();
- mAudioQueue.add(packet);
- }
- }
-
- /**
- * Starts the audio queue processor. The audio packet queue is serviced every 100 milliseconds. Subsequent calls
- * to this method once running are ignored.
- */
- private void startProcessor()
- {
- if(mEnabled && mScheduledTask == null)
- {
- if(mThreadPoolManager != null)
- {
- mScheduledTask = mThreadPoolManager.scheduleFixedRate(ThreadPoolManager.ThreadType.AUDIO_PROCESSING,
- mAudioQueueProcessor, 250, TimeUnit.MILLISECONDS );
- }
- }
- }
-
- /**
- * Stops the audio queue processor
- */
- private void stopProcessor()
- {
- if(mThreadPoolManager != null && mScheduledTask != null)
- {
- mThreadPoolManager.cancel(mScheduledTask);
- }
- }
-
- /**
- * Pauses or unpauses the broadcast
- */
- public void setPaused(boolean paused)
- {
- if(!getBroadcastState().isErrorState())
- {
- if(paused)
- {
- setBroadcastState(BroadcastState.PAUSED);
- }
- else
- {
- setBroadcastState(BroadcastState.READY);
- }
- }
- }
-
- /**
- * Registers the listener to receive broadcast state changes
- */
- public void addListener(Listener listener)
- {
- mBroadcastStateListeners.add(listener);
- }
-
- /**
- * Removes the listener from receiving broadcast state changes
- */
- public void removeListener(Listener listener)
- {
- mBroadcastStateListeners.remove(listener);
- }
-
- /**
- * Sets the state of the broadcast connection
- */
- protected void setBroadcastState(BroadcastState state)
- {
- if(mBroadcastState != state)
- {
- mLog.debug("Changing State to: " + state);
- mBroadcastState = state;
-
- for(Listener listener: mBroadcastStateListeners)
- {
- listener.receive(state);
- }
- }
- }
-
- /**
- * Current state of the broadcast connection
- */
- public BroadcastState getBroadcastState()
- {
- return mBroadcastState;
- }
-
- public boolean connected()
- {
- return getBroadcastState() == BroadcastState.CONNECTED;
- }
-
- /**
- * Indicates if this broadcaster can connect and is not currently in an error state or a connected state.
- */
- public boolean canConnect()
- {
- BroadcastState state = getBroadcastState();
-
- return state != BroadcastState.CONNECTED && !state.isErrorState();
- }
-
-
- /**
- * Audio packet queue processor. Runs via a scheduled thread pool task as a timing processor to instruct
- * sub-classes to broadcast queued audio packets. Internal state monitor ensures that subsequent invocations of
- * the broadcast method do not start until the previous invocation completes.
- */
- public class AudioQueueProcessor implements Runnable
- {
- private AtomicBoolean mProcessing = new AtomicBoolean();
-
- @Override
- public void run()
- {
- if(mProcessing.compareAndSet(false, true))
- {
- broadcast();
-
- mProcessing.set(false);
- }
- }
- }
-}
diff --git a/src/audio/broadcast/StreamManager.java b/src/audio/broadcast/StreamManager.java
index 801d4ccc..1b4570f9 100644
--- a/src/audio/broadcast/StreamManager.java
+++ b/src/audio/broadcast/StreamManager.java
@@ -39,8 +39,7 @@ public class StreamManager implements Listener
private static final long MAXIMUM_RECORDER_LIFESPAN_MILLIS = 30000; //30 seconds
private ThreadPoolManager mThreadPoolManager;
- private BroadcastFormat mBroadcastFormat;
- private Listener mOutputListener;
+ private AudioBroadcaster mAudioBroadcaster;
private long mDelay;
private Path mTempDirectory;
private Map mStreamRecorders = new HashMap<>();
@@ -57,24 +56,17 @@ public class StreamManager implements Listener
* has elapsed.
*
* @param threadPoolManager for scheduling runnables
- * @param broadcastFormat defines the output recording format
- * @param outputListener to receive completed streamable audio recordings
+ * @param audioBroadcaster to receive completed streamable audio recordings
* @param delay from recording start until audio recording is nominated to output listener
* @param tempDirectory where to store temporary audio recordings
*/
- public StreamManager(ThreadPoolManager threadPoolManager,
- BroadcastFormat broadcastFormat,
- Listener outputListener,
- long delay,
- Path tempDirectory)
+ public StreamManager(ThreadPoolManager threadPoolManager, AudioBroadcaster audioBroadcaster, long delay, Path tempDirectory)
{
assert(tempDirectory != null && Files.isDirectory(tempDirectory));
assert(mThreadPoolManager != null);
- assert(mBroadcastFormat != null);
mThreadPoolManager = threadPoolManager;
- mBroadcastFormat = broadcastFormat;
- mOutputListener = outputListener;
+ mAudioBroadcaster = audioBroadcaster;
mDelay = delay;
mTempDirectory = tempDirectory;
}
@@ -142,7 +134,7 @@ public class StreamManager implements Listener
else
{
AudioRecorder recorder = BroadcastFactory.getAudioRecorder(getTemporaryRecordingPath(),
- mBroadcastFormat);
+ mAudioBroadcaster.getBroadcastConfiguration().getBroadcastFormat());
recorder.start(mThreadPoolManager.getScheduledExecutorService());
recorder.receive(audioPacket);
mStreamRecorders.put(sourceChannelID, recorder);
@@ -176,11 +168,11 @@ public class StreamManager implements Listener
recorder.stop();
StreamableAudioRecording streamableAudioRecording = new StreamableAudioRecording(recorder.getPath(),
- recorder.getMetadata());
+ recorder.getMetadata(), recorder.getRecordingLength());
if(mDelay <= 0)
{
- mOutputListener.receive(streamableAudioRecording);
+ mAudioBroadcaster.receive(streamableAudioRecording);
}
else
{
@@ -198,9 +190,9 @@ public class StreamManager implements Listener
private Path getTemporaryRecordingPath()
{
StringBuilder sb = new StringBuilder();
- sb.append("temporary_streaming_file_");
+ sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
sb.append( TimeStamp.getLongTimeStamp( "_" ) );
- sb.append( mBroadcastFormat.getFileExtension() );
+ sb.append( mAudioBroadcaster.getBroadcastConfiguration().getBroadcastFormat().getFileExtension() );
Path temporaryRecordingPath = mTempDirectory.resolve(sb.toString());
@@ -222,9 +214,9 @@ public class StreamManager implements Listener
@Override
public void run()
{
- if(mOutputListener != null)
+ if(mAudioBroadcaster != null)
{
- mOutputListener.receive(mStreamableAudioRecording);
+ mAudioBroadcaster.receive(mStreamableAudioRecording);
}
}
}
diff --git a/src/audio/broadcast/StreamableAudioRecording.java b/src/audio/broadcast/StreamableAudioRecording.java
index 34cb5c0f..0b240ea1 100644
--- a/src/audio/broadcast/StreamableAudioRecording.java
+++ b/src/audio/broadcast/StreamableAudioRecording.java
@@ -26,11 +26,19 @@ public class StreamableAudioRecording
{
private Path mPath;
private AudioMetadata mAudioMetadata;
+ private long mRecordingLengthMilliSeconds;
- public StreamableAudioRecording(Path path, AudioMetadata audioMetadata)
+ /**
+ * Audio recording that is ready to be streamed
+ * @param path to the audio recording file
+ * @param audioMetadata associated with the recording
+ * @param recordingLength in milliseconds
+ */
+ public StreamableAudioRecording(Path path, AudioMetadata audioMetadata, long recordingLength)
{
mPath = path;
mAudioMetadata = audioMetadata;
+ mRecordingLengthMilliSeconds = recordingLength;
}
/**
@@ -48,4 +56,12 @@ public class StreamableAudioRecording
{
return mAudioMetadata;
}
+
+ /**
+ * Recording length in milliseconds
+ */
+ public long getRecordingLength()
+ {
+ return mRecordingLengthMilliSeconds;
+ }
}
diff --git a/src/audio/broadcast/broadcastify/BroadcastifyConfiguration.java b/src/audio/broadcast/broadcastify/BroadcastifyConfiguration.java
index b16d6432..55b677f7 100644
--- a/src/audio/broadcast/broadcastify/BroadcastifyConfiguration.java
+++ b/src/audio/broadcast/broadcastify/BroadcastifyConfiguration.java
@@ -18,6 +18,7 @@
******************************************************************************/
package audio.broadcast.broadcastify;
+import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastServerType;
import audio.broadcast.icecast.IcecastTCPConfiguration;
@@ -26,11 +27,12 @@ import javax.xml.bind.annotation.XmlAttribute;
public class BroadcastifyConfiguration extends IcecastTCPConfiguration
{
- public int mFeedID;
+ private int mFeedID;
public BroadcastifyConfiguration()
{
//No-arg constructor for JAXB
+ this(BroadcastFormat.MP3);
}
/**
@@ -47,6 +49,34 @@ public class BroadcastifyConfiguration extends IcecastTCPConfiguration
setSampleRate(8000);
}
+ @Override
+ public BroadcastConfiguration copyOf()
+ {
+ BroadcastifyConfiguration copy = new BroadcastifyConfiguration(getBroadcastFormat());
+
+ //Broadcast Configuration Parameters
+ copy.setName(getName());
+ copy.setHost(getHost());
+ copy.setPort(getPort());
+ copy.setPassword(getPassword());
+ copy.setDelay(getDelay());
+
+ copy.setEnabled(false);
+
+ //Icecast Configuration Parameters
+ copy.setUserName(getUserName());
+ copy.setMountPoint(getMountPoint());
+ copy.setDescription(getDescription());
+ copy.setGenre(getGenre());
+ copy.setPublic(isPublic());
+ copy.setURL(getURL());
+
+ //Broadcastify Configuration Parameters
+ copy.setFeedID(getFeedID());
+
+ return copy;
+ }
+
@Override
public BroadcastServerType getBroadcastServerType()
{
@@ -63,4 +93,10 @@ public class BroadcastifyConfiguration extends IcecastTCPConfiguration
{
mFeedID = feedID;
}
+
+ @Override
+ public boolean isValid()
+ {
+ return super.isValid() && (mFeedID > 0);
+ }
}
diff --git a/src/audio/broadcast/broadcastify/BroadcastifyConfigurationEditor.java b/src/audio/broadcast/broadcastify/BroadcastifyConfigurationEditor.java
index f0ef09b4..09819924 100644
--- a/src/audio/broadcast/broadcastify/BroadcastifyConfigurationEditor.java
+++ b/src/audio/broadcast/broadcastify/BroadcastifyConfigurationEditor.java
@@ -19,10 +19,14 @@
package audio.broadcast.broadcastify;
import audio.broadcast.BroadcastConfiguration;
-import audio.broadcast.BroadcastConfigurationEvent;
+import audio.broadcast.BroadcastEvent;
import audio.broadcast.BroadcastModel;
+import audio.broadcast.BroadcastServerType;
import gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import settings.SettingsManager;
import javax.swing.*;
import java.awt.*;
@@ -31,6 +35,9 @@ import java.awt.event.ActionListener;
public class BroadcastifyConfigurationEditor extends DocumentListenerEditor
{
+ private final static Logger mLog = LoggerFactory.getLogger( BroadcastifyConfigurationEditor.class );
+
+ private SettingsManager mSettingsManager;
private BroadcastModel mBroadcastModel;
private JTextField mFeedID;
private JTextField mName;
@@ -40,24 +47,27 @@ public class BroadcastifyConfigurationEditor extends DocumentListenerEditor 65535)", 1, 65535))
+ {
+ return true;
+ }
+
+ //Mount Point is optional, but required
+ if(!validateTextField(mMountPoint, "Invalid Mount Point", "Please specify a mount point."))
+ {
+ return true;
+ }
+
+ //Password is optional, but required
+ if(!validateTextField(mPassword, "Invalid Password", "Please specify a password."))
+ {
+ return true;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates a text field control for a non-null, non-empty value
+ * @param field to validate
+ * @param title to use for error dialog
+ * @param message to use for error dialog
+ * @return true if field contains a non-null, non-empty value
+ */
+ private boolean validateTextField(JTextField field, String title, String message)
+ {
+ String text = field.getText();
+
+ if(text == null || text.isEmpty())
+ {
+ JOptionPane.showMessageDialog(BroadcastifyConfigurationEditor.this, message, title,
+ JOptionPane.ERROR_MESSAGE);
+
+ field.requestFocus();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the text field control that contains an integer value for non-null, non-empty and within the
+ * specified min/max valid range.
+ * @param field to validate
+ * @param title to use for error dialog
+ * @param message to use for error dialog
+ * @param minValid value
+ * @param maxValid value
+ * @return true if field contains a non-null, non-empty value within the valid min/max range
+ */
+ private boolean validateIntegerTextField(JTextField field, String title, String message, int minValid, int maxValid)
+ {
+ if(validateTextField(field, title, message))
+ {
+ String text = field.getText();
+
+ try
+ {
+ int value = Integer.parseInt(text);
+
+ if(minValid <= value && value <= maxValid)
+ {
+ return true;
+ }
+ }
+ catch(Exception e)
+ {
+ //Do nothing, we couldn't parse the number value
+ }
+
+ JOptionPane.showMessageDialog(BroadcastifyConfigurationEditor.this, message, title,
+ JOptionPane.ERROR_MESSAGE);
+
+ field.requestFocus();
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses an Integer from the feed ID text control or returns 0 if the control doesn't contain an integer value.
+ */
+ private int getFeedID()
+ {
String feedID = mFeedID.getText();
if(feedID != null && !feedID.isEmpty())
{
try
{
- int id = Integer.parseInt(feedID);
- config.setFeedID(id);
+ return Integer.parseInt(feedID);
}
catch(Exception e)
{
@@ -173,17 +333,21 @@ public class BroadcastifyConfigurationEditor extends DocumentListenerEditor
+public class IcecastHTTPAudioBroadcaster extends AudioBroadcaster implements Publisher
{
- private final static Logger mLog = LoggerFactory.getLogger( IcecastHTTPBroadcaster.class );
+ private final static Logger mLog = LoggerFactory.getLogger( IcecastHTTPAudioBroadcaster.class );
private static final String HTTP_100_CONTINUE = "100-continue";
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
@@ -69,15 +69,13 @@ public class IcecastHTTPBroadcaster extends Broadcaster implements Publisher mPacketsToBroadcast = new ArrayList<>();
private AtomicBoolean mConnecting = new AtomicBoolean();
private Socket mSocket;
private DataOutputStream mOutputStream;
private DataInputStream mInputStream;
private DefaultAsyncHttpClient mDefaultAsyncHttpClient;
-
- private int mCounter;
+ private boolean mFirstMetadataUpdateSuppressed = false;
+ private byte[] mSilenceFrame;
/**
* Creates an Icecast 2.3.2 compatible broadcaster using TCP and a pseudo HTTP 1.0 protocol. This broadcaster is
* compatible with Icecast version 2.3.2 and older versions of the server software.
*
- * Note: use @see IcecastHTTPBroadcaster for Icecast version 2.4.x and newer.
+ * Note: use @see IcecastHTTPAudioBroadcaster for Icecast version 2.4.x and newer.
*
* @param configuration
- * @param audioConverter
*/
- public IcecastTCPBroadcaster(ThreadPoolManager threadPoolManager,
- IcecastTCPConfiguration configuration,
- IAudioConverter audioConverter)
+ public IcecastTCPAudioBroadcaster(DefaultAsyncHttpClient httpClient,
+ ThreadPoolManager threadPoolManager,
+ IcecastTCPConfiguration configuration)
{
- super(threadPoolManager, configuration, audioConverter);
+ super(threadPoolManager, configuration);
+
+ mDefaultAsyncHttpClient = httpClient;
}
/**
* Configuration information
*/
- private IcecastHTTPConfiguration getConfiguration()
+ private IcecastTCPConfiguration getConfiguration()
{
- return (IcecastHTTPConfiguration)getBroadcastConfiguration();
+ return (IcecastTCPConfiguration)getBroadcastConfiguration();
}
/**
* Broadcast any queued audio packets using Icecast V2 Protocol
*/
@Override
- public void broadcast()
+ protected void broadcastAudio(byte[] audio)
{
- if(connect())
+ boolean completed = false;
+
+ while(!completed)
{
- mAudioQueue.drainTo(mPacketsToBroadcast, 5);
-
- if (!mPacketsToBroadcast.isEmpty())
+ if(connect())
{
- byte[] convertedAudio = getAudioConverter().convert(mPacketsToBroadcast);
-
- mLog.debug("We have [" + mPacketsToBroadcast.size() +
- "] packets - sending:" + convertedAudio.length + " bytes");
-
try
{
- send(convertedAudio);
+ send(audio);
+ completed = true;
}
catch(SocketException se)
{
- disconnect();
+ //The remote server likely disconnected - setup to reconnect
+ mLog.error("Socket Exception on Icecast TCP Audio Broadcaster -- disconnecting and resetting", se);
+ disconnect(false);
}
catch(IOException e)
{
+ completed = true;
mLog.error("Error sending audio", e);
}
+ }
+ else
+ {
+ completed = true;
+ }
+ }
+ }
- mPacketsToBroadcast.clear();
+ @Override
+ protected void broadcastMetadata(AudioMetadata metadata)
+ {
+ if(connected() && mFirstMetadataUpdateSuppressed)
+ {
+ StringBuilder sb = new StringBuilder();
- mCounter++;
+ if(metadata != null)
+ {
+ Metadata to = metadata.getMetadata(MetadataType.TO);
- if(mCounter % 10 == 0)
+ sb.append("TO:");
+
+ if(to != null)
{
- updateMetadata("Song Count " + mCounter);
+ if(to.hasAlias())
+ {
+ sb.append(to.getAlias().getName());
+ }
+ else
+ {
+ sb.append(to.getValue());
+ }
+ }
+ else
+ {
+ sb.append("UNKNOWN");
+ }
+
+ Metadata from = metadata.getMetadata(MetadataType.FROM);
+
+ sb.append(" FROM:");
+
+ if(from != null)
+ {
+
+ if(from.hasAlias())
+ {
+ sb.append(from.getAlias().getName());
+ }
+ else
+ {
+ sb.append(from.getValue());
+ }
+ }
+ else
+ {
+ sb.append("UNKNOWN");
}
}
+ else
+ {
+ sb.append("Scanning ....");
+ }
+
+ try
+ {
+ if(mDefaultAsyncHttpClient == null)
+ {
+ mDefaultAsyncHttpClient = new DefaultAsyncHttpClient();
+ }
+
+ String songEncoded = URLEncoder.encode(sb.toString(), UTF8);
+
+ StringBuilder query = new StringBuilder();
+ query.append("/admin/metadata?mode=updinfo");
+ query.append("&mount=").append(getConfiguration().getMountPoint());
+ query.append("&charset=UTF%2d8");
+ query.append("&song=").append(songEncoded);
+
+ Uri uri = new Uri(Uri.HTTP, null, getConfiguration().getHost(), getConfiguration().getPort(),
+ query.toString(), null);
+
+ BoundRequestBuilder builder = mDefaultAsyncHttpClient.prepareGet(uri.toUrl());
+
+ //Use Basic (base64) authentication
+ Realm realm = new Realm.Builder(getConfiguration().getUserName(), getConfiguration().getPassword())
+ .setScheme(Realm.AuthScheme.BASIC).setUsePreemptiveAuth(true).build();
+ builder.setRealm(realm);
+ builder.addHeader(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
+
+ AsyncHandler handler = new AsyncCompletionHandler()
+ {
+ @Override
+ public Object onCompleted(Response response) throws Exception
+ {
+ return null;
+ }
+ };
+
+ mDefaultAsyncHttpClient.executeRequest(builder.build(), handler);
+ }
+ catch(Exception e)
+ {
+ mLog.debug("Error while updating metadata", e);
+ }
+ }
+ else if(connected())
+ {
+ mFirstMetadataUpdateSuppressed = true;
}
}
@@ -152,7 +256,7 @@ public class IcecastTCPBroadcaster extends Broadcaster
/**
* Disconnect from the remote server
*/
- public void disconnect()
+ public void disconnect(boolean shutdown)
{
if(mOutputStream != null)
{
@@ -197,7 +301,14 @@ public class IcecastTCPBroadcaster extends Broadcaster
mSocket = null;
}
- mLog.debug("Icecast stream is now disconnected");
+ if(shutdown)
+ {
+ setBroadcastState(BroadcastState.DISCONNECTED);
+ }
+ else
+ {
+ setBroadcastState(BroadcastState.READY);
+ }
}
/**
@@ -226,7 +337,6 @@ public class IcecastTCPBroadcaster extends Broadcaster
.append(SystemProperties.getInstance().getApplicationName()).append(TERMINATOR);
sb.append(IcecastHeader.CONTENT_TYPE.getValue()).append(SEPARATOR)
.append(getConfiguration().getBroadcastFormat().getValue()).append(TERMINATOR);
-
sb.append(IcecastHeader.PUBLIC.getValue()).append(SEPARATOR)
.append(getConfiguration().isPublic() ? "1" : "0").append(TERMINATOR);
@@ -334,6 +444,14 @@ public class IcecastTCPBroadcaster extends Broadcaster
getConfiguration().getPort() + "] - will reattempt connection periodically");
return;
}
+ catch(ConnectException ce)
+ {
+ setBroadcastState(BroadcastState.NO_SERVER);
+ mLog.error("Connection refused. Unable to create connection to streaming server host[" +
+ getConfiguration().getHost() + "] and port[" +
+ getConfiguration().getPort() + "]");
+ return;
+ }
catch(IOException ioe)
{
setBroadcastState(BroadcastState.ERROR);
@@ -457,7 +575,7 @@ public class IcecastTCPBroadcaster extends Broadcaster
{
setBroadcastState(BroadcastState.CONNECTED);
}
- else if(response.startsWith("Invalid Password"))
+ else if(response.contains("Invalid Password") || response.contains("Authentication Required"))
{
setBroadcastState(BroadcastState.INVALID_PASSWORD);
}
@@ -469,56 +587,20 @@ public class IcecastTCPBroadcaster extends Broadcaster
}
else
{
+ setBroadcastState(BroadcastState.CONNECTED);
mLog.debug("Response was empty");
}
}
- private void updateMetadata(String songName)
- {
- if(songName != null && !songName.isEmpty())
- {
- try
- {
- if(mDefaultAsyncHttpClient == null)
- {
- mDefaultAsyncHttpClient = new DefaultAsyncHttpClient();
- }
-
- String songEncoded = URLEncoder.encode(songName, UTF8);
-
- StringBuilder query = new StringBuilder();
- query.append("/admin/metadata?mode=updinfo");
- query.append("&mount=").append(getConfiguration().getMountPoint());
- query.append("&charset=UTF%2d8");
- query.append("&song=").append(songEncoded);
-
- Uri uri = new Uri(Uri.HTTP, null, getConfiguration().getHost(), getConfiguration().getPort(),
- query.toString(), null);
-
- BoundRequestBuilder builder = mDefaultAsyncHttpClient.prepareGet(uri.toUrl());
-
- //Use Basic (base64) authentication
- Realm realm = new Realm.Builder(getConfiguration().getUserName(), getConfiguration().getPassword())
- .setScheme(Realm.AuthScheme.BASIC).setUsePreemptiveAuth(true).build();
- builder.setRealm(realm);
- builder.addHeader(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
-
- mDefaultAsyncHttpClient.executeRequest(builder.build());
- }
- catch(Exception e)
- {
- mLog.debug("Error while updating metadata", e);
- }
- }
- }
-
public static void main(String[] args)
{
boolean test = false;
IcecastTCPConfiguration config = new IcecastTCPConfiguration(BroadcastFormat.MP3);
- config.setName("Broadcastify SDRTrunk #2");
+ String streamName = "Broadcastify SDRTrunk #2";
+
+ config.setName(streamName);
config.setHost("audio3.broadcastify.com");
config.setPort(80);
config.setMountPoint("/k0yrdpx7zn4h");
@@ -530,6 +612,11 @@ public class IcecastTCPBroadcaster extends Broadcaster
config.setURL("http://www.radioreference.com");
config.setBitRate(16);
+ AudioMetadata metadata = new AudioMetadata(0, false);
+ Alias toAlias = new Alias("ToAlias");
+ toAlias.addAliasID(new BroadcastChannel(streamName));
+ metadata.receive(new Metadata(MetadataType.TO, "1234", toAlias ));
+
if(test)
{
mLog.debug("Auth:" + config.getEncodedCredentials());
@@ -537,22 +624,29 @@ public class IcecastTCPBroadcaster extends Broadcaster
else
{
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
+ DefaultAsyncHttpClient httpClient = new DefaultAsyncHttpClient();
- final Broadcaster broadcaster = BroadcastFactory.getBroadcaster(threadPoolManager,config);
+ final AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(httpClient, threadPoolManager,config);
Path path = Paths.get("/home/denny/Music/PCM.wav");
mLog.debug("Opening: " + path.toString());
mLog.debug("Registering and starting audio playback");
+ int playCount = 0;
+
while(true)
{
mLog.debug("Playback started [" + path.toString() + "]");
+ toAlias.setName("Playback #" + playCount++);
+
try (AudioPacketMonoWaveReader reader = new AudioPacketMonoWaveReader(path, true))
{
- reader.setListener(broadcaster);
+ reader.setMetadata(metadata);
+ reader.setListener(audioBroadcaster);
reader.read();
+ audioBroadcaster.receive(new AudioPacket(AudioPacket.Type.END, metadata));
}
catch (IOException e)
{
diff --git a/src/audio/broadcast/icecast/IcecastTCPConfiguration.java b/src/audio/broadcast/icecast/IcecastTCPConfiguration.java
index 769648f3..d96bab5b 100644
--- a/src/audio/broadcast/icecast/IcecastTCPConfiguration.java
+++ b/src/audio/broadcast/icecast/IcecastTCPConfiguration.java
@@ -22,9 +22,10 @@ import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastServerType;
import audio.broadcast.broadcastify.BroadcastifyConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import java.util.Base64;
@@ -32,6 +33,8 @@ import java.util.Base64;
@XmlSeeAlso( {BroadcastifyConfiguration.class } )
public class IcecastTCPConfiguration extends BroadcastConfiguration
{
+ private final static Logger mLog = LoggerFactory.getLogger( IcecastTCPConfiguration.class );
+
private String mUserName;
private String mMountPoint;
private String mDescription;
@@ -44,6 +47,7 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
public IcecastTCPConfiguration()
{
+ setUserName("source");
//No-arg constructor for JAXB
}
@@ -54,6 +58,31 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
public IcecastTCPConfiguration(BroadcastFormat format)
{
super(format);
+ setUserName("source");
+ }
+
+ @Override
+ public BroadcastConfiguration copyOf()
+ {
+ IcecastTCPConfiguration copy = new IcecastTCPConfiguration(getBroadcastFormat());
+
+ //Broadcast Configuration Parameters
+ copy.setName(getName());
+ copy.setHost(getHost());
+ copy.setPort(getPort());
+ copy.setPassword(getPassword());
+ copy.setDelay(getDelay());
+ copy.setEnabled(false);
+
+ //Icecast Configuration Parameters
+ copy.setUserName(getUserName());
+ copy.setMountPoint(getMountPoint());
+ copy.setDescription(getDescription());
+ copy.setGenre(getGenre());
+ copy.setPublic(isPublic());
+ copy.setURL(getURL());
+
+ return copy;
}
@Override
@@ -79,6 +108,8 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
sb.append(getPassword());
}
+ mLog.debug("Connection String: " + sb.toString());
+
String base64 = Base64.getEncoder().encodeToString(sb.toString().getBytes());
return "Basic " + base64;
@@ -175,7 +206,7 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
}
/**
- * Public visibility of the broadcast
+ * Public visibility of the broadcastAudio
*/
@XmlAttribute( name="public" )
public boolean isPublic()
@@ -184,8 +215,8 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
}
/**
- * Sets public visibility of the broadcast
- * @param isPublic indicates if the broadcast should be visible to the public
+ * Sets public visibility of the broadcastAudio
+ * @param isPublic indicates if the broadcastAudio should be visible to the public
*/
public void setPublic(boolean isPublic)
{
@@ -193,7 +224,7 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
}
/**
- * Number of audio channels in the broadcast
+ * Number of audio channels in the broadcastAudio
*/
@XmlAttribute( name="channels" )
public int getChannels()
@@ -202,7 +233,7 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
}
/**
- * Sets the number of audio channels in the broadcast
+ * Sets the number of audio channels in the broadcastAudio
*/
public void setChannels(int channels)
{
@@ -254,7 +285,7 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
}
/**
- * URL associated with the broadcast where users can find additional details.
+ * URL associated with the broadcastAudio where users can find additional details.
*/
@XmlAttribute( name="url" )
public String getURL()
@@ -263,7 +294,7 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
}
/**
- * URL associated with the broadcast where users can find additional details.
+ * URL associated with the broadcastAudio where users can find additional details.
* @param url
*/
public void setURL(String url)
@@ -275,4 +306,35 @@ public class IcecastTCPConfiguration extends BroadcastConfiguration
{
return mURL != null;
}
+
+ @Override
+ public boolean isValid()
+ {
+ if(!super.isValid())
+ {
+ return false;
+ }
+ else if(mUserName == null)
+ {
+ return false;
+ }
+ else if(mMountPoint == null)
+ {
+ return false;
+ }
+ else if(mChannels != 1)
+ {
+ return false;
+ }
+ else if(mSampleRate <= 0)
+ {
+ return false;
+ }
+ else if(mBitRate <= 0)
+ {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/src/audio/broadcast/shoutcast/v1/ShoutcastV1Broadcaster.java b/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java
similarity index 89%
rename from src/audio/broadcast/shoutcast/v1/ShoutcastV1Broadcaster.java
rename to src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java
index da94c164..cbd32277 100644
--- a/src/audio/broadcast/shoutcast/v1/ShoutcastV1Broadcaster.java
+++ b/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java
@@ -19,13 +19,14 @@
package audio.broadcast.shoutcast.v1;
import audio.AudioPacket;
-import audio.broadcast.Broadcaster;
+import audio.broadcast.AudioBroadcaster;
import audio.broadcast.BroadcastFactory;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastMetadata;
import audio.broadcast.BroadcastState;
-import audio.convert.IAudioConverter;
+import audio.metadata.AudioMetadata;
import controller.ThreadPoolManager;
+import org.asynchttpclient.DefaultAsyncHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.wave.AudioPacketMonoWaveReader;
@@ -41,9 +42,9 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
-public class ShoutcastV1Broadcaster extends Broadcaster
+public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
{
- private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV1Broadcaster.class );
+ private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV1AudioBroadcaster.class );
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
private long mLastConnectionAttempt = 0;
@@ -58,13 +59,10 @@ public class ShoutcastV1Broadcaster extends Broadcaster
/**
* Creates a Shoutcast Version 1 broadcaster.
* @param configuration
- * @param audioConverter
*/
- public ShoutcastV1Broadcaster(ThreadPoolManager threadPoolManager,
- ShoutcastV1Configuration configuration,
- IAudioConverter audioConverter)
+ public ShoutcastV1AudioBroadcaster(ThreadPoolManager threadPoolManager, ShoutcastV1Configuration configuration)
{
- super(threadPoolManager, configuration, audioConverter);
+ super(threadPoolManager, configuration);
}
/**
@@ -75,34 +73,29 @@ public class ShoutcastV1Broadcaster extends Broadcaster
return (ShoutcastV1Configuration)getBroadcastConfiguration();
}
+ @Override
+ protected void broadcastMetadata(AudioMetadata metadata)
+ {
+ mLog.debug("Request to send audio metadata to shoutcast v1 server - needs code");
+ }
+
/**
* Process audio packets using Shoutcast V1 Protocol
*/
- public void broadcast()
+ @Override
+ protected void broadcastAudio(byte[] audio)
{
//If we're connected, send the audio, otherwise discard it
if(connect())
{
- mAudioQueue.drainTo(mPacketsToBroadcast, 5);
-
- if(!mPacketsToBroadcast.isEmpty())
+ try
{
- byte[] convertedAudio = getAudioConverter().convert(mPacketsToBroadcast);
-
- mPacketsToBroadcast.clear();
-
- if(convertedAudio != null)
- {
- try
- {
- send(convertedAudio);
- }
- catch(IOException e)
- {
- mLog.error("Error while dispatching audio", e);
- setBroadcastState(BroadcastState.BROADCAST_ERROR);
- }
- }
+ send(audio);
+ }
+ catch (IOException e)
+ {
+ mLog.error("Error while dispatching audio", e);
+ setBroadcastState(BroadcastState.BROADCAST_ERROR);
}
}
}
@@ -349,8 +342,9 @@ public class ShoutcastV1Broadcaster extends Broadcaster
config.setBitRate(16);
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
+ DefaultAsyncHttpClient httpClient = new DefaultAsyncHttpClient();
- final Broadcaster broadcaster = BroadcastFactory.getBroadcaster(threadPoolManager,config);
+ final AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(httpClient, threadPoolManager,config);
Path path = Paths.get("/home/denny/Music/PCM.wav");
mLog.debug("Opening: " + path.toString());
@@ -363,7 +357,7 @@ public class ShoutcastV1Broadcaster extends Broadcaster
try (AudioPacketMonoWaveReader reader = new AudioPacketMonoWaveReader(path, true))
{
- reader.setListener(broadcaster);
+ reader.setListener(audioBroadcaster);
reader.read();
}
catch (IOException e)
diff --git a/src/audio/broadcast/shoutcast/v1/ShoutcastV1Configuration.java b/src/audio/broadcast/shoutcast/v1/ShoutcastV1Configuration.java
index 3f5ac417..8b3c1e1a 100644
--- a/src/audio/broadcast/shoutcast/v1/ShoutcastV1Configuration.java
+++ b/src/audio/broadcast/shoutcast/v1/ShoutcastV1Configuration.java
@@ -21,6 +21,7 @@ package audio.broadcast.shoutcast.v1;
import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastServerType;
+import audio.broadcast.icecast.IcecastTCPConfiguration;
public class ShoutcastV1Configuration extends BroadcastConfiguration
{
@@ -31,11 +32,40 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration
private int mBitRate;
private String mURL;
+ public ShoutcastV1Configuration()
+ {
+ //No-arg JAXB constructor
+ }
+
public ShoutcastV1Configuration(BroadcastFormat format)
{
super(format);
}
+ @Override
+ public BroadcastConfiguration copyOf()
+ {
+ ShoutcastV1Configuration copy = new ShoutcastV1Configuration(getBroadcastFormat());
+
+ //Broadcast Configuration Parameters
+ copy.setName(getName());
+ copy.setHost(getHost());
+ copy.setPort(getPort());
+ copy.setPassword(getPassword());
+ copy.setDelay(getDelay());
+ copy.setEnabled(false);
+
+ //Icecast Configuration Parameters
+ copy.setStreamName(getStreamName());
+ copy.setGenre(getGenre());
+ copy.setPublic(isPublic());
+ copy.setChannels(getChannels());
+ copy.setBitRate(getBitRate());
+ copy.setURL(getURL());
+
+ return copy;
+ }
+
@Override
public BroadcastServerType getBroadcastServerType()
{
@@ -77,7 +107,7 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration
}
/**
- * Public visibility of the broadcast
+ * Public visibility of the broadcastAudio
*/
public boolean isPublic()
{
@@ -85,8 +115,8 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration
}
/**
- * Sets public visibility of the broadcast
- * @param isPublic indicates if the broadcast should be visible to the public
+ * Sets public visibility of the broadcastAudio
+ * @param isPublic indicates if the broadcastAudio should be visible to the public
*/
public void setPublic(boolean isPublic)
{
@@ -94,7 +124,7 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration
}
/**
- * Number of audio channels in the broadcast
+ * Number of audio channels in the broadcastAudio
*/
public int getChannels()
{
@@ -102,7 +132,7 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration
}
/**
- * Sets the number of audio channels in the broadcast
+ * Sets the number of audio channels in the broadcastAudio
*/
public void setChannels(int channels)
{
@@ -127,7 +157,7 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration
}
/**
- * URL associated with the broadcast where users can find additional details.
+ * URL associated with the broadcastAudio where users can find additional details.
*/
public String getURL()
{
@@ -135,7 +165,7 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration
}
/**
- * URL associated with the broadcast where users can find additional details.
+ * URL associated with the broadcastAudio where users can find additional details.
* @param url
*/
public void setURL(String url)
diff --git a/src/audio/broadcast/shoutcast/v2/ShoutcastV2Broadcaster.java b/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java
similarity index 91%
rename from src/audio/broadcast/shoutcast/v2/ShoutcastV2Broadcaster.java
rename to src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java
index 78a5992a..d1167e80 100644
--- a/src/audio/broadcast/shoutcast/v2/ShoutcastV2Broadcaster.java
+++ b/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java
@@ -19,10 +19,10 @@
package audio.broadcast.shoutcast.v2;
import audio.AudioPacket;
+import audio.broadcast.AudioBroadcaster;
import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastState;
-import audio.broadcast.Broadcaster;
import audio.broadcast.BroadcastFactory;
import audio.broadcast.shoutcast.v2.message.AuthenticateBroadcast;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyGenre;
@@ -39,8 +39,9 @@ import audio.broadcast.shoutcast.v2.message.UltravoxMessageFactory;
import audio.broadcast.shoutcast.v2.message.UltravoxMessageType;
import audio.broadcast.shoutcast.v2.message.UltravoxMetadata;
import audio.broadcast.shoutcast.v2.message.XMLMetadata;
-import audio.convert.IAudioConverter;
+import audio.metadata.AudioMetadata;
import controller.ThreadPoolManager;
+import org.asynchttpclient.DefaultAsyncHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
@@ -57,9 +58,9 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
-public class ShoutcastV2Broadcaster extends Broadcaster
+public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster
{
- private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2Broadcaster.class );
+ private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2AudioBroadcaster.class );
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
private static final boolean GET_RESPONSE = true;
@@ -75,13 +76,10 @@ public class ShoutcastV2Broadcaster extends Broadcaster
/**
* Creates a Shoutcast Version 2 broadcaster.
* @param configuration details for shoutcast version 2
- * @param audioConverter to convert PCM audio
*/
- public ShoutcastV2Broadcaster(ThreadPoolManager threadPoolManager,
- BroadcastConfiguration configuration,
- IAudioConverter audioConverter)
+ public ShoutcastV2AudioBroadcaster(ThreadPoolManager threadPoolManager, BroadcastConfiguration configuration)
{
- super(threadPoolManager, configuration, audioConverter);
+ super(threadPoolManager, configuration);
}
/**
@@ -92,41 +90,36 @@ public class ShoutcastV2Broadcaster extends Broadcaster
return (ShoutcastV2Configuration)getBroadcastConfiguration();
}
+ @Override
+ protected void broadcastMetadata(AudioMetadata metadata)
+ {
+ mLog.debug("Request to broadcast audio metadata to shoutcast v2 server - needs coding");
+ }
+
/**
- * Broadcast audio packets
+ * Broadcast audio
*/
- public void broadcast()
+ @Override
+ protected void broadcastAudio(byte[] audio)
{
//If we're connected, send the audio, otherwise discard it
if(connect())
{
- mAudioQueue.drainTo(mPacketsToBroadcast, 5);
+ List audioMessages = getAudioMessages(audio);
- if(!mPacketsToBroadcast.isEmpty())
+ try
{
- byte[] convertedAudio = getAudioConverter().convert(mPacketsToBroadcast);
-
- mPacketsToBroadcast.clear();
-
- if(convertedAudio != null)
+ for(UltravoxMessage audioMessage: audioMessages)
{
- List audioMessages = getAudioMessages(convertedAudio);
-
- try
- {
- for(UltravoxMessage audioMessage: audioMessages)
- {
- send(audioMessage, NO_RESPONSE_EXPECTED);
- }
- }
- catch(IOException e)
- {
- mLog.error("Error while dispatching audio", e);
- setBroadcastState(BroadcastState.BROADCAST_ERROR);
- return;
- }
+ send(audioMessage, NO_RESPONSE_EXPECTED);
}
}
+ catch(IOException e)
+ {
+ mLog.error("Error while dispatching audio", e);
+ setBroadcastState(BroadcastState.BROADCAST_ERROR);
+ return;
+ }
}
}
@@ -370,13 +363,13 @@ public class ShoutcastV2Broadcaster extends Broadcaster
response = send(icyName, NO_RESPONSE_EXPECTED);
}
- if (getShoutcastConfiguration().getStreamGenre() != null)
+ if (getShoutcastConfiguration().getGenre() != null)
{
- metadata.add(UltravoxMetadata.GENRE.asXML(getShoutcastConfiguration().getStreamGenre()));
+ metadata.add(UltravoxMetadata.GENRE.asXML(getShoutcastConfiguration().getGenre()));
ConfigureIcyGenre icyGenre = (ConfigureIcyGenre) UltravoxMessageFactory
.getMessage(UltravoxMessageType.CONFIGURE_ICY_GENRE);
- icyGenre.setGenre(getShoutcastConfiguration().getStreamGenre());
+ icyGenre.setGenre(getShoutcastConfiguration().getGenre());
response = send(icyGenre, NO_RESPONSE_EXPECTED);
}
@@ -534,7 +527,7 @@ public class ShoutcastV2Broadcaster extends Broadcaster
config.setPort(8000);
config.setPassword("denny3");
config.setStreamName("Denny's Audio Broadcast Test");
- config.setStreamGenre("Public Safety");
+ config.setGenre("Public Safety");
config.setPublic(true);
config.setURL("http://localhost:8000");
config.setBitRate(16);
@@ -545,8 +538,9 @@ public class ShoutcastV2Broadcaster extends Broadcaster
else
{
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
+ DefaultAsyncHttpClient httpClient = new DefaultAsyncHttpClient();
- final Broadcaster broadcaster = BroadcastFactory.getBroadcaster(threadPoolManager,config);
+ final AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(httpClient, threadPoolManager,config);
Path path = Paths.get("/home/denny/Music/PCM.wav");
mLog.debug("Opening: " + path.toString());
@@ -559,7 +553,7 @@ public class ShoutcastV2Broadcaster extends Broadcaster
try (AudioPacketMonoWaveReader reader = new AudioPacketMonoWaveReader(path, true))
{
- reader.setListener(broadcaster);
+ reader.setListener(audioBroadcaster);
reader.read();
}
catch (IOException e)
diff --git a/src/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java b/src/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java
index eb121759..b973edb4 100644
--- a/src/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java
+++ b/src/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java
@@ -28,16 +28,46 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
private String mUserID;
private int mBitRate;
private String mStreamName;
- private String mStreamGenre;
+ private String mGenre;
private String mURL;
private boolean mPublic = true;
+ public ShoutcastV2Configuration()
+ {
+ //No-arg JAXB constructor
+ }
public ShoutcastV2Configuration(BroadcastFormat format)
{
super(format);
}
+ @Override
+ public BroadcastConfiguration copyOf()
+ {
+ ShoutcastV2Configuration copy = new ShoutcastV2Configuration(getBroadcastFormat());
+
+ //Broadcast Configuration Parameters
+ copy.setName(getName());
+ copy.setHost(getHost());
+ copy.setPort(getPort());
+ copy.setPassword(getPassword());
+ copy.setDelay(getDelay());
+ copy.setEnabled(false);
+
+ //Shoutcast V2 Configuration Parameters
+ copy.setStreamID(getStreamID());
+ copy.setUserID(getUserID());
+ copy.setBitRate(getBitRate());
+ copy.setStreamName(getStreamName());
+ copy.setGenre(getGenre());
+ copy.setURL(getURL());
+ copy.setPublic(isPublic());
+
+ return copy;
+ }
+
+
@Override
public BroadcastServerType getBroadcastServerType()
{
@@ -117,21 +147,21 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
/**
* Stream genre
*/
- public String getStreamGenre()
+ public String getGenre()
{
- return mStreamGenre;
+ return mGenre;
}
/**
* Sets the genre tag for this stream
*/
- public void setStreamGenre(String genre)
+ public void setGenre(String genre)
{
- mStreamGenre = genre;
+ mGenre = genre;
}
/**
- * Public visibility of the broadcast
+ * Public visibility of the broadcastAudio
*/
public boolean isPublic()
{
@@ -139,8 +169,8 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
}
/**
- * Sets public visibility of the broadcast
- * @param isPublic indicates if the broadcast should be visible to the public
+ * Sets public visibility of the broadcastAudio
+ * @param isPublic indicates if the broadcastAudio should be visible to the public
*/
public void setPublic(boolean isPublic)
{
@@ -148,7 +178,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
}
/**
- * URL associated with the broadcast where users can find additional details.
+ * URL associated with the broadcastAudio where users can find additional details.
*/
public String getURL()
{
@@ -156,7 +186,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
}
/**
- * URL associated with the broadcast where users can find additional details.
+ * URL associated with the broadcastAudio where users can find additional details.
* @param url
*/
public void setURL(String url)
diff --git a/src/audio/broadcast/shoutcast/v2/message/StreamMimeType.java b/src/audio/broadcast/shoutcast/v2/message/StreamMimeType.java
index 8ce0a6c8..3bf32c77 100644
--- a/src/audio/broadcast/shoutcast/v2/message/StreamMimeType.java
+++ b/src/audio/broadcast/shoutcast/v2/message/StreamMimeType.java
@@ -45,7 +45,7 @@ public class StreamMimeType extends UltravoxMessage
}
/**
- * Sets the broadcast format or MIME type
+ * Sets the broadcastAudio format or MIME type
* @param format of the stream
*/
public void setFormat(BroadcastFormat format)
diff --git a/src/audio/broadcast/shoutcast/v2/message/TerminateBroadcast.java b/src/audio/broadcast/shoutcast/v2/message/TerminateBroadcast.java
index 125e8b13..26d3e1f5 100644
--- a/src/audio/broadcast/shoutcast/v2/message/TerminateBroadcast.java
+++ b/src/audio/broadcast/shoutcast/v2/message/TerminateBroadcast.java
@@ -21,7 +21,7 @@ package audio.broadcast.shoutcast.v2.message;
public class TerminateBroadcast extends UltravoxMessage
{
/**
- * Client request to server to terminate the broadcast
+ * Client request to server to terminate the broadcastAudio
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
diff --git a/src/audio/metadata/AudioMetadata.java b/src/audio/metadata/AudioMetadata.java
index 81e3a9ad..9bc19cd0 100644
--- a/src/audio/metadata/AudioMetadata.java
+++ b/src/audio/metadata/AudioMetadata.java
@@ -18,11 +18,15 @@
package audio.metadata;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+import alias.id.broadcast.BroadcastChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -312,6 +316,23 @@ public class AudioMetadata implements Listener
return mStreamable;
}
+ public Collection getBroadcastChannels()
+ {
+ Set broadcastChannels = new TreeSet<>();
+
+ for(Metadata metadata: mMetadata.values())
+ {
+ if(metadata.hasAlias() && metadata.getAlias().isStreamable())
+ {
+ Alias alias = metadata.getAlias();
+
+ broadcastChannels.addAll(alias.getBroadcastChannels());
+ }
+ }
+
+ return broadcastChannels;
+ }
+
public String toString()
{
StringBuilder sb = new StringBuilder();
diff --git a/src/controller/ControllerPanel.java b/src/controller/ControllerPanel.java
index c7c727df..64168378 100644
--- a/src/controller/ControllerPanel.java
+++ b/src/controller/ControllerPanel.java
@@ -91,7 +91,7 @@ public class ControllerPanel extends JPanel
mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager);
- mBroadcastPanel = new BroadcastPanel(broadcastModel);
+ mBroadcastPanel = new BroadcastPanel(broadcastModel, settingsManager);
mCallEventPanel = new CallEventPanel(settingsManager, channelProcessingManager);
diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java
index 10537da6..db8cd501 100644
--- a/src/gui/SDRTrunk.java
+++ b/src/gui/SDRTrunk.java
@@ -20,7 +20,6 @@ package gui;
import alias.AliasModel;
import alias.action.AliasActionManager;
import audio.AudioManager;
-import audio.broadcast.AudioBroadcastManager;
import audio.broadcast.BroadcastModel;
import com.jidesoft.swing.JideSplitPane;
import controller.ControllerPanel;
@@ -137,10 +136,9 @@ public class SDRTrunk implements Listener
AudioManager audioManager = new AudioManager(threadPoolManager, sourceManager.getMixerManager());
channelProcessingManager.addAudioPacketListener(audioManager);
- BroadcastModel broadcastModel = new BroadcastModel();
+ BroadcastModel broadcastModel = new BroadcastModel(threadPoolManager, mSettingsManager);
- AudioBroadcastManager audioBroadcastManager = new AudioBroadcastManager(threadPoolManager);
- channelProcessingManager.addAudioPacketListener(audioBroadcastManager);
+ channelProcessingManager.addAudioPacketListener(broadcastModel);
MapService mapService = new MapService(mSettingsManager);
channelProcessingManager.addMessageListener(mapService);
diff --git a/src/instrument/gui/DecoderSelectionFrame.java b/src/instrument/gui/DecoderSelectionFrame.java
index 7f761839..23239335 100644
--- a/src/instrument/gui/DecoderSelectionFrame.java
+++ b/src/instrument/gui/DecoderSelectionFrame.java
@@ -31,13 +31,13 @@ public class DecoderSelectionFrame extends JInternalFrame
public DecoderSelectionFrame( JDesktopPane desktop,
IControllableFileSource source )
{
+ ThreadPoolManager tpm = new ThreadPoolManager();
+
AliasModel aliasModel = new AliasModel();
- BroadcastModel broadcastModel = new BroadcastModel();
+ BroadcastModel broadcastModel = new BroadcastModel(tpm, null);
ChannelModel channelModel = new ChannelModel();
ChannelMapModel channelMapModel = new ChannelMapModel();
-
- ThreadPoolManager tpm = new ThreadPoolManager();
-
+
mPlaylistManager = new PlaylistManager( tpm, aliasModel, broadcastModel, channelModel, channelMapModel );
mDesktop = desktop;
diff --git a/src/playlist/PlaylistManager.java b/src/playlist/PlaylistManager.java
index a3d7938c..d50f7e03 100644
--- a/src/playlist/PlaylistManager.java
+++ b/src/playlist/PlaylistManager.java
@@ -33,7 +33,7 @@ import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
-import audio.broadcast.BroadcastConfigurationEvent;
+import audio.broadcast.BroadcastEvent;
import audio.broadcast.BroadcastModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -109,10 +109,10 @@ public class PlaylistManager implements ChannelEventListener
}
} );
- mBroadcastModel.addListener(new Listener()
+ mBroadcastModel.addListener(new Listener()
{
@Override
- public void receive(BroadcastConfigurationEvent broadcastConfigurationEvent)
+ public void receive(BroadcastEvent broadcastEvent)
{
schedulePlaylistSave();
}
@@ -346,7 +346,7 @@ public class PlaylistManager implements ChannelEventListener
}
catch ( Exception e )
{
- mLog.error( "coulcn't open inputstream to load playlist [" + playlistPath.toString() + "]", e );
+ mLog.error( "couldn't open input stream to load playlist [" + playlistPath.toString() + "]", e );
}
finally
{
diff --git a/src/record/AudioRecorder.java b/src/record/AudioRecorder.java
index 84d6270d..f310f734 100644
--- a/src/record/AudioRecorder.java
+++ b/src/record/AudioRecorder.java
@@ -55,6 +55,8 @@ public abstract class AudioRecorder extends Module implements Listener mProcessorHandle;
+ private long mSampleCount;
+
/**
* Abstract audio recorder that implements audio packet queueing and threaded audio conversion/writing to a file
*
@@ -95,6 +97,15 @@ public abstract class AudioRecorder extends Module implements Listener interface.
* This is necessary since you can't have multiple methods with the same erasure (ie Listener) in the
@@ -137,7 +148,7 @@ public abstract class AudioRecorder extends Module implements Listener implements Listener
{