Fix Twitch VOD Track audio routing in advanced + EB modes#1714
Conversation
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>
There was a problem hiding this comment.
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 viaGetTrackConfig/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 helperssetMixer/kSoundtrackArchiveTrackIdx. - Normalize
Get/SetLegacySettingsto treatTrackIndex/VodTrackIndexinbasic.inias 1-based (matching constructor defaults and the new API path); removes the+1/-1conversions. - In
osn-enhanced-broadcasting-advanced-streaming.cpp::Start, computetwitchVODSupported = isTwitchVODSupported()whenenableTwitchVODis true, and converttwitchTrackto a 0-based mixer index viaGetMixerIndexbefore forwarding toStartEnhancedBroadcastingStream.
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.
| // 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; |
There was a problem hiding this comment.
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.
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>
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::SetupTwitchSoundtrackAudiowas a fossilized copy of the legacy "Soundtrack by Twitch" plugin code:kSoundtrackArchiveTrackIdx = 5regardless 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 ofGetTrackConfig(twitchTrack), mismatching the 1-based contract honored by every other consumer inosn-audio-track.cpp.The legacy
GetLegacySettings/SetLegacySettingsstored the field as 0-based with-1/+1conversions, 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::Startcheckedstreaming->twitchVODSupported && streaming->enableTwitchVODto decide whether to request a VOD track from the multitrack output, but never settwitchVODSupported. The field defaulted tofalse(fromosn-streaming.hpp:44); regularAdvancedStreaming::Startcomputes it viaisTwitchVODSupported()beforeSetupTwitchSoundtrackAudio, but the EB Start skipped that.Net effect with EB on:
vod_track_mixer = nullopt-> multitrack output'screate_audio_encodersearly-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.cppGetMixerIndex(streaming->twitchTrack)(user's 1-based choice -> 0-based mixer).GetTrackConfig(streaming->twitchTrack); bail cleanly if the slot has no AudioTrack.obs_audio_encoder_createtime).StopTwitchSoundtrackAudio).GetLegacySettings/SetLegacySettingsto storetwitchTrack/audioTrackas 1-based, matching the constructor defaults and the new API path - eliminates the two-conventions-in-one-field footgun.kSoundtrackArchiveTrackIdx,setMixer()helper.osn-enhanced-broadcasting-advanced-streaming.cppstreaming->twitchVODSupported = streaming->isTwitchVODSupported()whenenableTwitchVODis true, mirroring the regular path.streaming->twitchTrackto a 0-based mixer index viaGetMixerIndexbefore passing toStartEnhancedBroadcastingStream(which forwards to multitrack output as a raw mixer_idx).Test plan
'Twitch VOD Track Encoder'created withtrack: 3(mixer 2). Audio muted on Track 3 via Advanced Audio Properties is excluded from VOD as expected.'multitrack video vod audio 0'encoder created; Go-live POST body contains"vod_track_audio": true. VOD receives the user-configured mix.Out of scope (follow-ups)
osn-simple-streaming.cpp::SetupTwitchSoundtrackAudiostill has the hardcoded mixer 5 pattern, but the VOD Track UI is gated to advanced output mode inGlobalSettings.tsxso it's unreachable from a normal desktop session. Separate cleanup if simple-mode VOD is ever wired up.streamArchive/SetupTwitchSoundtrackAudioare misnomers now (no Soundtrack plugin involvement) andoldMixer_desktopSource1/2onosn-streaming.hppare unused by the advanced path. Pure rename, separate PR.Generated with Claude Code