#786 Updates Broadcastify Calls api for new test endpoint and editor changes. Updates Icecast and Shoutcast configurations to correctly show configuration validation status. (#787)

Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-05-03 11:23:21 +00:00
committed by GitHub
parent c8b441dbd4
commit 08ea0e0f18
13 changed files with 194 additions and 154 deletions

View File

@@ -31,7 +31,6 @@ import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration; import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration; import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty; import javafx.beans.property.LongProperty;
@@ -59,19 +58,18 @@ public abstract class BroadcastConfiguration
private static int UNIQUE_ID = 0; private static int UNIQUE_ID = 0;
private BroadcastFormat mBroadcastFormat = BroadcastFormat.MP3; private BroadcastFormat mBroadcastFormat = BroadcastFormat.MP3;
private StringProperty mName = new SimpleStringProperty(); protected StringProperty mName = new SimpleStringProperty();
private StringProperty mHost = new SimpleStringProperty(); protected StringProperty mHost = new SimpleStringProperty();
private IntegerProperty mPort = new SimpleIntegerProperty(80); protected IntegerProperty mPort = new SimpleIntegerProperty(80);
private StringProperty mPassword = new SimpleStringProperty(); protected StringProperty mPassword = new SimpleStringProperty();
private LongProperty mDelay = new SimpleLongProperty(); protected LongProperty mDelay = new SimpleLongProperty();
private LongProperty mMaximumRecordingAge = new SimpleLongProperty(10 * 60 * 1000); //10 minutes default protected LongProperty mMaximumRecordingAge = new SimpleLongProperty(10 * 60 * 1000); //10 minutes default
private BooleanProperty mEnabled = new SimpleBooleanProperty(false); protected BooleanProperty mEnabled = new SimpleBooleanProperty(false);
protected BooleanProperty mValid = new SimpleBooleanProperty(); protected BooleanProperty mValid = new SimpleBooleanProperty();
private int mId = ++UNIQUE_ID; private int mId = ++UNIQUE_ID;
public BroadcastConfiguration() public BroadcastConfiguration()
{ {
mValid.bind(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)));
} }
/** /**

View File

@@ -45,7 +45,6 @@ import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
@@ -66,8 +65,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
private static final String MULTIPART_TYPE = "multipart"; private static final String MULTIPART_TYPE = "multipart";
private static final String DEFAULT_SUBTYPE = "form-data"; private static final String DEFAULT_SUBTYPE = "form-data";
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE; private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
private static final String API_ENDPOINT = BroadcastifyCallConfiguration.DEV_ENDPOINT;
// private static final String API_ENDPOINT = BroadcastifyCallConfiguration.PRODUCTION_ENDPOINT;
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>(); private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
private ScheduledFuture<?> mAudioRecordingProcessorFuture; private ScheduledFuture<?> mAudioRecordingProcessorFuture;
private HttpClient mHttpClient = HttpClient.newBuilder() private HttpClient mHttpClient = HttpClient.newBuilder()
@@ -98,14 +95,14 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
String response = testConnection(getBroadcastConfiguration()); String response = testConnection(getBroadcastConfiguration());
mLastConnectionAttempt = System.currentTimeMillis(); mLastConnectionAttempt = System.currentTimeMillis();
if(response != null) if(response != null && response.toLowerCase().startsWith("ok"))
{ {
mLog.error("Error connecting to Broadcastify calls server on startup [" + response + "]"); setBroadcastState(BroadcastState.CONNECTED);
setBroadcastState(BroadcastState.ERROR);
} }
else else
{ {
setBroadcastState(BroadcastState.CONNECTED); mLog.error("Error connecting to Broadcastify calls server on startup [" + response + "]");
setBroadcastState(BroadcastState.ERROR);
} }
if(mAudioRecordingProcessorFuture == null) if(mAudioRecordingProcessorFuture == null)
@@ -160,7 +157,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
String response = testConnection(getBroadcastConfiguration()); String response = testConnection(getBroadcastConfiguration());
mLastConnectionAttempt = System.currentTimeMillis(); mLastConnectionAttempt = System.currentTimeMillis();
if(response == null) if(response != null && response.toLowerCase().startsWith("ok"))
{ {
setBroadcastState(BroadcastState.CONNECTED); setBroadcastState(BroadcastState.CONNECTED);
} }
@@ -462,15 +459,10 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
BroadcastifyCallBuilder bodyBuilder = new BroadcastifyCallBuilder(); BroadcastifyCallBuilder bodyBuilder = new BroadcastifyCallBuilder();
bodyBuilder.addPart(FormField.API_KEY, configuration.getApiKey()) bodyBuilder.addPart(FormField.API_KEY, configuration.getApiKey())
.addPart(FormField.SYSTEM_ID, configuration.getSystemID()) .addPart(FormField.SYSTEM_ID, configuration.getSystemID())
.addPart(FormField.CALL_DURATION, 0) .addPart(FormField.TEST, 1);
.addPart(FormField.TIMESTAMP, (int)(System.currentTimeMillis() / 1E3))
.addPart(FormField.TALKGROUP_ID, 999)
.addPart(FormField.RADIO_ID, 999999)
.addPart(FormField.FREQUENCY, 450.00000)
.addPart(FormField.ENCODING, ENCODING_TYPE_MP3);
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_ENDPOINT)) .uri(URI.create(configuration.getHost()))
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary()) .header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
.header(HttpHeaders.USER_AGENT, "sdrtrunk") .header(HttpHeaders.USER_AGENT, "sdrtrunk")
.header(HttpHeaders.ACCEPT, "*/*") .header(HttpHeaders.ACCEPT, "*/*")
@@ -482,17 +474,8 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
try try
{ {
HttpResponse<String> response = httpClient.send(request, responseHandler); HttpResponse<String> response = httpClient.send(request, responseHandler);
String responseBody = response.body(); String responseBody = response.body();
if(response.statusCode() == 200 && responseBody != null && responseBody.startsWith("0 ")) return (responseBody != null ? responseBody : "(no response)") + " Status Code:" + response.statusCode();
{
//success!
return null;
}
else
{
return responseBody;
}
} }
catch(Exception e) catch(Exception e)
{ {
@@ -513,100 +496,33 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
{ {
mLog.debug("Starting ..."); mLog.debug("Starting ...");
HttpClient httpClient = HttpClient.newBuilder() BroadcastifyCallConfiguration config = new BroadcastifyCallConfiguration();
.version(HttpClient.Version.HTTP_1_1) config.setHost("https://api.broadcastify.com/call-upload-dev");
.followRedirects(HttpClient.Redirect.NORMAL) config.setApiKey("c33aae37-8572-11ea-bd8b-0ecc8ab9ccec");
.connectTimeout(Duration.ofSeconds(20)) config.setSystemID(11);
.build();
int ts = (int)(System.currentTimeMillis() / 1E3); String response = testConnection(config);
BroadcastifyCallBuilder builder = new BroadcastifyCallBuilder(); if(response == null)
builder.addPart(FormField.API_KEY, BroadcastifyCallConfiguration.SDRTRUNK_DEV_API_KEY)
.addPart(FormField.SYSTEM_ID, 11)
.addPart(FormField.CALL_DURATION, 3.0)
.addPart(FormField.TIMESTAMP, 1234568)
.addPart(FormField.TALKGROUP_ID, 1234)
.addPart(FormField.RADIO_ID, 123456)
.addPart(FormField.FREQUENCY, 450.12345)
.addPart(FormField.ENCODING, "mp3");
HttpRequest.BodyPublisher body = builder.build();
if(body != null)
{ {
try mLog.debug("Test Successful!");
}
else
{
if(response.contains("1 Invalid-API-Key"))
{ {
HttpRequest request = HttpRequest.newBuilder() mLog.error("Invalid API Key");
.uri(URI.create(BroadcastifyCallConfiguration.DEV_ENDPOINT))
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + builder.getBoundary())
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
.header(HttpHeaders.ACCEPT, "*/*")
.POST(body).build();
HttpResponse<String> response = null;
mLog.info("Submitting request for URL");
try
{
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
}
catch(Exception ioe)
{
mLog.error("Error", ioe);
}
if(response != null)
{
String url = response.body().toString();
mLog.info("URL Requested - Response: " + url);
if(response.statusCode() == 200)
{
if(url != null && url.startsWith("0 "))
{
String file = "/home/denny/SDRTrunk/recordings/20200426_160130CNYICC_Onondaga_TRAFFIC__TO_1_FROM_911005.mp3";
Path path = Path.of(file);
HttpRequest fileRequest = HttpRequest.newBuilder()
.uri(URI.create(url.substring(2)))
.header(HttpHeaders.CONTENT_TYPE, "audio/mpeg")
.POST(HttpRequest.BodyPublishers.ofFile(path))
.build();
mLog.info("Submitting file upload");
httpClient.sendAsync(fileRequest, HttpResponse.BodyHandlers.ofString())
.whenComplete((stringHttpResponse, throwable) -> {
mLog.debug("Upload complete");
if(throwable != null)
{
mLog.info("Upload Complete:" + stringHttpResponse.body());
}
else
{
throwable.printStackTrace();
}
});
}
else
{
mLog.error("Got HTTP 200 OK, but invalid response: " + url);
}
}
else
{
mLog.info("Error response:" + url);
}
}
} }
catch(Exception e) else if(response.contains("1 API-Key-Access-Denied"))
{ {
mLog.debug("Error", e); mLog.error("System ID not valid for API Key");
}
else
{
mLog.debug("Response: " + response);
} }
} }
while(true); mLog.debug("Finished!");
// mLog.debug("Finished!");
} }
} }

