Skip to content

Fix Twitch VOD Track audio routing in advanced + EB modes#1714

Merged
summeroff merged 4 commits into
stagingfrom
fix/twitch-vod-track-mixer-routing
May 29, 2026
Merged

Fix Twitch VOD Track audio routing in advanced + EB modes#1714
summeroff merged 4 commits into
stagingfrom
fix/twitch-vod-track-mixer-routing

Conversation

@summeroff
Copy link
Copy Markdown
Contributor

Summary

Regression introduced in 1.21's migration to the factory-API streaming path: the user-configurable Twitch VOD Track silently sent the wrong audio (or no separate VOD audio at all) to Twitch's VOD output, depending on streaming mode.

Tech support reported users on 1.21.1 seeing audio they'd muted on the VOD Track still present in their Twitch VOD. Two distinct root causes, fixed together since they share one user-visible symptom.

Root cause

1. Advanced streaming - hardcoded mixer + 0/1-based field confusion.
osn-advanced-streaming.cpp::SetupTwitchSoundtrackAudio was a fossilized copy of the legacy "Soundtrack by Twitch" plugin code:

  • The VOD encoder was created with kSoundtrackArchiveTrackIdx = 5 regardless of the user's track choice, so VOD audio always came from mixer 5 (UI Track 6).
  • audioTrackConfigs[streaming->twitchTrack] used raw array indexing instead of GetTrackConfig(twitchTrack), mismatching the 1-based contract honored by every other consumer in osn-audio-track.cpp.
  • It also silently mutated default desktop sources' mixer masks (carry-over from the Soundtrack plugin) - actively wrong once the VOD mixer is user-chosen.

The legacy GetLegacySettings/SetLegacySettings stored the field as 0-based with -1/+1 conversions, while the new factory-API setters stored 1-based values from the desktop UI. Same field, two conventions, depending on entry point.

2. Enhanced Broadcasting advanced - VOD track silently disabled.
osn-enhanced-broadcasting-advanced-streaming.cpp::Start checked streaming->twitchVODSupported && streaming->enableTwitchVOD to decide whether to request a VOD track from the multitrack output, but never set twitchVODSupported. The field defaulted to false (from osn-streaming.hpp:44); regular AdvancedStreaming::Start computes it via isTwitchVODSupported() before SetupTwitchSoundtrackAudio, but the EB Start skipped that.

Net effect with EB on: vod_track_mixer = nullopt -> multitrack output's create_audio_encoders early-returns at the VOD branch -> no VOD encoder ever attached -> Twitch's go-live POST body went out with "vod_track_audio": false -> Twitch returned no VOD audio configuration -> VOD silently used the main stream audio.

Fix

