diff --git a/src/main/java/io/github/dsheirer/alias/Alias.java b/src/main/java/io/github/dsheirer/alias/Alias.java
index 53abb627..d8554266 100644
--- a/src/main/java/io/github/dsheirer/alias/Alias.java
+++ b/src/main/java/io/github/dsheirer/alias/Alias.java
@@ -1,23 +1,20 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2025 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.alias;
@@ -30,13 +27,22 @@ import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.record.Record;
+import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
@@ -46,13 +52,6 @@ import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
/**
* Alias provides an aliasing (e.g. name, color, etc) container that is linked to multiple alias identifiers and
* provides a corresponding set of actions related to the alias.
@@ -74,6 +73,7 @@ public class Alias
private StringProperty mName = new SimpleStringProperty();
private ObservableList mAliasIDs = FXCollections.observableArrayList();
private ObservableList mAliasActions = FXCollections.observableArrayList();
+ private ObjectProperty mStreamTalkgroupAlias = new SimpleObjectProperty<>();
/**
* Constructs an instance and sets the specified name.
@@ -180,6 +180,12 @@ public class Alias
return mColor;
}
+ @JsonIgnore
+ public ObjectProperty streamTalkgroupAliasProperty()
+ {
+ return mStreamTalkgroupAlias;
+ }
+
/**
* Icon name property
*/
@@ -229,6 +235,24 @@ public class Alias
mName.set(name);
}
+ /**
+ * Alias name
+ */
+ @JacksonXmlProperty(isAttribute = true, localName = "stream_talkgroup_alias")
+ public StreamAsTalkgroup getStreamTalkgroupAlias()
+ {
+ return mStreamTalkgroupAlias.get();
+ }
+
+ /**
+ * Sets the stream as talkgroup value that will be used in the TO field for streamed audio calls.
+ * @param streamTalkgroupAlias with a talkgroup value.
+ */
+ public void setStreamTalkgroupAlias(StreamAsTalkgroup streamTalkgroupAlias)
+ {
+ mStreamTalkgroupAlias.set(streamTalkgroupAlias);
+ }
+
/**
* Alias list name for the alias list that this alias belongs to
*/
@@ -710,6 +734,7 @@ public class Alias
{
return (Alias a) -> new Observable[] {a.recordableProperty(), a.streamableProperty(), a.colorProperty(),
a.aliasListNameProperty(), a.groupProperty(), a.iconNameProperty(), a.nameProperty(), a.aliasIds(),
- a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty()};
+ a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty(), a.priorityProperty(),
+ a.streamTalkgroupAliasProperty()};
}
}
diff --git a/src/main/java/io/github/dsheirer/alias/id/AliasID.java b/src/main/java/io/github/dsheirer/alias/id/AliasID.java
index 79539e35..79e79f06 100644
--- a/src/main/java/io/github/dsheirer/alias/id/AliasID.java
+++ b/src/main/java/io/github/dsheirer/alias/id/AliasID.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2025 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
@@ -42,6 +42,7 @@ import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
+import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
@@ -70,6 +71,7 @@ import javafx.util.Callback;
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
@JsonSubTypes.Type(value = Record.class, name = "record"),
@JsonSubTypes.Type(value = SiteID.class, name = "siteID"),
+ @JsonSubTypes.Type(value = StreamAsTalkgroup.class, name = "streamAsTalkgroup"),
@JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"),
@JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"),
@JsonSubTypes.Type(value = TonesID.class, name = "tones"),
diff --git a/src/main/java/io/github/dsheirer/alias/id/talkgroup/StreamAsTalkgroup.java b/src/main/java/io/github/dsheirer/alias/id/talkgroup/StreamAsTalkgroup.java
new file mode 100644
index 00000000..97380ea2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/alias/id/talkgroup/StreamAsTalkgroup.java
@@ -0,0 +1,40 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2025 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.alias.id.talkgroup;
+
+import io.github.dsheirer.protocol.Protocol;
+
+/**
+ * Streaming talkgroup alias. For streamed audio metadata, this value is used to replace the decoded talkgroup or TO
+ * value with an alias value when there are talkgroup collisions resulting from streaming multiple channels to the
+ * same stream. This is common in P25 conventional repeater setups where each radio channel is using talkgroup 1.
+ */
+public class StreamAsTalkgroup extends Talkgroup
+{
+ public StreamAsTalkgroup(int talkgroup)
+ {
+ super(Protocol.UNKNOWN, talkgroup);
+ }
+
+ public StreamAsTalkgroup()
+ {
+ //No arg JAXB constructor
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java
index 442092bf..43ffa801 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2025 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,8 @@
package io.github.dsheirer.audio.broadcast.broadcastify;
import com.google.common.net.HttpHeaders;
+import io.github.dsheirer.alias.Alias;
+import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
@@ -32,6 +34,7 @@ import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
@@ -45,6 +48,7 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
+import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
@@ -76,6 +80,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster aliases = aliasList.getAliases(identifier);
+
+ for(Alias a: aliases)
+ {
+ if(a.getStreamTalkgroupAlias() != null)
+ {
+ return String.valueOf(a.getStreamTalkgroupAlias().getValue());
+ }
+ }
+ }
+ }
+ }
+
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return format(patchGroupIdentifier);
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastMetadata.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastMetadata.java
index 6fa78fc3..0ebd6d8c 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastMetadata.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastMetadata.java
@@ -1,23 +1,20 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2025 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.icecast;
@@ -25,21 +22,16 @@ import com.google.common.base.Joiner;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
-import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
-import io.github.dsheirer.properties.SystemProperties;
-import io.github.dsheirer.util.ThreadPool;
+import java.util.List;
+import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.List;
-
public class IcecastMetadata
{
private final static Logger mLog = LoggerFactory.getLogger(IcecastMetadata.class);
@@ -73,13 +65,23 @@ public class IcecastMetadata
if(to != null)
{
- sb.append("TO:").append(to);
-
List aliases = aliasList.getAliases(to);
- if(aliases != null && !aliases.isEmpty())
+ //Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
+ Optional streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
+
+ if(streamAs.isPresent())
{
- sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
+ sb.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
+ }
+ else
+ {
+ sb.append("TO:").append(to);
+
+ if(!aliases.isEmpty())
+ {
+ sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
+ }
}
}
else
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/openmhz/OpenMHzBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/openmhz/OpenMHzBroadcaster.java
index 8337a6aa..7a2f8bc7 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/openmhz/OpenMHzBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/openmhz/OpenMHzBroadcaster.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2025 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,56 +19,45 @@
package io.github.dsheirer.audio.broadcast.openmhz;
- import com.google.common.net.HttpHeaders;
- import com.google.common.base.Joiner;
- import io.github.dsheirer.alias.Alias;
- import io.github.dsheirer.alias.AliasList;
- import io.github.dsheirer.alias.AliasModel;
- import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
- import io.github.dsheirer.audio.broadcast.AudioRecording;
- import io.github.dsheirer.audio.broadcast.BroadcastEvent;
- import io.github.dsheirer.audio.broadcast.BroadcastState;
- import io.github.dsheirer.audio.convert.InputAudioFormat;
- import io.github.dsheirer.audio.convert.MP3Setting;
- import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
- import io.github.dsheirer.identifier.Form;
- import io.github.dsheirer.identifier.Identifier;
- import io.github.dsheirer.identifier.IdentifierClass;
- import io.github.dsheirer.identifier.MutableIdentifierCollection;
- import io.github.dsheirer.identifier.Role;
- import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
- import io.github.dsheirer.identifier.patch.PatchGroup;
- import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
- import io.github.dsheirer.identifier.radio.RadioIdentifier;
- import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
- import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
- import io.github.dsheirer.util.ThreadPool;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- import java.io.File;
- import java.nio.file.Files;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- import java.net.ConnectException;
- import java.net.URI;
- import java.net.URL;
- import java.net.URLConnection;
- import java.net.http.HttpClient;
- import java.net.http.HttpRequest;
- import java.net.http.HttpResponse;
- import java.time.Duration;
- import java.util.List;
- import java.util.Queue;
- import java.util.concurrent.CompletionException;
- import java.util.concurrent.LinkedTransferQueue;
- import java.util.concurrent.ScheduledFuture;
- import java.util.concurrent.TimeUnit;
+import com.google.common.net.HttpHeaders;
+import io.github.dsheirer.alias.Alias;
+import io.github.dsheirer.alias.AliasList;
+import io.github.dsheirer.alias.AliasModel;
+import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
+import io.github.dsheirer.audio.broadcast.AudioRecording;
+import io.github.dsheirer.audio.broadcast.BroadcastEvent;
+import io.github.dsheirer.audio.broadcast.BroadcastState;
+import io.github.dsheirer.audio.convert.InputAudioFormat;
+import io.github.dsheirer.audio.convert.MP3Setting;
+import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
+import io.github.dsheirer.identifier.Form;
+import io.github.dsheirer.identifier.Identifier;
+import io.github.dsheirer.identifier.IdentifierClass;
+import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
+import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
+import io.github.dsheirer.identifier.patch.PatchGroup;
+import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
+import io.github.dsheirer.identifier.radio.RadioIdentifier;
+import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
+import io.github.dsheirer.util.ThreadPool;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.util.List;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@@ -428,22 +417,40 @@
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*
*/
- private static String getTo(AudioRecording audioRecording)
+ private String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
- if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
+ if(identifier != null)
{
- return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
- }
- else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
- {
- return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
- talkgroupIdentifier.getProtocol()));
- }
- else if(identifier instanceof RadioIdentifier radioIdentifier)
- {
- return radioIdentifier.getValue().toString();
+ AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
+
+ if(aliasList != null)
+ {
+ List aliases = aliasList.getAliases(identifier);
+
+ //Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
+ Optional streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
+
+ if(streamAs.isPresent())
+ {
+ return String.valueOf(streamAs.get().getStreamTalkgroupAlias().getValue());
+ }
+ }
+
+ if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
+ {
+ return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
+ }
+ else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
+ {
+ return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
+ talkgroupIdentifier.getProtocol()));
+ }
+ else if(identifier instanceof RadioIdentifier radioIdentifier)
+ {
+ return radioIdentifier.getValue().toString();
+ }
}
return "0";
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/rdioscanner/RdioScannerBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/rdioscanner/RdioScannerBroadcaster.java
index fad9571f..468a42dd 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/rdioscanner/RdioScannerBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/rdioscanner/RdioScannerBroadcaster.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2025 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
@@ -34,11 +34,11 @@ import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import java.io.IOException;
@@ -49,6 +49,7 @@ import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
+import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
@@ -433,22 +434,40 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster aliases = aliasList.getAliases(identifier);
+
+ //Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
+ Optional streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
+
+ if(streamAs.isPresent())
+ {
+ return String.valueOf(streamAs.get().getStreamTalkgroupAlias().getValue());
+ }
+ }
+
+ if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
+ {
+ return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
+ }
+ else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
+ {
+ return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
+ talkgroupIdentifier.getProtocol()));
+ }
+ else if(identifier instanceof RadioIdentifier radioIdentifier)
+ {
+ return radioIdentifier.getValue().toString();
+ }
}
return "0";
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java
index 541c46f3..c8bf2879 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2025 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,17 @@ import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.util.ThreadPool;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
@@ -41,17 +52,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.UnsupportedEncodingException;
-import java.net.ConnectException;
-import java.net.InetSocketAddress;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.LinkedTransferQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-
public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater
{
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1BroadcastMetadataUpdater.class);
@@ -212,10 +212,20 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
if(to != null)
{
- sb.append("TO:").append(to);
-
List aliases = aliasList.getAliases(to);
+ //Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
+ Optional streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
+
+ if(streamAs.isPresent())
+ {
+ sb.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
+ }
+ else
+ {
+ sb.append("TO:").append(to);
+ }
+
if(!aliases.isEmpty())
{
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java
index f10fb690..9bf7cee8 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2025 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
@@ -49,6 +49,15 @@ import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.math3.util.FastMath;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
@@ -59,15 +68,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.ConnectException;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.LinkedTransferQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-
public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcaster implements IBroadcastMetadataUpdater
{
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV2AudioStreamingBroadcaster.class);
@@ -303,13 +303,23 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
if(to != null)
{
- sbTitle2.append("TO:").append(to);
-
List aliases = aliasList.getAliases(to);
- if(!aliases.isEmpty())
+ //Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
+ Optional streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
+
+ if(streamAs.isPresent())
{
- sbTitle2.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
+ sbTitle2.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
+ }
+ else
+ {
+ sbTitle2.append("TO:").append(to);
+
+ if(!aliases.isEmpty())
+ {
+ sbTitle2.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
+ }
}
}
else
diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java
index b6f3ddb8..70b5a9cf 100644
--- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java
+++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java
@@ -44,12 +44,14 @@ import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
+import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormatter;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import io.github.dsheirer.audio.broadcast.ConfiguredBroadcast;
import io.github.dsheirer.eventbus.MyEventBus;
+import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.gui.playlist.Editor;
import io.github.dsheirer.gui.playlist.alias.action.ActionEditor;
import io.github.dsheirer.gui.playlist.alias.action.ActionEditorFactory;
@@ -95,7 +97,9 @@ import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TextField;
+import javafx.scene.control.TextFormatter;
import javafx.scene.control.TitledPane;
+import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
@@ -152,6 +156,8 @@ public class AliasItemEditor extends Editor
private Button mDeleteActionButton;
private VBox mActionEditorBox;
private VBox mIdentifierEditorBox;
+ private TextField mStreamAsTalkgroupField;
+ private TextFormatter mStreamAsIntegerTextFormatter = new IntegerFormatter(1,0xFFFF);
private Map mIdentifierEditorMap = new HashMap<>();
private EmptyIdentifierEditor mEmptyIdentifierEditor = new EmptyIdentifierEditor();
@@ -358,6 +364,10 @@ public class AliasItemEditor extends Editor
alias.addAliasID(selected);
}
+ //Set or clear the 'Stream As Talkgroup' value.
+ Integer streamAsTalkgroup = mStreamAsIntegerTextFormatter.getValue();
+ alias.setStreamTalkgroupAlias(streamAsTalkgroup != null ? new StreamAsTalkgroup(streamAsTalkgroup) : null);
+
//Remove and replace the remaining non-audio identifiers
alias.removeNonAudioIdentifiers();
@@ -366,8 +376,16 @@ public class AliasItemEditor extends Editor
//Create a copy of the identifier so that the alias and the editor don't have the same instance
//and reset the overlap flag (if set) so that the alias list can reevaluate the overlap state.
AliasID copy = AliasFactory.copyOf(aliasID);
- copy.setOverlap(false);
- alias.addAliasID(copy);
+
+ if(copy != null)
+ {
+ copy.setOverlap(false);
+ alias.addAliasID(copy);
+ }
+ else
+ {
+ mLog.warn("Unable to create a copy of alias ID: " + aliasID);
+ }
}
//Remove and replace alias actions
@@ -415,6 +433,29 @@ public class AliasItemEditor extends Editor
MyEventBus.getGlobalEventBus().unregister(this);
}
+ /**
+ * Editor field for the stream this alias as a different talkgroup value
+ */
+ private TextField getStreamAsTalkgroupField()
+ {
+ if(mStreamAsTalkgroupField == null)
+ {
+ mStreamAsTalkgroupField = new TextField();
+ mStreamAsTalkgroupField.setDisable(true);
+ mStreamAsTalkgroupField.setTextFormatter(mStreamAsIntegerTextFormatter);
+ mStreamAsTalkgroupField.setTooltip(new Tooltip("When specified, all streamed call audio will use this " +
+ "talkgroup value in place of the decoded talkgroup value."));
+ mStreamAsTalkgroupField.textProperty().addListener((o, old, newV) -> {
+ if(getItem() != null)
+ {
+ modifiedProperty().set(true);
+ }
+ });
+ }
+
+ return mStreamAsTalkgroupField;
+ }
+
private VBox getTitledPanesBox()
{
if(mTitledPanesBox == null)
@@ -792,7 +833,7 @@ public class AliasItemEditor extends Editor
VBox selectedBox = new VBox();
VBox.setVgrow(getSelectedStreamsView(), Priority.ALWAYS);
- selectedBox.getChildren().addAll(new Label("Selected"),getSelectedStreamsView());
+ selectedBox.getChildren().addAll(new Label("Selected"), getSelectedStreamsView());
HBox hbox = new HBox();
hbox.setSpacing(10);
@@ -800,7 +841,17 @@ public class AliasItemEditor extends Editor
HBox.setHgrow(selectedBox, Priority.ALWAYS);
hbox.getChildren().addAll(availableBox, buttonBox, selectedBox);
- mStreamPane = new TitledPane("Streaming", hbox);
+ VBox vbox = new VBox();
+ vbox.setSpacing(10);
+ VBox.setVgrow(hbox, Priority.ALWAYS);
+ Label label = new Label("Stream this Alias as Talkgroup:");
+ HBox streamAsHBox = new HBox();
+ streamAsHBox.setAlignment(Pos.CENTER_LEFT);
+ streamAsHBox.setSpacing(10);
+ streamAsHBox.getChildren().addAll(label, getStreamAsTalkgroupField());
+ vbox.getChildren().addAll(hbox, streamAsHBox);
+
+ mStreamPane = new TitledPane("Streaming", vbox);
mStreamPane.setExpanded(false);
}
@@ -809,33 +860,44 @@ public class AliasItemEditor extends Editor
private void updateStreamViews()
{
- Platform.runLater(new Runnable()
- {
- @Override
- public void run()
+ Platform.runLater(() -> {
+ getAvailableStreamsView().getItems().clear();
+ getSelectedStreamsView().getItems().clear();
+ getAvailableStreamsView().setDisable(getItem() == null);
+ getSelectedStreamsView().setDisable(getItem() == null);
+ getStreamAsTalkgroupField().setDisable(getItem() == null);
+
+ if(getItem() != null)
{
- getAvailableStreamsView().getItems().clear();
- getSelectedStreamsView().getItems().clear();
- getAvailableStreamsView().setDisable(getItem() == null);
- getSelectedStreamsView().setDisable(getItem() == null);
+ List availableStreams = mPlaylistManager.getBroadcastModel().getBroadcastConfigurationNames();
- if(getItem() != null)
+ Set selectedChannels = getItem().getBroadcastChannels();
+
+ for(BroadcastChannel channel: selectedChannels)
{
- List availableStreams = mPlaylistManager.getBroadcastModel().getBroadcastConfigurationNames();
-
- Set selectedChannels = getItem().getBroadcastChannels();
-
- for(BroadcastChannel channel: selectedChannels)
+ if(availableStreams.contains(channel.getChannelName()))
{
- if(availableStreams.contains(channel.getChannelName()))
- {
- availableStreams.remove(channel.getChannelName());
- }
+ availableStreams.remove(channel.getChannelName());
}
-
- getSelectedStreamsView().getItems().addAll(selectedChannels);
- getAvailableStreamsView().getItems().addAll(availableStreams);
}
+
+ getSelectedStreamsView().getItems().addAll(selectedChannels);
+ getAvailableStreamsView().getItems().addAll(availableStreams);
+
+ AliasID streamAs = getItem().getStreamTalkgroupAlias();
+
+ if(streamAs instanceof StreamAsTalkgroup sat)
+ {
+ mStreamAsIntegerTextFormatter.setValue(sat.getValue());
+ }
+ else
+ {
+ mStreamAsIntegerTextFormatter.setValue(null);
+ }
+ }
+ else
+ {
+ mStreamAsIntegerTextFormatter.setValue(null);
}
});
}