View File

@@ -19,7 +19,6 @@
package io.github.dsheirer.audio.broadcast.broadcastify; package io.github.dsheirer.audio.broadcast.broadcastify;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastFormat;
@@ -27,6 +26,8 @@ import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/** /**
* Streaming configuration for Broadcastify Calls API. * Streaming configuration for Broadcastify Calls API.
@@ -36,13 +37,11 @@ import javafx.beans.property.SimpleIntegerProperty;
*/ */
public class BroadcastifyCallConfiguration extends BroadcastConfiguration public class BroadcastifyCallConfiguration extends BroadcastConfiguration
{ {
public static final String SDRTRUNK_DEV_API_KEY = "c33aae37-8572-11ea-bd8b-0ecc8ab9ccec"; public static final String DEVELOPMENT_ENDPOINT = "https://api.broadcastify.com/call-upload-dev";
public static final String SDRTRUNK_PRODUCTION_API_KEY = ""; //TODO: get production api key
public static final String DEV_ENDPOINT = "https://api.broadcastify.com/call-upload-dev";
public static final String PRODUCTION_ENDPOINT = "https://api.broadcastify.com/call-upload"; public static final String PRODUCTION_ENDPOINT = "https://api.broadcastify.com/call-upload";
public static final String CURRENT_ENDPOINT = DEV_ENDPOINT;
private IntegerProperty mSystemID = new SimpleIntegerProperty(); private IntegerProperty mSystemID = new SimpleIntegerProperty();
private StringProperty mApiKey = new SimpleStringProperty();
/** /**
* Constructor for faster jackson * Constructor for faster jackson
@@ -59,11 +58,16 @@ public class BroadcastifyCallConfiguration extends BroadcastConfiguration
public BroadcastifyCallConfiguration(BroadcastFormat format) public BroadcastifyCallConfiguration(BroadcastFormat format)
{ {
super(format); super(format);
setHost(CURRENT_ENDPOINT);
if(getHost() == null)
{
setHost(PRODUCTION_ENDPOINT);
}
//The parent class binds this property, so we unbind it and rebind it here //The parent class binds this property, so we unbind it and rebind it here
mValid.unbind(); mValid.unbind();
mValid.bind(Bindings.greaterThan(mSystemID, 0)); mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
Bindings.isNotNull(mHost)));
} }
/** /**
@@ -74,13 +78,30 @@ public class BroadcastifyCallConfiguration extends BroadcastConfiguration
return mSystemID; return mSystemID;
} }
/**
* API key as a property
*/
public StringProperty apiKeyProperty()
{
return mApiKey;
}
/** /**
* API Key * API Key
*/ */
@JsonIgnore @JacksonXmlProperty(isAttribute = true, localName = "api_key")
public String getApiKey() public String getApiKey()
{ {
return SDRTRUNK_DEV_API_KEY; return mApiKey.get();
}
/**
* Sets the api key
* @param apiKey
*/
public void setApiKey(String apiKey)
{
mApiKey.setValue(apiKey);
} }
/** /**

View File

@@ -31,7 +31,8 @@ public enum FormField
TALKGROUP_ID("tg"), TALKGROUP_ID("tg"),
RADIO_ID("src"), RADIO_ID("src"),
FREQUENCY("freq"), FREQUENCY("freq"),
ENCODING("enc"); ENCODING("enc"),
TEST("test");
private String mHeader; private String mHeader;

View File

@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.util.Base64; import java.util.Base64;
@@ -35,7 +37,7 @@ import java.util.Base64;
public abstract class IcecastConfiguration extends BroadcastConfiguration public abstract class IcecastConfiguration extends BroadcastConfiguration
{ {
protected String mUserName = "source"; protected String mUserName = "source";
private String mMountPoint = "/stream"; protected StringProperty mMountPoint = new SimpleStringProperty("/stream");
private String mDescription; private String mDescription;
private String mGenre; private String mGenre;
private boolean mPublic; private boolean mPublic;
@@ -51,6 +53,7 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration
public IcecastConfiguration() public IcecastConfiguration()
{ {
this(BroadcastFormat.MP3);
} }
/** /**
@@ -99,7 +102,7 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration
@JacksonXmlProperty(isAttribute = true, localName = "mount_point") @JacksonXmlProperty(isAttribute = true, localName = "mount_point")
public String getMountPoint() public String getMountPoint()
{ {
return mMountPoint; return mMountPoint.get();
} }
/** /**
@@ -113,18 +116,18 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration
{ {
if(mountPoint.startsWith("/")) if(mountPoint.startsWith("/"))
{ {
mMountPoint = mountPoint; mMountPoint.setValue(mountPoint);
} }
else else
{ {
mMountPoint = "/" + mountPoint; mMountPoint.setValue("/" + mountPoint);
} }
} }
} }
public boolean hasMountPoint() public boolean hasMountPoint()
{ {
return mMountPoint != null; return mMountPoint.get() != null;
} }
@JacksonXmlProperty(isAttribute = true, localName = "description") @JacksonXmlProperty(isAttribute = true, localName = "description")

View File

@@ -23,6 +23,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType; import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import javafx.beans.binding.Bindings;
@JsonSubTypes.Type(value=IcecastHTTPConfiguration.class, name="icecastHTTPConfiguration") @JsonSubTypes.Type(value=IcecastHTTPConfiguration.class, name="icecastHTTPConfiguration")
public class IcecastHTTPConfiguration extends IcecastConfiguration public class IcecastHTTPConfiguration extends IcecastConfiguration
@@ -40,6 +41,9 @@ public class IcecastHTTPConfiguration extends IcecastConfiguration
{ {
super(format); super(format);
setPassword("change me!"); setPassword("change me!");
mValid.bind(Bindings.and(Bindings.and(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)),
Bindings.isNotNull(mMountPoint)), Bindings.isNotNull(mPassword)));
} }
@Override @Override

View File

@@ -25,6 +25,7 @@ import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType; import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration; import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
import javafx.beans.binding.Bindings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -48,6 +49,9 @@ public class IcecastTCPConfiguration extends IcecastConfiguration
public IcecastTCPConfiguration(BroadcastFormat format) public IcecastTCPConfiguration(BroadcastFormat format)
{ {
super(format); super(format);
mValid.bind(Bindings.and(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)),
Bindings.isNotNull(mMountPoint)));
} }
@Override @Override

View File

@@ -22,6 +22,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType; import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import javafx.beans.binding.Bindings;
public class ShoutcastV1Configuration extends BroadcastConfiguration public class ShoutcastV1Configuration extends BroadcastConfiguration
{ {
@@ -31,14 +32,16 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration
private int mChannels = 1; private int mChannels = 1;
private int mBitRate = 16; private int mBitRate = 16;
//No-arg JAXB constructor
public ShoutcastV1Configuration() public ShoutcastV1Configuration()
{ {
//No-arg JAXB constructor this(BroadcastFormat.MP3);
} }
public ShoutcastV1Configuration(BroadcastFormat format) public ShoutcastV1Configuration(BroadcastFormat format)
{ {
super(format); super(format);
mValid.bind(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)));
} }
@Override @Override

View File

@@ -25,10 +25,13 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType; import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class ShoutcastV2Configuration extends BroadcastConfiguration public class ShoutcastV2Configuration extends BroadcastConfiguration
{ {
private int mStreamID; private IntegerProperty mStreamID = new SimpleIntegerProperty();
private String mUserID; private String mUserID;
private int mBitRate = 16; private int mBitRate = 16;
private String mGenre; private String mGenre;
@@ -43,6 +46,8 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
public ShoutcastV2Configuration(BroadcastFormat format) public ShoutcastV2Configuration(BroadcastFormat format)
{ {
super(format); super(format);
mValid.bind(Bindings.and(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)),
Bindings.greaterThan(mStreamID, -1)));
} }
@Override @Override
@@ -82,7 +87,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
@JacksonXmlProperty(isAttribute = true, localName = "stream_id") @JacksonXmlProperty(isAttribute = true, localName = "stream_id")
public int getStreamID() public int getStreamID()
{ {
return mStreamID; return mStreamID.get();
} }
/** /**
@@ -92,7 +97,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
*/ */
public void setStreamID(int streamID) public void setStreamID(int streamID)
{ {
mStreamID = streamID; mStreamID.set(streamID);
} }
/** /**

View File

@@ -91,8 +91,6 @@ public abstract class AbstractStreamEditor<T extends BroadcastConfiguration> ext
@Override @Override
public void save() public void save()
{ {
super.save();
BroadcastConfiguration configuration = getItem(); BroadcastConfiguration configuration = getItem();
if(configuration != null) if(configuration != null)
@@ -104,7 +102,7 @@ public abstract class AbstractStreamEditor<T extends BroadcastConfiguration> ext
configuration.setMaximumRecordingAge(getMaxAgeTextField().get() * 1000); //Convert seconds to millis configuration.setMaximumRecordingAge(getMaxAgeTextField().get() * 1000); //Convert seconds to millis
} }
modifiedProperty().set(false); super.save();
} }
protected abstract GridPane getEditorPane(); protected abstract GridPane getEditorPane();

View File

@@ -30,6 +30,7 @@ import javafx.scene.control.Alert;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonType;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -43,6 +44,8 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
private IntegerTextField mSystemIdTextField; private IntegerTextField mSystemIdTextField;
private Button mTestButton; private Button mTestButton;
private IntegerTextField mMaxAgeTextField; private IntegerTextField mMaxAgeTextField;
private TextField mApiKeyTextField;
private TextField mHostTextField;
private GridPane mEditorPane; private GridPane mEditorPane;
/** /**
@@ -60,14 +63,23 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
super.setItem(item); super.setItem(item);
getSystemIdTextField().setDisable(item == null); getSystemIdTextField().setDisable(item == null);
getApiKeyTextField().setDisable(item == null);
getHostTextField().setDisable(item == null);
getMaxAgeTextField().setDisable(item == null);
if(item != null) if(item != null)
{ {
getSystemIdTextField().set(item.getSystemID()); getSystemIdTextField().set(item.getSystemID());
getApiKeyTextField().setText(item.getApiKey());
getHostTextField().setText(item.getHost());
getMaxAgeTextField().set((int)(item.getMaximumRecordingAge() / 1000));
} }
else else
{ {
getSystemIdTextField().set(0); getSystemIdTextField().set(0);
getApiKeyTextField().setText(null);
getHostTextField().setText(null);
getMaxAgeTextField().set(0);
} }
modifiedProperty().set(false); modifiedProperty().set(false);
@@ -85,6 +97,9 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
{ {
int systemID = getSystemIdTextField().get() != null ? getSystemIdTextField().get() : 0; int systemID = getSystemIdTextField().get() != null ? getSystemIdTextField().get() : 0;
getItem().setSystemID(systemID); getItem().setSystemID(systemID);
getItem().setHost(getHostTextField().getText());
getItem().setApiKey(getApiKeyTextField().getText());
getItem().setMaximumRecordingAge(getMaxAgeTextField().get() * 1000);
} }
super.save(); super.save();
@@ -130,6 +145,14 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
GridPane.setConstraints(getNameTextField(), 1, row); GridPane.setConstraints(getNameTextField(), 1, row);
mEditorPane.getChildren().add(getNameTextField()); mEditorPane.getChildren().add(getNameTextField());
Label apiKeyLabel = new Label("API Key");
GridPane.setHalignment(apiKeyLabel, HPos.RIGHT);
GridPane.setConstraints(apiKeyLabel, 0, ++row);
mEditorPane.getChildren().add(apiKeyLabel);
GridPane.setConstraints(getApiKeyTextField(), 1, row);
mEditorPane.getChildren().add(getApiKeyTextField());
Label systemIdLabel = new Label("System ID"); Label systemIdLabel = new Label("System ID");
GridPane.setHalignment(systemIdLabel, HPos.RIGHT); GridPane.setHalignment(systemIdLabel, HPos.RIGHT);
GridPane.setConstraints(systemIdLabel, 0, ++row); GridPane.setConstraints(systemIdLabel, 0, ++row);
@@ -138,6 +161,14 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
GridPane.setConstraints(getSystemIdTextField(), 1, row); GridPane.setConstraints(getSystemIdTextField(), 1, row);
mEditorPane.getChildren().add(getSystemIdTextField()); mEditorPane.getChildren().add(getSystemIdTextField());
Label hostLabel = new Label("Broadcastify URL");
GridPane.setHalignment(hostLabel, HPos.RIGHT);
GridPane.setConstraints(hostLabel, 0, ++row);
mEditorPane.getChildren().add(hostLabel);
GridPane.setConstraints(getHostTextField(), 1, row);
mEditorPane.getChildren().add(getHostTextField());
Label maxAgeLabel = new Label("Max Recording Age (seconds)"); Label maxAgeLabel = new Label("Max Recording Age (seconds)");
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT); GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
GridPane.setConstraints(maxAgeLabel, 0, ++row); GridPane.setConstraints(maxAgeLabel, 0, ++row);
@@ -153,7 +184,7 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
return mEditorPane; return mEditorPane;
} }
protected IntegerTextField getMaxAgeTextField() private IntegerTextField getMaxAgeTextField()
{ {
if(mMaxAgeTextField == null) if(mMaxAgeTextField == null)
{ {
@@ -165,6 +196,30 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
return mMaxAgeTextField; return mMaxAgeTextField;
} }
private TextField getHostTextField()
{
if(mHostTextField == null)
{
mHostTextField = new TextField();
mHostTextField.setDisable(true);
mHostTextField.textProperty().addListener(mEditorModificationListener);
}
return mHostTextField;
}
private TextField getApiKeyTextField()
{
if(mApiKeyTextField == null)
{
mApiKeyTextField = new TextField();
mApiKeyTextField.setDisable(true);
mApiKeyTextField.textProperty().addListener(mEditorModificationListener);
}
return mApiKeyTextField;
}
private IntegerTextField getSystemIdTextField() private IntegerTextField getSystemIdTextField()
{ {
if(mSystemIdTextField == null) if(mSystemIdTextField == null)
@@ -184,6 +239,19 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
mTestButton = new Button("Test Connection"); mTestButton = new Button("Test Connection");
mTestButton.setOnAction(event -> { mTestButton.setOnAction(event -> {
int systemID = getSystemIdTextField().get(); int systemID = getSystemIdTextField().get();
String apiKey = getApiKeyTextField().getText();
String host = getHostTextField().getText();
if(apiKey == null || apiKey.isEmpty())
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Please enter an API Key",
ButtonType.OK);
alert.setTitle("Test Connection");
alert.setHeaderText("A valid API Key is required");
alert.initOwner(getTestButton().getScene().getWindow());
alert.show();
return;
}
if(systemID < 1) if(systemID < 1)
{ {
@@ -196,15 +264,29 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
return; return;
} }
if(host == null || host.isEmpty())
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Please enter a Broadcastify URL",
ButtonType.OK);
alert.setTitle("Test Connection");
alert.setHeaderText("A valid URL for Broadcastify is required");
alert.initOwner(getTestButton().getScene().getWindow());
alert.show();
return;
}
BroadcastifyCallConfiguration configToTest = new BroadcastifyCallConfiguration(); BroadcastifyCallConfiguration configToTest = new BroadcastifyCallConfiguration();
configToTest.setSystemID(systemID); configToTest.setSystemID(systemID);
configToTest.setApiKey(apiKey);
configToTest.setHost(host);
String result = BroadcastifyCallBroadcaster.testConnection(configToTest); String result = BroadcastifyCallBroadcaster.testConnection(configToTest);
if(result == null) if(result != null && result.toLowerCase().startsWith("ok"))
{ {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Test successful.", Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Test successful.",
ButtonType.OK); ButtonType.OK);
alert.setTitle("Test Connection"); alert.setTitle("Test Result");
alert.setHeaderText("Success!"); alert.setHeaderText("Success!");
alert.initOwner(getTestButton().getScene().getWindow()); alert.initOwner(getTestButton().getScene().getWindow());
alert.show(); alert.show();
@@ -212,9 +294,19 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor<Broadcastify
} }
else else
{ {
Alert alert = new Alert(Alert.AlertType.ERROR, "Error: " + result, String message = null;
ButtonType.OK);
alert.setTitle("Test Connection"); if(result != null && result.toLowerCase().startsWith("1 invalid-api-key"))
{
message = "Invalid API Key";
}
else if(result != null && result.toLowerCase().startsWith("1 api-key-access-denied"))
{
message = "Invalid System ID";
}
Alert alert = new Alert(Alert.AlertType.ERROR, "Error: " + message, ButtonType.OK);
alert.setTitle("Test Result");
alert.setHeaderText("Test Failed."); alert.setHeaderText("Test Failed.");
alert.initOwner(getTestButton().getScene().getWindow()); alert.initOwner(getTestButton().getScene().getWindow());
alert.show(); alert.show();

View File

@@ -51,8 +51,6 @@ public abstract class IcecastStreamEditor extends AbstractStreamEditor<IcecastCo
@Override @Override
public void setItem(IcecastConfiguration item) public void setItem(IcecastConfiguration item)
{ {
super.setItem(item);
getMountPointTextField().setDisable(item == null); getMountPointTextField().setDisable(item == null);
getUserNameTextField().setDisable(item == null); getUserNameTextField().setDisable(item == null);
getDescriptionTextField().setDisable(item == null); getDescriptionTextField().setDisable(item == null);
@@ -76,7 +74,7 @@ public abstract class IcecastStreamEditor extends AbstractStreamEditor<IcecastCo
getURLTextField().setText(null); getURLTextField().setText(null);
} }
modifiedProperty().set(false); super.setItem(item);
} }
@Override @Override

View File

@@ -476,9 +476,6 @@ public class StreamingEditor extends SplitPane
setText(type.toString()); setText(type.toString());
mBroadcastServerType = type; mBroadcastServerType = type;
//Temporary disable until we receive a production API key for broadcastify calls api
setDisable(mBroadcastServerType == BroadcastServerType.BROADCASTIFY_CALL);
setOnAction(event -> { setOnAction(event -> {
BroadcastConfiguration config = BroadcastFactory.getConfiguration(mBroadcastServerType, BroadcastFormat.MP3); BroadcastConfiguration config = BroadcastFactory.getConfiguration(mBroadcastServerType, BroadcastFormat.MP3);
ConfiguredBroadcast configuredBroadcast = mPlaylistManager.getBroadcastModel() ConfiguredBroadcast configuredBroadcast = mPlaylistManager.getBroadcastModel()