rockbox/apps/playlist.h
Adam N. Burke a824085057 skin: add %pX tag for time-based playlist progress
%pP reports playlist progress by position index, which treats every
track as equally long. For playlists with tracks of unequal length --
audiobooks with chapters anywhere from two minutes to an hour are the
motivating case -- position is a poor proxy for listening progress.

%pX reports the played percentage of the whole playlist by time: the
summed length of all preceding tracks plus the elapsed time in the
current one, relative to the playlist's total duration. It can be
used as a value, in a conditional, with %if(), or as a bar tag like
%pb.

If a playlist contains more than 500 tracks or the scan is taking too
long and the user aborts the tag will fallback to the behavior of %pP
except the progress through the current track will be included in the
returned percentage

--------------------------------------
Computing this needs every track's length, and reading metadata for
every track is too slow and disk-heavy for a tag that refreshes on the
WPS. Instead each track's length is estimated from its file size: the
skin engine scans the playlist in the background, a batch of files each
skin refresh, opening each file only to read its size (directory
metadata, no header parse). Size is turned into time by calibrating one
file of each type -- the first file of each extension is parsed once
with get_metadata to learn its bytes-per-second, and every later file
of that type reuses it. A single-format playlist, the usual audiobook
case, parses exactly one file and stat's the rest.

The result is an estimate -- bitrate varies within a type, especially
for VBR -- but it is cheap and accurate enough for a progress
indicator, and the playing track always contributes its exact elapsed
time. Per-track lengths are stored as two bytes of minutes each in a
movable buffer sized to the track count and allocated only while the
tag is in use; the cache is keyed on the track count and a crc of the
first, middle and last filenames, so a playlist swap or reshuffle is
caught. If the buffer cannot be allocated (a very large playlist on a
low-memory target) the tag falls back to position-based progress.

The buffer is allocated when playback starts (and when the now-playing
screen is opened with playback already active) rather than lazily on the
first WPS refresh, so the one-time allocation happens at a playback
boundary instead of during steady-state playback; it falls back to
allocating on first use if that point is missed.

Until the scan finishes the tag does not blank: it returns an instant
equal-weight estimate -- (completed tracks + fraction through the
current one) / track count -- which sharpens into the size-based value
as the scan fills in. So a theme can just use %pX and it is correct
from the first frame, and %?pX is true whenever a playlist is loaded.

Tested on a Sansa Clip Zip and in the simulator. On a 216-track,
~745 MB single-format audiobook playlist the length scan dropped from
~4150 ms (get_metadata on every track) to ~174 ms (one parse plus 215
file-size stats), roughly 24x lighter.

Change-Id: I6e572e78a10444bd513ddc77e30da04aa5153ef2
2026-06-27 12:29:31 -04:00

