mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 01:57:39 -05:00
Resolves #372 - Icecast reconnect following network outage. Updates
the lost connection timeout from default of 60 seconds down to 5 seconds so that the connection state updates more quickly. Removed complex error handling and replaced with a simpler approach to catch an error and disconnect and then attempt a reconnect. Added new 'Network Unavailable' error state that detects when the network connection is unavaialable. Updated the icecast metadata updater to close the TCP session after successfully writing metadata to the socket.
This commit is contained in:
@@ -245,7 +245,11 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
|||||||
{
|
{
|
||||||
if(mBroadcastState != state)
|
if(mBroadcastState != state)
|
||||||
{
|
{
|
||||||
mLog.info("[" + getStreamName() + "] status: " + state);
|
if(state == BroadcastState.CONNECTED || state == BroadcastState.DISCONNECTED)
|
||||||
|
{
|
||||||
|
mLog.info("[" + getStreamName() + "] status: " + state);
|
||||||
|
}
|
||||||
|
|
||||||
mBroadcastState = state;
|
mBroadcastState = state;
|
||||||
|
|
||||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ public enum BroadcastState
|
|||||||
*/
|
*/
|
||||||
MOUNT_POINT_IN_USE("Mount Point In Use", false),
|
MOUNT_POINT_IN_USE("Mount Point In Use", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network is unavailable or the server address cannot be resolved
|
||||||
|
*/
|
||||||
|
NETWORK_UNAVAILABLE("Network Unavailable", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server is not known or reachable
|
* Server is not known or reachable
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,9 +20,14 @@ package io.github.dsheirer.audio.broadcast;
|
|||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
import java.awt.*;
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
|
||||||
public class BroadcastStatusPanel extends JPanel
|
public class BroadcastStatusPanel extends JPanel
|
||||||
{
|
{
|
||||||
@@ -96,7 +101,8 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
setBackground(table.getBackground());
|
setBackground(table.getBackground());
|
||||||
setForeground(Color.LIGHT_GRAY);
|
setForeground(Color.LIGHT_GRAY);
|
||||||
}
|
}
|
||||||
else if(state == BroadcastState.INVALID_SETTINGS)
|
else if(state == BroadcastState.INVALID_SETTINGS ||
|
||||||
|
state == BroadcastState.NETWORK_UNAVAILABLE)
|
||||||
{
|
{
|
||||||
setBackground(Color.YELLOW);
|
setBackground(Color.YELLOW);
|
||||||
setForeground(table.getForeground());
|
setForeground(table.getForeground());
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
|||||||
import io.github.dsheirer.channel.metadata.Metadata;
|
import io.github.dsheirer.channel.metadata.Metadata;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import org.apache.mina.core.RuntimeIoException;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||||
import org.apache.mina.core.session.IoSession;
|
import org.apache.mina.core.session.IoSession;
|
||||||
@@ -35,7 +36,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.channels.UnresolvedAddressException;
|
import java.nio.channels.UnresolvedAddressException;
|
||||||
@@ -117,70 +117,64 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
|||||||
{
|
{
|
||||||
mMetadataQueue.offer(getSong(metadata));
|
mMetadataQueue.offer(getSong(metadata));
|
||||||
|
|
||||||
if(mUpdating.compareAndSet(false, true))
|
if(!mUpdating.get() && !mMetadataQueue.isEmpty())
|
||||||
{
|
{
|
||||||
String song = mMetadataQueue.poll();
|
Runnable runnable = new Runnable()
|
||||||
|
|
||||||
while(song != null)
|
|
||||||
{
|
{
|
||||||
HttpRequest updateRequest = createUpdateRequest(song);
|
@Override
|
||||||
|
public void run()
|
||||||
if(updateRequest != null)
|
|
||||||
{
|
{
|
||||||
ThreadPool.SCHEDULED.schedule(new Runnable()
|
if(!mMetadataQueue.isEmpty() && mUpdating.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
@Override
|
try
|
||||||
public void run()
|
|
||||||
{
|
{
|
||||||
try
|
ConnectFuture connectFuture = getSocketConnector()
|
||||||
{
|
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
|
||||||
ConnectFuture connectFuture = getSocketConnector()
|
mIcecastConfiguration.getPort()));
|
||||||
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
|
connectFuture.awaitUninterruptibly();
|
||||||
mIcecastConfiguration.getPort()));
|
IoSession session = connectFuture.getSession();
|
||||||
connectFuture.awaitUninterruptibly();
|
|
||||||
IoSession session = connectFuture.getSession();
|
|
||||||
|
|
||||||
if(session != null)
|
if(session != null)
|
||||||
{
|
|
||||||
session.write(updateRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(UnresolvedAddressException uae)
|
|
||||||
{
|
{
|
||||||
//Do nothing - the server is temporarily unavailable
|
String song = mMetadataQueue.poll();
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
Throwable throwableCause = e.getCause();
|
|
||||||
|
|
||||||
if(throwableCause instanceof ConnectException)
|
while(song != null)
|
||||||
{
|
{
|
||||||
//Do nothing, the server is unavailable
|
HttpRequest updateRequest = createUpdateRequest(song);
|
||||||
}
|
|
||||||
else if(throwableCause instanceof UnresolvedAddressException)
|
if(updateRequest != null)
|
||||||
{
|
|
||||||
//Do nothing - the server is temporarily unavailable
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(!mStackTraceLoggingSuppressed)
|
|
||||||
{
|
{
|
||||||
mLog.error("Error sending metadata update. Future errors will " +
|
session.write(updateRequest);
|
||||||
"be suppressed", e);
|
|
||||||
|
|
||||||
mStackTraceLoggingSuppressed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Fetch next metadata update to send
|
||||||
|
song = mMetadataQueue.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.closeNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0l, TimeUnit.SECONDS);
|
catch(UnresolvedAddressException | RuntimeIoException ure)
|
||||||
|
{
|
||||||
|
//Do nothing - the server is temporarily unavailable
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
Throwable throwableCause = e.getCause();
|
||||||
|
|
||||||
|
if(!mStackTraceLoggingSuppressed)
|
||||||
|
{
|
||||||
|
mLog.error("Error sending metadata update. Future errors will be suppressed", e);
|
||||||
|
mStackTraceLoggingSuppressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mUpdating.set(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//Fetch next metadata update to send
|
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||||
song = mMetadataQueue.poll();
|
|
||||||
}
|
|
||||||
|
|
||||||
mUpdating.set(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,15 +27,14 @@ import org.apache.mina.core.RuntimeIoException;
|
|||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||||
import org.apache.mina.core.session.IoSession;
|
import org.apache.mina.core.session.IoSession;
|
||||||
import org.apache.mina.core.write.WriteTimeoutException;
|
|
||||||
import org.apache.mina.filter.codec.ProtocolCodecFilter;
|
import org.apache.mina.filter.codec.ProtocolCodecFilter;
|
||||||
import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
import java.nio.channels.UnresolvedAddressException;
|
import java.nio.channels.UnresolvedAddressException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@@ -46,7 +45,8 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
private final static String TERMINATOR = "\r\n";
|
private final static String TERMINATOR = "\r\n";
|
||||||
private final static String SEPARATOR = ":";
|
private final static String SEPARATOR = ":";
|
||||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
|
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
|
||||||
private static final long CONNECTION_ATTEMPT_TIMEOUT = 5000; //5 seconds
|
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; //5 seconds
|
||||||
|
private static final int WRITE_TIMEOUT_SECONDS = 5;
|
||||||
|
|
||||||
private NioSocketConnector mSocketConnector;
|
private NioSocketConnector mSocketConnector;
|
||||||
private IoSession mStreamingSession = null;
|
private IoSession mStreamingSession = null;
|
||||||
@@ -101,7 +101,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
if(mSocketConnector == null)
|
if(mSocketConnector == null)
|
||||||
{
|
{
|
||||||
mSocketConnector = new NioSocketConnector();
|
mSocketConnector = new NioSocketConnector();
|
||||||
mSocketConnector.setConnectTimeoutCheckInterval(CONNECTION_ATTEMPT_TIMEOUT);
|
mSocketConnector.getSessionConfig().setWriteTimeout(WRITE_TIMEOUT_SECONDS);
|
||||||
|
|
||||||
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
|
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
|
||||||
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
|
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
|
||||||
@@ -127,45 +127,41 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
|
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
|
||||||
getBroadcastConfiguration().getPort()));
|
getBroadcastConfiguration().getPort()));
|
||||||
|
|
||||||
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT, TimeUnit.MILLISECONDS);
|
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
mStreamingSession = future.getSession();
|
if(connected)
|
||||||
|
{
|
||||||
|
mStreamingSession = future.getSession();
|
||||||
|
mConnecting.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(RuntimeIoException rioe)
|
||||||
|
{
|
||||||
|
if(rioe.getCause() instanceof SocketException)
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
|
||||||
|
mConnecting.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(UnresolvedAddressException uae)
|
catch(UnresolvedAddressException uae)
|
||||||
{
|
{
|
||||||
//What do you do with this? Nothing.
|
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
|
||||||
|
mConnecting.set(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
catch(InterruptedException ie)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
//We couldn't connect within the timeout period -- disconnect so that we can try again later
|
mLog.error("Error", e);
|
||||||
disconnect();
|
//Disregard ... we'll disconnect and try again
|
||||||
}
|
|
||||||
catch(RuntimeIoException rie)
|
|
||||||
{
|
|
||||||
Throwable throwableCause = rie.getCause();
|
|
||||||
|
|
||||||
if(throwableCause instanceof ConnectException)
|
|
||||||
{
|
|
||||||
setBroadcastState(BroadcastState.NO_SERVER);
|
|
||||||
}
|
|
||||||
else if(throwableCause != null)
|
|
||||||
{
|
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
|
||||||
mLog.debug("[" + getStreamName() + "] failed to connect", rie);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
|
||||||
mLog.debug("[" + getStreamName() + "] failed to connect - no exception is available");
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect();
|
|
||||||
}
|
}
|
||||||
catch(Throwable t)
|
catch(Throwable t)
|
||||||
{
|
{
|
||||||
disconnect();
|
mLog.error("Throwable error caught", t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disconnect();
|
||||||
mConnecting.set(false);
|
mConnecting.set(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -188,6 +184,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
mLastConnectionAttempt = System.currentTimeMillis();
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,49 +260,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(IoSession session, Throwable cause) throws Exception
|
public void exceptionCaught(IoSession session, Throwable cause) throws Exception
|
||||||
{
|
{
|
||||||
if(cause instanceof IOException)
|
if(!(cause instanceof IOException))
|
||||||
{
|
|
||||||
IOException ioe = (IOException)cause;
|
|
||||||
|
|
||||||
if(ioe.getMessage() != null)
|
|
||||||
{
|
|
||||||
String reason = ioe.getMessage();
|
|
||||||
|
|
||||||
if(connected())
|
|
||||||
{
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
else if(reason.startsWith("Connection reset"))
|
|
||||||
{
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
else if(reason.startsWith("Operation timed out"))
|
|
||||||
{
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(ioe instanceof WriteTimeoutException)
|
|
||||||
{
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
|
||||||
disconnect();
|
|
||||||
mLog.error("[" + getStreamName() + "] Unspecified IO error - streaming halted.", cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
|
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
|
||||||
disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user