fix: NDI output aspect ratio when started without Live#2
Open
CodeKing12 wants to merge 1 commit into
Open
Conversation
Three related changes that together eliminate the ~30px black margins
visible in Streamlabs/OBS when NDI is started before going Live:
1. RenderProjection: h="full" → h="vh"
The projection page root <main> used height:100%, which requires every
ancestor element to carry an explicit height. In Astro 5 the SolidJS
island is wrapped in an <astro-island> block element with no explicit
height, silently breaking the percentage chain. height:100vh is
viewport-relative and resolves correctly regardless of ancestor sizing,
so the content always fills the full window frame captured by NDI.
2. Layout.astro: <style> → <style is:global>
Astro 5 scopes component styles by default. The html,body{height:100%}
rule in a scoped block can be silently dropped because Astro cannot
add its scoping attribute to the root elements. is:global makes the
intent explicit and guarantees the rule applies.
3. AppSettingsDialog: workArea → bounds for stored projectionBounds
Auto-detected projection bounds were stored as display.workArea
(excludes the taskbar). resolveProjectionBounds() in main.ts already
overrides this at runtime when useCustomProjectionBounds is false, but
users who enable custom bounds received the workArea dimensions verbatim,
creating a window shorter than the display and causing NDI to stream at
the reduced height. Storing display.bounds removes the discrepancy.
https://claude.ai/code/session_0117ULWZhnNJFTTkGnba69Eg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
When NDI is started without first going Live, the projection window is rendered as a hidden off-screen
BrowserWindow(not fullscreen). In that state the CSSheight: 100%chain breaks silently:<astro-island>as a block element between<body>and the SolidJS component root. That element has no explicit height, soheight: 100%on<main>(inside the component) resolves against a zero-height ancestor — producing a collapsed or partial-height render.<style>does not reliably applyheight: 100%tohtml/bodyin Astro 5 because Astro cannot attach its scoping attribute to root elements; the rules may be silently dropped.The visible Live path avoids both issues because OS-level fullscreen forces the viewport to fill the display regardless of the CSS height chain.
A secondary contributing factor:
AppSettingsDialogstoreddisplay.workArea(which excludes the Windows taskbar, ~40–60 px shorter) instead ofdisplay.bounds. For users withuseCustomProjectionBounds: truethis meant the NDI window was sized shorter than the full display, producing equal top/bottom letterbox bars in Streamlabs's 16:9 canvas.resolveProjectionBounds()inmain.tsalready overrides this for the default (useCustomBounds: false) path, but the stored value was still wrong at the source.Changes
src/components/app/projection/RenderProjection.tsx<main>fromh="full"(height: 100%) toh="vh"(height: 100vh).100vhis viewport-relative and does not depend on the ancestor chain, so it fills the full window frame captured bybeginFrameSubscriptionregardless of<astro-island>wrapping.maxH="vh"(max-height is redundant when height is already100vh).src/layouts/Layout.astro<style>to<style is:global>sohtml, body { margin: 0; width: 100%; height: 100%; }is guaranteed to be emitted without Astro's scoping transform.Belt-and-suspenders: keeps the height chain intact for any future change that might rely on percentage heights.
src/components/modals/AppSettingsDialog.tsxhandleDisplaysUpdate:externalDisplay.workArea→externalDisplay.boundshandleDisplayChange:details.items[0].workArea→details.items[0].boundsRemoves the stored-value discrepancy at the source so
projectionBoundsalways reflects the full display dimensions.What was ruled out
ndi-sender.ts: Frame dimensions come fromimage.getSize()(actual captured frame), not fromDEFAULT_CONFIGwidth/height — no change needed there.main.ts/resolveProjectionBounds(): Already correctly usesdisplay.boundsat runtime for the default path — no change needed there.Test plan
useCustomProjectionBoundsin Settings and set custom bounds; verify NDI window respects the custom size (not overridden by theboundsfix).Generated by Claude Code