> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://fastpix.com/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://fastpix.com/docs/_mcp/server.

# Rotate video for fullscreen

> Learn how to control fullscreen interface rotation, observe what the system actually did, and choose how the picture fills the frame in the FastPix iOS Player.

Learn how to control fullscreen interface rotation, observe what the system actually did, and choose how the picture fills the frame in the FastPix iOS Player.

The FastPix iOS Player SDK rotates the interface for fullscreen playback and reports truthfully what came of the request. It does not rotate the video layer independently of the interface. It asks the window scene for an orientation, reads back what the system actually settled on, and tells you, including every case where nothing moved.

Rotation is requested through `UIWindowScene.requestGeometryUpdate`, a public API with no key-value coding and no deprecated calls. `onFullscreenOrientationChanged(isLandscape:)` fires only when the interface really moved, and reports the orientation it settled in, not the one that was asked for. Every request, granted or refused, is reported on `onFullscreenOrientationRequest(outcome:)`, and each refusal explains itself and says what to do instead. Auto-rotation is opt-out per player. Independently of rotation, `videoFillMode` decides how the picture is fitted into whatever shape the frame ends up.

To update your own custom layout as the device rotates, see [Handle orientation changes](/docs/ios-player/handle-orientation-changes).

## Control auto-rotation

Auto-rotation is on by default. Disable it per player when your own view controller owns orientation:

```swift
playerViewController.setupFullscreen(parent: self, container: playerView)

// On by default. Disable when your own view controller owns orientation.
playerViewController.setFullscreenAutoRotate(enabled: false)
```

## Observe what actually happened

Conform to `FastPixFullscreenDelegate`. `onFullscreenOrientationChanged(isLandscape:)` fires only when the interface orientation really changed, while `onFullscreenOrientationRequest(outcome:)` fires for every request, including the refusals:

```swift
extension VideoPlayerViewController: FastPixFullscreenDelegate {

    // Fires ONLY when the interface orientation really changed.
    func onFullscreenOrientationChanged(isLandscape: Bool) {
        layoutChrome(forLandscape: isLandscape)
    }

    // Fires for EVERY request, including the refusals.
    func onFullscreenOrientationRequest(outcome: FastPixOrientationOutcome) {
        print("[Orientation]", outcome.explanation)

        if case .rejectedBySystem = outcome {
            // Almost always: supportedInterfaceOrientations excludes the requested orientation.
            showManualRotateHint()
        }
    }
}
```

### FastPixOrientationOutcome

The outcome is deliberately not a `Bool`. "It worked", "there is no window scene to ask", and "the system refused" need different responses, and collapsing them into a flag leads to reporting rotations that never happened.

| Case                                           | Meaning                                                                                                                                                 |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.applied(isLandscape:)`                       | The system accepted the request and the interface is now in that orientation                                                                            |
| `.alreadyInRequestedOrientation(isLandscape:)` | Nothing needed to change, so no rotation animation occurred and chrome has nothing to do                                                                |
| `.notRequested`                                | Auto-rotation is off via `setFullscreenAutoRotate(enabled: false)`. You asked for this                                                                  |
| `.unavailableBeforeIOS16`                      | `requestGeometryUpdate` is iOS 16+. On earlier versions the only mechanism was private API, which this SDK does not use                                 |
| `.noWindowScene`                               | The player's view is not in a window. Call this after the view has been added to one                                                                    |
| `.rejectedBySystem(reason:)`                   | The system was asked and declined, usually because your `supportedInterfaceOrientations` does not include it. The SDK cannot and does not override that |

| Property      | Type     | Description                                                                               |
| ------------- | -------- | ----------------------------------------------------------------------------------------- |
| `didRotate`   | `Bool`   | True only for `.applied`                                                                  |
| `isLandscape` | `Bool?`  | The orientation at the end of this, whether or not it just moved. `nil` for every refusal |
| `explanation` | `String` | A loggable sentence, including the supported alternative for each refusal                 |

## Fill the rotated frame

Rotation changes the shape of the space the picture is drawn in, which makes fitting the next question. `videoFillMode` is applied to both surfaces the SDK can present through, the `AVPlayerViewController`'s own video gravity and the `AVPlayerLayer` attached for Picture in Picture, so a mode does not appear to take effect and then silently revert when Picture in Picture starts. It also survives the surface being rebuilt.

```swift
playerViewController.videoFillMode = .fill

if playerViewController.videoFillMode.cropsPicture {
    // Burned-in subtitles along the lower edge can be cropped away in this mode.
    print(playerViewController.videoFillMode.explanation)
}
```

### FastPixVideoFillMode

| Case       | `AVLayerVideoGravity` | Meaning                                                                                   |
| ---------- | --------------------- | ----------------------------------------------------------------------------------------- |
| `.fit`     | `.resizeAspect`       | Default. The whole picture, undistorted, with bars where the shapes differ                |
| `.fill`    | `.resizeAspectFill`   | Frame filled, nothing distorted, edges cropped away, including subtitles burned into them |
| `.stretch` | `.resize`             | Frame filled, nothing cropped, picture distorted to match the frame's shape               |

Derived properties: `preservesAspectRatio` (false only for `.stretch`), `cropsPicture` (true only for `.fill`), and `explanation`. `FastPixVideoFillMode.mode(for:)` maps a gravity constant back, returning `nil` rather than a confident wrong answer if AVFoundation adds a fourth value.

> **Note:**
>
> * The SDK requests an orientation; it cannot impose one. Your view controller's `supportedInterfaceOrientations` is the final authority, and `.rejectedBySystem` is what you get when it disallows the request.
> * On iOS 13 to 15, fullscreen does not rotate the interface (`.unavailableBeforeIOS16`). Drive rotation from your own `supportedInterfaceOrientations` instead, which works on every version, 16 and later included.
> * Call rotation-dependent APIs after the view is in a window. Off-window there is no scene to ask, which is `.noWindowScene` and a call-ordering mistake rather than a device limitation.
> * `videoFillMode` is presentation only. It does not change the decoded frames, the asset, or what any analytics or screenshot API reports.
> * Add overlay views such as subtitle labels and custom controls to `playerViewController.view` rather than `self.view`, so they stay positioned correctly across rotations and fullscreen transitions.

#### Frequently asked questions

The SDK rotates the interface by asking the window scene for an orientation through `UIWindowScene.requestGeometryUpdate`, a public iOS 16+ API, then reads back the orientation the system actually settled on. It does not rotate the video layer independently of the interface, and it does not use private API. Auto-rotation is on by default and can be turned off per player with `setFullscreenAutoRotate(enabled: false)`.

Your view controller's `supportedInterfaceOrientations` is the final authority, and the SDK cannot override it. When the system declines a request, the outcome is `.rejectedBySystem(reason:)`, usually because the requested orientation is not in `supportedInterfaceOrientations`. Observe `onFullscreenOrientationRequest(outcome:)` to see the reason and show a manual-rotate hint.

No. `requestGeometryUpdate` is iOS 16 and later, so on earlier versions the outcome is `.unavailableBeforeIOS16`. The only earlier mechanism was private API, which this SDK does not use. Drive rotation from your own `supportedInterfaceOrientations` instead, which works on every version.

`.fit` (the default) shows the whole picture undistorted with bars where the shapes differ. `.fill` fills the frame with nothing distorted but crops the edges away, including subtitles burned into them. `.stretch` fills the frame with nothing cropped but distorts the picture to match the frame's shape. `videoFillMode` is presentation only and does not change the decoded frames or what analytics report.