diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyFeedConfiguration.java b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyFeedConfiguration.java index 91f01275c..f9f9540ee 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyFeedConfiguration.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyFeedConfiguration.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 Dennis Sheirer - * * - * * 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 - * * 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 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. If not, see - * * ***************************************************************************** + * 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 + * 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 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. If not, see + * **************************************************************************** */ package io.github.dsheirer.audio.broadcast.broadcastify; @@ -35,6 +32,7 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration private final static Logger mLog = LoggerFactory.getLogger(BroadcastifyFeedConfiguration.class); private int mFeedID; + private boolean mVerboseLogging = false; public BroadcastifyFeedConfiguration() { @@ -123,4 +121,15 @@ public void setFeedID(int feedID) { mFeedID = feedID; } + + @JacksonXmlProperty(isAttribute = true, localName = "verbose_logging") + public boolean isVerboseLogging() + { + return mVerboseLogging; + } + + public void setVerboseLogging(boolean verboseLogging) + { + mVerboseLogging = verboseLogging; + } } diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java index 851e945c6..b5e28c10d 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * 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 @@ -20,6 +20,7 @@ import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.BroadcastState; +import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration; import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory; import io.github.dsheirer.audio.convert.InputAudioFormat; import io.github.dsheirer.audio.convert.MP3AudioConverter; @@ -39,6 +40,8 @@ import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.logging.LogLevel; +import org.apache.mina.filter.logging.LoggingFilter; import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +59,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster private IoSession mStreamingSession = null; private long mLastConnectionAttempt = 0; + private boolean mVerboseLogging = false; private AtomicBoolean mConnecting = new AtomicBoolean(); /** @@ -73,6 +77,11 @@ public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, InputAu MP3Setting mp3Setting, AliasModel aliasModel) { super(configuration, inputAudioFormat, mp3Setting, aliasModel); + + if(configuration instanceof BroadcastifyFeedConfiguration broadcastify) + { + mVerboseLogging = broadcastify.isVerboseLogging(); + } } /** @@ -132,9 +141,12 @@ private boolean connect() mSocketConnector = new NioSocketConnector(); mSocketConnector.getSessionConfig().setWriteTimeout(WRITE_TIMEOUT_SECONDS); -// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class); -// loggingFilter.setMessageSentLogLevel(LogLevel.NONE); -// mSocketConnector.getFilterChain().addLast("logger", loggingFilter); + if(mVerboseLogging) + { + LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class); + loggingFilter.setMessageSentLogLevel(LogLevel.NONE); + mSocketConnector.getFilterChain().addLast("logger", loggingFilter); + } mSocketConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new IcecastCodecFactory())); @@ -148,6 +160,10 @@ private boolean connect() @Override public void run() { + if(mVerboseLogging) + { + mLog.info("Attempting connection ..."); + } setBroadcastState(BroadcastState.CONNECTING); try @@ -156,19 +172,42 @@ public void run() .connect(new InetSocketAddress(getBroadcastConfiguration().getHost(), getBroadcastConfiguration().getPort())); + if(mVerboseLogging) + { + mLog.info("Socket created - asynchronous connect requested - entering wait period"); + } + boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS); if(connected) { + if(mVerboseLogging) + { + mLog.info("Connected."); + } + mStreamingSession = future.getSession(); mConnecting.set(false); return; } + else + { + if(mVerboseLogging) + { + mLog.info("Not Connected. Connection attempt timeout [" + CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS + "ms] exceeded"); + } + } } catch(RuntimeIoException rioe) { if(rioe.getCause() instanceof SocketException) { + if(mVerboseLogging) + { + mLog.info("Socket error. This usually indicates sdrtrunk can't reach the server " + + "address over the current network connection. Setting state to " + + "NETWORK UNAVAILABLE", rioe); + } setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE); mConnecting.set(false); return; @@ -176,12 +215,22 @@ public void run() } catch(UnresolvedAddressException uae) { + if(mVerboseLogging) + { + mLog.info("Unresolved Address error. This means the domain name services can't resolve " + + "the server URL to an IP address. Setting state to NETWORK UNAVAILABLE", uae); + } + setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE); mConnecting.set(false); return; } catch(Exception e) { + if(mVerboseLogging) + { + mLog.info("Unknown error. An error occurred while attempting to connect to the server.", e); + } mLog.error("Error", e); //Disregard ... we'll disconnect and try again } @@ -190,6 +239,11 @@ public void run() mLog.error("Throwable error caught", t); } + if(mVerboseLogging) + { + mLog.info("Starting disconnect sequence since an error occurred while trying to connect."); + } + disconnect(); mConnecting.set(false); } @@ -209,6 +263,11 @@ public void disconnect() { if(connected() && mStreamingSession != null) { + if(mVerboseLogging) + { + mLog.info("Routine disconnect requested from a connected state with a non-null streaming session"); + } + mStreamingSession.closeNow(); } else @@ -217,6 +276,12 @@ public void disconnect() //want to preserve the error state that got us here, so the user can see it. if(!getBroadcastState().isErrorState()) { + if(mVerboseLogging) + { + mLog.info("Disconnect requested - previous non-error state was [" + getBroadcastState() + + "] - changing state to DISCONNECTED"); + } + setBroadcastState(BroadcastState.DISCONNECTED); } @@ -282,12 +347,23 @@ public void sessionOpened(IoSession session) throws Exception mInlineActive = false; } + if(mVerboseLogging) + { + mLog.info("Session opened. Sending: " + sb); + } + + session.write(sb.toString()); } @Override public void sessionClosed(IoSession session) throws Exception { + if(mVerboseLogging) + { + mLog.info("Session closed. Setting connecting flag to false."); + } + mLastConnectionAttempt = System.currentTimeMillis(); //If there is already an error state, don't override it. Otherwise, set state to disconnected @@ -311,15 +387,23 @@ public void exceptionCaught(IoSession session, Throwable cause) throws Exception mLog.error("[" + getStreamName() + "] Broadcast error", cause); } + if(mVerboseLogging) + { + mLog.info("Session error caught.", cause); + } + disconnect(); } @Override public void messageReceived(IoSession session, Object object) throws Exception { - if(object instanceof String) + if(object instanceof String message) { - String message = (String) object; + if(mVerboseLogging) + { + mLog.info("Message Received [" + message + "]"); + } if(message != null && !message.trim().isEmpty()) { diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyStreamEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyStreamEditor.java index f79228044..01477fab4 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyStreamEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyStreamEditor.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 Dennis Sheirer - * * - * * 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 - * * 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 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. If not, see - * * ***************************************************************************** + * 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 + * 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 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. If not, see + * **************************************************************************** */ package io.github.dsheirer.gui.playlist.streaming; @@ -31,7 +28,9 @@ import javafx.geometry.Insets; import javafx.scene.control.Label; import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; import javafx.scene.layout.GridPane; +import org.controlsfx.control.ToggleSwitch; /** * Broadcastify streaming configuration editor @@ -39,6 +38,7 @@ public class BroadcastifyStreamEditor extends AbstractStreamEditor { private GridPane mEditorPane; + private ToggleSwitch mVerboseLoggingToggle; private TextField mMountPointTextField; private IntegerTextField mFeedIdTextField; @@ -54,16 +54,19 @@ public void setItem(BroadcastifyFeedConfiguration item) getFeedIdTextField().setDisable(item == null); getMountPointTextField().setDisable(item == null); + getVerboseLoggingToggle().setDisable(item == null); if(item != null) { getFeedIdTextField().set(item.getFeedID()); getMountPointTextField().setText(item.getMountPoint()); + getVerboseLoggingToggle().setSelected(item.isVerboseLogging()); } else { getFeedIdTextField().set(0); getMountPointTextField().setText(null); + getVerboseLoggingToggle().setSelected(false); } modifiedProperty().set(false); @@ -76,6 +79,7 @@ public void save() { getItem().setFeedID(getFeedIdTextField().get()); getItem().setMountPoint(getMountPointTextField().getText()); + getItem().setVerboseLogging(getVerboseLoggingToggle().isSelected()); } super.save(); @@ -178,11 +182,32 @@ protected GridPane getEditorPane() GridPane.setConstraints(getFeedIdTextField(), 1, 5); getEditorPane().getChildren().add(getFeedIdTextField()); + + Label loggingLabel = new Label("Verbose Logging"); + GridPane.setHalignment(loggingLabel, HPos.RIGHT); + GridPane.setConstraints(loggingLabel, 2, 5); + getEditorPane().getChildren().add(loggingLabel); + + GridPane.setConstraints(getVerboseLoggingToggle(), 3, 5); + getEditorPane().getChildren().add(getVerboseLoggingToggle()); } return mEditorPane; } + private ToggleSwitch getVerboseLoggingToggle() + { + if(mVerboseLoggingToggle == null) + { + mVerboseLoggingToggle = new ToggleSwitch(); + mVerboseLoggingToggle.setTooltip(new Tooltip("Turn on additional logging for connection troubleshooting")); + mVerboseLoggingToggle.setDisable(true); + mVerboseLoggingToggle.selectedProperty().addListener((ob, ol, ne) -> modifiedProperty().set(true)); + } + + return mVerboseLoggingToggle; + } + private TextField getMountPointTextField() { if(mMountPointTextField == null)