esp32-audio-player 0.1.0
Audio player engine for ESP32
Loading...
Searching...
No Matches
player.h File Reference

Playback engine: queue, gapless transitions, DSP, output negotiation, resume. More...

Include dependency graph for player.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  queue_item_t
 One queue entry. More...
 
struct  player_status_t
 
struct  player_config_t
 

Macros

#define PLAYER_QUEUE_MAX   20000
 Queue capacity.
 
#define PLAYER_VOLUME_MIN_DB   (-96.0f)
 Lowest volume the player sets (volume steps stop here).
 
#define PLAYER_FOLDER_MAX   2000
 Play the audio files of one folder (not its subfolders) in the order fs_list_dir() shows them, starting at index start.
 

Typedefs

typedef struct player player_t
 

Enumerations

enum  player_state_t { PLAYER_STOPPED = 0 , PLAYER_PLAYING , PLAYER_PAUSED }
 
enum  repeat_mode_t { REPEAT_OFF = 0 , REPEAT_ALL , REPEAT_ONE }
 

Functions

player_t * player_create (const player_config_t *cfg)
 
void player_destroy (player_t *p)
 
void player_cmd_play_items (player_t *p, const queue_item_t *items, uint32_t n, uint32_t start, bool shuffle)
 Queue semantics: the display order is the play order.
 
void player_cmd_play_tracks (player_t *p, const lib_id_t *ids, uint32_t n, uint32_t start, bool shuffle)
 
int player_cmd_play_folder (player_t *p, const char *dir, uint32_t start, bool shuffle)
 
void player_cmd_enqueue (player_t *p, const queue_item_t *items, uint32_t n, bool play_next)
 
void player_cmd_queue_remove (player_t *p, uint32_t index)
 
void player_cmd_queue_jump (player_t *p, uint32_t index)
 
void player_cmd_queue_clear (player_t *p)
 
void player_cmd_toggle_pause (player_t *p)
 
void player_cmd_pause (player_t *p)
 
void player_cmd_resume (player_t *p)
 from STOPPED with a non-empty queue: starts the current item
 
void player_cmd_stop (player_t *p)
 
void player_cmd_next (player_t *p)
 
void player_cmd_prev (player_t *p)
 restart the track if more than 3 s played
 
void player_cmd_seek_ms (player_t *p, uint32_t ms)
 
void player_cmd_seek_relative_ms (player_t *p, int32_t delta_ms)
 
void player_cmd_set_volume_db (player_t *p, float db)
 
void player_cmd_volume_step (player_t *p, int steps)
 
void player_cmd_set_shuffle (player_t *p, bool on)
 
void player_cmd_set_repeat (player_t *p, repeat_mode_t mode)
 
void player_cmd_set_dsp (player_t *p, const dsp_config_t *cfg)
 sw_volume, volume_db and out_bits are ignored: the player sets them from its volume, the sink capabilities (SINK_CAP_HW_VOLUME; SINK_CAP_DITHERS and SINK_CAP_BITPERFECT for the dither resolution, see sink.h) and the negotiated sink format.
 
void player_cmd_set_gapless (player_t *p, bool on)
 
void player_cmd_set_sleep_timer (player_t *p, uint32_t minutes)
 0 = off; fades out over 30 s
 
void player_cmd_set_volume_limit (player_t *p, float max_db)
 
void player_cmd_set_sink (player_t *p, audio_sink_t *sink)
 Switch output (mode change).
 
void player_get_status (player_t *p, player_status_t *out)
 
uint32_t player_queue_count (player_t *p)
 
int player_queue_get (player_t *p, uint32_t index, queue_item_t *out)
 Display order = play order (the shuffled order while shuffle is on).
 
int player_save_state (player_t *p)
 Audiobook positions are NOT stored by the player: the application calls library_save_position() (on pause, chapter change and every 30 s) and seeks on "Continue".
 
int player_restore_state (player_t *p, bool autoplay)
 Load state; starts paused at the saved position unless autoplay.
 
int32_t player_run_once (player_t *p)
 One iteration.
 
uint32_t player_wait_hint_ms (player_t *p)
 How long the caller may sleep after player_run_once() returned 0: 0 when there is more work right away, a few ms while the sink is full, longer while idle.
 
void player_set_chunk_frames (player_t *p, uint32_t frames)
 Frames the audio thread tries to decode per iteration (default 1024).
 

Detailed Description

Playback engine: queue, gapless transitions, DSP, output negotiation, resume.

