mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 09:37:36 -05:00
#1380 Improves Icecast streaming error handling and preserves streaming error messages for broadcast state to display in streaming status panel. Resolves issue with streaming status panel not showing icons and stream type. (#1381)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -25,19 +25,15 @@ import io.github.dsheirer.audio.convert.MP3FrameTools;
|
|||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
||||||
{
|
{
|
||||||
@@ -255,7 +251,6 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
|||||||
public boolean canConnect()
|
public boolean canConnect()
|
||||||
{
|
{
|
||||||
BroadcastState state = getBroadcastState();
|
BroadcastState state = getBroadcastState();
|
||||||
|
|
||||||
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,6 @@ import io.github.dsheirer.properties.SystemProperties;
|
|||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -48,6 +41,12 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
|
||||||
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
||||||
{
|
{
|
||||||
@@ -58,7 +57,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
|
|
||||||
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
||||||
|
|
||||||
public static final int COLUMN_SERVER_ICON = 0;
|
public static final int COLUMN_BROADCAST_SERVER_TYPE = 0;
|
||||||
public static final int COLUMN_STREAM_NAME = 1;
|
public static final int COLUMN_STREAM_NAME = 1;
|
||||||
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
||||||
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
||||||
@@ -67,7 +66,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
|
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
|
||||||
|
|
||||||
public static final String[] COLUMN_NAMES = new String[]
|
public static final String[] COLUMN_NAMES = new String[]
|
||||||
{"Streaming", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
||||||
|
|
||||||
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
||||||
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
||||||
@@ -575,15 +574,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
{
|
{
|
||||||
switch(columnIndex)
|
switch(columnIndex)
|
||||||
{
|
{
|
||||||
case COLUMN_SERVER_ICON:
|
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||||
String iconPath = configuredBroadcast.getBroadcastConfiguration()
|
return configuredBroadcast.getBroadcastServerType();
|
||||||
.getBroadcastServerType().getIconPath();
|
|
||||||
|
|
||||||
if(iconPath != null && mIconModel != null)
|
|
||||||
{
|
|
||||||
return mIconModel.getScaledIcon(iconPath, 14);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case COLUMN_STREAM_NAME:
|
case COLUMN_STREAM_NAME:
|
||||||
return configuredBroadcast.getBroadcastConfiguration().getName();
|
return configuredBroadcast.getBroadcastConfiguration().getName();
|
||||||
case COLUMN_BROADCASTER_STATUS:
|
case COLUMN_BROADCASTER_STATUS:
|
||||||
@@ -653,8 +645,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||||
case COLUMN_BROADCASTER_ERROR_COUNT:
|
case COLUMN_BROADCASTER_ERROR_COUNT:
|
||||||
return Integer.class;
|
return Integer.class;
|
||||||
case COLUMN_SERVER_ICON:
|
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||||
return ImageIcon.class;
|
return BroadcastServerType.class;
|
||||||
case COLUMN_STREAM_NAME:
|
case COLUMN_STREAM_NAME:
|
||||||
default:
|
default:
|
||||||
return String.class;
|
return String.class;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdrtrunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
public enum BroadcastServerType
|
public enum BroadcastServerType
|
||||||
@@ -23,17 +23,17 @@ public enum BroadcastServerType
|
|||||||
/**
|
/**
|
||||||
* Broadcastify feeds (ie streaming) service
|
* Broadcastify feeds (ie streaming) service
|
||||||
*/
|
*/
|
||||||
BROADCASTIFY("Broadcastify Feed", "/images/broadcastify.png"), //Icecast Server 2.3.2
|
BROADCASTIFY("Broadcastify Feed", "images/broadcastify.png"), //Icecast Server 2.3.2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcastify calls - completed audio recording push service
|
* Broadcastify calls - completed audio recording push service
|
||||||
*/
|
*/
|
||||||
BROADCASTIFY_CALL("Broadcastify Call", "/images/broadcastify.png"),
|
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
||||||
|
|
||||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
|
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||||
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
|
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||||
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
|
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||||
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
|
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||||
UNKNOWN("Unknown", null);
|
UNKNOWN("Unknown", null);
|
||||||
|
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdrtrunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
public enum BroadcastState
|
public enum BroadcastState
|
||||||
@@ -68,7 +68,7 @@ public enum BroadcastState
|
|||||||
/**
|
/**
|
||||||
* 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", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network is unavailable or the server address cannot be resolved
|
* Network is unavailable or the server address cannot be resolved
|
||||||
@@ -103,10 +103,10 @@ public enum BroadcastState
|
|||||||
private String mLabel;
|
private String mLabel;
|
||||||
private boolean mErrorState;
|
private boolean mErrorState;
|
||||||
|
|
||||||
private BroadcastState(String label, boolean connected)
|
BroadcastState(String label, boolean errorState)
|
||||||
{
|
{
|
||||||
mLabel = label;
|
mLabel = label;
|
||||||
mErrorState = connected;
|
mErrorState = errorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,23 +14,29 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
|
import io.github.dsheirer.icon.Icon;
|
||||||
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table of broadcast streams and statuses.
|
||||||
|
*/
|
||||||
public class BroadcastStatusPanel extends JPanel
|
public class BroadcastStatusPanel extends JPanel
|
||||||
{
|
{
|
||||||
private JTable mTable;
|
private JTable mTable;
|
||||||
@@ -41,6 +46,12 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private String mPreferenceKey;
|
private String mPreferenceKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param broadcastModel to access the streams
|
||||||
|
* @param userPreferences for configuring the panel
|
||||||
|
* @param preferenceKey to store column preferences for this panel.
|
||||||
|
*/
|
||||||
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
|
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
|
||||||
{
|
{
|
||||||
mBroadcastModel = broadcastModel;
|
mBroadcastModel = broadcastModel;
|
||||||
@@ -65,6 +76,7 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
|
||||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
||||||
|
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
|
||||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
||||||
|
|
||||||
mScrollPane = new JScrollPane(mTable);
|
mScrollPane = new JScrollPane(mTable);
|
||||||
@@ -72,6 +84,37 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
add(mScrollPane);
|
add(mScrollPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ServerTypeRenderer extends DefaultTableCellRenderer
|
||||||
|
{
|
||||||
|
public ServerTypeRenderer()
|
||||||
|
{
|
||||||
|
setOpaque(true);
|
||||||
|
setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||||
|
{
|
||||||
|
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
|
|
||||||
|
if(value instanceof BroadcastServerType broadcastServerType)
|
||||||
|
{
|
||||||
|
component.setText(broadcastServerType.toString());
|
||||||
|
Icon icon = new Icon("empty", broadcastServerType.getIconPath());
|
||||||
|
ImageIcon imageIcon = icon.getIcon();
|
||||||
|
ImageIcon scaledIcon = IconModel.getScaledIcon(imageIcon, 13);
|
||||||
|
component.setIcon(scaledIcon);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
component.setText(null);
|
||||||
|
component.setIcon(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom cell renderer for the broadcast state column.
|
* Custom cell renderer for the broadcast state column.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,13 +20,20 @@ package io.github.dsheirer.audio.broadcast.icecast;
|
|||||||
|
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
|
||||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
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 java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import org.apache.mina.core.RuntimeIoException;
|
import org.apache.mina.core.RuntimeIoException;
|
||||||
import org.apache.mina.core.buffer.IoBuffer;
|
import org.apache.mina.core.buffer.IoBuffer;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
@@ -42,13 +49,6 @@ 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.net.ConnectException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
||||||
@@ -143,8 +143,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
mSocketConnector = new NioSocketConnector();
|
mSocketConnector = new NioSocketConnector();
|
||||||
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
||||||
|
|
||||||
// mSocketConnector.getFilterChain().addLast("logger",
|
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
|
||||||
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
|
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
|
||||||
|
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
|
||||||
|
|
||||||
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
||||||
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
|
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
|
||||||
@@ -315,10 +316,49 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
|
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
|
||||||
* OK response and then play like everything is well and good.
|
* OK response and then play like everything is well and good.
|
||||||
*/
|
*/
|
||||||
if(cause instanceof ArrayIndexOutOfBoundsException &&
|
if(cause instanceof ArrayIndexOutOfBoundsException)
|
||||||
((ProtocolDecoderException)throwable).getHexdump().startsWith(HTTP_1_0_OK_HEX_DUMP))
|
|
||||||
{
|
{
|
||||||
setBroadcastState(BroadcastState.CONNECTED);
|
String hexDump = ((ProtocolDecoderException)throwable).getHexdump();
|
||||||
|
|
||||||
|
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HttpDumpMessage message = new HttpDumpMessage(hexDump);
|
||||||
|
|
||||||
|
if(message.hasHttpResponseCode())
|
||||||
|
{
|
||||||
|
switch(message.getHttpResponseCode())
|
||||||
|
{
|
||||||
|
case 403: //Forbidden
|
||||||
|
if(message.toString().contains("Mountpoint in use"))
|
||||||
|
{
|
||||||
|
mLog.error("Stream [" + getStreamName() + "] - unable to connect - mountpoint in use");
|
||||||
|
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -374,4 +414,97 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for parsing HTTP response messages from Icecast 2.4.2+
|
||||||
|
*/
|
||||||
|
public class HttpDumpMessage
|
||||||
|
{
|
||||||
|
private String mHexDump;
|
||||||
|
private String mMessage;
|
||||||
|
private int mHttpResponseCode = -1;
|
||||||
|
|
||||||
|
public HttpDumpMessage(String hexDump)
|
||||||
|
{
|
||||||
|
mHexDump = hexDump;
|
||||||
|
|
||||||
|
String[] split = mHexDump.split(" ");
|
||||||
|
byte[] bytes = new byte[split.length];
|
||||||
|
|
||||||
|
int pointer = 0;
|
||||||
|
for(String a : split)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int value = Integer.parseInt(a, 16);
|
||||||
|
bytes[pointer++] = (byte) (value & 0xFF);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
pointer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mMessage = new String(bytes);
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
|
||||||
|
Matcher m = pattern.matcher(mMessage);
|
||||||
|
|
||||||
|
if(m.find())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mHttpResponseCode = Integer.parseInt(m.group(1));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Unable to parse HTTP response code that was matched from message: " + m.group(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if an HTTP response code was parsed from the message
|
||||||
|
* @return true if it was parsed.
|
||||||
|
*/
|
||||||
|
public boolean hasHttpResponseCode()
|
||||||
|
{
|
||||||
|
return mHttpResponseCode != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response code parsed from the hexdump message
|
||||||
|
* @return value.
|
||||||
|
*/
|
||||||
|
public int getHttpResponseCode()
|
||||||
|
{
|
||||||
|
return mHttpResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
String text = "HTTP/1.0 403 Forbidden";
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
|
||||||
|
|
||||||
|
Matcher m = pattern.matcher(text);
|
||||||
|
|
||||||
|
if(m.find())
|
||||||
|
{
|
||||||
|
mLog.info("Matching Group Count: " + m.groupCount());
|
||||||
|
mLog.info("Match 0: " + m.group(0));
|
||||||
|
mLog.info("Match 1: " + m.group(1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("No Match");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,19 @@ package io.github.dsheirer.audio.broadcast.icecast;
|
|||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
|
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
|
||||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
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 java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.channels.UnresolvedAddressException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.apache.mina.core.RuntimeIoException;
|
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;
|
||||||
@@ -37,14 +43,6 @@ 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.net.InetSocketAddress;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.nio.channels.UnresolvedAddressException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
||||||
@@ -215,7 +213,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
//Only set broadcast state to disconnected if it's not already in an error state, to prevent a restart. We
|
||||||
|
//want to preserve the error state that got us here, so the user can see it.
|
||||||
|
if(!getBroadcastState().isErrorState())
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
mLastConnectionAttempt = System.currentTimeMillis();
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,6 +321,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
{
|
{
|
||||||
String message = (String) object;
|
String message = (String) object;
|
||||||
|
|
||||||
|
mLog.info("Stream [" + getStreamName() + "] message received: " + message);
|
||||||
if(message != null && !message.trim().isEmpty())
|
if(message != null && !message.trim().isEmpty())
|
||||||
{
|
{
|
||||||
if(message.startsWith("HTTP/1.0 200 OK"))
|
if(message.startsWith("HTTP/1.0 200 OK"))
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* *********************************************************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *********************************************************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.icon;
|
package io.github.dsheirer.icon;
|
||||||
|
|
||||||
@@ -22,15 +24,6 @@ import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||||
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 javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -43,6 +36,15 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
|
||||||
public class IconModel
|
public class IconModel
|
||||||
{
|
{
|
||||||
@@ -241,7 +243,6 @@ public class IconModel
|
|||||||
if(path != null)
|
if(path != null)
|
||||||
{
|
{
|
||||||
Icon icon = new Icon("", path);
|
Icon icon = new Icon("", path);
|
||||||
|
|
||||||
return getScaledIcon(icon.getIcon(), height);
|
return getScaledIcon(icon.getIcon(), height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user