Events
addEventListener
Subscribe to player events in your React components:
import TrackPlayer, { Event } from '@rntp/player';
import { useEffect } from 'react';
useEffect(() => {
const sub = TrackPlayer.addEventListener(Event.PlaybackError, (event) => {
console.error('Playback error', event.code, event.message);
});
return () => sub.remove();
}, []); Event catalogue
| Event | Value | Description |
|---|---|---|
| Event.PlaybackStateChanged | 'event.playback-state-changed' | — |
| Event.IsPlayingChanged | 'event.is-playing-changed' | — |
| Event.MediaItemTransition | 'event.media-item-transition' | — |
| Event.MediaMetadataChanged | 'event.media-metadata-changed' | — |
| Event.MetadataReceived | 'event.metadata-received' | — |
| Event.PlaybackError | 'event.playback-error' | — |
| Event.PlaybackProgressUpdated | 'event.playback-progress-updated' | — |
| Event.QueueChanged | 'event.queue-changed' | — |
| Event.RemotePlay | 'event.remote-play' | — |
| Event.RemotePause | 'event.remote-pause' | — |
| Event.RemoteNext | 'event.remote-next' | — |
| Event.RemotePrevious | 'event.remote-previous' | — |
| Event.RemoteStop | 'event.remote-stop' | — |
| Event.RemoteSeek | 'event.remote-seek' | — |
| Event.RemoteSkipForward | 'event.remote-skip-forward' | — |
| Event.RemoteSkipBackward | 'event.remote-skip-backward' | — |
| Event.SleepTimerTriggered | 'event.sleep-timer-triggered' | — |
Event payloads
PlaybackStateChanged
Fires when the playback state transitions.
| Field | Type | Default | Description |
|---|---|---|---|
| state * | PlaybackState | — | — |
TrackPlayer.addEventListener(Event.PlaybackStateChanged, ({ state }) => {
console.log('New state:', state);
}); IsPlayingChanged
Fires when playing status toggles.
| Field | Type | Default | Description |
|---|---|---|---|
| playing * | boolean | — | — |
TrackPlayer.addEventListener(Event.IsPlayingChanged, ({ playing }) => {
console.log('Playing:', playing);
}); MediaItemTransition
Fires when the active queue item changes.
| Field | Type | Default | Description |
|---|---|---|---|
| item * | MediaItem | null | — | — |
| index * | number | — | — |
TrackPlayer.addEventListener(Event.MediaItemTransition, ({ item, index }) => {
console.log('Now playing:', item?.title, 'at index', index);
}); MediaMetadataChanged
Fires when the effective metadata of the currently active media item changes — the view that backs getActiveMediaItem() and the system Now Playing info (lock screen, Bluetooth, CarPlay / Android Auto). This is the event most apps want: it accounts for track transitions, explicit updateMetadata calls, and the auto-update path that merges incoming stream metadata into the queued item.
| Field | Type | Default | Description |
|---|---|---|---|
| title | string | — | — |
| artist | string | — | — |
| albumTitle | string | — | — |
| artworkUrl | string | — | — |
| genre | string | — | — |
TrackPlayer.addEventListener(Event.MediaMetadataChanged, ({ title, artist }) => {
console.log('Now playing:', title, artist);
}); MetadataReceived
Fires for every metadata frame received off the wire (ICY blocks on Shoutcast/Icecast streams, ID3 frames in MP3/AAC streams, Vorbis comments, …). The payload reflects stream-derived fields only and is not merged with the queued media item — for the merged effective view, subscribe to MediaMetadataChanged.
Useful for analytics / scrobbling and for sanitization pipelines that want to filter stream metadata before writing it back (combine with autoUpdateMetadataFromStream: false and call updateMetadata yourself).
| Field | Type | Default | Description |
|---|---|---|---|
| title | string | — | — |
| artist | string | — | — |
| albumTitle | string | — | — |
| artworkUrl | string | — | — |
| genre | string | — | — |
TrackPlayer.addEventListener(Event.MetadataReceived, ({ title, artist }) => {
console.log('Stream pushed:', title, artist);
}); PlaybackError
Fires when a playback error occurs.
| Field | Type | Default | Description |
|---|---|---|---|
| code * | PlaybackErrorCode | — | — |
| message * | string | — | — |
TrackPlayer.addEventListener(Event.PlaybackError, ({ code, message }) => {
console.error(`[${code}] ${message}`);
if (code === 'network') TrackPlayer.retry();
}); PlaybackProgressUpdated
Fires periodically during playback when progressSync is configured. Contains the same payload sent to the HTTP endpoint and saved natively.
| Field | Type | Default | Description |
|---|---|---|---|
| mediaId * | string | — | — |
| position * | number | — | — |
| duration * | number | — | — |
| timestamp * | number | — | — |
TrackPlayer.addEventListener(Event.PlaybackProgressUpdated, ({ mediaId, position, duration, timestamp }) => {
console.log(`${mediaId}: ${position}/${duration}s at ${timestamp}`);
}); SleepTimerTriggered
Fires when a sleep timer pauses playback.
| Field | Type | Default | Description |
|---|---|---|---|
| type * | 'time' | 'mediaItem' | — | Which sleep timer mode triggered: countdown or media-item boundary. |
TrackPlayer.addEventListener(Event.SleepTimerTriggered, ({ type }) => {
console.log(`Sleep timer fired (${type})`);
}); QueueChanged
Fires when the queue is modified. No payload.
TrackPlayer.addEventListener(Event.QueueChanged, () => {
const queue = TrackPlayer.getQueue();
}); Remote control events
These fire in response to hardware buttons, lock screen controls, and notification actions. Handle them in your background event handler.
RemotePlay / RemotePause / RemoteNext / RemotePrevious / RemoteStop
All four fire with no payload.
RemoteSeek
| Field | Type | Default | Description |
|---|---|---|---|
| position * | number | — | — |
RemoteSkipForward
| Field | Type | Default | Description |
|---|---|---|---|
| interval * | number | — | — |
RemoteSkipBackward
| Field | Type | Default | Description |
|---|---|---|---|
| interval * | number | — | — |
Background event handler
By default (handling: 'native'), the system handles remote control events automatically — play, pause, skip, and seek all work without any JavaScript code. You do not need to register a background event handler for basic playback to work.
Register a handler when you need to intercept events yourself — for example, to update metadata, implement custom skip logic, or use handling: 'js' / 'hybrid':
import TrackPlayer, { Event, type BackgroundEvent } from '@rntp/player';
TrackPlayer.registerBackgroundEventHandler(() => async (event: BackgroundEvent) => {
switch (event.type) {
case Event.RemotePlay:
TrackPlayer.play();
break;
case Event.RemotePause:
TrackPlayer.pause();
break;
case Event.RemoteNext:
TrackPlayer.skipToNext();
break;
case Event.RemotePrevious:
TrackPlayer.skipToPrevious();
break;
case Event.RemoteSeek:
TrackPlayer.seekTo(event.position);
break;
}
}); setupPlayer, in a file loaded at app startup — not inside a component.