Threading model. The player never creates threads. Any thread may call the player_cmd_* functions; they only enqueue a command (thread-safe, non-blocking). One audio thread calls player_run_once() in a loop: it applies commands, decodes, processes and writes to the sink (the sink write paces the loop). On the host, tests call player_run_once() directly with a memory sink.

Gapless: when the current track has less than ~2 s left, the next decoder is opened; if its format matches, frames continue without a gap. If the sample rate family changes the sink is reopened (short mute). Precisely: when the negotiated sink format (rate, bits, channels, DoP) of the next track is identical, frames continue without a flush and the DSP/resampler state carries over; otherwise the old tail is played out, the sink drains and is reopened with sink->open() (close + open when the sink cannot reconfigure). Consecutive CUE tracks of one file share one decoder.

Signal path per chunk (default 1024 frames): decoder -> mono upmix -> resampler (only when the sink rate differs, e.g. Bluetooth) -> DSP at the sink rate (dither last) -> sink. DoP bypasses the resampler and the DSP. The status snapshot and EV_TRACK_CHANGED follow what is audible: after a gapless switch they change once the tail of the previous track has left the sink buffer (buffered_frames()).

Events: EV_PLAYER_STATE (a = state; also after repeat/shuffle changes), EV_TRACK_CHANGED (a = queue index), EV_POSITION (a = ms, about 4 per second while playing and after a seek), EV_VOLUME (f = dB), EV_QUEUE_CHANGED (a = queue length), EV_PLAYBACK_ERROR (a = core_err_t, text = file name; CORE_EUNSUPPORTED for DoP on a sink without DoP), EV_SLEEP_TIMER (a = seconds left, 0 = fired and paused, -1 = cancelled).

Macro Definition Documentation

◆ PLAYER_FOLDER_MAX

#define PLAYER_FOLDER_MAX   2000

Play the audio files of one folder (not its subfolders) in the order fs_list_dir() shows them, starting at index start.

Returns the number of files queued, CORE_ENOTFOUND when the folder has none, or another negative core_err_t. At most PLAYER_FOLDER_MAX entries are read.

◆ PLAYER_QUEUE_MAX

#define PLAYER_QUEUE_MAX   20000

Queue capacity.

Internally entries are stored compactly (library id, or a path in a string pool in PSRAM), so a 20 000-track queue costs well under 1 MB.

◆ PLAYER_VOLUME_MIN_DB

#define PLAYER_VOLUME_MIN_DB   (-96.0f)

Lowest volume the player sets (volume steps stop here).

The upper end is the user limit (volume_max_db), never above 0 dB.

Typedef Documentation

◆ player_t

typedef struct player player_t

Enumeration Type Documentation

◆ player_state_t

Enumerator
PLAYER_STOPPED 
PLAYER_PLAYING 
PLAYER_PAUSED 

◆ repeat_mode_t

Enumerator
REPEAT_OFF 
REPEAT_ALL 
REPEAT_ONE 

Function Documentation

◆ player_cmd_enqueue()

void player_cmd_enqueue ( player_t *  p,
const queue_item_t *  items,
uint32_t  n,
bool  play_next 
)

◆ player_cmd_next()

void player_cmd_next ( player_t *  p)

◆ player_cmd_pause()

void player_cmd_pause ( player_t *  p)

◆ player_cmd_play_folder()

int player_cmd_play_folder ( player_t *  p,
const char *  dir,
uint32_t  start,
bool  shuffle 
)

◆ player_cmd_play_items()

void player_cmd_play_items ( player_t *  p,
const queue_item_t *  items,
uint32_t  n,
uint32_t  start,
bool  shuffle 
)

Queue semantics: the display order is the play order.

Shuffle is a seeded Fisher-Yates order with the current item first; switching it off restores the original order, and neither direction interrupts the current track. play_next inserts after the current item, enqueue into an empty queue does not start playback. At the end of the queue (repeat off) the player stops and rewinds to the first item. next/prev keep the state (paused stays paused with the new item prepared). Repeated volume and seek commands are merged. Entries without id and path are ignored. Replace the queue and start playing items[start]. shuffle=true shuffles the queue but keeps items[start] first.

◆ player_cmd_play_tracks()

void player_cmd_play_tracks ( player_t *  p,
const lib_id_t *  ids,
uint32_t  n,
uint32_t  start,
bool  shuffle 
)

◆ player_cmd_prev()

void player_cmd_prev ( player_t *  p)

restart the track if more than 3 s played

◆ player_cmd_queue_clear()

void player_cmd_queue_clear ( player_t *  p)

◆ player_cmd_queue_jump()

