> 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.

# Build a custom player UI

> Learn how to replace the default FastPix player controls with your own custom UI.

Learn how to replace the default FastPix player controls with your own custom UI.

The FastPix iOS Player SDK lets you hide the default controls and build a fully custom video player interface. You can add your own play/pause button, seek bar, gesture-based interactions, and custom layouts while still using the FastPix engine for playback, buffering, analytics, and playlists.

## Hide default controls

Disable the SDK's built-in player UI:

```swift
playerViewController.hideDefaultControls = true
```

## Embed the player in a custom view

Place the FastPix player inside your own `UIView` so you can overlay custom controls on top:

```swift
func prepareAvPlayerController() {
    addChild(playerViewController)
    playerView.addSubview(playerViewController.view)
    playerViewController.didMove(toParent: self)

    playerViewController.view.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activate([
        playerViewController.view.topAnchor.constraint(equalTo: playerView.topAnchor),
        playerViewController.view.bottomAnchor.constraint(equalTo: playerView.bottomAnchor),
        playerViewController.view.leadingAnchor.constraint(equalTo: playerView.leadingAnchor),
        playerViewController.view.trailingAnchor.constraint(equalTo: playerView.trailingAnchor)
    ])
}
```

## Add a custom play/pause button

Create a play/pause button and connect it to the player. You can either use `togglePlayPause()` for automatic switching, or manually call `play()` and `pause()`.

```swift
private func setupPlayPauseButton() {
    playPauseButton = UIButton(type: .system)
    playPauseButton.translatesAutoresizingMaskIntoConstraints = false
    playPauseButton.tintColor = .white
    playPauseButton.backgroundColor = UIColor.black.withAlphaComponent(0.55)
    playPauseButton.layer.cornerRadius = 32
    playPauseButton.clipsToBounds = true

    let icon = UIImage(systemName: "pause.fill")?.withConfiguration(
        UIImage.SymbolConfiguration(pointSize: 30, weight: .bold)
    )
    playPauseButton.setImage(icon, for: .normal)

    playPauseButton.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside)
    playerView.addSubview(playPauseButton)

    NSLayoutConstraint.activate([
        playPauseButton.centerXAnchor.constraint(equalTo: playerView.centerXAnchor),
        playPauseButton.centerYAnchor.constraint(equalTo: playerView.centerYAnchor),
        playPauseButton.widthAnchor.constraint(equalToConstant: 64),
        playPauseButton.heightAnchor.constraint(equalToConstant: 64)
    ])
}

@objc private func playPauseTapped() {
    // Option 1: Toggle automatically (recommended)
    playerViewController.togglePlayPause()

    // Option 2: Manually control playback
    // playerViewController.play()
    // playerViewController.pause()
}
```

## Auto-update the play/pause button

Observe the player's `timeControlStatus` to keep the button icon in sync with the playback state:

```swift
playerStatusObserver = player.observe(\.timeControlStatus, options: [.new, .initial]) {
    [weak self] player, _ in
    DispatchQueue.main.async {
        self?.updatePlayPauseButton(for: player.timeControlStatus)
    }
}
```

## Add tap-to-show/hide controls

Use a tap gesture to toggle the visibility of your custom controls:

```swift
@objc private func togglePlayerControls() {
    let shouldShow = playPauseButton.alpha == 0
    shouldShow ? showAllControls(animated: true) : hideAllControls(animated: true)
}
```

## Auto-hide controls after a delay

Automatically hide controls after a period of inactivity:

```swift
private func autoHideControls(after delay: TimeInterval = 3.0) {
    controlsHideWorkItem?.cancel()
    let workItem = DispatchWorkItem { [weak self] in
        self?.hideAllControls(animated: true)
    }
    controlsHideWorkItem = workItem
    DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
}
```

## What's next

* [Add a custom seek bar](/docs/ios-player/add-a-custom-seek-bar) for scrubbing through video.
* [Add forward and rewind controls](/docs/ios-player/add-forward-and-rewind-controls) for seek buttons.
* [Handle orientation changes](/docs/ios-player/handle-orientation-changes) to adapt layout for landscape mode.