Skip to main content

API Reference

Complete API reference for @bbplayer/orpheus including the native module, React hooks, enums, interfaces, and event system.

Module Export

import { Orpheus } from '@bbplayer/orpheus'
The Orpheus object is the main native module instance providing all playback functionality.

Properties

restorePlaybackPositionEnabled

Orpheus.restorePlaybackPositionEnabled: boolean
Enables automatic restoration of playback position on app restart.

loudnessNormalizationEnabled

Orpheus.loudnessNormalizationEnabled: boolean
Enables ReplayGain-style loudness normalization (Android only).

autoplayOnStartEnabled

Orpheus.autoplayOnStartEnabled: boolean
Automatically resumes playback when the app starts.

isDesktopLyricsShown

Orpheus.isDesktopLyricsShown: boolean
Returns whether desktop lyrics window is currently visible (Android only).

isDesktopLyricsLocked

Orpheus.isDesktopLyricsLocked: boolean
Returns whether desktop lyrics window is locked against dragging (Android only).

Playback Methods

play()

play(): Promise<void>
Starts or resumes playback.
await Orpheus.play()

pause()

pause(): Promise<void>
Pauses playback.
await Orpheus.pause()

seekTo()

seekTo(seconds: number): Promise<void>
Seeks to a specific position in seconds.
seconds
number
required
Target position in seconds
await Orpheus.seekTo(120) // Seek to 2:00

getPosition()

getPosition(): Promise<number>
Returns the current playback position in seconds.
const position = await Orpheus.getPosition()

getDuration()

getDuration(): Promise<number>
Returns the total duration of the current track in seconds.
const duration = await Orpheus.getDuration()

getBuffered()

getBuffered(): Promise<number>
Returns the buffered position in seconds.
const buffered = await Orpheus.getBuffered()

getIsPlaying()

getIsPlaying(): Promise<boolean>
Returns whether playback is currently active.
const isPlaying = await Orpheus.getIsPlaying()

Queue Methods

addToEnd()

addToEnd(
  tracks: Track[],
  startFromId?: string,
  clearQueue?: boolean
): Promise<void>
Adds tracks to the end of the queue without deduplication.
tracks
Track[]
required
Array of tracks to add
startFromId
string
Optional track ID to start playing immediately after adding
clearQueue
boolean
Whether to clear the existing queue before adding
await Orpheus.addToEnd(
  [track1, track2, track3],
  track1.id,  // Start playing track1
  true        // Clear existing queue
)

playNext()

playNext(track: Track): Promise<void>
Inserts a track to play immediately after the current track.
track
Track
required
Track to insert as next
await Orpheus.playNext(track)

removeTrack()

removeTrack(index: number): Promise<void>
Removes a track from the queue by index.
index
number
required
Zero-based queue index
await Orpheus.removeTrack(3)

clear()

clear(): Promise<void>
Clears the entire playback queue.
await Orpheus.clear()

getQueue()

getQueue(): Promise<Track[]>
Returns the current playback queue.
const queue = await Orpheus.getQueue()

getCurrentTrack()

getCurrentTrack(): Promise<Track | null>
Returns the currently playing track, or null if none.
const track = await Orpheus.getCurrentTrack()
if (track) {
  console.log('Now playing:', track.title)
}

getCurrentIndex()

getCurrentIndex(): Promise<number>
Returns the current queue index.
const index = await Orpheus.getCurrentIndex()

getIndexTrack()

getIndexTrack(index: number): Promise<Track | null>
Returns the track at a specific queue index.
index
number
required
Zero-based queue index
const track = await Orpheus.getIndexTrack(5)

skipTo()

skipTo(index: number): Promise<void>
Skips to a specific track by queue index.
index
number
required
Zero-based queue index to skip to
await Orpheus.skipTo(7)

skipToNext()

skipToNext(): Promise<void>
Skips to the next track in the queue.
await Orpheus.skipToNext()

skipToPrevious()

skipToPrevious(): Promise<void>
Skips to the previous track in the queue.
await Orpheus.skipToPrevious()

Repeat and Shuffle

setRepeatMode()

