diff --git a/TODO.md b/TODO.md
index f8d80bf..008598d 100644
--- a/TODO.md
+++ b/TODO.md
@@ -59,6 +59,10 @@ audio and initial OBS source inventory their own soft gateway budgets, so normal
or pathological capture cadence cannot rate-limit and disconnect the Companion
control connection.
+Experimental.10 deduplicates OBS source inventories and unchanged source-state
+notifications at both ends, removing the high-volume source-update feedback seen
+with large scene collections while retaining the non-disconnecting safety budget.
+
Release-blocking work remains:
- Package the native whisper.cpp worker as the Windows CUDA runtime, then benchmark
diff --git a/companion/installer/Lumi.Companion.iss b/companion/installer/Lumi.Companion.iss
index e4ece72..16cd263 100644
--- a/companion/installer/Lumi.Companion.iss
+++ b/companion/installer/Lumi.Companion.iss
@@ -1,5 +1,5 @@
#ifndef AppVersion
- #define AppVersion "0.1.0-experimental.9"
+ #define AppVersion "0.1.0-experimental.10"
#endif
#ifndef SourceRoot
#error SourceRoot must point at the self-contained Companion publish directory.
diff --git a/companion/scripts/publish-companion.ps1 b/companion/scripts/publish-companion.ps1
index 1e440fd..8bd7a04 100644
--- a/companion/scripts/publish-companion.ps1
+++ b/companion/scripts/publish-companion.ps1
@@ -1,5 +1,5 @@
param(
- [string]$Version = "0.1.0-experimental.9",
+ [string]$Version = "0.1.0-experimental.10",
[string]$BridgeVersion = "0.1.0-experimental.5"
)
diff --git a/companion/src/Lumi.Companion.App/CompanionRuntime.cs b/companion/src/Lumi.Companion.App/CompanionRuntime.cs
index 74f9d02..256bb8d 100644
--- a/companion/src/Lumi.Companion.App/CompanionRuntime.cs
+++ b/companion/src/Lumi.Companion.App/CompanionRuntime.cs
@@ -564,7 +564,10 @@ public sealed class CompanionRuntime : IAsyncDisposable
ReadString(source, "display_name") ?? ReadString(source, "name") ?? "OBS source",
ReadBoolean(source, "program_active") || ReadBoolean(source, "active"),
ReadBoolean(source, "source_missing") || ReadBoolean(source, "missing")))
- .Where(source => Guid.TryParse(source.Uuid, out _)).ToArray();
+ .Where(source => Guid.TryParse(source.Uuid, out _))
+ .GroupBy(source => source.Uuid)
+ .Select(group => group.Last())
+ .ToArray();
_bridgeSelectionAttached = null;
ObsSourcesChanged?.Invoke(ObsSources);
foreach (var source in ObsSources) await SendSourceUpdateAsync(source);
@@ -576,6 +579,8 @@ public sealed class CompanionRuntime : IAsyncDisposable
var source = new ObsSource(ReadString(message, "source_uuid") ?? string.Empty, ReadString(message, "display_name") ?? "OBS source", ReadBoolean(message, "program_active"), ReadBoolean(message, "source_missing"));
if (Guid.TryParse(source.Uuid, out _))
{
+ var previous = ObsSources.FirstOrDefault(item => item.Uuid == source.Uuid);
+ if (previous == source) return;
ObsSources = ObsSources.Where(item => item.Uuid != source.Uuid).Append(source).OrderBy(item => item.Name).ToArray();
ObsSourcesChanged?.Invoke(ObsSources);
await SendSourceUpdateAsync(source);
diff --git a/companion/src/Lumi.Companion.App/Lumi.Companion.App.csproj b/companion/src/Lumi.Companion.App/Lumi.Companion.App.csproj
index c22cecc..f0567ec 100644
--- a/companion/src/Lumi.Companion.App/Lumi.Companion.App.csproj
+++ b/companion/src/Lumi.Companion.App/Lumi.Companion.App.csproj
@@ -5,7 +5,7 @@
enable
enable
app.manifest
- 0.1.0-experimental.9
+ 0.1.0-experimental.10
0.1.0.0
diff --git a/plugins/lumi_transcription/backend/companion/gateway.js b/plugins/lumi_transcription/backend/companion/gateway.js
index c7a2808..650385a 100644
--- a/plugins/lumi_transcription/backend/companion/gateway.js
+++ b/plugins/lumi_transcription/backend/companion/gateway.js
@@ -36,6 +36,7 @@ class CompanionGateway {
let sourceMessagesInWindow = 0;
let audioMessagesDropped = 0;
let sourceMessagesDropped = 0;
+ const sourceStates = new Map();
let messageChain = Promise.resolve();
const send = (type, payload, sessionId = session?.id || null) => {
if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify(envelope(type, payload, sessionId)));
@@ -65,6 +66,13 @@ class CompanionGateway {
}
const message = parseEnvelope(body);
if (helloComplete && message.type === "source_update") {
+ const sourceId = String(message.payload?.source_uuid || "");
+ const signature = JSON.stringify(message.payload || {});
+ if (sourceId && sourceStates.get(sourceId) === signature) return;
+ if (sourceId) {
+ sourceStates.set(sourceId, signature);
+ if (sourceStates.size > 2048) sourceStates.delete(sourceStates.keys().next().value);
+ }
sourceMessagesInWindow += 1;
if (sourceMessagesInWindow > MAX_SOURCE_MESSAGES_PER_SECOND) { sourceMessagesDropped += 1; return; }
} else {
diff --git a/plugins/lumi_transcription/companion_manifest.json b/plugins/lumi_transcription/companion_manifest.json
index cd48480..fcc2970 100644
--- a/plugins/lumi_transcription/companion_manifest.json
+++ b/plugins/lumi_transcription/companion_manifest.json
@@ -1,17 +1,17 @@
{
"schema_version": 1,
- "version": "0.1.0-experimental.9",
+ "version": "0.1.0-experimental.10",
"signed": false,
- "release_notes": "Prevents OBS audio and initial source-inventory bursts from rate-limiting the Companion connection by sending steady 20 ms audio frames and bounding high-volume capture traffic separately from control messages.",
+ "release_notes": "Prevents OBS capture bursts from rate-limiting the Companion connection, sends steady 20 ms audio frames, and removes duplicate source-state traffic from large scene collections.",
"installer": {
"id": "windows-x64-installer",
"platform": "win32",
"architecture": "x64",
"label": "Windows x64 per-user installer",
"filename": "Lumi.Companion-Setup.exe",
- "url": "https://git.rolfsvaag.no/Rolfsvaag_Datateknikk/Lumi/releases/download/companion-v0.1.0-experimental.9/Lumi.Companion-Setup.exe",
- "sha256": "22bdb0f351e5b4960e2c1ae40e3bdbd8de8a8d6326b30714defb5eb202c0efcf",
- "bytes": 32039974
+ "url": "https://git.rolfsvaag.no/Rolfsvaag_Datateknikk/Lumi/releases/download/companion-v0.1.0-experimental.10/Lumi.Companion-Setup.exe",
+ "sha256": "8207620c1fa2a3f6371c7f5fad40f9a467146a3c44068ed6ae655ee701d98e6a",
+ "bytes": 32031916
},
"artifacts": [
{
@@ -20,9 +20,9 @@
"architecture": "x64",
"label": "Windows x64 self-contained",
"filename": "Lumi.Companion-win-x64.zip",
- "url": "https://git.rolfsvaag.no/Rolfsvaag_Datateknikk/Lumi/releases/download/companion-v0.1.0-experimental.9/Lumi.Companion-win-x64.zip",
- "sha256": "70d9a780859824aa7b8a12e9f25ae8871778b83458889237f171484b2a200dbc",
- "bytes": 41757969,
+ "url": "https://git.rolfsvaag.no/Rolfsvaag_Datateknikk/Lumi/releases/download/companion-v0.1.0-experimental.10/Lumi.Companion-win-x64.zip",
+ "sha256": "f5a00c9bb778790caec596da475c33b3f7c8e6e67921da75e1e8e3b463bee624",
+ "bytes": 41757850,
"entrypoint": "Lumi.Companion.App.exe"
}
]
diff --git a/plugins/lumi_transcription/plugin.json b/plugins/lumi_transcription/plugin.json
index 21bb26b..9ee6be0 100644
--- a/plugins/lumi_transcription/plugin.json
+++ b/plugins/lumi_transcription/plugin.json
@@ -1,7 +1,7 @@
{
"id": "lumi_transcription",
"name": "Lumi Transcription",
- "version": "0.1.0-experimental.9",
+ "version": "0.1.0-experimental.10",
"description": "Server-hosted whisper.cpp transcription for Lumi Companion and OBS closed captions.",
"main": "index.js",
"channel": "experimental",
diff --git a/plugins/lumi_transcription/tests/verify.js b/plugins/lumi_transcription/tests/verify.js
index 2e3a303..63ba710 100644
--- a/plugins/lumi_transcription/tests/verify.js
+++ b/plugins/lumi_transcription/tests/verify.js
@@ -117,6 +117,7 @@ function verifyLocalhostTransportPolicy() {
}
function verifyCompanionVersionOrdering() {
+ assert.equal(plugin.compareVersions("0.1.0-experimental.10", "0.1.0-experimental.9"), 1);
assert.equal(plugin.compareVersions("0.1.0-experimental.9", "0.1.0-experimental.8"), 1);
assert.equal(plugin.compareVersions("0.1.0-experimental.8", "0.1.0-experimental.7"), 1);
assert.equal(plugin.compareVersions("0.1.0-experimental.7", "0.1.0-experimental.6"), 1);