Skip to content

Fix LOE bugs. - #6176

Merged
wesrupert merged 3 commits into
masterfrom
mw_fix_loe
Sep 10, 2026
Merged

Fix LOE bugs.#6176
wesrupert merged 3 commits into
masterfrom
mw_fix_loe

Conversation

@michelinewu

@michelinewu michelinewu commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Fix Live Output Editing Regressions in Dual Stream Display, Enhanced Broadcasting, and Title Persistence

Summary

Two commits on mw_fix_loe. The first (1027a1b34) fixes how the both-display value used for Twitch dual streaming interacts with Live Output Editing (LOE) and stream shift. The second (8282ea831) fixes prepopulate() wiping an in-progress title/description edit every time the user toggles a platform in the Go Live window.

Issues

getValidatedDisplay coerced both to horizontal at the wrong layer.

Enhanced broadcasting was decided after the stream key was already sent.

createEnhancedBroadcastMultistream inferred the Twitch display from the wrong signal.

Enhanced broadcasting stayed enabled during stream shift.

Stream shift's Twitch sync read back the OBS runtime flag it had just forced off.

Stream shift showed a numeric category ID instead of its name.

prepopulate() discarded unsaved title/description edits on every platform toggle.

Fixes

getValidatedDisplay is removed. shouldSetupDualOutput now defaults an unset display to horizontal inline and separately treats any enabled platform with both as sufficient to force dual output mode. getSavedPlatformSettings now passes both through unchanged (savedDestinations[platform]?.display ?? 'horizontal'), leaving LOE's incompatibility with dual streaming to be enforced at the point the display is actually used rather than at the point it's stored.

TwitchService.beforeGoLive now resolves the enhanced-broadcasting/dual-stream/LOE branch (via a new IPlatformService.setupLiveOutputStream hook, implemented on TwitchService as setEnhancedBroadcasting(false)) before the stream-key write, so the decision applies to the stream that's actually starting.

createEnhancedBroadcastMultistream now derives the display from this.views.getPlatformDisplayType('twitch') when dual output mode is active, falling back to horizontal otherwise — reading the actual platform-to-display assignment instead of guessing from the ingest URL.

streaming.ts's enhanced-broadcasting eligibility check adds !this.views.isStreamShiftMode alongside the existing LOE check.

The stream-shift Twitch sync now reads this.state.settings.isEnhancedBroadcasting (the persisted user preference) instead of the OBS runtime flag, and resolves settings.game_id to a name via this.fetchGame() before assigning it to game/gameName, falling back to the existing channel info on fetch failure.

GoLiveSettingsModule.prepopulate takes an optional { preserveCommonFields?: boolean }. When set, it snapshots commonFields before the rebuild and restores the title/description afterward (editedCommonFields.title || this.state.commonFields.title, same for description). switchPlatforms is the only caller that passes it — every other prepopulate() call site is a window open, where the freshly fetched values should win.

Files changed: app/components-react/windows/go-live/useGoLiveSettings.ts, app/services/platforms/index.ts, app/services/platforms/twitch.ts, app/services/streaming/streaming-view.ts, app/services/streaming/streaming.ts

Performance Implications

None materially. Most of this is reordering existing logic and replacing one indirection (getValidatedDisplay) with an inline default — no new work on the hot path. The one addition is a fetchGame() HTTP call during stream-shift settings sync, and only when the shifted stream reports a game_id; that sync already makes a Twitch API call in the same Promise.all, so this adds one more request to an already network-bound, infrequent (shift-triggered) path, not to Go Live or steady-state streaming.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The functional changes appear consistent and scoped to the described LOE/Stream Shift bugs, with only minor maintainability nits noted.

Pull request overview

This PR adjusts the Go Live / streaming pipeline to fix Live Output Editing (LOE) and Stream Shift edge cases around Twitch enhanced broadcasting and dual-output display selection, ensuring the correct OBS context and persisted preferences are used.

Changes:

  • Prevent Twitch enhanced broadcasting from being enabled during Stream Shift and LOE flows, and resolve enhanced broadcasting earlier in the Twitch go-live sequence.
  • Fix enhanced broadcasting multistream canvas selection by using Twitch’s assigned display (instead of inferring from ingest server).
  • Preserve user-typed shared title/description when Go Live settings are re-prepopulated after toggling platforms.