setRepeatMode(mode: RepeatMode): Promise<void>
Sets the repeat mode.
mode
RepeatMode
required
One of: RepeatMode.OFF, RepeatMode.TRACK, RepeatMode.QUEUE
import { RepeatMode } from '@bbplayer/orpheus'
await Orpheus.setRepeatMode(RepeatMode.QUEUE)

getRepeatMode()

getRepeatMode(): Promise<RepeatMode>
Returns the current repeat mode.
const mode = await Orpheus.getRepeatMode()

setShuffleMode()

setShuffleMode(enabled: boolean): Promise<void>
Enables or disables shuffle mode.
enabled
boolean
required
Whether to enable shuffle
await Orpheus.setShuffleMode(true)

getShuffleMode()

getShuffleMode(): Promise<boolean>
Returns whether shuffle mode is enabled.
const shuffled = await Orpheus.getShuffleMode()

Bilibili Integration

setBilibiliCookie()

setBilibiliCookie(cookie: string): void
Sets the authentication cookie for Bilibili API requests.
Full cookie string including SESSDATA
Orpheus.setBilibiliCookie('SESSDATA=abc123; buvid3=xyz...')

Download Methods

downloadTrack()

downloadTrack(track: Track): Promise<void>
Downloads a single track for offline playback.
track
Track
required
Track to download
await Orpheus.downloadTrack(track)

multiDownload()

multiDownload(tracks: Track[]): Promise<void>
Batch downloads multiple tracks.
tracks
Track[]
required
Array of tracks to download
await Orpheus.multiDownload([track1, track2, track3])

removeDownload()

removeDownload(id: string): Promise<void>
Removes a download task and its downloaded file.
id
string
required
Track ID to remove
await Orpheus.removeDownload('track-id')

removeDownloads()

removeDownloads(ids: string[]): Promise<void>
Batch removes multiple download tasks.
ids
string[]
required
Array of track IDs to remove
await Orpheus.removeDownloads(['id1', 'id2', 'id3'])

removeAllDownloads()

removeAllDownloads(): Promise<void>
Removes all download tasks including completed files.
await Orpheus.removeAllDownloads()

getDownloads()

getDownloads(): Promise<DownloadTask[]>
Returns all download tasks.
DownloadTask[]
array
Array of download task objects
const downloads = await Orpheus.getDownloads()
downloads.forEach(task => {
  console.log(`${task.track?.title}: ${task.percentDownloaded}%`)
})

getDownloadStatusByIds()

getDownloadStatusByIds(ids: string[]): Promise<Record<string, DownloadState>>
Returns download states for specific track IDs.
ids
string[]
required
Track IDs to query
const statuses = await Orpheus.getDownloadStatusByIds(['id1', 'id2'])
console.log(statuses) // { 'id1': DownloadState.COMPLETED, 'id2': DownloadState.DOWNLOADING }

clearUncompletedDownloadTasks()

clearUncompletedDownloadTasks(): Promise<void>
Removes all incomplete download tasks (keeps completed downloads).
await Orpheus.clearUncompletedDownloadTasks()

getUncompletedDownloadTasks()

getUncompletedDownloadTasks(): Promise<DownloadTask[]>
Returns only incomplete download tasks.
const pending = await Orpheus.getUncompletedDownloadTasks()

downloadMissingCovers()

downloadMissingCovers(): Promise<number>
Downloads cover art for tracks that have audio but no cover.
count
number
Number of covers started downloading
const count = await Orpheus.downloadMissingCovers()
console.log(`Downloading ${count} covers`)

getDownloadedCoverUri()

getDownloadedCoverUri(trackId: string): string | null
Returns the local file URI for a downloaded cover, or null if not available.
trackId
string
required
Track ID to query
const uri = Orpheus.getDownloadedCoverUri('track-id')
if (uri) {
  console.log('Cover at:', uri) // file:///...
}

Export Methods (Android)

selectDirectory()

selectDirectory(): Promise<string | null>
Opens Android’s directory picker and returns the selected SAF URI.
uri
string | null
SAF URI string, or null if cancelled
const uri = await Orpheus.selectDirectory()
if (uri) {
  console.log('Selected:', uri)
}

exportDownloads()

