Events

addEventListener

Subscribe to player events in your React components:

TrackPlayer.addEventListener(event:T,listener:EventPayloadByEvent[T] extends never ? () => void : (event: EventPayloadByEvent[T]) => void):EmitterSubscription
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

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

FieldTypeDefaultDescription
state * PlaybackState
TrackPlayer.addEventListener(Event.PlaybackStateChanged, ({ state }) => {
  console.log('New state:', state);
});

IsPlayingChanged

Fires when playing status toggles.

FieldTypeDefaultDescription
playing * boolean
TrackPlayer.addEventListener(Event.IsPlayingChanged, ({ playing }) => {
  console.log('Playing:', playing);
});

MediaItemTransition

Fires when the active queue item changes.

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

FieldTypeDefaultDescription
title string
artist string
albumTitle string
artworkUrl string
genre string
TrackPlayer.addEventListener(Event.MediaMetadataChanged, ({ title, artist }) => {
  console.log('Now playing:', title, artist);
});
The `useActiveMediaItem` hook subscribes to this event internally, so UI that uses the hook stays in sync with the lock screen automatically.

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

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

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

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

FieldTypeDefaultDescription
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

FieldTypeDefaultDescription
position * number

RemoteSkipForward

FieldTypeDefaultDescription
interval * number

RemoteSkipBackward

FieldTypeDefaultDescription
interval * number

Background event handler

TrackPlayer.registerBackgroundEventHandler(factory:() => BackgroundEventHandler):void

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;
  }
});
If registered, it must be called before setupPlayer, in a file loaded at app startup — not inside a component.
ende