Skip to content

Commit 056bccb

Browse files
authored
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
1 parent e00cd90 commit 056bccb

File tree

7 files changed

+111
-27
lines changed

7 files changed

+111
-27
lines changed

src/main/java/io/github/dsheirer/audio/broadcast/AbstractAudioBroadcaster.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
3434
private Listener<BroadcastEvent> mBroadcastEventListener;
3535
private T mBroadcastConfiguration;
3636
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
37+
protected ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
3738
protected int mStreamedAudioCount = 0;
3839
protected int mErrorAudioCount = 0;
3940
protected int mAgedOffAudioCount = 0;
@@ -55,6 +56,14 @@ public ObjectProperty<BroadcastState> broadcastStateProperty()
5556
return mBroadcastState;
5657
}
5758

59+
/**
60+
* Observable last bad broadcast state property
61+
*/
62+
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
63+
{
64+
return mLastBadBroadcastState;
65+
}
66+
5867
/**
5968
* Current state of the broadcastAudio connection
6069
*/
@@ -68,10 +77,26 @@ public BroadcastState getBroadcastState()
6877
*/
6978
public void setBroadcastState(BroadcastState broadcastState)
7079
{
80+
if(broadcastState == BroadcastState.CONNECTED)
81+
{
82+
mLastBadBroadcastState.setValue(null);
83+
}
84+
else if(broadcastState.isErrorState() || broadcastState.isWarningState())
85+
{
86+
mLastBadBroadcastState.setValue(broadcastState);
87+
}
7188
mBroadcastState.setValue(broadcastState);
7289
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
7390
}
7491

92+
/**
93+
* Last bad state of the broadcastAudio connection
94+
*/
95+
public BroadcastState getLastBadBroadcastState()
96+
{
97+
return mLastBadBroadcastState.get();
98+
}
99+
75100
/**
76101
* Starts the broadcaster
77102
*/

src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingBroadcaster.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,17 @@ public void setBroadcastState(BroadcastState state)
211211

212212
super.setBroadcastState(state);
213213

214-
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
214+
if(mBroadcastState.get() != null)
215215
{
216-
stop();
216+
if(mBroadcastState.get().isErrorState())
217+
{
218+
mLog.error("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
219+
stop();
220+
}
221+
else if(mBroadcastState.get().isWarningState())
222+
{
223+
mLog.warn("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
224+
}
217225
}
218226

219227
if(!connected())

src/main/java/io/github/dsheirer/audio/broadcast/BroadcastState.java

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,90 +23,92 @@ public enum BroadcastState
2323
/**
2424
* General error in configuration that causes the remote server to reject the connection
2525
*/
26-
CONFIGURATION_ERROR("Configuration Error", true),
26+
CONFIGURATION_ERROR("Configuration Error", true, false),
2727

2828
/**
2929
* Connected to the broadcastAudio server and capable of streaming
3030
*/
31-
CONNECTED("Connected", false),
31+
CONNECTED("Connected", false, false),
3232

3333
/**
3434
* Connection interrupted, attempting to reconnect.
3535
*/
36-
CONNECTING("Connecting", false),
36+
CONNECTING("Connecting", false, false),
3737

3838
/**
3939
* Indicates the configuration is disabled
4040
*/
41-
DISABLED("Disabled", true),
41+
DISABLED("Disabled", true, false),
4242

4343
/**
4444
* Disconnected from the broadcastAudio server
4545
*/
46-
DISCONNECTED("Disconnected", false),
46+
DISCONNECTED("Disconnected", false, false),
4747

4848
/**
4949
* Invalid credentials - user name or password
5050
*/
51-
INVALID_CREDENTIALS("Invalid User Name/Password", true),
51+
INVALID_CREDENTIALS("Invalid User Name/Password", true, false),
5252

5353
/**
5454
* Invalid mount point
5555
*/
56-
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true),
56+
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true, false),
5757

5858
/**
5959
* Invalid configuration settings
6060
*/
61-
INVALID_SETTINGS("Invalid Settings", true),
61+
INVALID_SETTINGS("Invalid Settings", true, false),
6262

6363
/**
6464
* Remote server max sources has been exceeded
6565
*/
66-
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", true),
66+
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", false, true),
6767

6868
/**
6969
* Specified mount point is already in use
7070
*/
71-
MOUNT_POINT_IN_USE("Mount Point In Use", false),
71+
MOUNT_POINT_IN_USE("Mount Point In Use", false, true),
7272

7373
/**
7474
* Network is unavailable or the server address cannot be resolved
7575
*/
76-
NETWORK_UNAVAILABLE("Network Unavailable", false),
76+
NETWORK_UNAVAILABLE("Network Unavailable", false, false),
7777

7878
/**
7979
* Server is not known or reachable
8080
*/
81-
NO_SERVER("No Server", false),
81+
NO_SERVER("No Server", false, false),
8282

8383
/**
8484
* Initial state with no connection attempted.
8585
*/
86-
READY("Ready", false),
86+
READY("Ready", false, false),
8787

8888
/**
8989
* Error while broadcasting stream data. Temporary error state to allow connection to be reset.
9090
*/
91-
TEMPORARY_BROADCAST_ERROR("Temporary Broadcast Error", false),
91+
TEMPORARY_BROADCAST_ERROR("Temporary Broadcast Error", false, false),
9292

9393
/**
9494
* Unsupported audio format
9595
*/
96-
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true),
96+
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true, false),
9797

9898
/**
9999
* Unspecified error
100100
*/
101-
ERROR("Error", true);
101+
ERROR("Error", true, false);
102102

103103
private String mLabel;
104104
private boolean mErrorState;
105+
private boolean mWarningState;
105106