190 lines
8.4 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2002 by wavey@wavey.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __PLAYLIST_H__
#define __PLAYLIST_H__
#include <stdbool.h>
#include "config.h"
#include "file.h"
#include "kernel.h"
#include "metadata.h"
#include "rbpaths.h"
#include "chunk_alloc.h"
#define PLAYLIST_ATTR_QUEUED 0x01
#define PLAYLIST_ATTR_INSERTED 0x02
#define PLAYLIST_ATTR_SKIPPED 0x04
#define PLAYLIST_ATTR_RETRIEVE_ID3_ATTEMPTED 0x08
#define PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED 0x10
#define PLAYLIST_DISPLAY_COUNT 10
#define PLAYLIST_UNTITLED_PREFIX "Playlist "
#define PLAYLIST_FLAG_MODIFIED (1u << 0) /* playlist was manually modified */
#define PLAYLIST_FLAG_DIRPLAY (1u << 1) /* enable directory skipping */
enum playlist_command {
PLAYLIST_COMMAND_PLAYLIST,
PLAYLIST_COMMAND_ADD,
PLAYLIST_COMMAND_QUEUE,
PLAYLIST_COMMAND_DELETE,
PLAYLIST_COMMAND_SHUFFLE,
PLAYLIST_COMMAND_UNSHUFFLE,
PLAYLIST_COMMAND_RESET,
PLAYLIST_COMMAND_FLAGS,
PLAYLIST_COMMAND_COMMENT,
PLAYLIST_COMMAND_ERROR = PLAYLIST_COMMAND_COMMENT + 1 /* Internal */
};
enum {
PLAYLIST_PREPEND = -1,
PLAYLIST_INSERT = -2,
PLAYLIST_INSERT_LAST = -3,
PLAYLIST_INSERT_FIRST = -4,
PLAYLIST_INSERT_SHUFFLED = -5,
PLAYLIST_REPLACE = -6,
PLAYLIST_INSERT_LAST_SHUFFLED = -7,
PLAYLIST_INSERT_LAST_ROTATED = -8
};
struct playlist_info
{
bool utf8; /* playlist is in .m3u8 format */
bool control_created; /* has control file been created? */
unsigned int flags; /* flags for misc. state */
int fd; /* descriptor of the open playlist file */
int control_fd; /* descriptor of the open control file */
int max_playlist_size; /* Max number of files in playlist. Mirror of
global_settings.max_files_in_playlist */
unsigned long *indices; /* array of indices */
int index; /* index of current playing track */
int first_index; /* index of first song in playlist */
int amount; /* number of tracks in the index */
int last_insert_pos; /* last position we inserted a track */
bool started; /* has playlist been started? */
int last_shuffled_start; /* number of tracks when insert last
shuffled command start */
int seed; /* shuffle seed */
unsigned long created_tick;
struct mutex mutex; /* mutex for control file access */
#ifdef HAVE_DIRCACHE
int dcfrefs_handle;
#endif
int dirlen; /* Length of the path to the playlist file */
char filename[MAX_PATH]; /* path name of m3u playlist on disk */
/* full path of control file (with extra room for extensions) */
char control_filename[sizeof(PLAYLIST_CONTROL_FILE) + 8];
};
struct playlist_track_info
{
char filename[MAX_PATH]; /* path name of mp3 file */
int attr; /* playlist attributes for track */
int index; /* index of track in playlist */
int display_index; /* index of track for display */
};
struct playlist_insert_context {
struct playlist_info* playlist;
int position;
bool queue;
bool progress;
bool initialized;
int count;
int32_t count_langid;
};
/* Exported functions only for current playlist. */
void playlist_init(void) INIT_ATTR;
void playlist_shutdown(void);
int playlist_create(const char *dir, const char *file);
int playlist_resume(void);
int playlist_shuffle(int random_seed, int start_index);
unsigned int playlist_get_filename_crc32(struct playlist_info *playlist,
int index);
void playlist_resume_track(int start_index, unsigned int crc,
unsigned long elapsed, unsigned long offset);
void playlist_start(int start_index, unsigned long elapsed,
unsigned long offset);
bool playlist_check(int steps);
const char *playlist_peek(int steps, char* buf, size_t buf_size);
int playlist_next(int steps);
bool playlist_next_dir(int direction);
int playlist_get_resume_info(int *resume_index);
int playlist_update_resume_info(const struct mp3entry* id3);
int playlist_get_display_index(void);
int playlist_amount(void);
void playlist_set_last_shuffled_start(void);
struct playlist_info *playlist_get_current(void);
bool playlist_dynamic_only(void);
/* Exported functions for all playlists. Pass NULL for playlist_info
structure to work with current playlist. */
size_t playlist_get_index_bufsz(size_t max_sz);
struct playlist_info* playlist_load(const char* dir, const char* file,
void* index_buffer, int index_buffer_size,
void* temp_buffer, int temp_buffer_size);
int playlist_set_current(struct playlist_info* playlist);
void playlist_close(struct playlist_info* playlist);
void playlist_sync(struct playlist_info* playlist);
int playlist_insert_track(struct playlist_info* playlist, const char *filename,
int position, bool queue, bool sync);
int playlist_insert_context_create(struct playlist_info* playlist,
struct playlist_insert_context *context,
int position, bool queue, bool progress);
int playlist_insert_context_add(struct playlist_insert_context *context,
const char *filename);
void playlist_insert_context_release(struct playlist_insert_context *context);
int playlist_insert_directory(struct playlist_info* playlist,
const char *dirname, int position, bool queue,
bool recurse, struct playlist_insert_context *context);
int playlist_insert_playlist(struct playlist_info* playlist, const char *filename,
int position, bool queue);
bool playlist_entries_iterate(const char *filename,
struct playlist_insert_context *pl_context,
bool (*action_cb)(const char *file_name));
void playlist_skip_entry(struct playlist_info *playlist, int steps);
int playlist_delete(struct playlist_info* playlist, int index);
int playlist_move(struct playlist_info* playlist, int index, int new_index);
int playlist_randomise(struct playlist_info* playlist, unsigned int seed,
bool start_current);
int playlist_sort(struct playlist_info* playlist, bool start_current);
bool playlist_modified(const struct playlist_info* playlist);
void playlist_set_modified(struct playlist_info* playlist, bool modified);
bool playlist_allow_dirplay(const struct playlist_info* playlist);
int playlist_get_first_index(const struct playlist_info* playlist);
int playlist_get_seed(const struct playlist_info* playlist);
int playlist_amount_ex(const struct playlist_info* playlist);
char *playlist_name(const struct playlist_info* playlist, char *buf,
int buf_size);
char *playlist_get_name(const struct playlist_info* playlist, char *buf,
int buf_size);
int playlist_get_track_info(struct playlist_info* playlist, int index,
struct playlist_track_info* info);
int playlist_save(struct playlist_info* playlist, char *filename);
int playlist_directory_tracksearch(const char* dirname, bool recurse,
int (*callback)(char*, void*),
void* context);
int playlist_remove_all_tracks(struct playlist_info *playlist);
#endif /* __PLAYLIST_H__ */