File summaries
File Description
app/services/streaming/streaming.ts Adjusts enhanced broadcasting eligibility during Stream Shift; fixes enhanced broadcast multistream display selection logic.
app/services/streaming/streaming-view.ts Simplifies/clarifies dual-output setup decisions and display defaults; adds notes about cleanup candidates.
app/services/platforms/twitch.ts Resolves enhanced broadcasting earlier in go-live; forces enhanced broadcasting off for Stream Shift/LOE; resolves Stream Shift category ID to name without overwriting user preference.
app/services/platforms/index.ts Extends platform service surface with setupLiveOutputStream hook.
app/components-react/windows/go-live/useGoLiveSettings.ts Adds an option to preserve common fields across prepopulate calls; uses it when toggling platforms to avoid dropping typed title/description.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app/components-react/windows/go-live/useGoLiveSettings.ts
Comment on lines +519 to +522
async setupLiveOutputStream(options?: IGoLiveSettings): Promise<void> {
// Live output editing not compatible with enhanced broadcasting, so disable it here
this.settingsService.setEnhancedBroadcasting(false);
}
Copilot AI review requested due to automatic review settings September 10, 2026 18:25

@blackxored blackxored left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I can only spot check, there's a lot of display logic here I don't have the context for.

delete settings.platforms[platform];
},
);
if (!view.isMidStreamMode) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't this covered by the conditional above it?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The new “preserve common fields” logic uses || and will overwrite intentionally cleared (empty) title/description values during refresh, causing incorrect user-facing behavior.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

app/components-react/windows/go-live/useGoLiveSettings.ts:411

  • When preserving common fields after updateSettings, the || fallback treats an intentionally cleared title/description (empty string) as falsy and replaces it with the newly-prepopulated value. This makes it impossible for a user to clear these fields while a prepopulate refresh happens.
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@bundlemon

bundlemon Bot commented Sep 10, 2026

Copy link
Copy Markdown

BundleMon

Files updated (1)
Status Path Size Limits
renderer.(hash).js
10.54MB (+330B 0%) -
Unchanged files (3)
Status Path Size Limits
vendors~renderer.(hash).js
4.67MB -
updater.js
115.29KB -
guest-api.js
40.23KB -

Total files change +330B 0%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@wesrupert
wesrupert merged commit 92c837d into master Sep 10, 2026
10 of 14 checks passed
@wesrupert
wesrupert deleted the mw_fix_loe branch September 10, 2026 18:48
@summeroff

Copy link
Copy Markdown
Contributor

Post-merge review findings:

  1. High — saved display: 'both' leaks into runtime LOE routing

The change in streaming-view.ts preserves both in the Go Live settings. That is useful for persistence, but the raw value is still consumed by runtime topology:

  • activeDisplayPlatforms adds a both platform to the vertical display as well as horizontal.
  • LOE's liveOutputDisplays therefore contains both displays, and RestreamService.setupIngest() configures both OBS ingests.
  • Twitch itself is normalized to landscape by getPlatformDisplayType(), so the vertical output can be started without a Twitch target.
  • YouTube's beforeGoLive() still branches directly on display === 'both', so LOE can invoke its actual dual-stream setup.

This conflicts with the stated rule that LOE cannot dual stream. Preserve the saved both preference, but normalize the effective runtime assignment to horizontal while LOE is enabled (including activeDisplayPlatforms, liveOutputDisplays, and the YouTube dual-stream branch).