exportDownloads(
  ids: string[],
  destinationUri: string,
  filenamePattern: string | null,
  embedLyrics: boolean,
  convertToLrc: boolean,
  cropCoverArt: boolean
): Promise<void>
Batch exports downloaded tracks with metadata embedding.
ids
string[]
required
Track IDs to export
destinationUri
string
required
SAF directory URI from selectDirectory()
filenamePattern
string | null
required
Filename template using variables: {id}, {name}, {artist}, {bvid}, {cid}
embedLyrics
boolean
required
Whether to embed cached lyrics into M4A metadata
convertToLrc
boolean
required
Convert SPL lyrics to standard LRC (only if embedLyrics is true)
cropCoverArt
boolean
required
Crop cover art to square aspect ratio
const uri = await Orpheus.selectDirectory()
if (uri) {
  await Orpheus.exportDownloads(
    ['id1', 'id2'],
    uri,
    '{artist} - {name}',
    true,   // Embed lyrics
    false,  // Keep SPL format
    false   // Keep original aspect ratio
  )
}

Cache Methods

getLruCachedUris()

getLruCachedUris(uris: string[]): string[]
Checks which URIs are fully cached in the LRU stream cache.
uris
string[]
required
Array of orpheus:// URIs to check
cachedUris
string[]
Subset of input URIs that are fully cached
const cached = Orpheus.getLruCachedUris([
  'orpheus://bilibili?bvid=BV1xx411c7mD&cid=123456789',
  'orpheus://bilibili?bvid=BV1yy422c8mE&cid=987654321'
])
console.log('Cached:', cached)

Desktop Lyrics Methods (Android)

checkOverlayPermission()

checkOverlayPermission(): Promise<boolean>
Checks if overlay permission is granted.
const hasPermission = await Orpheus.checkOverlayPermission()

requestOverlayPermission()

requestOverlayPermission(): Promise<void>
Opens system settings to request overlay permission.
await Orpheus.requestOverlayPermission()

showDesktopLyrics()

showDesktopLyrics(): Promise<void>
Shows the desktop lyrics floating window.
await Orpheus.showDesktopLyrics()

hideDesktopLyrics()

hideDesktopLyrics(): Promise<void>
Hides the desktop lyrics floating window.
await Orpheus.hideDesktopLyrics()

setDesktopLyrics()

setDesktopLyrics(lyricsJson: string): Promise<void>
Sets the lyrics content for desktop display.
lyricsJson
string
required
JSON string containing lyrics data with timestamps
const lyrics = {
  lines: [
    { time: 0, text: 'Line 1' },
    { time: 5000, text: 'Line 2' }
  ]
}
await Orpheus.setDesktopLyrics(JSON.stringify(lyrics))

Advanced Methods

setPlaybackSpeed()

setPlaybackSpeed(speed: number): Promise<void>
Sets playback speed multiplier.
speed
number
required
Speed multiplier (typically 0.5 to 2.0)
await Orpheus.setPlaybackSpeed(1.25)

getPlaybackSpeed()

getPlaybackSpeed(): Promise<number>
Returns current playback speed.
const speed = await Orpheus.getPlaybackSpeed()

setSleepTimer()

setSleepTimer(durationMs: number): Promise<void>
Sets a sleep timer to pause playback after a duration.
durationMs
number
required
Duration in milliseconds
await Orpheus.setSleepTimer(30 * 60 * 1000) // 30 minutes

getSleepTimerEndTime()

getSleepTimerEndTime(): Promise<number | null>
Returns the sleep timer end timestamp, or null if not set.
const endTime = await Orpheus.getSleepTimerEndTime()
if (endTime) {
  console.log('Timer ends at:', new Date(endTime))
}

cancelSleepTimer()

cancelSleepTimer(): Promise<void>
Cancels the active sleep timer.
await Orpheus.cancelSleepTimer()

updateSpectrumData()

updateSpectrumData(destination: Float32Array): void
Synchronously updates a Float32Array with current FFT spectrum data (Android only).
destination
Float32Array
required
Pre-allocated Float32Array of length SPECTRUM_SIZE (512)
import { SPECTRUM_SIZE } from '@bbplayer/orpheus'

const spectrum = new Float32Array(SPECTRUM_SIZE)

function animate() {
  Orpheus.updateSpectrumData(spectrum)
  // Use spectrum data for visualization
  requestAnimationFrame(animate)
}

debugTriggerError()

