Skip to content

ContentScale.Fit doesn't work propely in iOS #241

Description

@shivambhavsar

How iOS renders the video

On iOS the library doesn't draw into a Compose canvas — it hosts a native AVPlayerLayer inside a UIKitView (interop layer that sits above Compose). Scaling is applied in two places at once (VideoPlayerSurface.ios.kt):

  1. Compose-side sizing — a modifier that sets the interop view's box shape:
    contentScale.toCanvasModifier(aspectRatio = playerState.aspectRatio, ...)
  2. Native-side gravity — AVPlayerLayer.videoGravity (Fit → resizeAspect, Crop → resizeAspectFill).

What Fit does — and why it breaks

For ContentScale.Fit, toCanvasModifier (in ContentScaleCanvasUtils.kt) returns:
Modifier.fillMaxHeight().aspectRatio(aspectRatio)
So the video host is sized to fill the height, then take width = height × aspectRatio. That whole thing hinges on aspectRatio being correct. It isn't. In VideoPlayerState.ios.kt:

private var _videoAspectRatio by mutableStateOf(16.0 / 9.0) // reactive, defaults to 16:9
override val aspectRatio: Float = _videoAspectRatio.toFloat() // plain val — evaluated ONCE

That's the bug. _videoAspectRatio is a reactive state and does get corrected later (line ~266, when item.presentationSize finally resolves the real dimensions). But aspectRatio is a plain val initialized = _videoAspectRatio.toFloat() at construction. It reads the state once, captures the 16:9 default, and freezes — it's not a get() getter, so it never recomposes when the true size arrives.

Consequences:

  • The Fit layout always uses 16:9, no matter the actual clip.
  • Reels are portrait (~9:16), so the surface becomes a landscape 16:9 box centered on screen — the video ends up mis-scaled with big black bands, the opposite of a full-bleed reel. And the native resizeAspect then letterboxes inside that already-wrong box, compounding it.
  • It's worse in our setup specifically: we reuse one player state across all reels (openUri swaps media in place, the state object is built once), so aspectRatio is frozen at 16:9 for the entire session and never even reflects the first video. That's the "flaky aspectRatio" our config comment refers to.

The clean fix: make aspectRatio reactive — override val aspectRatio: Float get() = _videoAspectRatio.toFloat() — which would let real Fit work on iOS.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions