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

# Listen to playback events

> Learn how to observe playback state changes, buffering, seek events, and more using the `PlaybackListener` interface.

Learn how to observe playback state changes, buffering, seek events, and more using the `PlaybackListener` interface.

All callbacks are invoked on the main thread.

## Attach a playback listener

```kotlin
val listener = object : PlaybackListener {
    override fun onPlay() {
        // Playback started or resumed
    }
    override fun onPause() {
        // Playback paused (not called for ended/idle transitions)
    }
    override fun onPlaybackStateChanged(isPlaying: Boolean) {
        // Unified play/pause state change
    }
    override fun onPlayerReady(durationMs: Long) {
        // Called once per media item when the player reaches READY the first time
    }
    override fun onTimeUpdate(currentPositionMs: Long, durationMs: Long, bufferedPositionMs: Long) {
        // Periodic time updates during active playback (default ~500ms)
    }
    override fun onSeekStart(currentPositionMs: Long) {
        // Fired when a seek begins (user scrub or programmatic seekTo)
    }
    override fun onSeekEnd(fromPositionMs: Long, toPositionMs: Long, durationMs: Long) {
        // Fired when seek completes
    }
    override fun onBufferingStart() {
        // Transition to buffering state
    }
    override fun onBufferingEnd() {
        // Transition from buffering to ready
    }
    override fun onFirstFrameRendered() {
        // First video frame is on screen (hide your poster)
    }
    override fun onVolumeChanged(volumeLevel: Float) {
        // Device volume changed (0.0..1.0)
    }
    override fun onMuteStateChanged(isMuted: Boolean) {
        // Mute state changed
    }
    override fun onPlaybackRateChanged(rate: Float) {
        // Playback speed updated
    }
    override fun onCompleted() {
        // Reached end of media (still called even if loop=true, before repeating)
    }
    override fun onError(error: PlaybackException) {
        // Playback or validation error
    }
}
player.addPlaybackListener(listener)
```

## Callback details

* **`onPlayerReady(durationMs)`** fires once per media item when the player reaches READY for the first time, not on every rebuffer or seek.
* **`onTimeUpdate(...)`** fires periodically during active playback (default \~500ms) and stops automatically when paused, ended, or when no listeners remain.
* **`onSeekStart` / `onSeekEnd`** trigger for both user scrubs and programmatic `seekTo` calls.
* **`onBufferingStart` / `onBufferingEnd`** fire on buffering transitions. Use these to show or hide a loading spinner.
* **`onCompleted()`** fires when reaching the end of media. If `loop = true`, this callback still fires before the video repeats.
* **`onFirstFrameRendered()`** fires when the first video frame is on screen. Use it to hide a poster or placeholder image.

For video quality changes, the `onVideoQualityChanged(quality, source)` callback is covered in [Switch video quality](/docs/android-player/switch-video-quality).

## Remove the listener

Remove the listener when it's no longer needed to avoid leaks:

```kotlin
player.removePlaybackListener(listener)
```

#### Frequently asked questions

The `PlaybackListener` interface delivers `onPlay`, `onPause`, `onPlaybackStateChanged`, `onPlayerReady`, `onTimeUpdate`, `onSeekStart`, `onSeekEnd`, `onBufferingStart`, `onBufferingEnd`, `onFirstFrameRendered`, `onVolumeChanged`, `onMuteStateChanged`, `onPlaybackRateChanged`, `onCompleted`, and `onError`.

Implement `onBufferingStart` and `onBufferingEnd`. They fire on buffering transitions, so use them to show a loading spinner when buffering begins and hide it when playback resumes.

The `onCompleted` callback fires when playback reaches the end of the media. It still fires even when `loop = true`, called just before the video repeats.

All `PlaybackListener` callbacks are invoked on the main thread, so you can update your UI directly from inside them without switching threads.

## What's next

* [Handle playback errors](/docs/android-player/handle-playback-errors) to respond to the `onError` callback.
* [Switch video quality](/docs/android-player/switch-video-quality) for the `onVideoQualityChanged` callback.
* [Control video playback](/docs/android-player/control-video-playback) for play, pause, and seek control.