mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 10:07:26 -05:00
Improvements to icecast failure handling (#1401)
- Add mechanism to persist failure reason while continuing to reconnect - Add 'warning' attribute for failure conditions that may be temporary, but still need to be reported - Add logging for failures - Prevent possibly temporary server-side conditions from blocking reconnect attempts - Detect icecast 2.4 auth failures - Prevent generic errors from overriding existing error state
This commit is contained in:
@@ -34,6 +34,7 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
|
|||||||
private Listener<BroadcastEvent> mBroadcastEventListener;
|
private Listener<BroadcastEvent> mBroadcastEventListener;
|
||||||
private T mBroadcastConfiguration;
|
private T mBroadcastConfiguration;
|
||||||
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
|
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
|
||||||
|
protected ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
|
||||||
protected int mStreamedAudioCount = 0;
|
protected int mStreamedAudioCount = 0;
|
||||||
protected int mErrorAudioCount = 0;
|
protected int mErrorAudioCount = 0;
|
||||||
protected int mAgedOffAudioCount = 0;
|
protected int mAgedOffAudioCount = 0;
|
||||||
@@ -55,6 +56,14 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
|
|||||||
return mBroadcastState;
|
return mBroadcastState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable last bad broadcast state property
|
||||||
|
*/
|
||||||
|
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
|
||||||
|
{
|
||||||
|
return mLastBadBroadcastState;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current state of the broadcastAudio connection
|
* Current state of the broadcastAudio connection
|
||||||
*/
|
*/
|
||||||
@@ -68,10 +77,26 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
|
|||||||
*/
|
*/
|
||||||
public void setBroadcastState(BroadcastState broadcastState)
|
public void setBroadcastState(BroadcastState broadcastState)
|
||||||
{
|
{
|
||||||
|
if(broadcastState == BroadcastState.CONNECTED)
|
||||||
|
{
|
||||||
|
mLastBadBroadcastState.setValue(null);
|
||||||
|
}
|
||||||
|
else if(broadcastState.isErrorState() || broadcastState.isWarningState())
|
||||||
|
{
|
||||||
|
mLastBadBroadcastState.setValue(broadcastState);
|
||||||
|
}
|
||||||
mBroadcastState.setValue(broadcastState);
|
mBroadcastState.setValue(broadcastState);
|
||||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last bad state of the broadcastAudio connection
|
||||||
|
*/
|
||||||
|
public BroadcastState getLastBadBroadcastState()
|
||||||
|
{
|
||||||
|
return mLastBadBroadcastState.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the broadcaster
|
* Starts the broadcaster
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -211,9 +211,17 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
|||||||
|
|
||||||
super.setBroadcastState(state);
|
super.setBroadcastState(state);
|
||||||
|
|
||||||
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
|
if(mBroadcastState.get() != null)
|
||||||
{
|
{
|
||||||
stop();
|
if(mBroadcastState.get().isErrorState())
|
||||||
|
{
|
||||||
|
mLog.error("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
else if(mBroadcastState.get().isWarningState())
|
||||||
|
{
|
||||||
|
mLog.warn("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!connected())
|
if(!connected())
|
||||||
|
|||||||
@@ -23,90 +23,92 @@ public enum BroadcastState
|
|||||||
/**
|
/**
|
||||||
* General error in configuration that causes the remote server to reject the connection
|
* General error in configuration that causes the remote server to reject the connection
|
||||||
*/
|
*/
|
||||||
CONFIGURATION_ERROR("Configuration Error", true),
|
CONFIGURATION_ERROR("Configuration Error", true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connected to the broadcastAudio server and capable of streaming
|
* Connected to the broadcastAudio server and capable of streaming
|
||||||
*/
|
*/
|
||||||
CONNECTED("Connected", false),
|
CONNECTED("Connected", false, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection interrupted, attempting to reconnect.
|
* Connection interrupted, attempting to reconnect.
|
||||||
*/
|
*/
|
||||||
CONNECTING("Connecting", false),
|
CONNECTING("Connecting", false, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the configuration is disabled
|
* Indicates the configuration is disabled
|
||||||
*/
|
*/
|
||||||
DISABLED("Disabled", true),
|
DISABLED("Disabled", true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnected from the broadcastAudio server
|
* Disconnected from the broadcastAudio server
|
||||||
*/
|
*/
|
||||||
DISCONNECTED("Disconnected", false),
|
DISCONNECTED("Disconnected", false, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalid credentials - user name or password
|
* Invalid credentials - user name or password
|
||||||
*/
|
*/
|
||||||
INVALID_CREDENTIALS("Invalid User Name/Password", true),
|
INVALID_CREDENTIALS("Invalid User Name/Password", true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalid mount point
|
* Invalid mount point
|
||||||
*/
|
*/
|
||||||
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true),
|
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalid configuration settings
|
* Invalid configuration settings
|
||||||
*/
|
*/
|
||||||
INVALID_SETTINGS("Invalid Settings", true),
|
INVALID_SETTINGS("Invalid Settings", true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote server max sources has been exceeded
|
* Remote server max sources has been exceeded
|
||||||
*/
|
*/
|
||||||
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", true),
|
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", false, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specified mount point is already in use
|
* Specified mount point is already in use
|
||||||
*/
|
*/
|
||||||
MOUNT_POINT_IN_USE("Mount Point In Use", false),
|
MOUNT_POINT_IN_USE("Mount Point In Use", false, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network is unavailable or the server address cannot be resolved
|
* Network is unavailable or the server address cannot be resolved
|
||||||
*/
|
*/
|
||||||
NETWORK_UNAVAILABLE("Network Unavailable", false),
|
NETWORK_UNAVAILABLE("Network Unavailable", false, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server is not known or reachable
|
* Server is not known or reachable
|
||||||
*/
|
*/
|
||||||
NO_SERVER("No Server", false),
|
NO_SERVER("No Server", false, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initial state with no connection attempted.
|
* Initial state with no connection attempted.
|
||||||
*/
|
*/
|
||||||
READY("Ready", false),
|
READY("Ready", false, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error while broadcasting stream data. Temporary error state to allow connection to be reset.
|
* Error while broadcasting stream data. Temporary error state to allow connection to be reset.
|
||||||
*/
|
*/
|
||||||
TEMPORARY_BROADCAST_ERROR("Temporary Broadcast Error", false),
|
TEMPORARY_BROADCAST_ERROR("Temporary Broadcast Error", false, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsupported audio format
|
* Unsupported audio format
|
||||||
*/
|
*/
|
||||||
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true),
|
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unspecified error
|
* Unspecified error
|
||||||
*/
|
*/
|
||||||
ERROR("Error", true);
|
ERROR("Error", true, false);
|
||||||
|
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
private boolean mErrorState;
|
private boolean mErrorState;
|
||||||
|
private boolean mWarningState;
|
||||||
|
|
||||||
BroadcastState(String label, boolean errorState)
|
BroadcastState(String label, boolean errorState, boolean warningState)
|
||||||
{
|
{
|
||||||
mLabel = label;
|
mLabel = label;
|
||||||
mErrorState = errorState;
|
mErrorState = errorState;
|
||||||
|
mWarningState = warningState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
@@ -118,4 +120,9 @@ public enum BroadcastState
|
|||||||
{
|
{
|
||||||
return mErrorState;
|
return mErrorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isWarningState()
|
||||||
|
{
|
||||||
|
return mWarningState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class ConfiguredBroadcast
|
|||||||
private BroadcastConfiguration mBroadcastConfiguration;
|
private BroadcastConfiguration mBroadcastConfiguration;
|
||||||
private AbstractAudioBroadcaster mAudioBroadcaster;
|
private AbstractAudioBroadcaster mAudioBroadcaster;
|
||||||
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>();
|
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>();
|
||||||
|
private ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -89,6 +90,14 @@ public class ConfiguredBroadcast
|
|||||||
return mBroadcastState;
|
return mBroadcastState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last bad broadcast state of the configured audio broadcaster (optional)
|
||||||
|
*/
|
||||||
|
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
|
||||||
|
{
|
||||||
|
return mLastBadBroadcastState;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the audio broadcaster
|
* Sets the audio broadcaster
|
||||||
* @param audioBroadcaster to use for this configuration
|
* @param audioBroadcaster to use for this configuration
|
||||||
@@ -96,11 +105,13 @@ public class ConfiguredBroadcast
|
|||||||
public void setAudioBroadcaster(AbstractAudioBroadcaster audioBroadcaster)
|
public void setAudioBroadcaster(AbstractAudioBroadcaster audioBroadcaster)
|
||||||
{
|
{
|
||||||
mBroadcastState.unbind();
|
mBroadcastState.unbind();
|
||||||
|
mLastBadBroadcastState.unbind();
|
||||||
mAudioBroadcaster = audioBroadcaster;
|
mAudioBroadcaster = audioBroadcaster;
|
||||||
|
|
||||||
if(audioBroadcaster != null)
|
if(audioBroadcaster != null)
|
||||||
{
|
{
|
||||||
mBroadcastState.bind(mAudioBroadcaster.broadcastStateProperty());
|
mBroadcastState.bind(mAudioBroadcaster.broadcastStateProperty());
|
||||||
|
mLastBadBroadcastState.bind(mAudioBroadcaster.lastBadBroadcastStateProperty());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -120,6 +131,7 @@ public class ConfiguredBroadcast
|
|||||||
{
|
{
|
||||||
mBroadcastState.setValue(BroadcastState.CONFIGURATION_ERROR);
|
mBroadcastState.setValue(BroadcastState.CONFIGURATION_ERROR);
|
||||||
}
|
}
|
||||||
|
mLastBadBroadcastState.setValue(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -341,15 +341,29 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
mLog.error("String [" + getStreamName() + "] - HTTP 403 protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 401: //Unauthorized
|
||||||
|
if(message.toString().contains("Authentication Required"))
|
||||||
|
{
|
||||||
|
mLog.error("Stream [" + getStreamName() + "] - unable to connect - invalid credentials");
|
||||||
|
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("String [" + getStreamName() + "] - HTTP 401 protocol decoder error - message:\n\n" + message);
|
||||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -401,9 +415,16 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
disconnect();
|
disconnect();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
/**
|
||||||
disconnect();
|
* Only allow a generic error to update state if we've not already experienced a more
|
||||||
|
* specific error. Otherwise, trailing messages will clear the more meaningful error state.
|
||||||
|
*/
|
||||||
|
if(!getBroadcastState().isErrorState())
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
}
|
||||||
mLog.debug("Unspecified error: " + response.toString() + " Class:" + object.getClass());
|
mLog.debug("Unspecified error: " + response.toString() + " Class:" + object.getClass());
|
||||||
|
disconnect();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,7 +347,15 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.error("Unrecognized server response:" + message);
|
mLog.error("Unrecognized server response:" + message);
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
|
||||||
|
/**
|
||||||
|
* Only allow a generic error to update state if we've not already experienced a more
|
||||||
|
* specific error. Otherwise, trailing messages will clear the more meaningful error state.
|
||||||
|
*/
|
||||||
|
if(!getBroadcastState().isErrorState())
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -440,7 +440,10 @@ public class StreamingEditor extends SplitPane
|
|||||||
TableColumn stateColumn = new TableColumn("Stream Status");
|
TableColumn stateColumn = new TableColumn("Stream Status");
|
||||||
stateColumn.setCellValueFactory(new PropertyValueFactory<>("broadcastState"));
|
stateColumn.setCellValueFactory(new PropertyValueFactory<>("broadcastState"));
|
||||||
|
|
||||||
mConfiguredBroadcastTableView.getColumns().addAll(enabledColumn, nameColumn, typeColumn, stateColumn);
|
TableColumn errorColumn = new TableColumn("Last Error");
|
||||||
|
errorColumn.setCellValueFactory(new PropertyValueFactory<>("lastBadBroadcastState"));
|
||||||
|
|
||||||
|
mConfiguredBroadcastTableView.getColumns().addAll(enabledColumn, nameColumn, typeColumn, stateColumn, errorColumn);
|
||||||
|
|
||||||
mConfiguredBroadcastTableView.getSelectionModel().selectedItemProperty()
|
mConfiguredBroadcastTableView.getSelectionModel().selectedItemProperty()
|
||||||
.addListener((observable, oldValue, newValue) -> setBroadcastConfiguration(newValue));
|
.addListener((observable, oldValue, newValue) -> setBroadcastConfiguration(newValue));
|
||||||
|
|||||||
Reference in New Issue
Block a user