Relevant paths:

  • getPlatformDisplayType(platform: TPlatform): TDisplayType {
    const display = this.settings.platforms[platform]?.display ?? 'horizontal';
    return display === 'both' ? 'horizontal' : display;
    }
    getOutputDisplayType(output?: TDisplayOutput): TDisplayType {
    if (output) {
    return output === 'both' ? 'horizontal' : output;
    }
    return this.settings.recording === 'both' ? 'horizontal' : this.settings.recording;
    }
    get outputDisplay(): TDisplayType {
    return this.settings.recording === 'both' ? 'horizontal' : this.settings.recording;
    }
    /**
    * Returns the enabled platforms according to their assigned display
    */
    get activeDisplayPlatforms(): TDisplayPlatforms {
    return this.enabledPlatforms.reduce(
    (displayPlatforms: TDisplayPlatforms, platform: TPlatform) => {
    const display = this.getPlatformDisplayType(platform);
    displayPlatforms[display].push(platform);
    // if the platform is set to 'both' display, add it to both horizontal and vertical
    // for analytics purposes
    if (this.settings.platforms[platform]?.display === 'both') {
    displayPlatforms.vertical.push(platform);
    }
  • // make sure platforms assigned to the vertical display in dual output mode still go live in
    // single output mode
    // Note: `both` is deliberately passed through. It must not be collapsed here, because this
    // value seeds the Go Live window and is written straight back by `save()`, so coercing it
    // would overwrite the user's saved dual stream choice. Live output editing's inability to
    // dual stream is enforced where the display is used, not where it is stored.
    // The `?? 'horizontal'` matters: without it a platform with no saved display yields
    // `undefined` here, and callers that index by display rather than defaulting it break.
    const display =
    this.isDualOutputMode && savedDestinations
    ? savedDestinations[platform]?.display ?? 'horizontal'
    : 'horizontal';
    return {
    ...settings,
    display,
  • } else if (this.streamInfo.isLiveOutputEditingEnabled || this.streamInfo.isDualOutputMode) {
    // Set the ingest for each display being restreamed.
    // In live output editing mode, every display must use the restream servers so that a target
    // can switch between displays mid-stream, so use every display with a target.
    const displays = this.streamInfo.isLiveOutputEditingEnabled
    ? this.streamInfo.liveOutputDisplays
    : this.streamInfo.displaysToRestream;
    // Await the settings for every display with `allSettled`. Otherwise `beforeGoLive` resolves before the
    // stream settings have been written and `createStreaming` reads stale values.
    await Promise.allSettled(
    displays.map(async display => {
    const mode = this.getMode(display);
    const settings = await this.fetchUserSettings(mode);
    this.setStreamSettingsForDisplay(display, settings.streamKey, ingest);
    }),
  • // start dual output
    if (this.views.isDualOutputMode) {
    if (this.views.isTwitchDualStreaming) {
    await this.createStreaming({
    output: 'both',
    audioTrack: this.getStreamingAudioTrack(),
    start: true,
    context: 'enhancedBroadcasting',
    isEnhancedBroadcasting: true,
    });
    } else if (this.state.enhancedBroadcasting) {
    // Figure out which display Twitch is streaming to and create the enhanced broadcasting instance with that display.
    // For enhanced broadcasting while multistreaming one of the displays, the horizontal and vertical streaming instances will be handled in `handleStreamingSignal`.
    // If Twitch is the only target for one of the displays, the other display streaming instance will also be handled in `handleStreamingSignal`.
    await this.createEnhancedBroadcastDualOutput();
    } else if (this.views.isYouTubeDualStreaming) {
    // To show the vertical stream in the YouTube mobile app, the vertical stream needs to be started last.
    // So create the horizontal stream instance first
    this.createStreaming({
    output: 'horizontal',
    audioTrack: this.getStreamingAudioTrack(),
    start: true,
    context: 'horizontal',
    isEnhancedBroadcasting: false,
    });
    } else {
    // For dual output without enhanced broadcasting, the vertical stream instance will be created and started after the horizontal stream.
    this.createStreaming({
    output: 'vertical',
    audioTrack: this.getStreamingAudioTrack(),
    start: true,
    context: 'vertical',
    isEnhancedBroadcasting: false,
    });
    }
  1. Medium — deliberately cleared title/description values are not preserved

The restoration uses editedCommonFields.title || fetchedTitle and the equivalent for description. An intentional empty string is falsy, so toggling a platform restores the fetched value instead of keeping the field cleared. Restore the snapshot directly, or use nullish semantics if undefined is meaningful.

Relevant code:

  • // Prepopulating rebuilds each platform's settings from the service, which drops a title or
    // description the user has typed but not submitted. Put the typed values back.
    if (editedCommonFields) {
    this.state.updateCommonFields({
    title: editedCommonFields.title || this.state.commonFields.title,
    description: editedCommonFields.description || this.state.commonFields.description,
    });

Active-output safety: Moving setEnhancedBroadcasting(false) earlier should not itself reconfigure or replace an active OBS output. The native setter only changes the Enhanced Broadcasting configuration flag, and the same setter was already called later in the LOE beforeGoLive path before this PR. The main concern is the separate both-display routing issue above.

I did not find tests covering LOE + a saved both display or preservation of intentionally empty common fields. Those cases would be valuable regression tests.

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.

5 participants