> For the complete documentation index, see [llms.txt](https://docs.nuclearplayer.com/nuclear/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nuclearplayer.com/nuclear/plugins/playback.md).

# Playback

## Playback API for plugins

The Playback API controls audio states: play, pause, stop, seek, and subscribe to state changes. It doesn't handle track navigation, use the Queue API for that.

{% hint style="info" %}
Access playback via `api.Playback.*` in your plugin's lifecycle hooks. All methods are asynchronous and return Promises.
{% endhint %}

***

## Core concepts

### Playback state

Playback state is exposed through a single object:

```typescript
type PlaybackState = {
  status: PlaybackStatus;
  seek: number;      // Current position in seconds
  duration: number;  // Total duration in seconds
};

type PlaybackStatus = 'playing' | 'paused' | 'stopped';
```

`seek` and `duration` are always in seconds, not milliseconds.

### Repeat modes

```typescript
type RepeatMode = 'off' | 'all' | 'one';
```

* `off` - Stop at the end of the queue
* `all` - Loop back to the beginning when reaching the end
* `one` - Repeat the current track indefinitely

Repeat mode is stored in the settings under the `core.playback.repeat` key.

### Shuffle

When shuffle is enabled, `goToNext()` and `goToPrevious()` on the Queue API pick random indices instead of sequential ones. The algorithm avoids repeating the same track twice in a row.

Shuffle state is stored in the settings under the `core.playback.shuffle` key.

### Playback vs. Queue

These two domains divide playback responsibilities:

| Domain       | Responsibility                                            |
| ------------ | --------------------------------------------------------- |
| **Playback** | Audio transport: play, pause, stop, seek, shuffle, repeat |
| **Queue**    | Track navigation: next, previous                          |

***

## Usage

{% tabs %}
{% tab title="Reading state" %}

```typescript
import type { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';

export default {
  async onEnable(api: NuclearPluginAPI) {
    const state = await api.Playback.getState();
    api.Logger.info(`Status: ${state.status}`);
    api.Logger.info(`Position: ${state.seek}s / ${state.duration}s`);
  },
};
```

{% endtab %}

{% tab title="Transport controls" %}

```typescript
import type { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';

export default {
  async onEnable(api: NuclearPluginAPI) {
    await api.Playback.play();
    await api.Playback.pause();
    await api.Playback.stop();

    // Toggle between play and pause.
    // If stopped, this starts playback.
    await api.Playback.toggle();
  },
};
```

{% endtab %}

{% tab title="Seeking" %}

```typescript
import type { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';

export default {
  async onEnable(api: NuclearPluginAPI) {
    // Jump to 90 seconds into the current track
    await api.Playback.seekTo(90);

    // Seek relative to the current position
    const state = await api.Playback.getState();
    await api.Playback.seekTo(state.seek + 10); // Forward 10s
  },
};
```

{% endtab %}

{% tab title="Subscribing" %}

```typescript
import type { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';

export default {
  async onEnable(api: NuclearPluginAPI) {
    const unsubscribe = api.Playback.subscribe((state) => {
      if (state.status === 'playing') {
        api.Logger.debug(`${state.seek.toFixed(1)}s / ${state.duration.toFixed(1)}s`);
      }
    });

    // Always clean up
    return () => {
      unsubscribe();
    };
  },
};
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
`subscribe` fires frequently during playback as the seek position updates. Keep your listener lightweight to avoid blocking the UI thread.
{% endhint %}

***

## Reference

```typescript
// State
api.Playback.getState(): Promise<PlaybackState>

// Transport
api.Playback.play(): Promise<void>
api.Playback.pause(): Promise<void>
api.Playback.stop(): Promise<void>
api.Playback.toggle(): Promise<void>

// Seeking
api.Playback.seekTo(seconds: number): Promise<void>

// Shuffle
api.Playback.isShuffleEnabled(): Promise<boolean>
api.Playback.setShuffleEnabled(enabled: boolean): Promise<void>

// Repeat
api.Playback.getRepeatMode(): Promise<RepeatMode>
api.Playback.setRepeatMode(mode: RepeatMode): Promise<void>

// Subscriptions
api.Playback.subscribe(listener: PlaybackListener): () => void
```

### Types

```typescript
type PlaybackStatus = 'playing' | 'paused' | 'stopped';

type PlaybackState = {
  status: PlaybackStatus;
  seek: number;      // Seconds
  duration: number;  // Seconds
};

type PlaybackListener = (state: PlaybackState) => void;

type RepeatMode = 'off' | 'all' | 'one';
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.nuclearplayer.com/nuclear/plugins/playback.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
