Deduplicate OBS source state traffic

This commit is contained in:
Franz Rolfsvaag 2026-07-22 21:41:03 +02:00
parent 27ae8dcb81
commit fc5a9029ea
9 changed files with 31 additions and 13 deletions

View File

@ -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

View File

@ -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.

View File

@ -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"
)

View File

@ -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);

View File

@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>0.1.0-experimental.9</Version>
<Version>0.1.0-experimental.10</Version>
<AssemblyVersion>0.1.0.0</AssemblyVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -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 {

View File

@ -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"
}
]

View File

@ -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",

View File

@ -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);