mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
* WIP - Adds support for aliasing P25 Phase 2 tones.Playlist editor - adds editor support for alias actions * Playlist editor implementation and integration into application is complete. Adds JavaFX window monitoring to persist window size/location across application runs. Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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
|
||||
@@ -29,6 +29,9 @@ 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 javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -68,11 +71,12 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
public static final String[] COLUMN_NAMES = new String[]
|
||||
{"Streaming", "Name", "Status", "Queued", "Streamed", "Aged Off"};
|
||||
|
||||
private List<BroadcastConfiguration> mBroadcastConfigurations = new CopyOnWriteArrayList<>();
|
||||
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
||||
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
||||
private ObservableList<BroadcastConfiguration> mBroadcastConfigurations =
|
||||
FXCollections.observableArrayList(BroadcastConfiguration.extractor());
|
||||
private List<AudioRecording> mRecordingQueue = new CopyOnWriteArrayList<>();
|
||||
|
||||
private Map<String,BroadcastConfiguration> mBroadcastConfigurationMap = new HashMap<>();
|
||||
private Map<String,AudioBroadcaster> mBroadcasterMap = new HashMap<>();
|
||||
private Map<Integer,AudioBroadcaster> mBroadcasterMap = new HashMap<>();
|
||||
private IconManager mIconManager;
|
||||
private AliasModel mAliasModel;
|
||||
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
|
||||
@@ -89,6 +93,31 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
|
||||
|
||||
removeOrphanedTemporaryRecordings();
|
||||
mBroadcastConfigurations.addListener(new ConfigurationChangeListener());
|
||||
|
||||
//TODO: remove broadcast of change events and simply have the playlist manager register as a listener on the configs
|
||||
//TODO: list to detect changes and execute playlist saves.
|
||||
}
|
||||
|
||||
/**
|
||||
* List of broadcast configurations with (optional) audio broadcasters created from each configuration
|
||||
*/
|
||||
public ObservableList<ConfiguredBroadcast> getConfiguredBroadcasts()
|
||||
{
|
||||
return mConfiguredBroadcasts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all broadcast configurations and shuts down any running broadcasters.
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
List<BroadcastConfiguration> configsToRemove = new ArrayList<>(mBroadcastConfigurations);
|
||||
|
||||
for(BroadcastConfiguration configToRemove: configsToRemove)
|
||||
{
|
||||
removeBroadcastConfiguration(configToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +138,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
/**
|
||||
* Current list of broadcastAudio configurations
|
||||
*/
|
||||
public List<BroadcastConfiguration> getBroadcastConfigurations()
|
||||
public ObservableList<BroadcastConfiguration> getBroadcastConfigurations()
|
||||
{
|
||||
return mBroadcastConfigurations;
|
||||
}
|
||||
@@ -128,7 +157,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
/**
|
||||
* Adds the broadcastAudio configuration to this model
|
||||
*/
|
||||
public void addBroadcastConfiguration(BroadcastConfiguration configuration)
|
||||
public ConfiguredBroadcast addBroadcastConfiguration(BroadcastConfiguration configuration)
|
||||
{
|
||||
if(configuration != null)
|
||||
{
|
||||
@@ -137,16 +166,20 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
if(!mBroadcastConfigurations.contains(configuration))
|
||||
{
|
||||
mBroadcastConfigurations.add(configuration);
|
||||
ConfiguredBroadcast configuredBroadcast = new ConfiguredBroadcast(configuration);
|
||||
mConfiguredBroadcasts.add(configuredBroadcast);
|
||||
|
||||
int index = mBroadcastConfigurations.size() - 1;
|
||||
|
||||
fireTableRowsInserted(index, index);
|
||||
|
||||
mBroadcastConfigurationMap.put(configuration.getName(), configuration);
|
||||
|
||||
process(new BroadcastEvent(configuration, BroadcastEvent.Event.CONFIGURATION_ADD));
|
||||
|
||||
return configuredBroadcast;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,7 +292,15 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
|
||||
mBroadcastConfigurations.remove(broadcastConfiguration);
|
||||
|
||||
mBroadcastConfigurationMap.remove(broadcastConfiguration.getName());
|
||||
Iterator<ConfiguredBroadcast> it = mConfiguredBroadcasts.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
if(it.next().getBroadcastConfiguration() == broadcastConfiguration)
|
||||
{
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
process(new BroadcastEvent(broadcastConfiguration, BroadcastEvent.Event.CONFIGURATION_DELETE));
|
||||
|
||||
@@ -305,16 +346,32 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
*/
|
||||
private void createBroadcaster(BroadcastConfiguration broadcastConfiguration)
|
||||
{
|
||||
if(broadcastConfiguration != null && broadcastConfiguration.isEnabled() && broadcastConfiguration.isValid() &&
|
||||
!mBroadcasterMap.containsKey(broadcastConfiguration.getName()))
|
||||
if(broadcastConfiguration != null && broadcastConfiguration.isEnabled() && broadcastConfiguration.isValid())
|
||||
{
|
||||
//Remove current broadcaster if one exists
|
||||
deleteBroadcaster(broadcastConfiguration.getId());
|
||||
|
||||
AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration, mAliasModel);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
audioBroadcaster.setListener(broadcastEvent -> process(broadcastEvent));
|
||||
audioBroadcaster.start();
|
||||
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getName(), audioBroadcaster);
|
||||
|
||||
Iterator<ConfiguredBroadcast> it = mConfiguredBroadcasts.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
ConfiguredBroadcast configuredBroadcast = it.next();
|
||||
|
||||
if(configuredBroadcast.getBroadcastConfiguration() == broadcastConfiguration)
|
||||
{
|
||||
configuredBroadcast.setAudioBroadcaster(audioBroadcaster);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getId(), audioBroadcaster);
|
||||
|
||||
int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
|
||||
|
||||
if(index >= 0)
|
||||
@@ -330,16 +387,29 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
/**
|
||||
* Shut down a broadcaster created from the configuration and remove it from this model
|
||||
*/
|
||||
private void deleteBroadcaster(String name)
|
||||
private void deleteBroadcaster(int id)
|
||||
{
|
||||
if(name != null && mBroadcasterMap.containsKey(name))
|
||||
if(mBroadcasterMap.containsKey(id))
|
||||
{
|
||||
AudioBroadcaster audioBroadcaster = mBroadcasterMap.remove(name);
|
||||
AudioBroadcaster audioBroadcaster = mBroadcasterMap.remove(id);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
Iterator<ConfiguredBroadcast> it = mConfiguredBroadcasts.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
ConfiguredBroadcast configuredBroadcast = it.next();
|
||||
|
||||
if(configuredBroadcast.getBroadcastConfiguration().getId() == id)
|
||||
{
|
||||
configuredBroadcast.setAudioBroadcaster(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
audioBroadcaster.stop();
|
||||
audioBroadcaster.removeListener();
|
||||
audioBroadcaster.dispose();
|
||||
|
||||
int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
|
||||
|
||||
@@ -358,7 +428,15 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
*/
|
||||
public BroadcastConfiguration getBroadcastConfiguration(String streamName)
|
||||
{
|
||||
return mBroadcastConfigurationMap.get(streamName);
|
||||
for(BroadcastConfiguration config: mBroadcastConfigurations)
|
||||
{
|
||||
if(config.getName() != null && config.getName().contentEquals(streamName))
|
||||
{
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,24 +477,23 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
break;
|
||||
case CONFIGURATION_CHANGE:
|
||||
BroadcastConfiguration broadcastConfiguration = broadcastEvent.getBroadcastConfiguration();
|
||||
int index = mBroadcastConfigurations.indexOf(broadcastConfiguration);
|
||||
|
||||
//Delete the existing broadcaster for any broadcast configuration changes
|
||||
String previousChannelName = cleanupMapAssociations(broadcastConfiguration);
|
||||
deleteBroadcaster(previousChannelName);
|
||||
//Delete the broadcaster if it exists
|
||||
deleteBroadcaster(broadcastConfiguration.getId());
|
||||
|
||||
//If the configuration is enabled, create a new broadcaster after a brief delay
|
||||
if(broadcastConfiguration.isEnabled())
|
||||
{
|
||||
//Delay restarting the broadcaster to allow remote server time to cleanup
|
||||
ThreadPool.SCHEDULED.schedule(new DelayedBroadcasterStartup(broadcastConfiguration),
|
||||
3, TimeUnit.SECONDS);
|
||||
1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
int index = mBroadcastConfigurations.indexOf(broadcastConfiguration);
|
||||
fireTableRowsUpdated(index, index);
|
||||
break;
|
||||
case CONFIGURATION_DELETE:
|
||||
deleteBroadcaster(broadcastEvent.getBroadcastConfiguration().getName());
|
||||
deleteBroadcaster(broadcastEvent.getBroadcastConfiguration().getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -457,37 +534,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
broadcast(broadcastEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the name of a broadcast configuration changes, remove the old map associations so that they can be replaced
|
||||
* with the new map associations. Remove any broadcaster that is associated with the old configuration name.
|
||||
*
|
||||
* @param broadcastConfiguration
|
||||
* @return previous or current channel name that has been replaced with the current configuration channel name so
|
||||
* that anything else associated with the previous name can be cleaned up.
|
||||
*/
|
||||
private String cleanupMapAssociations(BroadcastConfiguration broadcastConfiguration)
|
||||
{
|
||||
String oldName = null;
|
||||
|
||||
for(Map.Entry<String,BroadcastConfiguration> entry : mBroadcastConfigurationMap.entrySet())
|
||||
{
|
||||
if(entry.getValue() == broadcastConfiguration)
|
||||
{
|
||||
oldName = entry.getKey();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(oldName != null)
|
||||
{
|
||||
mBroadcastConfigurationMap.remove(oldName);
|
||||
}
|
||||
|
||||
mBroadcastConfigurationMap.put(broadcastConfiguration.getName(), broadcastConfiguration);
|
||||
|
||||
return oldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
@@ -649,37 +695,51 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
*/
|
||||
private void removeOrphanedTemporaryRecordings()
|
||||
{
|
||||
Path path = SystemProperties.getInstance().getApplicationFolder(BroadcastModel.TEMPORARY_STREAM_DIRECTORY);
|
||||
|
||||
if(path != null && Files.isDirectory(path))
|
||||
ThreadPool.SCHEDULED.submit(new Runnable()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(TEMPORARY_STREAM_FILE_SUFFIX);
|
||||
sb.append("*.*");
|
||||
|
||||
try(DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path, sb.toString()))
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
directoryStream.forEach(new Consumer<Path>()
|
||||
try
|
||||
{
|
||||
@Override
|
||||
public void accept(Path path)
|
||||
Path path = SystemProperties.getInstance().getApplicationFolder(BroadcastModel.TEMPORARY_STREAM_DIRECTORY);
|
||||
|
||||
if(path != null && Files.isDirectory(path))
|
||||
{
|
||||
try
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(TEMPORARY_STREAM_FILE_SUFFIX);
|
||||
sb.append("*.*");
|
||||
|
||||
try(DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path, sb.toString()))
|
||||
{
|
||||
Files.delete(path);
|
||||
directoryStream.forEach(new Consumer<Path>()
|
||||
{
|
||||
@Override
|
||||
public void accept(Path path)
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.delete(path);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Couldn't delete orphaned temporary recording: " + path.toString(), ioe);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Couldn't delete orphaned temporary recording: " + path.toString(), ioe);
|
||||
mLog.error("Error discovering orphaned temporary stream recording files", ioe);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error during cleanup of orphaned temporary streaming recording files");
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error discovering orphaned temporary stream recording files", ioe);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -743,4 +803,29 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List change listener to detect broadcast configuration changes and broadcast a change event
|
||||
*/
|
||||
public class ConfigurationChangeListener implements ListChangeListener<BroadcastConfiguration>
|
||||
{
|
||||
@Override
|
||||
public void onChanged(Change<? extends BroadcastConfiguration> change)
|
||||
{
|
||||
while(change.next())
|
||||
{
|
||||
if(change.wasUpdated())
|
||||
{
|
||||
for(int x = change.getFrom(); x < change.getTo(); x++)
|
||||
{
|
||||
BroadcastConfiguration config = change.getList().get(x);
|
||||
BroadcastEvent event = new BroadcastEvent(config, BroadcastEvent.Event.CONFIGURATION_CHANGE);
|
||||
process(event);
|
||||
broadcast(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user