debugTriggerError(): Promise<void>
Triggers a test error for debugging error handling.
await Orpheus.debugTriggerError()

React Hooks

useOrpheus()

function useOrpheus(): {
  state: PlaybackState
  isPlaying: boolean
  position: number
  duration: number
  buffered: number
  currentTrack: Track | null
  currentIndex: number
}
All-in-one hook combining playback state, progress, and current track.
import { useOrpheus } from '@bbplayer/orpheus'

function Player() {
  const { state, isPlaying, position, duration, currentTrack } = useOrpheus()
  
  return (
    <View>
      <Text>{currentTrack?.title}</Text>
      <Text>{position} / {duration}</Text>
    </View>
  )
}

useProgress()

function useProgress(): {
  position: number
  duration: number
  buffered: number
}
Hook for tracking playback progress.
import { useProgress } from '@bbplayer/orpheus'

function ProgressBar() {
  const { position, duration, buffered } = useProgress()
  return <Slider value={position} max={duration} />
}

usePlaybackState()

function usePlaybackState(): PlaybackState
Hook for tracking playback state changes.
import { usePlaybackState, PlaybackState } from '@bbplayer/orpheus'

function LoadingIndicator() {
  const state = usePlaybackState()
  return state === PlaybackState.BUFFERING ? <Spinner /> : null
}

useIsPlaying()

function useIsPlaying(): boolean
Hook for tracking play/pause state.
import { useIsPlaying } from '@bbplayer/orpheus'

function PlayButton() {
  const isPlaying = useIsPlaying()
  return <Icon name={isPlaying ? 'pause' : 'play'} />
}

useCurrentTrack()

function useCurrentTrack(): {
  track: Track | null
  index: number
}
Hook for tracking the current track and queue index.
import { useCurrentTrack } from '@bbplayer/orpheus'

function NowPlaying() {
  const { track, index } = useCurrentTrack()
  return (
    <View>
      <Text>{track?.title}</Text>
      <Text>Track {index + 1}</Text>
    </View>
  )
}

Headless Task

registerOrpheusHeadlessTask()

function registerOrpheusHeadlessTask(
  task: (event: OrpheusHeadlessEvent) => Promise<void>
): void
Registers a background task handler for playback events.
task
function
required
Async function that handles headless events
import { registerOrpheusHeadlessTask } from '@bbplayer/orpheus'

registerOrpheusHeadlessTask(async (event) => {
  switch (event.eventName) {
    case 'onTrackStarted':
      // Handle track start
      break
    case 'onTrackFinished':
      // Handle track finish
      break
    case 'onTrackPaused':
      // Handle pause
      break
    case 'onTrackResumed':
      // Handle resume
      break
  }
})

Events

Orpheus uses an event emitter pattern for real-time notifications.

Event Listener Pattern

const subscription = Orpheus.addListener('eventName', (event) => {
  // Handle event
})

// Later: remove listener
subscription.remove()

onPlaybackStateChanged

Orpheus.addListener('onPlaybackStateChanged', (event: { state: PlaybackState }) => {
  console.log('State:', event.state)
})
Fired when playback state changes (IDLE, BUFFERING, READY, ENDED).

onTrackStarted

Orpheus.addListener('onTrackStarted', (event: { 
  trackId: string
  reason: number 
}) => {
  console.log('Started:', event.trackId, 'Reason:', event.reason)
})
Fired when a track starts playing.

onTrackFinished

Orpheus.addListener('onTrackFinished', (event: {
  trackId: string
  finalPosition: number
  duration: number
}) => {
  console.log(`Finished ${event.trackId} at ${event.finalPosition}s`)
})
Fired when a track finishes playing.

onPositionUpdate

Orpheus.addListener('onPositionUpdate', (event: {
  position: number
  duration: number
  buffered: number
}) => {
  console.log(`${event.position}s / ${event.duration}s`)
})
Fired periodically with playback progress (typically every second).

onIsPlayingChanged

Orpheus.addListener('onIsPlayingChanged', (event: { status: boolean }) => {
  console.log('Playing:', event.status)
})
Fired when play/pause state changes.

onPlayerError

Orpheus.addListener('onPlayerError', (event: PlaybackErrorEvent) => {
  if (event.platform === 'android') {
    console.error('Android error:', event.errorCodeName, event.message)
    console.error('Stack:', event.stackTrace)
  } else {
    console.error('iOS error:', event.error)
  }
})
Fired when a playback error occurs. Event structure differs by platform.

