> For the complete documentation index, see [llms.txt](https://seanime.gitbook.io/seanime-extensions/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://seanime.gitbook.io/seanime-extensions/plugins/ui/anime-library/playback-external.md).

# Playback (External)

Interact with the desktop media player integrations.

This API allows you to control the interface between Seanime and desktop media players (MPV, IINA, VLC, MPC-HC).

## Permissions

{% hint style="warning" %}
`playback` permission is required
{% endhint %}

<pre class="language-json"><code class="lang-json">{
  //...
  "plugin": {
    "permissions": {
<strong>      "scopes": ["playback"]
</strong>    }
  }
}
</code></pre>

## Core methods

### playUsingMediaPlayer

**`playUsingMediaPlayer(filePath)`**

Plays a local file using the configured media player, with automatic tracking.

**Parameters:**

* `filePath`: String - Path to a scanned local video file

**Note:** This only works with files properly scanned by Seanime. Using it with unscanned files will result in tracking errors.

**Example:**

```typescript
// Play a local file with tracking
try {
  await ctx.playback.playUsingMediaPlayer("/anime/One Piece/One Piece - 1015.mkv")
  console.log("Playback started successfully")
} catch (error) {
  console.error("Playback error:", error)
}
```

### streamUsingMediaPlayer

`streamUsingMediaPlayer(windowTitle, streamUrl, anime, aniDbEpisode)`

Streams a video from a URL using the configured media player, with automatic tracking.

**Parameters:**

* `windowTitle`: String - Title for the player window
* `streamUrl`: String - URL of the video stream
* `anime`: AL\_BaseAnime - AniList anime object
* `aniDbEpisode`: String - AniDB episode number

**Example:**

```typescript
// Stream an episode with proper tracking
const anime = $anilist.getAnime(21) // One Piece
try {
  await ctx.playback.streamUsingMediaPlayer(
    "One Piece - Episode 1015", 
    "https://example.com/streams/one-piece-1015.mkv",
    anime,
    "1015"
  )
  console.log("Stream started successfully")
} catch (error) {
  console.error("Stream error:", error)
}
```

### registerEventListener

`registerEventListener(callback)`

Registers a listener for playback events.

**Parameters:**

* `callback`: Function(event: PlaybackEvent) - Function called when an event occurs

**Example:**

```typescript
// Listen for playback events
// Callback triggered every 1-3 seconds
const unsubscribe = ctx.playback.registerEventListener((event) => {
   //
   // Local file playback
   //
   if (event.isVideoStarted || event.isVideoCompleted || event.isIsVideoStopped) {
     // Video started
     if (event.isVideoStated) {
        console.log(event.startedEvent?.filename)
        return
     }
     
     // Video completed
     if (event.isVideoCompleted) {
        console.log(event.completedEvent?.filename)
        return
     }
     
     // Video stopped
     if (event.isIsVideoStopped) {
        console.log(event.stoppedEvent?.reason)
        return
     }
     
     // The playback state
     if (event.state) {
       console.log("Media title", event.state.mediaTitle)
       console.log("Episode number", event.state.episodeNumber) 
       console.log("Completion percentage", event.state.completionPercentage)
     }
     if(event.status) {
       console.log("Is Playing", event.status.playing)
       console.log("Current time", event.status.currentTimeInSeconds)
       console.log("Duration", event.status.durationInSeconds)
     }
   }
  
   //
   // Stream playback
   //
   if (event.isStreamStarted || event.isStreamCompleted || event.isStreamStopped) {
     // Stream started
     if (event.isStreamStarted) {
        console.log(event.startedEvent?.filename)
        return
     }
     
      // Stream completed
      if (event.isStreamCompleted) {
        console.log(event.completedEvent?.filename)
        return
      }
     
      // Stream stopped
      if (event.isStreamStopped) {
        console.log(event.stoppedEvent?.reason)
        return
      }
     
     // The stream playback state
     if (event.state) {
        console.log("Media title", event.state.mediaTitle)
        console.log("Episode number", event.state.episodeNumber) 
        console.log("Completion percentage", event.state.completionPercentage)
     }
     if(event.status) {
        console.log("Is Playing", event.status.playing)
        console.log("Current time", event.status.currentTimeInSeconds)
        console.log("Duration", event.status.durationInSeconds)
     }
   }
})

// Later, to stop listening
unsubscribe()
```

### pause

Pauses the current playback.

**Example:**

```typescript
try {
  ctx.playback.pause()
  console.log("Playback paused")
} catch (error) {
  console.error("Could not pause:", error)
}
```

### resume

Resumes the paused playback.

**Example:**

```typescript
// Resume after pausing
try {
  ctx.playback.resume()
  console.log("Playback resumed")
} catch (error) {
  console.error("Could not resume:", error)
}
```

### seekTo

Seeks to a specific position in the current playback.

**Parameters:**

* `seconds`: Number - The position to seek to in seconds

**Example:**

```typescript
// Skip ahead 30 seconds
try {
  ctx.playback.seekTo(currentTimeInSeconds + 30)
  console.log("Skipped forward 30 seconds")
} catch (error) {
  console.error("Could not seek:", error)
}
```

### cancel

Cancels the current playback.

**Example:**

```typescript
try {
  ctx.playback.cancel()
  console.log("Playback canceled")
} catch (error) {
  console.error("Could not cancel playback:", error)
}
```

### startManualTracking

`startManualTracking(opts)`

Starts manual progress tracking for media that is not being launched through Seanime's integrated playback APIs.

If `clientId` is omitted, Seanime broadcasts it to all clients.

**Parameters:**

* `opts`: `PlaybackManualTrackingOptions`
* `opts.mediaId`: Number - AniList media ID to track
* `opts.episodeNumber`: Number - Episode number to sync
* `opts.clientId`: String - Optional client ID override

**Example:**

```typescript
await ctx.playback.startManualTracking({
  mediaId: 21,
  episodeNumber: 1,
})
```

### syncCurrentProgress

`syncCurrentProgress()`

Synchronizes the currently tracked progress with AniList.

This is most useful after `startManualTracking()` in custom player or external-player-link workflows.

**Example:**

```typescript
await ctx.playback.syncCurrentProgress()
```

### cancelManualTracking

`cancelManualTracking()`

Stops the current manual tracking session.

**Example:**

```typescript
ctx.playback.cancelManualTracking()
```

### getNextEpisode

Gets the next episode to play after the current one.

**Example:**

```typescript
// Check if there's a next episode
try {
  const nextEpisode = await ctx.playback.getNextEpisode()
  if (nextEpisode) {
    console.log(`Next episode: ${nextEpisode.name}`)
  } else {
    console.log("No next episode available")
  }
} catch (error) {
  console.error("Error getting next episode:", error)
}
```

### playNextEpisode

Plays the next episode for the current media.

**Example:**

```typescript
// Play next episode when current is almost done
ctx.playback.registerEventListener((event) => {
  if (event.status && event.status.completionPercentage > 95) {
    try {
      ctx.playback.playNextEpisode()
    } catch(e) {}
  }
})
```

## Best Practices

#### Media Tracking

The playback API is designed for tracked media files that are part of the Seanime library:

```typescript
// Good practice: Play scanned files for proper tracking
const localFile = getScannedFile() // Get a file that's in the library
ctx.playback.playUsingMediaPlayer(localFile.path)

// Bad practice: Playing unscanned files won't track properly
ctx.playback.playUsingMediaPlayer("/random/video.mp4") // Will cause tracking errors
```
