feat: signed A/V sync offset (−500..+500), negative advances by eating live stream head (OBS eat-head semantics)

Positive offsets still delay the whole mix via the delay line (lip-sync fix);
negative offsets now ARM once at StartLive and drop |N| ms off the pipe's write
head so audio events land earlier when audio runs BEHIND video. Slider relabeled
AUDIO SYNC, Min −500, locked while live/recording (IsEditMode). LayoutStore and
VM clamp to −500..500.

OBS reference for eat-the-head negative sync: https://obsproject.com/kb/obs-studio/buffering-time (negative sync values pull audio earlier by discarding buffered player audio).

Test: StartLive_NegativeOffset_AdvancesAudio_ByDroppingTheStreamHead (6x0.9 head
must be eaten before 0.2 bed reaches the wire).
This commit is contained in:
2026-09-14 12:30:44 -07:00
parent 11a7af2dc0
commit b22d08eca6
12 changed files with 219 additions and 144 deletions
+4 -2
View File
@@ -10,8 +10,10 @@ namespace ytLive.Services.Audio;
/// the same audio N ms later out — so it is trivially testable and carries no
/// state outside its own ring.
///
/// Positive offsets only (0..500 ms). Advancing audio (<see cref="TimeSpan"/>
/// would need a video-side delay and is out of scope for the audio layer.
/// Positive offsets only (0..500 ms). Advancing audio (the audio runs BEHIND
/// the video) is handled upstream in <see cref="AudioMixer"/>, not here: the
/// mixer eats the first |N| ms of the live stream head (OBS's negative sync —
/// see <see cref="AudioMixer.StartLive"/>), keeping this line a pure delay.
/// Changing the delay flushes the line, so a live slider change clicks rather
/// than smearing. Safe to call from any thread — the mixer owns it.
/// </summary>