From 52b948f195ef99f7ae824379abe654c1a646ed47 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 17 Oct 2026 15:32:42 -0400 Subject: [PATCH] 0.1.4: a call's audio from another site's copy Trunk Recorder Pro's systems now have sites (trunk-recorder-plugin 0.4.0): a call heard on several starts and ends once, by its first site's copy, and its live audio comes from whichever copy has it, which can have another call id. Such audio now finds the call by its system and talkgroup, and calls are kept even when that first copy isn't recorded, so a stream on a patched talkgroup still gets it, with its radio and frequency. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 +++++++++ Cargo.lock | 6 +++--- Cargo.toml | 4 ++-- README.md | 4 ++++ src/main.rs | 22 ++++++++++++++++++++-- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06905a1..640aae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [0.1.4] + +- A system's sites: Trunk Recorder Pro now has systems with several sites, and + a call heard on more than one starts and ends once, its live audio coming + from whichever site's copy has it. Audio from a copy that wasn't the one + that started now still finds its call (its patched talkgroups, radio and + frequency), also when that first copy wasn't recorded. +- Built with trunk-recorder-plugin 0.4.0. + ## [0.1.3] - A system can have streams of its own (the recorder's per-system override of the streams); they carry only its audio, and the recorder-wide streams no longer carry it. diff --git a/Cargo.lock b/Cargo.lock index 2675b56..9e338ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,7 +162,7 @@ dependencies = [ [[package]] name = "simplestream" -version = "0.1.3" +version = "0.1.4" dependencies = [ "schemars", "serde", @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "trunk-recorder-plugin" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f6a3ae3954c34ae02ca4a347777d30481ec4f6b456c6adc7bbe59e8117751e" +checksum = "587edb957af1d5bd2cd1a8a0e362419d731d95b78e3ca4181a1494d0f8831753" dependencies = [ "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 153991f..a703119 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "simplestream" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Streams the audio of calls as they're recorded to other programs, over UDP or TCP." license = "GPL-3.0-or-later" @@ -9,7 +9,7 @@ authors = ["Luke Berndt "] publish = false [dependencies] -trunk-recorder-plugin = "0.3.0" +trunk-recorder-plugin = "0.4.0" schemars = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/README.md b/README.md index c57ac36..e92271d 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ A list of **streams**. Each sends some of the audio to one place: Talkgroup numbers repeat between systems, so stream each system to a port of its own, or turn on JSON (which names the system). +A system can have several sites. A call heard on more than one is one call: +its audio streams once, from whichever site's copy has it at the moment, and +`short_name` is the system's. + ## Packets Each packet is one slice of audio, with, depending on the settings: diff --git a/src/main.rs b/src/main.rs index db6661e..a5db959 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,7 +115,7 @@ impl Out { struct SimpleStream { outs: Vec, - /// The calls recording now. + /// The calls going now (each once, however many sites hear it). calls: HashMap, } @@ -167,7 +167,9 @@ impl Plugin for SimpleStream { } fn call_start(&mut self, call: CallInfo) { + // (Kept even when not recorded here: another site's copy of it may be, and its audio is this call's.) if !call.recording { + self.calls.insert(call.id, call); return; } let short_name = call.short_name.clone(); @@ -194,7 +196,10 @@ impl Plugin for SimpleStream { fn audio(&mut self, chunk: AudioChunk) { let pcm = base64::decode(&chunk.pcm); - let call = self.calls.get(&chunk.call_id); + // A call heard on several sites plays from whichever copy has it: one that wasn't + // told to start is the call that was (the same system and talkgroup). + let twin = || self.calls.values().find(|c| c.short_name == chunk.short_name && c.talkgroup == chunk.talkgroup); + let call = self.calls.get(&chunk.call_id).or_else(twin); let short_name = chunk.short_name.as_str(); let talkgroups = call.map_or(vec![chunk.talkgroup], talkgroups); for o in self.outs.iter().filter(|o| o.wants_system(short_name)) { @@ -372,6 +377,19 @@ mod tests { assert_eq!(last.extra["packetsSent"], 1); } + /// A call heard on several sites is started once, by its first site's copy (here not + /// recorded); its audio can come from another site's copy, with another id: it is still the call. + #[test] + fn audio_from_another_sites_copy() { + let (s, url) = udp(); + let mut first = call(1, 101, vec![101, 202]); + first.recording = false; + testing::run::([hello(json!([{ "url": url, "TGID": 202, "sendJSON": true }])), HostMessage::CallStart(first), audio(9, 101, &[1])]); + let (a, pcm) = split(&recv(&s).unwrap()); + assert_eq!((a["talkgroup"].clone(), a["src"].clone(), a["freq"].clone()), (json!(202), json!(1234), json!(851012500u64)), "{a}"); + assert_eq!(pcm, [1, 0]); + } + #[test] fn json_and_call_events() { let (s, url) = udp();