---
title: "Playing screen"
description: "Display artwork and playback controls."
---

## Example

<div className="pattern-example">
<div>

<p>Pass your player as <code>playback</code>. The screen connects its native clock and transport commands.</p>

<p>Titles and artist names stay on one line and scroll when they don't fit.</p>

<p>Progress and elapsed time update natively. The screen derives buffering and initial loading from the player, and keeps the pause icon visible while playback is requested.</p>

```tsx
import { PlayingScreen } from "ink";
import artwork from "../assets/wallsocket.jpg";

<PlayingScreen
  image={artwork}
  title="Cops and robbers"
  artists={[{ name: "underscores" }]}
  playback={player}
  actions={actions}
/>
```
<p>Use a player from <a href="/audio">Audio</a>. <code>PlayingScreen</code> provides the whole screen; do not wrap it in <code>Screen</code>.</p>

</div>
<div className="pattern-preview">
<img src="/images/patterns/playing.png" alt="Artwork, track details, progress and playback controls with bottom actions." width="1080" height="1240" />
</div>
</div>

## Native playback clock

`PlayingScreen` reads the native clock from its playback binding. Apps do not pass position, speed or seek handlers separately. See [native playback progress](/audio#keep-visual-progress-native).

Custom players implement the exported `Playback` interface: state, `toggle()` and `seek()`. Optional `previous()`, `next()` and `seekBy()` methods enable the corresponding buttons. Native clock bindings remain internal to Ink players. Custom players and controlled previews render their supplied state.

Transport commands and bottom actions may return promises. The screen prevents overlapping commands, shows failures and retries the command that failed. App-specific commands supplied in the binding still run, including persistence and sleep-timer logic.

## Upcoming artwork

Pass up to two cover sources in `preloadImages`, such as the next and previous tracks. Ink prepares them at the playing screen’s image size and keeps the current cover visible while a replacement loads.

```tsx
<PlayingScreen image={track.artwork} preloadImages={nearbyCovers} {...playbackProps} />
```

## Without artwork

<div className="pattern-example">
<div>

<p>Omit <code>image</code> to centre the main controls.</p>

</div>
<div className="pattern-preview">
<img src="/images/patterns/playing-no-image.png" alt="Playback controls centred without artwork." width="1080" height="1240" />
</div>
</div>

## Seek buttons

<div className="pattern-example">
<div>

<p>Set <code>kind: "skip"</code> and <code>seconds</code> to 5, 10 or 30 to use relative seeking.</p>

```tsx
previous={{ kind: "skip", seconds: 10 }}
next={{ kind: "skip", seconds: 30 }}
```
<p><code>onLongPress</code> adds a separate long-press action. It runs once and suppresses the tap on release. <code>disabled</code> prevents both actions.</p>

</div>
<div className="pattern-preview">
<img src="/images/patterns/playing-seek.png" alt="Playback controls with 10-second rewind and 30-second forward icons." width="1080" height="1240" />
</div>
</div>

## Bottom actions

<div className="pattern-example">
<div>

<p>Actions are spaced evenly at the bottom. Use <code>selected</code> for an underline, or change the icon.</p>

<p>Use <code>label</code> instead of <code>icon</code> for text actions, such as playback speed or download progress. Numbers align automatically.</p>

```tsx
import { repeatFilled, repeatOneFilled, shuffleFilled } from "ink/icons";

const actions = [
  { icon: shuffleFilled, selected: shuffled, onPress: toggleShuffle },
  { icon: repeatOne ? repeatOneFilled : repeatFilled,
    selected: repeating, onPress: cycleRepeat },
];
```

</div>
<div className="pattern-preview">
**Inactive**

<img src="/images/patterns/playing.png" alt="Shuffle and repeat without underlines." width="1080" height="1240" />

**Active**

<img src="/images/patterns/playing-active.png" alt="Underlined shuffle and repeat-one controls." width="1080" height="1240" />

</div>
</div>

Transport controls default to `{ kind: "track" }`, which calls the matching `previous()` or `next()` method on `playback`. `{ kind: "skip", seconds: 10 }` calls `seekBy()` with the matching direction and interval. Unavailable methods disable the control.

Use `{ kind: "custom", icon, onPress }` for a custom transport action. Its icon and handler are explicit, so there is no default action to override. Every variant accepts `onLongPress` and `disabled`; long presses remain separate from taps.

## Title and artists

Use `onTitlePress` for the track title and an `onPress` on each artist to open their pages. An image reserves its space while loading.
