Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,8 @@ Frontend and shell:
Preserve the 16 kHz 256-sample hop adapter, one-hop delay compensation,
bounded shared-timeline queue, and Stop/Pause/EOF tail flush. Model construction
must emit no stdout because that channel carries the sidecar JSON protocol.
Initialize the model and connect output consumers before opening upstream
capture pipes; opening those pipes starts bounded producer writes.
The token-protected Meeting device test must reuse this path, remain explicit
and local-only, return only bounded level/activity statistics, and always stop
its ephemeral sidecar capture without persisting or uploading PCM. Product
Expand Down
4 changes: 2 additions & 2 deletions Frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "scriber",
"version": "0.5.126",
"version": "0.5.127",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
29 changes: 19 additions & 10 deletions Frontend/src-tauri/src/audio_sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3754,6 +3754,20 @@ fn run_meeting_aec_relay(
.map(|handle| *handle as HANDLE)
.collect();
let result = (|| -> Result<(), String> {
// Connecting upstream pipes starts bounded capture writes. Prepare the
// model and all consumers first so a cold model cannot fill those pipes.
let mut aec = if aec_enabled {
// Force the real pipe regression beyond its buffer/write deadline
// in tests; production performs no artificial wait.
#[cfg(test)]
thread::sleep(Duration::from_millis(1500));
Some(MeetingEnhancer::new()?)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Start LocalVQE before the capture producers

When MeetingEnhancer::new() takes five seconds or longer on a cold or contended machine, the microphone and loopback writer threads that start_meeting_capture already spawned hit their FRAME_PIPE_CONNECT_TIMEOUT while this initialization runs. They then close their server ends before the relay reaches open_frame_pipe_reader, so the meeting proceeds into a relay/source failure instead of recording. Initialize the relay/model before starting those producer sessions, or make their connection lifetime cover initialization while retaining the no-read-before-ready ordering.

AGENTS.md reference: AGENTS.md:L391-L392

Useful? React with 👍 / 👎.

} else {
None
};
for handle in &outputs {
wait_for_meeting_output_pipe_client(*handle, &stop_rx)?;
}
let microphone = open_frame_pipe_reader(&microphone_pipe)?;
let system = match open_frame_pipe_reader(&system_pipe) {
Ok(handle) => handle,
Expand All @@ -3765,14 +3779,6 @@ fn run_meeting_aec_relay(
}
};
let processing = (|| -> Result<(), String> {
for handle in &outputs {
wait_for_meeting_output_pipe_client(*handle, &stop_rx)?;
}
let mut aec = if aec_enabled {
Some(MeetingEnhancer::new()?)
} else {
None
};
let mut relay_sequence = 0u64;
let mut microphone_frame = Some(read_meeting_frame(microphone, &stop_rx)?);
let mut system_frame = Some(read_meeting_frame(system, &stop_rx)?);
Expand Down Expand Up @@ -6732,10 +6738,13 @@ mod tests {
})
})
.collect();
let capture_id = response["payload"]["meetingCaptureId"].as_str().unwrap();
for _ in 0..3 {
ready_rx.recv_timeout(Duration::from_secs(10)).unwrap();
if let Err(error) = ready_rx.recv_timeout(Duration::from_secs(10)) {
let stopped = state.stop_meeting_capture(capture_id, "testFailureCleanup");
panic!("Meeting frames not ready: {error}; stopped={stopped}");
}
}
let capture_id = response["payload"]["meetingCaptureId"].as_str().unwrap();
let stopped = state.stop_meeting_capture(capture_id, "meetingCaptureStop");
assert_eq!(stopped["stopped"], true);
assert_eq!(stopped["relay"]["relayError"], Value::Null, "{stopped}");
Expand Down
2 changes: 2 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ Meetings:
loopback sources at 48 kHz in shared mode; it never opens a camera/video
device. The microphone and loopback frames are downsampled to 16 kHz before
the single pinned LocalVQE v1.3 model reduces echo, noise, and reverberation.
The relay initializes the model and connects consumers before opening its
upstream capture pipes, so cold model startup cannot overflow producer buffers.
Its 256-sample inference hops are adapted to the 160-sample transport frames;
the relay holds at most four frames, removes the native one-hop pre-roll,
and flushes the final partial hop before EOF. All three tracks retain the
Expand Down
2 changes: 1 addition & 1 deletion native/scriber-audio-sidecar/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion native/scriber-audio-sidecar/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "scriber-audio-sidecar"
version = "0.2.0"
version = "0.2.1"
description = "Scriber audio capture worker"
authors = ["Scriber"]
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion src/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import re

__version__ = "0.5.126"
__version__ = "0.5.127"

_SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$")

Expand Down
5 changes: 4 additions & 1 deletion tests/test_audio_worker_build_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,8 @@ def test_audio_cache_only_validation_does_not_require_a_local_rust_compiler(tmp_
check=False,
)
assert result.returncode != 0
assert "cache is missing, stale, or does not match worker version 0.2.0" in result.stderr
worker_version = tomllib.loads((ROOT / "native/scriber-audio-sidecar/Cargo.toml").read_text(encoding="utf-8"))[
"package"
]["version"]
assert f"cache is missing, stale, or does not match worker version {worker_version}" in result.stderr
assert "rustc" not in result.stderr
Loading