onDownloadUpdated

Orpheus.addListener('onDownloadUpdated', (event: DownloadTask) => {
  console.log(`Download ${event.id}: ${event.percentDownloaded}%`)
  console.log('State:', event.state)
})
Fired when download progress or state changes.

onCoverDownloadProgress

Orpheus.addListener('onCoverDownloadProgress', (event: {
  current: number
  total: number
  trackId: string
  status: 'success' | 'failed'
}) => {
  console.log(`Cover ${event.current}/${event.total}: ${event.status}`)
})
Fired during batch cover art download operations.

onPlaybackSpeedChanged

Orpheus.addListener('onPlaybackSpeedChanged', (event: { speed: number }) => {
  console.log('Speed:', event.speed)
})
Fired when playback speed changes.

onExportProgress

Orpheus.addListener('onExportProgress', (event: {
  progress?: number
  currentId: string
  index?: number
  total?: number
  status: 'success' | 'error'
  message?: string
}) => {
  console.log(`Exporting [${event.index}/${event.total}]: ${event.currentId}`)
  if (event.progress !== undefined) {
    console.log(`Progress: ${event.progress}%`)
  }
})
Fired during batch export operations.

onHeadlessEvent

Orpheus.addListener('onHeadlessEvent', (event: OrpheusHeadlessEvent) => {
  console.log('Headless event:', event.eventName)
})
Fired for headless task events (iOS only - Android uses native headless service).

Types and Interfaces

Track

interface Track {
  id: string
  url: string
  title?: string
  artist?: string
  artwork?: string
  duration?: number
}

DownloadTask

interface DownloadTask {
  id: string
  state: DownloadState
  percentDownloaded: number
  bytesDownloaded: number
  contentLength: number
  track?: Track
}

PlaybackErrorEvent

type PlaybackErrorEvent = AndroidPlaybackErrorEvent | IosPlaybackErrorEvent

interface AndroidPlaybackErrorEvent {
  platform: 'android'
  errorCode: number
  errorCodeName: string | null
  timestamp: string
  message: string | null
  stackTrace: string
  rootCauseClass: string
  rootCauseMessage: string
}

interface IosPlaybackErrorEvent {
  platform: 'ios'
  error: string
}

OrpheusHeadlessEvent

type OrpheusHeadlessEvent =
  | OrpheusHeadlessTrackStartedEvent
  | OrpheusHeadlessTrackFinishedEvent
  | OrpheusHeadlessTrackPausedEvent
  | OrpheusHeadlessTrackResumedEvent

interface OrpheusHeadlessTrackStartedEvent {
  eventName: 'onTrackStarted'
  trackId: string
  reason: number
}

interface OrpheusHeadlessTrackFinishedEvent {
  eventName: 'onTrackFinished'
  trackId: string
  finalPosition: number
  duration: number
}

interface OrpheusHeadlessTrackPausedEvent {
  eventName: 'onTrackPaused'
}

interface OrpheusHeadlessTrackResumedEvent {
  eventName: 'onTrackResumed'
}

Enums

PlaybackState

enum PlaybackState {
  IDLE = 1,
  BUFFERING = 2,
  READY = 3,
  ENDED = 4
}

RepeatMode

enum RepeatMode {
  OFF = 0,
  TRACK = 1,
  QUEUE = 2
}

TransitionReason

enum TransitionReason {
  REPEAT = 0,
  AUTO = 1,
  SEEK = 2,
  PLAYLIST_CHANGED = 3
}

DownloadState

enum DownloadState {
  QUEUED = 0,
  STOPPED = 1,
  DOWNLOADING = 2,
  COMPLETED = 3,
  FAILED = 4,
  REMOVING = 5,
  RESTARTING = 7
}

Constants

SPECTRUM_SIZE

export const SPECTRUM_SIZE = 512
The valid length for FFT spectrum data. Use this to initialize the Float32Array for updateSpectrumData().
import { SPECTRUM_SIZE } from '@bbplayer/orpheus'

const spectrum = new Float32Array(SPECTRUM_SIZE)

Build docs developers (and LLMs) love