106-
BroadcastState(String label, boolean errorState)
107+
BroadcastState(String label, boolean errorState, boolean warningState)
107108
{
108109
mLabel = label;
109110
mErrorState = errorState;
111+
mWarningState = warningState;
110112
}
111113

112114
public String toString()
@@ -118,4 +120,9 @@ public boolean isErrorState()
118120
{
119121
return mErrorState;
120122
}
123+
124+
public boolean isWarningState()
125+
{
126+
return mWarningState;
127+
}
121128
}

src/main/java/io/github/dsheirer/audio/broadcast/ConfiguredBroadcast.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class ConfiguredBroadcast
3737
private BroadcastConfiguration mBroadcastConfiguration;
3838
private AbstractAudioBroadcaster mAudioBroadcaster;
3939
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>();
40+
private ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
4041

4142
/**
4243
* Constructs an instance
@@ -89,18 +90,28 @@ public ObjectProperty<BroadcastState> broadcastStateProperty()
8990
return mBroadcastState;
9091
}
9192

93+
/**
94+
* Last bad broadcast state of the configured audio broadcaster (optional)
95+
*/
96+
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
97+
{
98+
return mLastBadBroadcastState;
99+
}
100+
92101
/**
93102
* Sets the audio broadcaster
94103
* @param audioBroadcaster to use for this configuration
95104
*/
96105
public void setAudioBroadcaster(AbstractAudioBroadcaster audioBroadcaster)
97106
{
98107
mBroadcastState.unbind();
108+
mLastBadBroadcastState.unbind();
99109
mAudioBroadcaster = audioBroadcaster;
100110

101111
if(audioBroadcaster != null)
102112
{
103113
mBroadcastState.bind(mAudioBroadcaster.broadcastStateProperty());
114+
mLastBadBroadcastState.bind(mAudioBroadcaster.lastBadBroadcastStateProperty());
104115
}
105116
else
106117
{
@@ -120,6 +131,7 @@ private void updateBroadcastState()
120131
{
121132
mBroadcastState.setValue(BroadcastState.CONFIGURATION_ERROR);
122133
}
134+
mLastBadBroadcastState.setValue(null);
123135
}
124136
}
125137

src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,15 +341,29 @@ public void exceptionCaught(IoSession session, Throwable throwable) throws Excep
341341
}
342342
else
343343
{
344-
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
344+
mLog.error("String [" + getStreamName() + "] - HTTP 403 protocol decoder error - message:\n\n" + message);
345+
setBroadcastState(BroadcastState.DISCONNECTED);
346+
disconnect();
347+
}
348+
break;
349+
case 401: //Unauthorized
350+
if(message.toString().contains("Authentication Required"))
351+
{
352+
mLog.error("Stream [" + getStreamName() + "] - unable to connect - invalid credentials");
353+
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
354+
disconnect();
355+
}
356+
else
357+
{
358+
mLog.error("String [" + getStreamName() + "] - HTTP 401 protocol decoder error - message:\n\n" + message);
345359
setBroadcastState(BroadcastState.DISCONNECTED);
346360
disconnect();
347361
}
348362
break;
349363
default:
350-
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
351-
setBroadcastState(BroadcastState.DISCONNECTED);
352-
disconnect();
364+
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
365+
setBroadcastState(BroadcastState.DISCONNECTED);
366+
disconnect();
353367
}
354368
}
355369
else
@@ -401,9 +415,16 @@ public void messageReceived(IoSession session, Object object) throws Exception
401415
disconnect();
402416
break;
403417
default:
404-
setBroadcastState(BroadcastState.ERROR);
405-
disconnect();
418+
/**
419+
* Only allow a generic error to update state if we've not already experienced a more
420+
* specific error. Otherwise, trailing messages will clear the more meaningful error state.
421+
*/
422+
if(!getBroadcastState().isErrorState())
423+
{
424+
setBroadcastState(BroadcastState.ERROR);
425+
}
406426
mLog.debug("Unspecified error: " + response.toString() + " Class:" + object.getClass());
427+
disconnect();
407428
break;
408429
}
409430
}

src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,15 @@ else if(message.contains("HTTP/1.1 501"))
347347
else
348348
{
349349
mLog.error("Unrecognized server response:" + message);
350-
setBroadcastState(BroadcastState.ERROR);
350+
351+
/**
352+
* Only allow a generic error to update state if we've not already experienced a more
353+
* specific error. Otherwise, trailing messages will clear the more meaningful error state.
354+
*/
355+
if(!getBroadcastState().isErrorState())
356+
{
357+
setBroadcastState(BroadcastState.ERROR);
358+
}
351359
}
352360
}
353361
}

src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,10 @@ protected void updateItem(Boolean item, boolean empty)
440440
TableColumn stateColumn = new TableColumn("Stream Status");
441441
stateColumn.setCellValueFactory(new PropertyValueFactory<>("broadcastState"));
442442

443-
mConfiguredBroadcastTableView.getColumns().addAll(enabledColumn, nameColumn, typeColumn, stateColumn);
443+
TableColumn errorColumn = new TableColumn("Last Error");
444+
errorColumn.setCellValueFactory(new PropertyValueFactory<>("lastBadBroadcastState"));
445+
446+
mConfiguredBroadcastTableView.getColumns().addAll(enabledColumn, nameColumn, typeColumn, stateColumn, errorColumn);
444447

445448
mConfiguredBroadcastTableView.getSelectionModel().selectedItemProperty()
446449
.addListener((observable, oldValue, newValue) -> setBroadcastConfiguration(newValue));

0 commit comments

Comments
 (0)