From 08ea0e0f185539b11af2cea85f8896fb3eab7a88 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sun, 3 May 2020 11:23:21 +0000 Subject: [PATCH] #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 --- .../broadcast/BroadcastConfiguration.java | 16 +- .../BroadcastifyCallBroadcaster.java | 138 ++++-------------- .../BroadcastifyCallConfiguration.java | 39 +++-- .../broadcast/broadcastify/FormField.java | 3 +- .../icecast/IcecastConfiguration.java | 13 +- .../icecast/IcecastHTTPConfiguration.java | 4 + .../icecast/IcecastTCPConfiguration.java | 4 + .../v1/ShoutcastV1Configuration.java | 5 +- .../v2/ShoutcastV2Configuration.java | 11 +- .../streaming/AbstractStreamEditor.java | 4 +- .../streaming/BroadcastifyCallEditor.java | 104 ++++++++++++- .../streaming/IcecastStreamEditor.java | 4 +- .../playlist/streaming/StreamingEditor.java | 3 - 13 files changed, 194 insertions(+), 154 deletions(-) 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 e5669926..f2ee82b8 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastConfiguration.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastConfiguration.java @@ -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.v2.ShoutcastV2Configuration; import javafx.beans.Observable; -import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.LongProperty; @@ -59,19 +58,18 @@ public abstract class BroadcastConfiguration private static int UNIQUE_ID = 0; private BroadcastFormat mBroadcastFormat = BroadcastFormat.MP3; - private StringProperty mName = new SimpleStringProperty(); - private StringProperty mHost = new SimpleStringProperty(); - private IntegerProperty mPort = new SimpleIntegerProperty(80); - private StringProperty mPassword = new SimpleStringProperty(); - private LongProperty mDelay = new SimpleLongProperty(); - private LongProperty mMaximumRecordingAge = new SimpleLongProperty(10 * 60 * 1000); //10 minutes default - private BooleanProperty mEnabled = new SimpleBooleanProperty(false); + protected StringProperty mName = new SimpleStringProperty(); + protected StringProperty mHost = new SimpleStringProperty(); + protected IntegerProperty mPort = new SimpleIntegerProperty(80); + protected StringProperty mPassword = new SimpleStringProperty(); + protected LongProperty mDelay = new SimpleLongProperty(); + protected LongProperty mMaximumRecordingAge = new SimpleLongProperty(10 * 60 * 1000); //10 minutes default + protected BooleanProperty mEnabled = new SimpleBooleanProperty(false); protected BooleanProperty mValid = new SimpleBooleanProperty(); private int mId = ++UNIQUE_ID; public BroadcastConfiguration() { - mValid.bind(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0))); } /** diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java index afeb919c..048290ea 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java @@ -45,7 +45,6 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.nio.file.Path; import java.time.Duration; import java.util.List; import java.util.Queue; @@ -66,8 +65,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster mAudioRecordingQueue = new LinkedTransferQueue<>(); private ScheduledFuture mAudioRecordingProcessorFuture; private HttpClient mHttpClient = HttpClient.newBuilder() @@ -98,14 +95,14 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster response = httpClient.send(request, responseHandler); - String responseBody = response.body(); - if(response.statusCode() == 200 && responseBody != null && responseBody.startsWith("0 ")) - { - //success! - return null; - } - else - { - return responseBody; - } + return (responseBody != null ? responseBody : "(no response)") + " Status Code:" + response.statusCode(); } catch(Exception e) { @@ -513,100 +496,33 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster 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); - } - } + mLog.error("Invalid API Key"); } - 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!"); } } diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallConfiguration.java b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallConfiguration.java index 6117cc2e..563c9019 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallConfiguration.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallConfiguration.java @@ -19,7 +19,6 @@ package io.github.dsheirer.audio.broadcast.broadcastify; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; 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.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; /** * Streaming configuration for Broadcastify Calls API. @@ -36,13 +37,11 @@ import javafx.beans.property.SimpleIntegerProperty; */ public class BroadcastifyCallConfiguration extends BroadcastConfiguration { - public static final String SDRTRUNK_DEV_API_KEY = "c33aae37-8572-11ea-bd8b-0ecc8ab9ccec"; - 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 DEVELOPMENT_ENDPOINT = "https://api.broadcastify.com/call-upload-dev"; 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 StringProperty mApiKey = new SimpleStringProperty(); /** * Constructor for faster jackson @@ -59,11 +58,16 @@ public class BroadcastifyCallConfiguration extends BroadcastConfiguration public BroadcastifyCallConfiguration(BroadcastFormat 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 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; } + /** + * API key as a property + */ + public StringProperty apiKeyProperty() + { + return mApiKey; + } + /** * API Key */ - @JsonIgnore + @JacksonXmlProperty(isAttribute = true, localName = "api_key") 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); } /** diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/FormField.java b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/FormField.java index 453b5656..eebf912c 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/FormField.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/FormField.java @@ -31,7 +31,8 @@ public enum FormField TALKGROUP_ID("tg"), RADIO_ID("src"), FREQUENCY("freq"), - ENCODING("enc"); + ENCODING("enc"), + TEST("test"); private String mHeader; diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastConfiguration.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastConfiguration.java index 682dc7ed..c7a91581 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastConfiguration.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastConfiguration.java @@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.BroadcastFormat; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import java.util.Base64; @@ -35,7 +37,7 @@ import java.util.Base64; public abstract class IcecastConfiguration extends BroadcastConfiguration { protected String mUserName = "source"; - private String mMountPoint = "/stream"; + protected StringProperty mMountPoint = new SimpleStringProperty("/stream"); private String mDescription; private String mGenre; private boolean mPublic; @@ -51,6 +53,7 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration public IcecastConfiguration() { + this(BroadcastFormat.MP3); } /** @@ -99,7 +102,7 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration @JacksonXmlProperty(isAttribute = true, localName = "mount_point") public String getMountPoint() { - return mMountPoint; + return mMountPoint.get(); } /** @@ -113,18 +116,18 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration { if(mountPoint.startsWith("/")) { - mMountPoint = mountPoint; + mMountPoint.setValue(mountPoint); } else { - mMountPoint = "/" + mountPoint; + mMountPoint.setValue("/" + mountPoint); } } } public boolean hasMountPoint() { - return mMountPoint != null; + return mMountPoint.get() != null; } @JacksonXmlProperty(isAttribute = true, localName = "description") diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPConfiguration.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPConfiguration.java index b4b1f99a..6599d96e 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPConfiguration.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPConfiguration.java @@ -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.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastServerType; +import javafx.beans.binding.Bindings; @JsonSubTypes.Type(value=IcecastHTTPConfiguration.class, name="icecastHTTPConfiguration") public class IcecastHTTPConfiguration extends IcecastConfiguration @@ -40,6 +41,9 @@ public class IcecastHTTPConfiguration extends IcecastConfiguration { super(format); 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 diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPConfiguration.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPConfiguration.java index 6a101cf3..19cd9fdc 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPConfiguration.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPConfiguration.java @@ -25,6 +25,7 @@ import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastServerType; import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration; +import javafx.beans.binding.Bindings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +49,9 @@ public class IcecastTCPConfiguration extends IcecastConfiguration public IcecastTCPConfiguration(BroadcastFormat format) { super(format); + + mValid.bind(Bindings.and(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)), + Bindings.isNotNull(mMountPoint))); } @Override diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1Configuration.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1Configuration.java index db8c0dc3..6c0cfba5 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1Configuration.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1Configuration.java @@ -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.BroadcastFormat; import io.github.dsheirer.audio.broadcast.BroadcastServerType; +import javafx.beans.binding.Bindings; public class ShoutcastV1Configuration extends BroadcastConfiguration { @@ -31,14 +32,16 @@ public class ShoutcastV1Configuration extends BroadcastConfiguration private int mChannels = 1; private int mBitRate = 16; + //No-arg JAXB constructor public ShoutcastV1Configuration() { - //No-arg JAXB constructor + this(BroadcastFormat.MP3); } public ShoutcastV1Configuration(BroadcastFormat format) { super(format); + mValid.bind(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0))); } @Override diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java index 20f6b656..5e832970 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java @@ -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.BroadcastFormat; 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 { - private int mStreamID; + private IntegerProperty mStreamID = new SimpleIntegerProperty(); private String mUserID; private int mBitRate = 16; private String mGenre; @@ -43,6 +46,8 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration public ShoutcastV2Configuration(BroadcastFormat format) { super(format); + mValid.bind(Bindings.and(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)), + Bindings.greaterThan(mStreamID, -1))); } @Override @@ -82,7 +87,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration @JacksonXmlProperty(isAttribute = true, localName = "stream_id") public int getStreamID() { - return mStreamID; + return mStreamID.get(); } /** @@ -92,7 +97,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration */ public void setStreamID(int streamID) { - mStreamID = streamID; + mStreamID.set(streamID); } /** diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractStreamEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractStreamEditor.java index 9b2c46ca..6c0f88b4 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractStreamEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractStreamEditor.java @@ -91,8 +91,6 @@ public abstract class AbstractStreamEditor ext @Override public void save() { - super.save(); - BroadcastConfiguration configuration = getItem(); if(configuration != null) @@ -104,7 +102,7 @@ public abstract class AbstractStreamEditor ext configuration.setMaximumRecordingAge(getMaxAgeTextField().get() * 1000); //Convert seconds to millis } - modifiedProperty().set(false); + super.save(); } protected abstract GridPane getEditorPane(); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyCallEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyCallEditor.java index 772499cd..1f1419e3 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyCallEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyCallEditor.java @@ -30,6 +30,7 @@ import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.Label; +import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +44,8 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor { 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) { @@ -196,15 +264,29 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor { BroadcastConfiguration config = BroadcastFactory.getConfiguration(mBroadcastServerType, BroadcastFormat.MP3); ConfiguredBroadcast configuredBroadcast = mPlaylistManager.getBroadcastModel()