Release v4.7: keep trying your configured peer after a reboot (#15)
Removes the heartbeat "adopt a live address" feature (added v1.6). On a LAN with more than one RemSound machine it could latch a receiver onto an unrelated sender that happened to be pinging the audio port — then never recover to the real peer, needing a manual restart (the singer's #15, with log). The feature guessed peer identity from an untracked ping source with no way to verify it was the same peer; on the stable VPN/LAN addresses RemSound is actually used with, it only ever caused harm, since same-address reconnect already works via the continuous heartbeat. Removed TryAdoptLiveHeartbeatAddress + IsPrivateLanAddress (MainForm) and GetUntrackedPingSources + recentPingSources (HeartbeatService); the identity-safe discovery-based following (by verified peer ID) stays. Manual troubleshooting entry rewritten to match. Also bundles the held changes since v4.6: status reads line-by-line with GB totals and double-press-to-copy, CPU/memory in the status, the Install Scripts folder, and the what's-new-after-failed-update fix. Version 4.7; About + RELEASE_NOTES updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
843be9b7fb
commit
64d5cb5a86
@@ -20,6 +20,35 @@ internal sealed class AboutDialog : Form
|
||||
/// updates" path.</summary>
|
||||
private const string ReleaseNotes =
|
||||
"""
|
||||
RemSound v4.7
|
||||
|
||||
Sound now comes back on its own after a reboot.
|
||||
|
||||
If RemSound started before your network or VPN
|
||||
was up, it could latch onto a different machine
|
||||
on your network and stay silent until you closed
|
||||
and reopened it. It now keeps trying the peer you
|
||||
chose and connects the moment it answers — no
|
||||
manual reconnect. (Thank you to the singer who
|
||||
reported this and sent the log that pinned it
|
||||
down.)
|
||||
|
||||
A few improvements for screen-reader users. The
|
||||
"Speak the RemSound status information" hotkey
|
||||
now reads the status a line at a time, shows big
|
||||
data totals in gigabytes, and a quick double
|
||||
press copies the status to the clipboard so you
|
||||
can share it. The status also now includes how
|
||||
much CPU and memory RemSound itself is using.
|
||||
|
||||
If RemSound ever won't start because the .NET
|
||||
runtime is missing, there's a new "Install
|
||||
Scripts" folder next to the program with a
|
||||
one-click installer for it.
|
||||
|
||||
And the "what's new" notes no longer pop up a
|
||||
second time after an update that didn't finish.
|
||||
|
||||
RemSound v4.6
|
||||
|
||||
Follow your Windows default audio device.
|
||||
|
||||
@@ -322,9 +322,6 @@ public sealed class MainForm : Form
|
||||
private int continuousTuneIntervalSec = 5;
|
||||
private long lastObservedUnderrunCount;
|
||||
private HeartbeatService? heartbeatService;
|
||||
// Last time TryAdoptLiveHeartbeatAddress re-pointed the sender at a peer's live address.
|
||||
// Gives a fresh endpoint time to prove healthy before another swap can fire (anti-thrash).
|
||||
private DateTime lastAddressAdoptionUtc = DateTime.MinValue;
|
||||
// Tracks whether each peer was last considered CONNECTED, for the connect/disconnect cues.
|
||||
// "Connected" now means audio is actually flowing OR the heartbeat is healthy — not the
|
||||
// heartbeat alone (see DetectAndAnnouncePeerHealthTransitions). The bool, rather than the
|
||||
@@ -1201,7 +1198,6 @@ public sealed class MainForm : Form
|
||||
UpdateStatus();
|
||||
SnapshotLogIfDue();
|
||||
EnsureRequestedAudioRunning();
|
||||
TryAdoptLiveHeartbeatAddress();
|
||||
// Refresh the Connectivity tab's peer lists from the same 1 Hz tick — replaces
|
||||
// the dialog's old 1.5 s dedicated refresh timer. Each Sync* helper short-circuits
|
||||
// when its signature is unchanged so NVDA isn't spammed with re-announcements.
|
||||
@@ -5277,7 +5273,7 @@ public sealed class MainForm : Form
|
||||
// heard as heavy crackle (Tech Singer's Win7-over-VPN report, 2026-05-31). Keeping the
|
||||
// endpoint pinned while it's still passing heartbeats stops the thrash. A genuine move
|
||||
// (DHCP renewal, Wi-Fi switch) makes the old endpoint go unreachable first, at which
|
||||
// point the guard lets the move through; TryAdoptLiveHeartbeatAddress backs it up.
|
||||
// point the guard lets the move through.
|
||||
foreach (var (id, oldEndpoint) in selectedPeerEndpoints.ToList())
|
||||
{
|
||||
if (!knownPeers.TryGetValue(id, out var peer)) continue;
|
||||
@@ -5366,58 +5362,6 @@ public sealed class MainForm : Form
|
||||
/// messier multi-peer case is left for the user to sort out by hand. Runs once per second
|
||||
/// from the status ticker. 2026-05-15.
|
||||
/// </summary>
|
||||
private void TryAdoptLiveHeartbeatAddress()
|
||||
{
|
||||
if (heartbeatService is null || !connected) return;
|
||||
// Cooldown: adoption re-points the sender; give a freshly-adopted endpoint time to
|
||||
// prove healthy (or fail) before another swap can fire.
|
||||
if (DateTime.UtcNow - lastAddressAdoptionUtc < TimeSpan.FromSeconds(10)) return;
|
||||
|
||||
var unreachable = heartbeatService.GetAllPeerHealth()
|
||||
.Where(h => h.State == PeerHealthState.Unreachable)
|
||||
.ToList();
|
||||
if (unreachable.Count != 1) return; // 0 = nothing wrong; 2+ = ambiguous
|
||||
|
||||
var liveSources = heartbeatService.GetUntrackedPingSources();
|
||||
if (liveSources.Count != 1) return; // 0 = no candidate; 2+ = ambiguous
|
||||
|
||||
var deadEp = unreachable[0].AudioEndpoint;
|
||||
var liveAddr = liveSources[0];
|
||||
if (liveAddr.Equals(deadEp.Address)) return; // same machine — nothing to adopt
|
||||
if (!IsPrivateLanAddress(liveAddr)) return; // never adopt a public / relay source
|
||||
|
||||
// Find the selected-peer entry whose endpoint is the dead one.
|
||||
var match = selectedPeerEndpoints
|
||||
.FirstOrDefault(kv => kv.Value.Address.Equals(deadEp.Address) && kv.Value.Port == deadEp.Port);
|
||||
if (match.Key == Guid.Empty) return;
|
||||
|
||||
// Reuse the dead endpoint's port — a peer that moved on the LAN keeps its audio port.
|
||||
var newEp = new IPEndPoint(liveAddr, deadEp.Port);
|
||||
selectedPeerEndpoints[match.Key] = newEp;
|
||||
var label = selectedPeerLabels.GetValueOrDefault(match.Key, deadEp.Address.ToString());
|
||||
logFile.Event($"heartbeat: adopted live address for \"{label}\": {deadEp} unreachable, peer is pinging from {newEp}");
|
||||
lastAddressAdoptionUtc = DateTime.UtcNow;
|
||||
|
||||
// ApplyAudioRuntime re-points BOTH the audio sender (SetReceivers) and heartbeat
|
||||
// tracking (SetTrackedPeers); PushAllowedReceiveSenders re-points the receiver
|
||||
// allow-list; PushDiscoveryUnicastHints feeds the new address to discovery too.
|
||||
ApplyAudioRuntime();
|
||||
PushAllowedReceiveSenders();
|
||||
PushDiscoveryUnicastHints();
|
||||
}
|
||||
|
||||
/// <summary>True if <paramref name="addr"/> is an IPv4 RFC1918 private-range address
|
||||
/// (10/8, 172.16/12, 192.168/16). Gates stale-address adoption so a relay's public
|
||||
/// source address can never be mistaken for a peer that moved on the LAN.</summary>
|
||||
private static bool IsPrivateLanAddress(IPAddress addr)
|
||||
{
|
||||
if (addr.AddressFamily != AddressFamily.InterNetwork) return false;
|
||||
var b = addr.GetAddressBytes();
|
||||
return b[0] == 10
|
||||
|| (b[0] == 172 && b[1] >= 16 && b[1] <= 31)
|
||||
|| (b[0] == 192 && b[1] == 168);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wipes the rolling max-gap window and pushes <see cref="lastSourceChangeUtc"/> forward,
|
||||
/// so the next continuous auto-tune tick has nothing to react to. Called whenever a user
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
tag_name on the latest GitHub release; bump it on every public release. The
|
||||
AssemblyVersion / FileVersion default to this value, and Assembly.GetName().Version
|
||||
is what the About dialog and the updater both read. -->
|
||||
<Version>4.6</Version>
|
||||
<Version>4.7</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user