osn-advanced-streaming.cpp

  • Route the VOD encoder through GetMixerIndex(streaming->twitchTrack) (user's 1-based choice -> 0-based mixer).
  • Look the bitrate config up via GetTrackConfig(streaming->twitchTrack); bail cleanly if the slot has no AudioTrack.
  • Recreate the encoder when the mixer index would change between streams (mixer is fixed at obs_audio_encoder_create time).
  • Drop the silent mutation of default desktop sources' mixer masks (and the corresponding restore in StopTwitchSoundtrackAudio).
  • Normalize GetLegacySettings/SetLegacySettings to store twitchTrack/audioTrack as 1-based, matching the constructor defaults and the new API path - eliminates the two-conventions-in-one-field footgun.
  • Dead-code removal: kSoundtrackArchiveTrackIdx, setMixer() helper.

osn-enhanced-broadcasting-advanced-streaming.cpp

  • Compute streaming->twitchVODSupported = streaming->isTwitchVODSupported() when enableTwitchVOD is true, mirroring the regular path.
  • Convert streaming->twitchTrack to a 0-based mixer index via GetMixerIndex before passing to StartEnhancedBroadcastingStream (which forwards to multitrack output as a raw mixer_idx).

Test plan

  • Regular advanced + VOD enabled, VOD Track = 3. Log shows 'Twitch VOD Track Encoder' created with track: 3 (mixer 2). Audio muted on Track 3 via Advanced Audio Properties is excluded from VOD as expected.
  • Enhanced Broadcasting + VOD enabled. Log shows 'multitrack video vod audio 0' encoder created; Go-live POST body contains "vod_track_audio": true. VOD receives the user-configured mix.
  • Same scenarios with EB toggled off then on again - both modes route VOD audio correctly without restart.
  • Twitch Dual Streaming (single connection, both canvases) + VOD. Same EB code path as scenario 2; covered transitively.

Out of scope (follow-ups)

  • osn-simple-streaming.cpp::SetupTwitchSoundtrackAudio still has the hardcoded mixer 5 pattern, but the VOD Track UI is gated to advanced output mode in GlobalSettings.tsx so it's unreachable from a normal desktop session. Separate cleanup if simple-mode VOD is ever wired up.
  • streamArchive / SetupTwitchSoundtrackAudio are misnomers now (no Soundtrack plugin involvement) and oldMixer_desktopSource1/2 on osn-streaming.hpp are unused by the advanced path. Pure rename, separate PR.

Generated with Claude Code

The 1.21 migration to the factory-API streaming path broke the VOD
Track audio output in two distinct ways:

osn-advanced-streaming.cpp::SetupTwitchSoundtrackAudio hardcoded the
VOD encoder to mixer 5 (a leftover from the Soundtrack-by-Twitch
plugin) and indexed audioTrackConfigs[] directly, ignoring both the
user's track selection and the 1-based contract honored by
GetTrackConfig/GetMixerIndex. Route the encoder to the mixer matching
the user's choice, look the bitrate config up via the helper, recreate
the encoder if the user changed the VOD track between streams, and
drop the now-incorrect side effect that mutated default desktop
source mixer masks.

osn-enhanced-broadcasting-advanced-streaming.cpp::Start never called
isTwitchVODSupported(), so twitchVODSupported stayed at its default
false. That caused vod_track_mixer to be nullopt and the multitrack
output to omit the VOD audio configuration entirely - Twitch's VOD
silently fell back to the main stream audio. Mirror the regular
AdvancedStreaming::Start check, and pass the user's mixer through
GetMixerIndex so the multitrack output gets a 0-based index.

Also normalize GetLegacySettings/SetLegacySettings to store the same
1-based track number as the new API path, eliminating the
two-conventions-in-one-field footgun.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a regression where the Twitch VOD Track sent wrong (or no) audio in the new factory-API streaming path. Two distinct root causes are addressed: the advanced streaming path hardcoded the VOD mixer to track 5 (and used raw 0-based array indexing inconsistent with the rest of the codebase), and the Enhanced Broadcasting path never computed twitchVODSupported, so the VOD encoder was never requested. The legacy settings load/save is also normalized to store twitchTrack/audioTrack as 1-based, eliminating a "two conventions in one field" footgun.

Changes:

  • In osn-advanced-streaming.cpp, route the VOD encoder through the user's chosen track via GetTrackConfig/GetMixerIndex, recreate the encoder when the mixer changes between streams, and drop the obsolete desktop-source mixer-mask mutation (and its restore in stop). Remove dead helpers setMixer/kSoundtrackArchiveTrackIdx.
  • Normalize Get/SetLegacySettings to treat TrackIndex/VodTrackIndex in basic.ini as 1-based (matching constructor defaults and the new API path); removes the +1/-1 conversions.
  • In osn-enhanced-broadcasting-advanced-streaming.cpp::Start, compute twitchVODSupported = isTwitchVODSupported() when enableTwitchVOD is true, and convert twitchTrack to a 0-based mixer index via GetMixerIndex before forwarding to StartEnhancedBroadcastingStream.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
obs-studio-server/source/osn-advanced-streaming.cpp Routes the Twitch VOD encoder via the user-selected track (1-based) using the AudioTrack helpers, recreates the encoder when the chosen mixer changes, removes Soundtrack-plugin carryover (desktop source mixer mutation and dead constants), and normalizes legacy settings to 1-based.
obs-studio-server/source/osn-enhanced-broadcasting-advanced-streaming.cpp Computes twitchVODSupported at Start (mirroring the regular path) and converts the 1-based twitchTrack to a 0-based mixer index before passing it to the multitrack output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread obs-studio-server/source/osn-enhanced-broadcasting-advanced-streaming.cpp Outdated
// expects a 0-based mixer index for vod_track_mixer.
auto vod_track_mixer = (streaming->twitchVODSupported && streaming->enableTwitchVOD)
? std::optional<size_t>{osn::IAudioTrack::GetMixerIndex(streaming->twitchTrack)}
: std::nullopt;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we mirror the regular advanced streaming guard here before converting the selected VOD track to a mixer index? GetMixerIndex() assumes twitchTrack is a valid 1-based track number; if settings are stale/corrupt and twitchTrack is 0 or > NUM_AUDIO_TRACKS, this can underflow or pass an invalid mixer into enhanced broadcasting.

summeroff and others added 3 commits May 29, 2026 09:58
GetMixerIndex does trackNumber - 1 with no validation, so an invalid
twitchTrack (0 from underflow, or > NUM_AUDIO_TRACKS) silently produced an
UINT32_MAX mixer index for vod_track_mixer. Mirror SetupTwitchSoundtrackAudio's
GetTrackConfig null-bail so EB skips VOD silently when the slot is
unconfigured, matching the regular AdvancedStreaming path's failure mode.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Let names and code carry the rest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@summeroff summeroff merged commit 1c54906 into staging May 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants