diff --git a/.idea/misc.xml b/.idea/misc.xml
index 6746d627..08f2781a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,7 @@
-
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index a6d25f94..74800ded 100644
--- a/build.gradle
+++ b/build.gradle
@@ -70,8 +70,8 @@ dependencies {
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
implementation 'org.apache.commons:commons-lang3:3.8.1'
implementation 'org.apache.commons:commons-io:1.3.2'
- implementation 'org.apache.mina:mina-core:2.0.19'
- implementation 'org.apache.mina:mina-http:2.0.19'
+ implementation 'org.apache.mina:mina-core:2.1.3'
+ implementation 'org.apache.mina:mina-http:2.1.3'
implementation 'org.controlsfx:controlsfx:11.0.1'
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'org.usb4java:libusb4java:1.3.0'
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/AbstractAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/AbstractAudioBroadcaster.java
new file mode 100644
index 00000000..c6aa11ee
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/AbstractAudioBroadcaster.java
@@ -0,0 +1,177 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2020 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 io.github.dsheirer.audio.broadcast;
+
+import io.github.dsheirer.sample.Listener;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base Audio Broadcaster class
+ */
+public abstract class AbstractAudioBroadcaster implements Listener
+{
+ private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioBroadcaster.class);
+ private Listener mBroadcastEventListener;
+ private T mBroadcastConfiguration;
+ protected ObjectProperty mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
+ protected int mStreamedAudioCount = 0;
+ protected int mErrorAudioCount = 0;
+ protected int mAgedOffAudioCount = 0;
+
+ /**
+ * Constructs an instance
+ * @param broadcastConfiguration to use for this broadcaster
+ */
+ public AbstractAudioBroadcaster(T broadcastConfiguration)
+ {
+ mBroadcastConfiguration = broadcastConfiguration;
+ }
+
+ /**
+ * Observable broadcast state property
+ */
+ public ObjectProperty broadcastStateProperty()
+ {
+ return mBroadcastState;
+ }
+
+ /**
+ * Current state of the broadcastAudio connection
+ */
+ public BroadcastState getBroadcastState()
+ {
+ return mBroadcastState.get();
+ }
+
+ /**
+ * Sets or changes the broadcast state
+ */
+ public void setBroadcastState(BroadcastState broadcastState)
+ {
+ mBroadcastState.setValue(broadcastState);
+ broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
+ }
+
+ /**
+ * Starts the broadcaster
+ */
+ public abstract void start();
+
+ /**
+ * Stops the broadcaster
+ */
+ public abstract void stop();
+
+ /**
+ * Perform any shutdown related tasks.
+ */
+ public abstract void dispose();
+
+ /**
+ * Access the configuration for the broadcaster
+ */
+ public T getBroadcastConfiguration()
+ {
+ return mBroadcastConfiguration;
+ }
+
+ /**
+ * Registers the listener to receive broadcast events/state changes
+ */
+ public void setListener(Listener listener)
+ {
+ mBroadcastEventListener = listener;
+ }
+
+ /**
+ * Removes the listener from receiving broadcast events/state changes
+ */
+ public void removeListener()
+ {
+ mBroadcastEventListener = null;
+ }
+
+ /**
+ * Broadcasts the event to any registered listener
+ */
+ protected void broadcast(BroadcastEvent event)
+ {
+ if(mBroadcastEventListener != null)
+ {
+ mBroadcastEventListener.receive(event);
+ }
+ }
+
+ /**
+ * Number of audio recordings streamed to remote server
+ */
+ public int getStreamedAudioCount()
+ {
+ return mStreamedAudioCount;
+ }
+
+ /**
+ * Increments the streamed or uploaded audio count by one
+ */
+ public void incrementStreamedAudioCount()
+ {
+ mStreamedAudioCount++;
+ }
+
+ /**
+ * Number of audio recordings that were removed for exceeding age limit
+ */
+ public int getAgedOffAudioCount()
+ {
+ return mAgedOffAudioCount;
+ }
+
+ /**
+ * Increments the aged-off audio count by one
+ */
+ public void incrementAgedOffAudioCount()
+ {
+ mAgedOffAudioCount++;
+ }
+
+ /**
+ * Number of audio recordings awaiting streaming or upload
+ */
+ public abstract int getAudioQueueSize();
+
+ /**
+ * Total audio upload/stream error count
+ */
+ public int getAudioErrorCount()
+ {
+ return mErrorAudioCount;
+ }
+
+ /**
+ * Increments the error audio count by one
+ */
+ public void incrementErrorAudioCount()
+ {
+ mErrorAudioCount++;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/AudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingBroadcaster.java
similarity index 83%
rename from src/main/java/io/github/dsheirer/audio/broadcast/AudioBroadcaster.java
rename to src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingBroadcaster.java
index 341871a8..05641152 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/AudioBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingBroadcaster.java
@@ -23,10 +23,7 @@ package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.audio.convert.ISilenceGenerator;
import io.github.dsheirer.identifier.IdentifierCollection;
-import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,25 +37,17 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-public abstract class AudioBroadcaster implements Listener
+public abstract class AudioStreamingBroadcaster extends AbstractAudioBroadcaster
{
- private final static Logger mLog = LoggerFactory.getLogger(AudioBroadcaster.class);
+ private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingBroadcaster.class);
public static final int PROCESSOR_RUN_INTERVAL_MS = 1000;
-
private ScheduledFuture mRecordingQueueProcessorFuture;
private RecordingQueueProcessor mRecordingQueueProcessor = new RecordingQueueProcessor();
private Queue mAudioRecordingQueue = new LinkedTransferQueue<>();
-
private ISilenceGenerator mSilenceGenerator;
- private Listener mBroadcastEventListener;
- private ObjectProperty mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
-
- private int mStreamedAudioCount = 0;
- private int mAgedOffAudioCount = 0;
- private BroadcastConfiguration mBroadcastConfiguration;
private long mDelay;
private long mMaximumRecordingAge;
private AtomicBoolean mStreaming = new AtomicBoolean();
@@ -84,22 +73,14 @@ public abstract class AudioBroadcaster implements Listener
* The last audio packet's metadata is automatically attached to the closed audio recording when it is enqueued for
* broadcast. That metadata will be updated on the remote server once the audio recording is opened for streaming.
*/
- public AudioBroadcaster(BroadcastConfiguration broadcastConfiguration)
+ public AudioStreamingBroadcaster(T broadcastConfiguration)
{
- mBroadcastConfiguration = broadcastConfiguration;
- mDelay = mBroadcastConfiguration.getDelay();
- mMaximumRecordingAge = mBroadcastConfiguration.getMaximumRecordingAge();
+ super(broadcastConfiguration);
+ mDelay = getBroadcastConfiguration().getDelay();
+ mMaximumRecordingAge = getBroadcastConfiguration().getMaximumRecordingAge();
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat());
}
- /**
- * Observable broadcast state property
- */
- public ObjectProperty broadcastStateProperty()
- {
- return mBroadcastState;
- }
-
public void dispose()
{
}
@@ -186,27 +167,11 @@ public abstract class AudioBroadcaster implements Listener
/**
* Size of recording queue for recordings awaiting streaming
*/
- public int getQueueSize()
+ public int getAudioQueueSize()
{
return mAudioRecordingQueue.size();
}
- /**
- * Number of audio recordings streamed to remote server
- */
- public int getStreamedAudioCount()
- {
- return mStreamedAudioCount;
- }
-
- /**
- * Number of audio recordings that were removed for exceeding age limit
- */
- public int getAgedOffAudioCount()
- {
- return mAgedOffAudioCount;
- }
-
/**
* Primary insert method for the stream manager to nominate completed audio recordings for broadcast.
*
@@ -225,45 +190,10 @@ public abstract class AudioBroadcaster implements Listener
}
}
- /**
- * Broadcast configuration used by this broadcaster
- */
- public BroadcastConfiguration getBroadcastConfiguration()
- {
- return mBroadcastConfiguration;
- }
-
- /**
- * Registers the listener to receive broadcast events/state changes
- */
- public void setListener(Listener listener)
- {
- mBroadcastEventListener = listener;
- }
-
- /**
- * Removes the listener from receiving broadcast events/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)
+ public void setBroadcastState(BroadcastState state)
{
if(mBroadcastState.get() != state)
{
@@ -272,11 +202,9 @@ public abstract class AudioBroadcaster implements Listener
mLog.info("[" + getStreamName() + "] status: " + state);
}
- mBroadcastState.setValue(state);
+ super.setBroadcastState(state);
- broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
-
- if(mBroadcastState.get().isErrorState())
+ if(mBroadcastState.get() instanceof BroadcastState && ((BroadcastState)mBroadcastState.get()).isErrorState())
{
stop();
}
@@ -300,14 +228,6 @@ public abstract class AudioBroadcaster implements Listener
}
}
- /**
- * Current state of the broadcastAudio connection
- */
- public BroadcastState getBroadcastState()
- {
- return mBroadcastState.get();
- }
-
/**
* Indicates if the broadcaster is currently connected to the remote server
*/
@@ -417,7 +337,7 @@ public abstract class AudioBroadcaster implements Listener
if(mInputStream != null)
{
mStreamedAudioCount++;
- broadcast(new BroadcastEvent(AudioBroadcaster.this,
+ broadcast(new BroadcastEvent(AudioStreamingBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
metadataUpdateRequired = true;
}
@@ -435,7 +355,7 @@ public abstract class AudioBroadcaster implements Listener
nextRecording = mAudioRecordingQueue.remove();
nextRecording.removePendingReplay();
mAgedOffAudioCount++;
- broadcast(new BroadcastEvent(AudioBroadcaster.this,
+ broadcast(new BroadcastEvent(AudioStreamingBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
nextRecording = mAudioRecordingQueue.peek();
}
@@ -482,7 +402,7 @@ public abstract class AudioBroadcaster implements Listener
nextRecording.removePendingReplay();
- broadcast(new BroadcastEvent(AudioBroadcaster.this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
+ broadcast(new BroadcastEvent(AudioStreamingBroadcaster.this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
}
//If we closed out a recording and don't have a new/next recording, send an empty metadata update
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastConfiguration.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastConfiguration.java
index 33e463a6..e5669926 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastConfiguration.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastConfiguration.java
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
@@ -46,6 +47,7 @@ import java.net.SocketAddress;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
+ @JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
@@ -64,11 +66,12 @@ public abstract class BroadcastConfiguration
private LongProperty mDelay = new SimpleLongProperty();
private LongProperty mMaximumRecordingAge = new SimpleLongProperty(10 * 60 * 1000); //10 minutes default
private BooleanProperty mEnabled = new SimpleBooleanProperty(false);
- private BooleanProperty mValid;
+ protected BooleanProperty mValid = new SimpleBooleanProperty();
private int mId = ++UNIQUE_ID;
public BroadcastConfiguration()
{
+ mValid.bind(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)));
}
/**
@@ -155,12 +158,6 @@ public abstract class BroadcastConfiguration
*/
public BooleanProperty validProperty()
{
- if(mValid == null)
- {
- mValid = new SimpleBooleanProperty();
- mValid.bind(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)));
- }
-
return mValid;
}
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastEvent.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastEvent.java
index 802c9814..16131e87 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastEvent.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastEvent.java
@@ -21,7 +21,7 @@ package io.github.dsheirer.audio.broadcast;
public class BroadcastEvent
{
private BroadcastConfiguration mBroadcastConfiguration;
- private AudioBroadcaster mAudioBroadcaster;
+ private AbstractAudioBroadcaster mAudioBroadcaster;
private Event mEvent;
/**
@@ -36,7 +36,7 @@ public class BroadcastEvent
mEvent = event;
}
- public BroadcastEvent(AudioBroadcaster audioBroadcaster, Event event)
+ public BroadcastEvent(AbstractAudioBroadcaster audioBroadcaster, Event event)
{
mAudioBroadcaster = audioBroadcaster;
mEvent = event;
@@ -47,7 +47,7 @@ public class BroadcastEvent
return mBroadcastConfiguration;
}
- public AudioBroadcaster getAudioBroadcaster()
+ public AbstractAudioBroadcaster getAudioBroadcaster()
{
return mAudioBroadcaster;
}
@@ -83,6 +83,7 @@ public class BroadcastEvent
BROADCASTER_STATE_CHANGE,
BROADCASTER_STREAMED_COUNT_CHANGE,
BROADCASTER_AGED_OFF_COUNT_CHANGE,
+ BROADCASTER_ERROR_COUNT_CHANGE,
BROADCASTER_DELETE,
CONFIGURATION_ADD,
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java
index 85a55e90..ad663354 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java
@@ -22,14 +22,16 @@
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.alias.AliasModel;
-import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyConfiguration;
+import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster;
+import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
+import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcaster;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
-import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioBroadcaster;
+import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
import io.github.dsheirer.audio.convert.IAudioConverter;
import io.github.dsheirer.audio.convert.ISilenceGenerator;
@@ -51,7 +53,7 @@ public class BroadcastFactory
* @param configuration describing the server and audio types
* @return configured broadcaster or null
*/
- public static AudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel)
+ public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel)
{
if(configuration != null)
{
@@ -61,8 +63,10 @@ public class BroadcastFactory
{
switch(configuration.getBroadcastServerType())
{
+ case BROADCASTIFY_CALL:
+ return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration, aliasModel);
case BROADCASTIFY:
- return new IcecastTCPAudioBroadcaster((BroadcastifyConfiguration) configuration, aliasModel);
+ return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration, aliasModel);
case ICECAST_TCP:
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, aliasModel);
case ICECAST_HTTP:
@@ -70,7 +74,7 @@ public class BroadcastFactory
case SHOUTCAST_V1:
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, aliasModel);
case SHOUTCAST_V2:
- return new ShoutcastV2AudioBroadcaster((ShoutcastV2Configuration) configuration, aliasModel);
+ return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration, aliasModel);
case UNKNOWN:
default:
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
@@ -112,8 +116,10 @@ public class BroadcastFactory
{
switch(serverType)
{
+ case BROADCASTIFY_CALL:
+ return new BroadcastifyCallConfiguration(format);
case BROADCASTIFY:
- return new BroadcastifyConfiguration(format);
+ return new BroadcastifyFeedConfiguration(format);
case ICECAST_HTTP:
return new IcecastHTTPConfiguration(format);
case ICECAST_TCP:
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java
index 50be596b..a5940160 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java
@@ -30,7 +30,6 @@ import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import javafx.collections.FXCollections;
-import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +41,7 @@ import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -67,19 +67,19 @@ public class BroadcastModel extends AbstractTableModel implements Listener