Merge pull request #385 from DSheirer/372-icecast-streaming-reconnect

372 Icecast Streaming Reconnect and Metadata Updates
This commit is contained in:
Denny Sheirer
2018-08-05 07:48:28 -04:00
committed by GitHub
5 changed files with 94 additions and 125 deletions

View File

@@ -245,7 +245,11 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
{
if(mBroadcastState != state)
{
mLog.info("[" + getStreamName() + "] status: " + state);
if(state == BroadcastState.CONNECTED || state == BroadcastState.DISCONNECTED)
{
mLog.info("[" + getStreamName() + "] status: " + state);
}
mBroadcastState = state;
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));

View File

@@ -70,6 +70,11 @@ public enum BroadcastState
*/
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
*/

View File

@@ -20,9 +20,14 @@ package io.github.dsheirer.audio.broadcast;
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 java.awt.*;
import java.awt.Color;
import java.awt.Component;
public class BroadcastStatusPanel extends JPanel
{
@@ -96,7 +101,8 @@ public class BroadcastStatusPanel extends JPanel
setBackground(table.getBackground());
setForeground(Color.LIGHT_GRAY);
}
else if(state == BroadcastState.INVALID_SETTINGS)
else if(state == BroadcastState.INVALID_SETTINGS ||
state == BroadcastState.NETWORK_UNAVAILABLE)
{
setBackground(Color.YELLOW);
setForeground(table.getForeground());

View File

@@ -22,6 +22,7 @@ import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
@@ -35,7 +36,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.nio.channels.UnresolvedAddressException;
@@ -117,70 +117,64 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
{
mMetadataQueue.offer(getSong(metadata));
if(mUpdating.compareAndSet(false, true))
if(!mUpdating.get() && !mMetadataQueue.isEmpty())
{
String song = mMetadataQueue.poll();
while(song != null)
Runnable runnable = new Runnable()
{
HttpRequest updateRequest = createUpdateRequest(song);
if(updateRequest != null)
@Override
public void run()
{
ThreadPool.SCHEDULED.schedule(new Runnable()
if(!mMetadataQueue.isEmpty() && mUpdating.compareAndSet(false, true))
{
@Override
public void run()
try
{
try
{
ConnectFuture connectFuture = getSocketConnector()
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
mIcecastConfiguration.getPort()));
connectFuture.awaitUninterruptibly();
IoSession session = connectFuture.getSession();
ConnectFuture connectFuture = getSocketConnector()
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
mIcecastConfiguration.getPort()));
connectFuture.awaitUninterruptibly();
IoSession session = connectFuture.getSession();
if(session != null)
{
session.write(updateRequest);
}
}
catch(UnresolvedAddressException uae)
if(session != null)
{
//Do nothing - the server is temporarily unavailable
}
catch(Exception e)
{
Throwable throwableCause = e.getCause();
String song = mMetadataQueue.poll();
if(throwableCause instanceof ConnectException)
while(song != null)
{
//Do nothing, the server is unavailable
}
else if(throwableCause instanceof UnresolvedAddressException)
{
//Do nothing - the server is temporarily unavailable
}
else
{
if(!mStackTraceLoggingSuppressed)
HttpRequest updateRequest = createUpdateRequest(song);
if(updateRequest != null)
{
mLog.error("Error sending metadata update. Future errors will " +
"be suppressed", e);
mStackTraceLoggingSuppressed = true;
session.write(updateRequest);
}
//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
song = mMetadataQueue.poll();
}
mUpdating.set(false);
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
}
}

View File

@@ -27,15 +27,14 @@ import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
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.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.UnresolvedAddressException;
import java.util.concurrent.TimeUnit;
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 SEPARATOR = ":";
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 IoSession mStreamingSession = null;
@@ -101,7 +101,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
if(mSocketConnector == null)
{
mSocketConnector = new NioSocketConnector();
mSocketConnector.setConnectTimeoutCheckInterval(CONNECTION_ATTEMPT_TIMEOUT);
mSocketConnector.getSessionConfig().setWriteTimeout(WRITE_TIMEOUT_SECONDS);
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
@@ -127,45 +127,41 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
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)
{
//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
disconnect();
}
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();
mLog.error("Error", e);
//Disregard ... we'll disconnect and try again
}
catch(Throwable t)
{
disconnect();
mLog.error("Throwable error caught", t);
}
disconnect();
mConnecting.set(false);
}
};
@@ -188,6 +184,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
}
else
{
setBroadcastState(BroadcastState.DISCONNECTED);
mLastConnectionAttempt = System.currentTimeMillis();
}
}
@@ -263,49 +260,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception
{
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
if(!(cause instanceof IOException))
{
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
setBroadcastState(BroadcastState.ERROR);
disconnect();
}
disconnect();
}
@Override