From a39a533fc7805f78aa9191a26cf771cb3437b182 Mon Sep 17 00:00:00 2001 From: Dennis Sheirer Date: Sun, 26 Nov 2023 08:15:25 -0500 Subject: [PATCH] #1723 Initial commit - audio call management feature. -Implements Spring Boot and spring dependency injection - significant code refactoring. -Creates database repository with Calls table -Implements AudioManager to update database and manage audio recordings. -Calls paging and searching -Adds audio recording to wave file and places file in the calls database table. -Audio spectrum view with 64 bins. -Updates audio playback manager to process the incoming calls queue and support auto, auto-locked, replay, and mute playback modes. -Adds channel lock/unlock and mute/unmute features. -Adds column configuration editor to change column order & visibility and adds table state monitoring to save any user customization of the column ordering. -Adds call table column sort ordering to the saved table state and restores the sort on application launch. -Adds audio playback channel view options for: Complete, Standard and Minimal. Updates user preferences Audio | Playback section to allow user to select preferred view style. --- build.gradle | 15 +- .../io/github/dsheirer/alias/AliasModel.java | 2 + .../dsheirer/audio/AbstractAudioModule.java | 45 +- .../github/dsheirer/audio/AudioManager.java | 317 ++++++ .../dsheirer/audio/DuplicateCallDetector.java | 25 +- .../dsheirer/audio/IAudioSegmentListener.java | 3 +- .../dsheirer/audio/IAudioSegmentProvider.java | 3 +- .../broadcast/AudioStreamingManager.java | 115 ++- .../audio/broadcast/BroadcastFactory.java | 31 +- .../audio/broadcast/BroadcastModel.java | 25 +- .../audio/broadcast/BroadcastServerType.java | 6 +- .../audio/broadcast/BroadcastStatusPanel.java | 32 +- .../BroadcastifyCallBroadcaster.java | 7 +- .../icecast/IcecastAudioBroadcaster.java | 9 +- .../IcecastBroadcastMetadataUpdater.java | 22 +- .../icecast/IcecastHTTPAudioBroadcaster.java | 10 +- .../icecast/IcecastTCPAudioBroadcaster.java | 10 +- .../broadcast/openmhz/OpenMHzBroadcaster.java | 91 +- .../broadcast/openmhz/OpenMHzEditor.java | 36 +- .../rdioscanner/RdioScannerBroadcaster.java | 11 +- .../v1/ShoutcastV1AudioBroadcaster.java | 18 +- .../ShoutcastV1BroadcastMetadataUpdater.java | 28 +- .../ShoutcastV2AudioStreamingBroadcaster.java | 28 +- .../audio/call/ActiveAudioSegment.java | 462 +++++++++ .../audio/call/AudioPlaybackControlView.java | 241 +++++ .../audio/{ => call}/AudioSegment.java | 120 +-- .../{ => call}/AudioSegmentBroadcaster.java | 9 +- .../io/github/dsheirer/audio/call/Call.java | 489 +++++++++ .../dsheirer/audio/call/CallRepository.java | 55 + .../github/dsheirer/audio/call/CallView.java | 964 ++++++++++++++++++ .../dsheirer/audio/call/CompletedCall.java | 29 + .../audio/call/ICallEventListener.java | 51 + .../audio/call/IPageRequestListener.java | 32 + .../dsheirer/audio/call/PagingController.java | 236 +++++ .../dsheirer/audio/call/SearchColumn.java | 54 + .../audio/codec/mbe/JmbeAudioModule.java | 24 +- .../codec/mbe/MBECallSequenceConverter.java | 57 +- .../audio/playback/AudioChannelPanel.java | 73 +- .../audio/playback/AudioChannelsPanel.java | 21 +- .../dsheirer/audio/playback/AudioOutput.java | 54 +- .../dsheirer/audio/playback/AudioPanel.java | 101 +- .../audio/playback/AudioPlaybackManager.java | 64 +- .../audio/playback/MonoAudioOutput.java | 8 +- .../audio/playback/StereoAudioOutput.java | 6 +- .../AudioPlaybackChannelController.java | 441 ++++++++ .../playbackfx/AudioPlaybackChannelView.java | 271 +++++ .../AudioPlaybackChannelViewComplete.java | 213 ++++ .../AudioPlaybackChannelViewMinimal.java | 95 ++ .../AudioPlaybackChannelViewStandard.java | 115 +++ .../playbackfx/AudioPlaybackChannelsView.java | 179 ++++ .../playbackfx/AudioPlaybackController.java | 357 +++++++ .../audio/playbackfx/AudioSpectrumView.java | 260 +++++ .../IAudioPlaybackStatusListener.java | 41 + .../IPanelRevalidationRequestListener.java | 28 + .../audio/playbackfx/PlaybackMode.java | 44 + .../audio/playbackfx/PlaybackView.java | 47 + .../channel/details/ChannelDetailPanel.java | 29 +- .../channel/metadata/ChannelMetadata.java | 19 +- .../metadata/ChannelMetadataModel.java | 70 +- .../metadata/ChannelMetadataPanel.java | 59 +- .../channel/metadata/NowPlayingPanel.java | 66 +- .../channel/state/MultiChannelState.java | 10 +- .../channel/state/SingleChannelState.java | 9 +- .../dsheirer/controller/ControllerPanel.java | 123 ++- .../controller/channel/ChannelModel.java | 48 +- .../channel/ChannelProcessingManager.java | 61 +- .../channel/ChannelSelectionManager.java | 21 +- .../channel/map/ChannelMapModel.java | 35 +- .../github/dsheirer/eventbus/MyEventBus.java | 15 +- .../dsheirer/gui/JavaFxWindowManager.java | 145 +-- .../java/io/github/dsheirer/gui/SDRTrunk.java | 847 ++------------- .../io/github/dsheirer/gui/SDRTrunkUI.java | 709 +++++++++++++ .../dsheirer/gui/SpringJavaFxBeanFactory.java | 710 +++++++++++++ .../dsheirer/gui/SpringSwingBeanFactory.java | 249 +++++ .../dsheirer/gui/calls/CallAudioPanel.java | 30 + .../gui/channelizer/ChannelizerViewer.java | 22 +- .../gui/channelizer/ChannelizerViewer2.java | 4 +- .../HeterodyneChannelizerViewer.java | 22 +- .../gui/channelizer/SynthesizerViewer.java | 6 +- .../control/TimestampTableCellFactory.java | 64 ++ .../github/dsheirer/gui/icon/IconManager.java | 22 +- .../gui/javafx/table/TableColumnEditor.java | 250 +++++ .../dsheirer/gui/javafx/table/TableState.java | 126 +++ .../table/TableViewColumnController.java | 241 +++++ .../dsheirer/gui/playlist/PlaylistEditor.java | 64 +- .../playlist/PlaylistEditorApplication.java | 23 +- .../gui/playlist/alias/AliasBulkEditor.java | 45 +- .../alias/AliasConfigurationEditor.java | 51 +- .../gui/playlist/alias/AliasEditor.java | 57 +- .../gui/playlist/alias/AliasItemEditor.java | 57 +- .../alias/AliasViewByIdentifierEditor.java | 45 +- .../alias/AliasViewByRecordingEditor.java | 32 +- .../channel/AMConfigurationEditor.java | 17 +- .../channel/ChannelConfigurationEditor.java | 52 +- .../ChannelConfigurationEditorFactory.java | 81 -- .../gui/playlist/channel/ChannelEditor.java | 64 +- .../channel/DMRConfigurationEditor.java | 17 +- .../channel/LTRConfigurationEditor.java | 17 +- .../channel/LTRNetConfigurationEditor.java | 17 +- .../channel/MPT1327ConfigurationEditor.java | 23 +- .../channel/NBFMConfigurationEditor.java | 19 +- .../channel/P25P1ConfigurationEditor.java | 17 +- .../channel/P25P2ConfigurationEditor.java | 16 +- .../channel/PassportConfigurationEditor.java | 16 +- .../channel/UnknownConfigurationEditor.java | 15 +- .../playlist/channelMap/ChannelMapEditor.java | 71 +- .../manager/PlaylistManagerEditor.java | 80 +- .../playlist/radioreference/AgencyEditor.java | 37 +- .../radioreference/AgencyFrequencyEditor.java | 28 +- .../radioreference/CountyAgencyEditor.java | 39 + .../radioreference/CountySystemEditor.java | 39 + .../radioreference/FrequencyEditor.java | 25 +- .../playlist/radioreference/LoginDialog.java | 8 +- .../radioreference/NationalAgencyEditor.java | 39 + .../radioreference/RadioReferenceEditor.java | 111 +- .../playlist/radioreference/SiteEditor.java | 52 +- .../radioreference/StateAgencyEditor.java | 39 + .../radioreference/StateSystemEditor.java | 39 + .../playlist/radioreference/SystemEditor.java | 41 +- .../SystemSiteSelectionEditor.java | 57 +- .../SystemTalkgroupSelectionEditor.java | 33 +- .../radioreference/TalkgroupEditor.java | 22 +- .../streaming/AbstractBroadcastEditor.java | 33 +- .../streaming/AbstractStreamEditor.java | 33 +- .../streaming/BroadcastifyCallEditor.java | 9 +- .../streaming/BroadcastifyStreamEditor.java | 33 +- .../streaming/IcecastHTTPStreamEditor.java | 35 +- .../streaming/IcecastStreamEditor.java | 33 +- .../streaming/IcecastTCPStreamEditor.java | 35 +- .../playlist/streaming/RdioScannerEditor.java | 5 +- .../streaming/ShoutcastV1StreamEditor.java | 35 +- .../streaming/ShoutcastV2StreamEditor.java | 35 +- .../streaming/StreamAliasSelectionEditor.java | 57 +- .../streaming/StreamEditorFactory.java | 59 -- .../playlist/streaming/StreamingEditor.java | 76 +- .../streaming/UnknownStreamEditor.java | 35 +- .../dsheirer/gui/power/ChannelPowerPanel.java | 32 +- .../DecodeEventViewPreferenceEditor.java | 50 +- .../gui/preference/PreferenceEditor.java | 40 + .../preference/PreferenceEditorFactory.java | 69 -- .../gui/preference/PreferenceEditorType.java | 6 +- .../TalkgroupFormatPreferenceEditor.java | 30 +- .../gui/preference/UserPreferencesEditor.java | 59 +- .../ApplicationPreferenceEditor.java | 23 +- .../VectorCalibrationPreferenceEditor.java | 26 +- .../call/CallManagementPreferenceEditor.java | 20 +- .../decoder/JmbeLibraryPreferenceEditor.java | 58 +- .../directory/DirectoryPreferenceEditor.java | 33 +- .../preference/mp3/MP3PreferenceEditor.java | 24 +- .../playback/PlaybackPreferenceEditor.java | 61 +- .../record/RecordPreferenceEditor.java | 23 +- .../tuner/TunerPreferenceEditor.java | 32 +- .../github/dsheirer/gui/viewer/DmrViewer.java | 3 + .../dsheirer/gui/viewer/P25P1Viewer.java | 3 + .../io/github/dsheirer/icon/IconModel.java | 135 +-- .../io/github/dsheirer/jmbe/JmbeEditor.java | 19 +- .../github/dsheirer/log/ApplicationLog.java | 128 ++- .../dsheirer/log/CachingLogAppender.java | 67 ++ .../java/io/github/dsheirer/map/MapPanel.java | 31 +- .../io/github/dsheirer/map/MapService.java | 19 +- .../dsheirer/map/PlottableEntityPainter.java | 19 +- .../dsheirer/map/PlottableEntityRenderer.java | 28 +- .../dsheirer/module/ProcessingChain.java | 11 +- .../module/decode/DecoderFactory.java | 90 +- .../decode/dmr/audio/DMRAudioModule.java | 2 +- .../module/decode/event/DecodeEventModel.java | 27 +- .../module/decode/event/DecodeEventPanel.java | 43 +- .../decode/event/MessageActivityPanel.java | 8 +- .../decode/p25/phase1/P25P1MessageFramer.java | 5 +- .../decode/p25/phase2/P25P2MessageFramer.java | 146 +-- .../module/log/DecodeEventLogger.java | 13 +- .../dsheirer/module/log/EventLogManager.java | 33 +- .../dsheirer/monitor/ResourceMonitor.java | 8 +- .../dsheirer/playlist/PlaylistManager.java | 104 +- .../preference/IPreferenceUpdateListener.java | 28 + .../dsheirer/preference/UserPreferences.java | 86 +- .../application/ApplicationPreference.java | 29 +- .../playback/PlaybackPreference.java | 42 +- .../swing/JTableColumnWidthMonitor.java | 30 +- .../dsheirer/properties/SystemProperties.java | 6 +- .../record/AudioRecordingManager.java | 137 +-- .../dsheirer/record/AudioSegmentRecorder.java | 2 +- .../record/binary/BinaryRecorder.java | 1 + .../radioreference/RadioReference.java | 82 +- .../dsheirer/settings/SettingsManager.java | 62 +- .../TunerConfigurationManager.java | 19 +- .../source/tuner/manager/TunerManager.java | 17 +- .../recording/AddRecordingTunerDialog.java | 33 +- .../sdrplay/rspDuo/RspDuoTuner1Editor.java | 2 +- .../sdrplay/rspDuo/RspDuoTuner2Editor.java | 2 +- .../tuner/ui/DiscoveredTunerEditor.java | 12 +- .../source/tuner/ui/DiscoveredTunerModel.java | 2 + .../dsheirer/source/tuner/ui/TunerEditor.java | 5 +- .../tuner/ui/TunerSpectralDisplayManager.java | 50 +- .../source/tuner/ui/TunerViewPanel.java | 29 +- .../spectrum/ComplexDftProcessor.java | 1 - .../DisableSpectrumWaterfallMenuItem.java | 20 +- .../spectrum/FrequencyOverlayPanel.java | 11 +- .../dsheirer/spectrum/OverlayPanel.java | 20 +- .../dsheirer/spectrum/ShowTunerMenuItem.java | 49 +- .../spectrum/SpectralDisplayPanel.java | 57 +- .../dsheirer/spectrum/SpectrumFrame.java | 55 +- .../dsheirer/spectrum/SpectrumPanel.java | 34 +- .../dsheirer/spectrum/WaterfallPanel.java | 37 +- src/main/resources/application.properties | 54 + src/main/resources/logback.xml | 20 +- 206 files changed, 10838 insertions(+), 3884 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/audio/AudioManager.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/ActiveAudioSegment.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/AudioPlaybackControlView.java rename src/main/java/io/github/dsheirer/audio/{ => call}/AudioSegment.java (85%) rename src/main/java/io/github/dsheirer/audio/{ => call}/AudioSegmentBroadcaster.java (88%) create mode 100644 src/main/java/io/github/dsheirer/audio/call/Call.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/CallRepository.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/CallView.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/CompletedCall.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/ICallEventListener.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/IPageRequestListener.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/PagingController.java create mode 100644 src/main/java/io/github/dsheirer/audio/call/SearchColumn.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelController.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelView.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelViewComplete.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelViewMinimal.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelViewStandard.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelsView.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackController.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/AudioSpectrumView.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/IAudioPlaybackStatusListener.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/IPanelRevalidationRequestListener.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/PlaybackMode.java create mode 100644 src/main/java/io/github/dsheirer/audio/playbackfx/PlaybackView.java create mode 100644 src/main/java/io/github/dsheirer/gui/SDRTrunkUI.java create mode 100644 src/main/java/io/github/dsheirer/gui/SpringJavaFxBeanFactory.java create mode 100644 src/main/java/io/github/dsheirer/gui/SpringSwingBeanFactory.java create mode 100644 src/main/java/io/github/dsheirer/gui/calls/CallAudioPanel.java create mode 100644 src/main/java/io/github/dsheirer/gui/control/TimestampTableCellFactory.java create mode 100644 src/main/java/io/github/dsheirer/gui/javafx/table/TableColumnEditor.java create mode 100644 src/main/java/io/github/dsheirer/gui/javafx/table/TableState.java create mode 100644 src/main/java/io/github/dsheirer/gui/javafx/table/TableViewColumnController.java delete mode 100644 src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditorFactory.java create mode 100644 src/main/java/io/github/dsheirer/gui/playlist/radioreference/CountyAgencyEditor.java create mode 100644 src/main/java/io/github/dsheirer/gui/playlist/radioreference/CountySystemEditor.java create mode 100644 src/main/java/io/github/dsheirer/gui/playlist/radioreference/NationalAgencyEditor.java create mode 100644 src/main/java/io/github/dsheirer/gui/playlist/radioreference/StateAgencyEditor.java create mode 100644 src/main/java/io/github/dsheirer/gui/playlist/radioreference/StateSystemEditor.java delete mode 100644 src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamEditorFactory.java create mode 100644 src/main/java/io/github/dsheirer/gui/preference/PreferenceEditor.java create mode 100644 src/main/java/io/github/dsheirer/log/CachingLogAppender.java create mode 100644 src/main/java/io/github/dsheirer/preference/IPreferenceUpdateListener.java create mode 100644 src/main/resources/application.properties diff --git a/build.gradle b/build.gradle index f0c899099..3e1742aca 100644 --- a/build.gradle +++ b/build.gradle @@ -23,8 +23,8 @@ import java.text.SimpleDateFormat * Instructions for building/compiling the sdrtrunk application. * * Prerequisites: - * Install and configure an OpenJDK version 19+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK) - * - Optional: install and configure Gradle 7.6+ + * Install and configure an OpenJDK version 20 that includes the JavaFX modules (e.g. Bellsoft Liberica JDK) + * - Optional: install and configure Gradle 8.2+ * * Scenario 1: run the application via gradle command line from the source code root directory: * command: ./gradlew run @@ -46,6 +46,8 @@ plugins { id 'java' id 'idea' id 'org.beryx.runtime' version '1.12.7' + id 'org.springframework.boot' version '3.1.5' + id 'io.spring.dependency-management' version '1.1.3' } repositories { @@ -92,6 +94,7 @@ dependencies { implementation 'ch.qos.logback:logback-core:1.4.5' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0' implementation 'com.fazecast:jSerialComm:2.9.3' + implementation 'com.github.jiconfont:jiconfont-elusive:2.0.3' implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1' implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0' implementation 'com.github.jiconfont:jiconfont-swing:1.0.1' @@ -103,6 +106,7 @@ dependencies { implementation 'com.mpatric:mp3agic:0.9.1' implementation 'commons-io:commons-io:2.11.0' implementation 'eu.hansolo:charts:1.0.5' + implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu implementation 'io.github.dsheirer:radio-reference-api:15.1.9' implementation 'javax.usb:usb-api:1.0.2' implementation 'net.coderazzi:tablefilter-swing:5.5.4' @@ -110,15 +114,18 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'org.apache.commons:commons-math3:3.6.1' implementation 'org.apache.commons:commons-csv:1.9.0' + implementation 'org.apache.derby:derby' + implementation 'org.apache.derby:derbytools' implementation 'org.apache.mina:mina-core:2.2.1' implementation 'org.apache.mina:mina-http:2.2.1' implementation 'org.controlsfx:controlsfx:11.1.2' implementation 'org.rauschig:jarchivelib:1.2.0' implementation 'org.slf4j:slf4j-api:2.0.5' + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.usb4java:libusb4java:1.3.0' implementation 'org.usb4java:usb4java:1.3.0' implementation 'org.usb4java:usb4java-javax:1.3.0' - implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu implementation 'pl.edu.icm:JLargeArrays:1.6' } @@ -230,7 +237,7 @@ def configure(org.beryx.runtime.RuntimeZipTask rt, List jvmArgs) { //jdk.incubator.vector - needed for Project Panama foreign function and vector apis //jdk.accessibility is used with assistive technologies like screen readers //java.management for JVM resource monitoring - rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management') + rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management', 'java.instrument') //Use auto-detected modules and 'add' any specified modules. rt.extension.additive.set(true) diff --git a/src/main/java/io/github/dsheirer/alias/AliasModel.java b/src/main/java/io/github/dsheirer/alias/AliasModel.java index b9d093342..e609d7223 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasModel.java +++ b/src/main/java/io/github/dsheirer/alias/AliasModel.java @@ -33,12 +33,14 @@ import javafx.collections.ObservableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; /** * Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a * set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing * identifiers produced by channel decoder(s). */ +@Component("aliasModel") public class AliasModel { private final static Logger mLog = LoggerFactory.getLogger(AliasModel.class); diff --git a/src/main/java/io/github/dsheirer/audio/AbstractAudioModule.java b/src/main/java/io/github/dsheirer/audio/AbstractAudioModule.java index beae5d6ec..5c8234ba0 100644 --- a/src/main/java/io/github/dsheirer/audio/AbstractAudioModule.java +++ b/src/main/java/io/github/dsheirer/audio/AbstractAudioModule.java @@ -20,6 +20,7 @@ package io.github.dsheirer.audio; import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.audio.call.AudioSegment; import io.github.dsheirer.identifier.IdentifierUpdateListener; import io.github.dsheirer.identifier.IdentifierUpdateNotification; import io.github.dsheirer.identifier.MutableIdentifierCollection; @@ -89,7 +90,7 @@ protected void closeAudioSegment() { mAudioSegment.completeProperty().set(true); mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment); - mAudioSegment.decrementConsumerCount(); + mAudioSegment.removeLease(getClass().toString()); mAudioSegment = null; } } @@ -112,7 +113,7 @@ public AudioSegment getAudioSegment() if(mAudioSegment == null) { mAudioSegment = new AudioSegment(mAliasList, getTimeslot()); - mAudioSegment.incrementConsumerCount(); + mAudioSegment.addLease(getClass().toString()); mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers()); mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment); @@ -123,7 +124,6 @@ public AudioSegment getAudioSegment() if(mAudioSegmentListener != null) { - mAudioSegment.incrementConsumerCount(); mAudioSegmentListener.receive(mAudioSegment); } @@ -136,26 +136,33 @@ public AudioSegment getAudioSegment() public void addAudio(float[] audioBuffer) { - AudioSegment audioSegment = getAudioSegment(); - - //If the current segment exceeds the max samples length, close it so that a new segment gets generated - //and then link the segments together - if(mAudioSampleCount >= mMaxSegmentAudioSampleLength) + if(audioBuffer != null) { - AudioSegment previous = getAudioSegment(); - closeAudioSegment(); - audioSegment = getAudioSegment(); - audioSegment.linkTo(previous); - } + AudioSegment audioSegment = getAudioSegment(); - try - { - audioSegment.addAudio(audioBuffer); - mAudioSampleCount += audioBuffer.length; + //If the current segment exceeds the max samples length, close it so that a new segment gets generated + //and then link the segments together + if(mAudioSampleCount >= mMaxSegmentAudioSampleLength) + { + AudioSegment previous = getAudioSegment(); + closeAudioSegment(); + audioSegment = getAudioSegment(); + audioSegment.linkTo(previous); + } + + try + { + audioSegment.addAudio(audioBuffer); + mAudioSampleCount += audioBuffer.length; + } + catch(Exception e) + { + closeAudioSegment(); + } } - catch(Exception e) + else { - closeAudioSegment(); + mLog.info("Attempt to add null audio from " + getClass()); } } diff --git a/src/main/java/io/github/dsheirer/audio/AudioManager.java b/src/main/java/io/github/dsheirer/audio/AudioManager.java new file mode 100644 index 000000000..9a55c949e --- /dev/null +++ b/src/main/java/io/github/dsheirer/audio/AudioManager.java @@ -0,0 +1,317 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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 + * **************************************************************************** + */ + +package io.github.dsheirer.audio; + +import io.github.dsheirer.audio.broadcast.AudioStreamingManager; +import io.github.dsheirer.audio.call.ActiveAudioSegment; +import io.github.dsheirer.audio.call.AudioSegment; +import io.github.dsheirer.audio.call.Call; +import io.github.dsheirer.audio.call.CallRepository; +import io.github.dsheirer.audio.call.ICallEventListener; +import io.github.dsheirer.audio.playback.AudioPlaybackManager; +import io.github.dsheirer.audio.playbackfx.AudioPlaybackController; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.record.AudioRecordingManager; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.util.ThreadPool; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.LinkedTransferQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Manages handling and distribution of audio segments and calls across the application. + */ +@Component("audioManager") +public class AudioManager implements Listener +{ + private static final Logger LOGGER = LoggerFactory.getLogger(AudioManager.class); + + @Resource + private AudioPlaybackManager mAudioPlaybackManager; + @Resource + private AudioPlaybackController mAudioPlaybackController; + @Resource + private AudioRecordingManager mAudioRecordingManager; + @Resource + private AudioStreamingManager mAudioStreamingManager; + @Resource + private DuplicateCallDetector mDuplicateCallDetector; + @Resource + private UserPreferences mUserPreferences; + private CallRepository mCallRepository; + private List mCallListeners = new CopyOnWriteArrayList<>(); + private ActiveAudioSegmentProcessor mProcessor = new ActiveAudioSegmentProcessor(); + private ScheduledFuture mProcessorFuture; + + /** + * Constructs an instance + */ + @Autowired + public AudioManager(CallRepository callRepository) + { + mCallRepository = callRepository; + } + + @PostConstruct + public void postConstruct() + { + mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 100, 500, TimeUnit.MILLISECONDS); + + //Register call event listeners + add(mAudioRecordingManager); + add(mAudioPlaybackController); + add(mAudioStreamingManager); + } + + @PreDestroy + public void preDestroy() + { + if(mProcessorFuture != null) + { + mProcessorFuture.cancel(true); + mProcessorFuture = null; + } + LOGGER.info("AudioManager shutting down"); + mProcessor.shutdown(); + } + + /** + * Adds the call listener to be notified of call events. + * @param listener to add + */ + public void add(ICallEventListener listener) + { + if(!mCallListeners.contains(listener)) + { + mCallListeners.add(listener); + } + } + + /** + * Broadcasts a call added event to registered listeners. + * @param call that was added + */ + private void broadcastAdded(Call call) + { + for(ICallEventListener listener: mCallListeners) + { + listener.added(call); + } + } + + /** + * Broadcasts a call updated event to registered listeners. + * @param call that was updated + */ + private void broadcastUpdated(Call call) + { + for(ICallEventListener listener: mCallListeners) + { + listener.updated(call); + } + } + + /** + * Broadcasts a call completed event to registered listeners. + * @param call that was completed + * @param audioSegment associated with the call. + */ + private void broadcastCompleted(Call call, AudioSegment audioSegment) + { + for(ICallEventListener listener: mCallListeners) + { + audioSegment.addLease(listener.getClass().toString()); + listener.completed(call, audioSegment); + } + } + + /** + * Broadcasts a call deleted event to registered listeners. + * @param call that was deleted + */ + private void broadcastDeleted(Call call) + { + for(ICallEventListener listener: mCallListeners) + { + listener.deleted(call); + } + } + + /** + * Removes the listener from receiving call event notifications. + * @param listener to remove + */ + public void remove(ICallEventListener listener) + { + mCallListeners.remove(listener); + } + + @Override + public void receive(AudioSegment audioSegment) + { + //Let the duplicate call detector process the segment first to detect duplicates. + audioSegment.addLease(mDuplicateCallDetector.getClass().toString()); + mDuplicateCallDetector.receive(audioSegment); + audioSegment.addLease(mProcessor.getClass().toString()); + mProcessor.add(audioSegment); + + //TODO: remove for cleanup +// audioSegment.addLease(mAudioPlaybackManager.getClass().toString()); +// mAudioPlaybackManager.receive(audioSegment); + } + + /** + * Active audio segment processor. Single thread processor that runs as a scheduled task to process incoming + * audio segments, update existing active audio segments, and process completed audio segments. + */ + private class ActiveAudioSegmentProcessor implements Runnable + { + private LinkedTransferQueue mQueuedAudioSegments = new LinkedTransferQueue<>(); + private List mAudioSegmentsToProcess = new ArrayList<>(); + private Map mActiveAudioSegmentsMap = new HashMap<>(); + private AtomicBoolean mProcessing = new AtomicBoolean(); + + /** + * Adds the new audio segment to be processed + * @param audioSegment to process + */ + public void add(AudioSegment audioSegment) + { + mQueuedAudioSegments.add(audioSegment); + } + + /** + * Performs shutdown and cleanup of any active audio segments that remain after the threaded processing + * is terminated. + */ + public void shutdown() + { + for(Map.Entry entry: mActiveAudioSegmentsMap.entrySet()) + { + //Invoke dispose() method to remove the lease on the audio segment + entry.getValue().dispose(); + } + mActiveAudioSegmentsMap.clear(); + + for(AudioSegment audioSegment: mQueuedAudioSegments) + { + audioSegment.removeLease(getClass().toString()); + } + mQueuedAudioSegments.clear(); + } + + @Override + public void run() + { + if(mProcessing.compareAndSet(false, true)) + { + //Process existing active audio segments + try + { + Iterator> it = mActiveAudioSegmentsMap.entrySet().iterator(); + + while(it.hasNext()) + { + ActiveAudioSegment activeAudioSegment = it.next().getValue(); + + if(activeAudioSegment.update()) + { + broadcastUpdated(activeAudioSegment.getCall()); + } + + if(activeAudioSegment.isComplete()) + { + //Process residual audio and close the wave recording file. + activeAudioSegment.closeAudio(); + + Call call = activeAudioSegment.getCall(); + call.setComplete(true); + + try + { + mCallRepository.save(call); + } + catch(Exception e) + { + LOGGER.error("Couldn't update call in database"); + } + broadcastCompleted(call, activeAudioSegment.getAudioSegment()); + activeAudioSegment.dispose(); + it.remove(); + } + } + } + catch(Throwable t) + { + LOGGER.error("Error while processing existing active audio segments", t); + } + + //Process new audio segments + try + { + mQueuedAudioSegments.drainTo(mAudioSegmentsToProcess); + + for(AudioSegment audioSegment: mAudioSegmentsToProcess) + { + if(mActiveAudioSegmentsMap.containsKey(audioSegment)) + { + ActiveAudioSegment activeAudioSegment = mActiveAudioSegmentsMap.get(audioSegment); + + if(activeAudioSegment.update()) + { + broadcastUpdated(activeAudioSegment.getCall()); + } + } + else + { + Call call = ActiveAudioSegment.create(audioSegment); + ActiveAudioSegment activeAudioSegment = new ActiveAudioSegment(audioSegment, call, mUserPreferences); + mActiveAudioSegmentsMap.put(audioSegment, activeAudioSegment); + mCallRepository.save(call); + broadcastAdded(call); + } + } + + mAudioSegmentsToProcess.clear(); + } + catch(Throwable t) + { + LOGGER.error("Error while processing existing active audio segments", t); + } + + mProcessing.set(false); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java b/src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java index 9d3dac9f9..c79a51425 100644 --- a/src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java +++ b/src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java @@ -19,6 +19,7 @@ package io.github.dsheirer.audio; +import io.github.dsheirer.audio.call.AudioSegment; import io.github.dsheirer.identifier.Form; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierClass; @@ -31,6 +32,8 @@ import io.github.dsheirer.preference.duplicate.CallManagementPreference; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.ThreadPool; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -41,6 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; /** * Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method. @@ -48,15 +52,26 @@ * Note: system in this context refers to the system name value that is used in channel configurations. All decoder * channels must share the same system name for call duplication detection. */ +@Component("duplicateCallDetector") public class DuplicateCallDetector implements Listener { private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class); private CallManagementPreference mCallManagementPreference; private Map mDetectorMap = new HashMap(); + @Resource + private UserPreferences mUserPreferences; - public DuplicateCallDetector(UserPreferences userPreferences) + /** + * Constructs an instance + */ + public DuplicateCallDetector() { - mCallManagementPreference = userPreferences.getDuplicateCallDetectionPreference(); + } + + @PostConstruct + public void postConstruct() + { + mCallManagementPreference = mUserPreferences.getCallManagementPreference(); } @Override @@ -81,7 +96,9 @@ public void receive(AudioSegment audioSegment) mDetectorMap.put(system, detector); } + audioSegment.addLease(detector.getClass().toString()); detector.add(audioSegment); + audioSegment.removeLease(getClass().toString()); } } } @@ -241,7 +258,7 @@ private void process() if(complete) { - audioSegment.decrementConsumerCount(); + audioSegment.removeLease(getClass().toString()); } return complete; @@ -270,7 +287,7 @@ private void process() if(isDuplicate(current, toCheck)) { toCheck.setDuplicate(true); - toCheck.decrementConsumerCount(); + toCheck.removeLease(getClass().toString()); duplicates.add(toCheck); } } diff --git a/src/main/java/io/github/dsheirer/audio/IAudioSegmentListener.java b/src/main/java/io/github/dsheirer/audio/IAudioSegmentListener.java index 8a25ffc93..27f6db7f4 100644 --- a/src/main/java/io/github/dsheirer/audio/IAudioSegmentListener.java +++ b/src/main/java/io/github/dsheirer/audio/IAudioSegmentListener.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -19,6 +19,7 @@ package io.github.dsheirer.audio; +import io.github.dsheirer.audio.call.AudioSegment; import io.github.dsheirer.sample.Listener; /** diff --git a/src/main/java/io/github/dsheirer/audio/IAudioSegmentProvider.java b/src/main/java/io/github/dsheirer/audio/IAudioSegmentProvider.java index 41a44ff44..473df8992 100644 --- a/src/main/java/io/github/dsheirer/audio/IAudioSegmentProvider.java +++ b/src/main/java/io/github/dsheirer/audio/IAudioSegmentProvider.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -19,6 +19,7 @@ package io.github.dsheirer.audio; +import io.github.dsheirer.audio.call.AudioSegment; import io.github.dsheirer.sample.Listener; /** diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java index ea6d4d15b..39f152693 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java @@ -22,7 +22,10 @@ import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; -import io.github.dsheirer.audio.AudioSegment; +import io.github.dsheirer.audio.call.AudioSegment; +import io.github.dsheirer.audio.call.Call; +import io.github.dsheirer.audio.call.CompletedCall; +import io.github.dsheirer.audio.call.ICallEventListener; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.identifier.MutableIdentifierCollection; @@ -32,14 +35,15 @@ import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.AudioSegmentRecorder; import io.github.dsheirer.record.RecordFormat; -import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.LinkedTransferQueue; @@ -47,59 +51,50 @@ import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; /** * Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on * disk and enqueues the temporary recording for streaming. */ -public class AudioStreamingManager implements Listener +@Component("audioStreamingManager") +public class AudioStreamingManager implements ICallEventListener { private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingManager.class); - private LinkedTransferQueue mNewAudioSegments = new LinkedTransferQueue<>(); - private List mAudioSegments = new ArrayList<>(); - private Listener mAudioRecordingListener; - private BroadcastFormat mBroadcastFormat; + @Resource + private BroadcastModel mBroadcastModel; + @Resource private UserPreferences mUserPreferences; + private LinkedTransferQueue mCompletedCallQueue = new LinkedTransferQueue<>(); + private List mCompletedCalls = new ArrayList<>(); + private BroadcastFormat mBroadcastFormat = BroadcastFormat.MP3; private ScheduledFuture mAudioSegmentProcessorFuture; private int mNextRecordingNumber = 1; /** * Constructs an instance - * @param listener to receive completed audio recordings - * @param broadcastFormat for temporary recordings - * @param userPreferences to manage recording directories */ - public AudioStreamingManager(Listener listener, BroadcastFormat broadcastFormat, UserPreferences userPreferences) + public AudioStreamingManager() { - mAudioRecordingListener = listener; - mBroadcastFormat = broadcastFormat; - mUserPreferences = userPreferences; - } - - /** - * Primary receive method - */ - @Override - public void receive(AudioSegment audioSegment) - { - mNewAudioSegments.add(audioSegment); } /** * Starts the scheduled audio segment processor */ + @PostConstruct public void start() { if(mAudioSegmentProcessorFuture == null) { - mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(), - 0, 250, TimeUnit.MILLISECONDS); + mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new CompletedCallProcessor(), + 0, 250, TimeUnit.MILLISECONDS); } } /** * Stops the scheduled audio segment processor */ + @PreDestroy public void stop() { if(mAudioSegmentProcessorFuture != null) @@ -108,51 +103,60 @@ public void stop() mAudioSegmentProcessorFuture = null; } - for(AudioSegment audioSegment: mNewAudioSegments) + for(CompletedCall completedCall: mCompletedCallQueue) { - audioSegment.decrementConsumerCount(); + completedCall.audioSegment().removeLease(getClass().toString()); } - mNewAudioSegments.clear(); + mCompletedCallQueue.clear(); - for(AudioSegment audioSegment: mAudioSegments) + for(CompletedCall completedCall: mCompletedCalls) { - audioSegment.decrementConsumerCount(); + completedCall.audioSegment().removeLease(getClass().toString()); } - mAudioSegments.clear(); + mCompletedCalls.clear(); } + /** + * Implements the ICallEventListener interface to receive and enqueue completed calls to process for streaming. + * @param call that was completed. + * @param audioSegment for the call + */ + @Override + public void completed(Call call, AudioSegment audioSegment) + { + mCompletedCallQueue.add(new CompletedCall(call, audioSegment)); + } + + @Override + public void added(Call call) {} //Not implemented + @Override + public void updated(Call call) {} //Not implemented + @Override + public void deleted(Call call) {} //Not implemented + /** * Main processing method to process audio segments */ - private void processAudioSegments() + private void processCompletedCalls() { - mNewAudioSegments.drainTo(mAudioSegments); + CompletedCall completedCall = mCompletedCallQueue.poll(); - Iterator it = mAudioSegments.iterator(); - AudioSegment audioSegment; - while(it.hasNext()) + while(completedCall != null) { - audioSegment = it.next(); + AudioSegment audioSegment = completedCall.audioSegment(); - if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateStreamingSuppressionEnabled()) + if(!(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateStreamingSuppressionEnabled())) { - it.remove(); - audioSegment.decrementConsumerCount(); - } - else if(audioSegment.completeProperty().get()) - { - it.remove(); - - if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels()) + if(mBroadcastModel != null && audioSegment.hasBroadcastChannels()) { IdentifierCollection identifiers = new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers()); if(identifiers.getToIdentifier() instanceof PatchGroupIdentifier patchGroupIdentifier) { - if(mUserPreferences.getDuplicateCallDetectionPreference() + if(mUserPreferences.getCallManagementPreference() .getPatchGroupStreamingOption() == PatchGroupStreamingOption.TALKGROUPS) { //Decompose the patch group into the individual (patched) talkgroups and process the audio @@ -203,9 +207,10 @@ else if(audioSegment.completeProperty().get()) processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels()); } } - - audioSegment.decrementConsumerCount(); } + + audioSegment.removeLease(getClass().toString()); + completedCall = mCompletedCallQueue.poll(); } } @@ -235,7 +240,7 @@ private void processAudioSegment(AudioSegment audioSegment, IdentifierCollection AudioRecording audioRecording = new AudioRecording(path, broadcastChannels, identifierCollection, audioSegment.getStartTimestamp(), length); - mAudioRecordingListener.receive(audioRecording); + mBroadcastModel.receive(audioRecording); } catch(IOException ioe) { @@ -269,20 +274,20 @@ private Path getTemporaryRecordingPath() } /** - * Scheduled runnable to process audio segments. + * Scheduled runnable to process completed calls. */ - public class AudioSegmentProcessor implements Runnable + public class CompletedCallProcessor implements Runnable { @Override public void run() { try { - processAudioSegments(); + processCompletedCalls(); } catch(Throwable t) { - mLog.error("Error processing audio segments for streaming", t); + mLog.error("Error processing completed calls for streaming", t); } } } diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java index f513938d3..9a51ac7e3 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -22,16 +22,14 @@ import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster; import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration; import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration; -import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster; -import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration; -import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerFeedConfiguration; -import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster; -import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration; -import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzFeedConfiguration; import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster; import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration; import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster; import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration; +import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster; +import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration; +import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster; +import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration; import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcaster; import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration; import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster; @@ -65,29 +63,26 @@ public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration con switch(configuration.getBroadcastServerType()) { case BROADCASTIFY_CALL: - return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration, - inputAudioFormat, mp3Setting, aliasModel); + return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration); case RDIOSCANNER_CALL: - return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration, - inputAudioFormat, mp3Setting, aliasModel); + return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration); case OPENMHZ: - return new OpenMHzBroadcaster((OpenMHzConfiguration)configuration, - inputAudioFormat, mp3Setting, aliasModel); + return new OpenMHzBroadcaster((OpenMHzConfiguration)configuration); case BROADCASTIFY: return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration, - inputAudioFormat, mp3Setting, aliasModel); + inputAudioFormat, mp3Setting); case ICECAST_TCP: return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat, - mp3Setting, aliasModel); + mp3Setting); case ICECAST_HTTP: return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat, - mp3Setting, aliasModel); + mp3Setting); case SHOUTCAST_V1: return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat, - mp3Setting, aliasModel); + mp3Setting); case SHOUTCAST_V2: return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration, - inputAudioFormat, mp3Setting, aliasModel); + inputAudioFormat, mp3Setting); case UNKNOWN: default: mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name()); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java index 6cd6fdf5f..c8754ae45 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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,12 +20,13 @@ import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; -import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.ThreadPool; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; @@ -45,9 +46,11 @@ import javafx.collections.ObservableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import javax.swing.table.AbstractTableModel; +@Component("broadcastModel") public class BroadcastModel extends AbstractTableModel implements Listener { private final static Logger mLog = LoggerFactory.getLogger(BroadcastModel.class); @@ -68,28 +71,30 @@ public class BroadcastModel extends AbstractTableModel implements Listener