void player_cmd_queue_jump ( player_t *  p,
uint32_t  index 
)

◆ player_cmd_queue_remove()

void player_cmd_queue_remove ( player_t *  p,
uint32_t  index 
)

◆ player_cmd_resume()

void player_cmd_resume ( player_t *  p)

from STOPPED with a non-empty queue: starts the current item

◆ player_cmd_seek_ms()

void player_cmd_seek_ms ( player_t *  p,
uint32_t  ms 
)

◆ player_cmd_seek_relative_ms()

void player_cmd_seek_relative_ms ( player_t *  p,
int32_t  delta_ms 
)

◆ player_cmd_set_dsp()

void player_cmd_set_dsp ( player_t *  p,
const dsp_config_t *  cfg 
)

sw_volume, volume_db and out_bits are ignored: the player sets them from its volume, the sink capabilities (SINK_CAP_HW_VOLUME; SINK_CAP_DITHERS and SINK_CAP_BITPERFECT for the dither resolution, see sink.h) and the negotiated sink format.

◆ player_cmd_set_gapless()

void player_cmd_set_gapless ( player_t *  p,
bool  on 
)

◆ player_cmd_set_repeat()

void player_cmd_set_repeat ( player_t *  p,
repeat_mode_t  mode 
)

◆ player_cmd_set_shuffle()

void player_cmd_set_shuffle ( player_t *  p,
bool  on 
)

◆ player_cmd_set_sink()

void player_cmd_set_sink ( player_t *  p,
audio_sink_t *  sink 
)

Switch output (mode change).

The player stops writing to the old sink, closes it and continues on the new one from the same position. NULL = no output (paused).

◆ player_cmd_set_sleep_timer()

void player_cmd_set_sleep_timer ( player_t *  p,
uint32_t  minutes 
)

0 = off; fades out over 30 s

◆ player_cmd_set_volume_db()

void player_cmd_set_volume_db ( player_t *  p,
float  db 
)

◆ player_cmd_set_volume_limit()

void player_cmd_set_volume_limit ( player_t *  p,
float  max_db 
)

◆ player_cmd_stop()

void player_cmd_stop ( player_t *  p)

◆ player_cmd_toggle_pause()

void player_cmd_toggle_pause ( player_t *  p)

◆ player_cmd_volume_step()

void player_cmd_volume_step ( player_t *  p,
int  steps 
)

◆ player_create()

player_t * player_create ( const player_config_t *  cfg)

◆ player_destroy()

void player_destroy ( player_t *  p)

◆ player_get_status()

void player_get_status ( player_t *  p,
player_status_t *  out 
)

◆ player_queue_count()

uint32_t player_queue_count ( player_t *  p)

◆ player_queue_get()

int player_queue_get ( player_t *  p,
uint32_t  index,
queue_item_t *  out 
)

Display order = play order (the shuffled order while shuffle is on).

Library entries get their path filled in from the library. CORE_ENOTFOUND past the end. display order

◆ player_restore_state()

int player_restore_state ( player_t *  p,
bool  autoplay 
)

Load state; starts paused at the saved position unless autoplay.

Paths are resolved to library ids where possible. CORE_ENOTFOUND when there is no saved state.

◆ player_run_once()

int32_t player_run_once ( player_t *  p)

One iteration.

Returns frames written to the sink, 0 when idle (stopped/paused/no sink): the caller should then wait for a command (player_wait_hint_ms()).

◆ player_save_state()

int player_save_state ( player_t *  p)

Audiobook positions are NOT stored by the player: the application calls library_save_position() (on pause, chapter change and every 30 s) and seeks on "Continue".

Save queue (queue.m3u8), index, position, volume, shuffle/repeat to state_dir. Any thread; the queue is copied in small slices, so the audio thread is never held up. queue.m3u8 is a valid UTF-8 playlist in play order with absolute paths; "#EXT-EMP-" comment lines carry the original order, CUE ranges and library ids. Both files are replaced atomically (tmp, remove, rename). player.ini is written on every call; queue.m3u8 only when the queue (items or order) changed since this player last wrote it, or the file is missing, so periodic saves while playing cost one small write.

◆ player_set_chunk_frames()

void player_set_chunk_frames ( player_t *  p,
uint32_t  frames 
)

Frames the audio thread tries to decode per iteration (default 1024).

◆ player_wait_hint_ms()

uint32_t player_wait_hint_ms ( player_t *  p)

How long the caller may sleep after player_run_once() returned 0: 0 when there is more work right away, a few ms while the sink is full, longer while idle.