FS6338: Playlist playing time

Originally by Stephane Doyon.
Updated by Alex Wallis, Igor Poretsky, and myself.

Change-Id: I15a06f7774c886cefd9c2cb93230d67de3e5f9a9
This commit is contained in:
Solomon Peachy 2018-12-14 08:20:25 -05:00
parent 39b64f7d4d
commit fe95127c45
5 changed files with 447 additions and 31 deletions

View file

@ -237,7 +237,7 @@
*: "Loading... %d%% done (%s)" *: "Loading... %d%% done (%s)"
</dest> </dest>
<voice> <voice>
*: "" *: "Loading"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>
@ -13829,3 +13829,157 @@
*: "Never" *: "Never"
</voice> </voice>
</phrase> </phrase>
<phrase>
id: LANG_PLAYTIME_ELAPSED
desc: playing time screen
user: core
<source>
*: "Playlist elapsed: %s / %s %ld%%"
</source>
<dest>
*: "Playlist elapsed: %s / %s %ld%%"
</dest>
<voice>
*: "Playlist elapsed"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_TRK_ELAPSED
desc: playing time screen
user: core
<source>
*: "Track elapsed: %s / %s %ld%%"
</source>
<dest>
*: "Track elapsed: %s / %s %ld%%"
</dest>
<voice>
*: "Track elapsed"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_REMAINING
desc: playing time screen
user: core
<source>
*: "Playlist remaining: %s"
</source>
<dest>
*: "Playlist remaining: %s"
</dest>
<voice>
*: "Playlist remaining"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_TRK_REMAINING
desc: playing time screen
user: core
<source>
*: "Track remaining: %s"
</source>
<dest>
*: "Track remaining: %s"
</dest>
<voice>
*: "Track remaining"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_TRACK
desc: playing time screen
user: core
<source>
*: "Track %d / %d %d%%"
</source>
<dest>
*: "Track %d / %d %d%%"
</dest>
<voice>
*: "Track"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_STORAGE
desc: playing time screen
user: core
<source>
*: "Storage: %s (done %s, remaining %s)"
</source>
<dest>
*: "Storage: %s (done %s, remaining %s)"
</dest>
<voice>
*: "Storage"
</voice>
</phrase>
<phrase>
id: VOICE_PLAYTIME_DONE
desc: playing time screen
user: core
<source>
*: ""
</source>
<dest>
*: ""
</dest>
<voice>
*: "Done"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_AVG_TRACK_SIZE
desc: playing time screen
user: core
<source>
*: "Average track size: %s"
</source>
<dest>
*: "Average track size: %s"
</dest>
<voice>
*: "Average track size"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_AVG_BITRATE
desc: playing time screen
user: core
<source>
*: "Average bitrate: %ld kbps"
</source>
<dest>
*: "Average bitrate: %ld kbps"
</dest>
<voice>
*: "Average bit rate"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_ERROR
desc: playing time screen
user: core
<source>
*: "Error while gathering info"
</source>
<dest>
*: "Error while gathering info"
</dest>
<voice>
*: "Error while gathering info"
</voice>
</phrase>
<phrase>
id: LANG_PLAYING_TIME
desc: onplay menu
user: core
<source>
*: "Playing time"
</source>
<dest>
*: "Playing time"
</dest>
<voice>
*: "Playing time"
</voice>
</phrase>

View file

@ -192,6 +192,263 @@ static int bookmark_menu_callback(int action,
return action; return action;
} }
/* playing_time screen context */
struct playing_time_info {
int curr_playing; /* index of currently playing track in playlist */
int nb_tracks; /* how many tracks in playlist */
/* seconds before and after current position, and total. Datatype
allows for values up to 68years. If I had kept it in ms
though, it would have overflowed at 24days, which takes
something like 8.5GB at 32kbps, and so we could conceivably
have playlists lasting longer than that. */
long secs_bef, secs_aft, secs_ttl;
long trk_secs_bef, trk_secs_aft, trk_secs_ttl;
/* kilobytes played before and after current pos, and total.
Kilobytes because bytes would overflow. Data type range is up
to 2TB. */
long kbs_bef, kbs_aft, kbs_ttl;
};
/* list callback for playing_time screen */
static const char * playing_time_get_or_speak_info(int selected_item, void * data,
char *buf, size_t buffer_len,
bool say_it)
{
struct playing_time_info *pti = (struct playing_time_info *)data;
switch(selected_item) {
case 0: { /* elapsed and total time */
char timestr1[25], timestr2[25];
format_time_auto(timestr1, sizeof(timestr1), pti->secs_bef,
UNIT_SEC, false);
format_time_auto(timestr2, sizeof(timestr2), pti->secs_ttl,
UNIT_SEC, false);
long elapsed_perc; /* percentage of duration elapsed */
if (pti->secs_ttl == 0)
elapsed_perc = 0;
else if (pti->secs_ttl <= 0xFFFFFF)
elapsed_perc = pti->secs_bef *100 / pti->secs_ttl;
else /* sacrifice some precision to avoid overflow */
elapsed_perc = (pti->secs_bef>>7) *100 /(pti->secs_ttl>>7);
snprintf(buf, buffer_len, str(LANG_PLAYTIME_ELAPSED),
timestr1, timestr2, elapsed_perc);
if (say_it)
talk_ids(false, LANG_PLAYTIME_ELAPSED,
TALK_ID(pti->secs_bef, UNIT_TIME),
VOICE_OF,
TALK_ID(pti->secs_ttl, UNIT_TIME),
VOICE_PAUSE,
TALK_ID(elapsed_perc, UNIT_PERCENT));
break;
}
case 1: { /* playlist remaining time */
char timestr[25];
format_time_auto(timestr, sizeof(timestr), pti->secs_aft,
UNIT_SEC, false);
snprintf(buf, buffer_len, str(LANG_PLAYTIME_REMAINING),
timestr);
if (say_it)
talk_ids(false, LANG_PLAYTIME_REMAINING,
TALK_ID(pti->secs_aft, UNIT_TIME));
break;
}
case 2: { /* track elapsed and duration */
char timestr1[25], timestr2[25];
format_time_auto(timestr1, sizeof(timestr1), pti->trk_secs_bef,
UNIT_SEC, false);
format_time_auto(timestr2, sizeof(timestr2), pti->trk_secs_ttl,
UNIT_SEC, false);
long elapsed_perc; /* percentage of duration elapsed */
if (pti->trk_secs_ttl == 0)
elapsed_perc = 0;
else if (pti->trk_secs_ttl <= 0xFFFFFF)
elapsed_perc = pti->trk_secs_bef *100 / pti->trk_secs_ttl;
else /* sacrifice some precision to avoid overflow */
elapsed_perc = (pti->trk_secs_bef>>7) *100 /(pti->trk_secs_ttl>>7);
snprintf(buf, buffer_len, str(LANG_PLAYTIME_TRK_ELAPSED),
timestr1, timestr2, elapsed_perc);
if (say_it)
talk_ids(false, LANG_PLAYTIME_TRK_ELAPSED,
TALK_ID(pti->trk_secs_bef, UNIT_TIME),
VOICE_OF,
TALK_ID(pti->trk_secs_ttl, UNIT_TIME),
VOICE_PAUSE,
TALK_ID(elapsed_perc, UNIT_PERCENT));
break;
}
case 3: { /* track remaining time */
char timestr[25];
format_time_auto(timestr, sizeof(timestr), pti->trk_secs_aft,
UNIT_SEC, false);
snprintf(buf, buffer_len, str(LANG_PLAYTIME_TRK_REMAINING),
timestr);
if (say_it)
talk_ids(false, LANG_PLAYTIME_TRK_REMAINING,
TALK_ID(pti->trk_secs_aft, UNIT_TIME));
break;
}
case 4: { /* track index */
int track_perc = (pti->curr_playing+1) *100 / pti->nb_tracks;
snprintf(buf, buffer_len, str(LANG_PLAYTIME_TRACK),
pti->curr_playing, pti->nb_tracks, track_perc);
if (say_it)
talk_ids(false, LANG_PLAYTIME_TRACK,
TALK_ID(pti->curr_playing+1, UNIT_INT),
VOICE_OF,
TALK_ID(pti->nb_tracks, UNIT_INT),
VOICE_PAUSE,
TALK_ID(track_perc, UNIT_PERCENT));
break;
}
case 5: { /* storage size */
char str1[10], str2[10], str3[10];
output_dyn_value(str1, sizeof(str1), pti->kbs_ttl, kibyte_units, 3, true);
output_dyn_value(str2, sizeof(str2), pti->kbs_bef, kibyte_units, 3, true);
output_dyn_value(str3, sizeof(str3), pti->kbs_aft, kibyte_units, 3, true);
snprintf(buf, buffer_len, str(LANG_PLAYTIME_STORAGE),
str1,str2,str3);
if (say_it) {
talk_id(LANG_PLAYTIME_STORAGE, false);
output_dyn_value(NULL, 0, pti->kbs_ttl, kibyte_units, 3, true);
talk_ids(true, VOICE_PAUSE, VOICE_PLAYTIME_DONE);
output_dyn_value(NULL, 0, pti->kbs_bef, kibyte_units, 3, true);
talk_id(LANG_PLAYTIME_REMAINING, true);
output_dyn_value(NULL, 0, pti->kbs_aft, kibyte_units, 3, true);
}
break;
}
case 6: { /* Average track file size */
char str[10];
long avg_track_size = pti->kbs_ttl /pti->nb_tracks;
output_dyn_value(str, sizeof(str), avg_track_size, kibyte_units, 3, true);
snprintf(buf, buffer_len, str(LANG_PLAYTIME_AVG_TRACK_SIZE),
str);
if (say_it) {
talk_id(LANG_PLAYTIME_AVG_TRACK_SIZE, false);
output_dyn_value(NULL, 0, avg_track_size, kibyte_units, 3, true);
}
break;
}
case 7: { /* Average bitrate */
/* Convert power of 2 kilobytes to power of 10 kilobits */
long avg_bitrate = pti->kbs_ttl / pti->secs_ttl *1024 *8 /1000;
snprintf(buf, buffer_len, str(LANG_PLAYTIME_AVG_BITRATE),
avg_bitrate);
if (say_it)
talk_ids(false, LANG_PLAYTIME_AVG_BITRATE,
TALK_ID(avg_bitrate, UNIT_KBIT));
break;
}
}
return buf;
}
static const char * playing_time_get_info(int selected_item, void * data,
char *buffer, size_t buffer_len)
{
return playing_time_get_or_speak_info(selected_item, data,
buffer, buffer_len, false);
}
static int playing_time_speak_info(int selected_item, void * data)
{
static char buffer[MAX_PATH];
playing_time_get_or_speak_info(selected_item, data,
buffer, MAX_PATH, true);
return 0;
}
/* playing time screen: shows total and elapsed playlist duration and
other stats */
static bool playing_time(void)
{
unsigned long talked_tick = current_tick;
struct playing_time_info pti;
struct playlist_track_info pltrack;
struct mp3entry id3;
int i, fd, ret;
pti.nb_tracks = playlist_amount();
playlist_get_resume_info(&pti.curr_playing);
struct mp3entry *curr_id3 = audio_current_track();
if (pti.curr_playing == -1 || !curr_id3)
return false;
pti.secs_bef = pti.trk_secs_bef = curr_id3->elapsed/1000;
pti.secs_aft = pti.trk_secs_aft
= (curr_id3->length -curr_id3->elapsed)/1000;
pti.kbs_bef = curr_id3->offset/1024;
pti.kbs_aft = (curr_id3->filesize -curr_id3->offset)/1024;
splash(0, ID2P(LANG_WAIT));
/* Go through each file in the playlist and get its stats. For
huge playlists this can take a while... The reference position
is the position at the moment this function was invoked,
although playback continues forward. */
for (i = 0; i < pti.nb_tracks; i++) {
/* Show a splash while we are loading. */
splashf(0, str(LANG_LOADING_PERCENT),
i*100/pti.nb_tracks, str(LANG_OFF_ABORT));
/* Voice equivalent */
if (TIME_AFTER(current_tick, talked_tick+5*HZ)) {
talked_tick = current_tick;
talk_ids(false, LANG_LOADING_PERCENT,
TALK_ID(i*100/pti.nb_tracks, UNIT_PERCENT));
}
if (action_userabort(TIMEOUT_NOBLOCK))
goto exit;
if (i == pti.curr_playing)
continue;
if (playlist_get_track_info(NULL, i, &pltrack) < 0)
goto error;
if ((fd = open(pltrack.filename, O_RDONLY)) < 0)
goto error;
ret = get_metadata(&id3, fd, pltrack.filename);
close(fd);
if (!ret)
goto error;
if (i < pti.curr_playing) {
pti.secs_bef += id3.length/1000;
pti.kbs_bef += id3.filesize/1024;
} else {
pti.secs_aft += id3.length/1000;
pti.kbs_aft += id3.filesize/1024;
}
}
pti.secs_ttl = pti.secs_bef +pti.secs_aft;
pti.trk_secs_ttl = pti.trk_secs_bef +pti.trk_secs_aft;
pti.kbs_ttl = pti.kbs_bef +pti.kbs_aft;
struct gui_synclist pt_lists;
int key;
gui_synclist_init(&pt_lists, &playing_time_get_info, &pti, true, 1, NULL);
if (global_settings.talk_menu)
gui_synclist_set_voice_callback(&pt_lists, playing_time_speak_info);
gui_synclist_set_nb_items(&pt_lists, 8);
gui_synclist_draw(&pt_lists);
gui_syncstatusbar_draw(&statusbars, true);
gui_synclist_speak_item(&pt_lists);
while (true) {
gui_syncstatusbar_draw(&statusbars, false);
if (list_do_action(CONTEXT_LIST, HZ/2,
&pt_lists, &key, LIST_WRAP_UNLESS_HELD) == 0
&& key!=ACTION_NONE && key!=ACTION_UNKNOWN)
{
talk_force_shutup();
return(default_event_handler(key) == SYS_USB_CONNECTED);
}
}
error:
splash(HZ, ID2P(LANG_PLAYTIME_ERROR));
exit:
return false;
}
/* CONTEXT_WPS playlist options */ /* CONTEXT_WPS playlist options */
static bool shuffle_playlist(void) static bool shuffle_playlist(void)
{ {
@ -213,10 +470,12 @@ MENUITEM_FUNCTION(playlist_save_item, 0, ID2P(LANG_SAVE_DYNAMIC_PLAYLIST),
save_playlist, NULL, NULL, Icon_Playlist); save_playlist, NULL, NULL, Icon_Playlist);
MENUITEM_FUNCTION(reshuffle_item, 0, ID2P(LANG_SHUFFLE_PLAYLIST), MENUITEM_FUNCTION(reshuffle_item, 0, ID2P(LANG_SHUFFLE_PLAYLIST),
shuffle_playlist, NULL, NULL, Icon_Playlist); shuffle_playlist, NULL, NULL, Icon_Playlist);
MENUITEM_FUNCTION(playing_time_item, 0, ID2P(LANG_PLAYING_TIME),
playing_time, NULL, NULL, Icon_Playlist);
MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_PLAYLIST), MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_PLAYLIST),
NULL, Icon_Playlist, NULL, Icon_Playlist,
&view_cur_playlist, &search_playlist_item, &view_cur_playlist, &search_playlist_item,
&playlist_save_item, &reshuffle_item &playlist_save_item, &reshuffle_item, &playing_time_item
); );
/* CONTEXT_[TREE|ID3DB] playlist options */ /* CONTEXT_[TREE|ID3DB] playlist options */
@ -381,18 +640,18 @@ MENUITEM_FUNCTION(view_playlist_item, 0, ID2P(LANG_VIEW),
MAKE_ONPLAYMENU( tree_playlist_menu, ID2P(LANG_CURRENT_PLAYLIST), MAKE_ONPLAYMENU( tree_playlist_menu, ID2P(LANG_CURRENT_PLAYLIST),
treeplaylist_callback, Icon_Playlist, treeplaylist_callback, Icon_Playlist,
/* view */ /* view */
&view_playlist_item, &view_playlist_item,
/* insert */ /* insert */
&i_pl_item, &i_first_pl_item, &i_last_pl_item, &i_pl_item, &i_first_pl_item, &i_last_pl_item,
&i_shuf_pl_item, &i_last_shuf_pl_item, &i_shuf_pl_item, &i_last_shuf_pl_item,
/* queue */ /* queue */
&q_pl_item, &q_first_pl_item, &q_last_pl_item, &q_pl_item, &q_first_pl_item, &q_last_pl_item,
&q_shuf_pl_item, &q_last_shuf_pl_item, &q_shuf_pl_item, &q_last_shuf_pl_item,
/* replace */ /* replace */
&replace_pl_item &replace_pl_item
); );
@ -941,7 +1200,7 @@ static int clipboard_pastedirectory(struct dirrecurse_params *src,
a move instead */ a move instead */
flags |= PASTE_EXDEV; flags |= PASTE_EXDEV;
break; break;
} }
#endif /* HAVE_MULTIVOLUME */ #endif /* HAVE_MULTIVOLUME */
} }
} }
@ -999,7 +1258,7 @@ static int clipboard_pastedirectory(struct dirrecurse_params *src,
rc = OPRC_CANCELLED; rc = OPRC_CANCELLED;
break; break;
} }
DEBUGF("Copy %s to %s\n", src->path, target->path); DEBUGF("Copy %s to %s\n", src->path, target->path);
if (info.attribute & ATTR_DIRECTORY) { if (info.attribute & ATTR_DIRECTORY) {
@ -1151,16 +1410,16 @@ static int ratingitem_callback(int action,const struct menu_item_ex *this_item)
MENUITEM_FUNCTION(rating_item, 0, ID2P(LANG_MENU_SET_RATING), MENUITEM_FUNCTION(rating_item, 0, ID2P(LANG_MENU_SET_RATING),
set_rating_inline, NULL, set_rating_inline, NULL,
ratingitem_callback, Icon_Questionmark); ratingitem_callback, Icon_Questionmark);
#endif #endif
#ifdef HAVE_PICTUREFLOW_INTEGRATION #ifdef HAVE_PICTUREFLOW_INTEGRATION
MENUITEM_RETURNVALUE(pictureflow_item, ID2P(LANG_ONPLAY_PICTUREFLOW), MENUITEM_RETURNVALUE(pictureflow_item, ID2P(LANG_ONPLAY_PICTUREFLOW),
GO_TO_PICTUREFLOW, NULL, Icon_NOICON); GO_TO_PICTUREFLOW, NULL, Icon_NOICON);
#endif #endif
static bool view_cue(void) static bool view_cue(void)
{ {
struct mp3entry* id3 = audio_current_track(); struct mp3entry* id3 = audio_current_track();
if(id3 && id3->cuesheet) if (id3 && id3->cuesheet)
{ {
browse_cuesheet(id3->cuesheet); browse_cuesheet(id3->cuesheet);
} }
@ -1274,7 +1533,7 @@ MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_SET_AS_REC_DIR),
#endif #endif
static bool set_startdir(void) static bool set_startdir(void)
{ {
snprintf(global_settings.start_directory, snprintf(global_settings.start_directory,
sizeof(global_settings.start_directory), sizeof(global_settings.start_directory),
"%s/", selected_file); "%s/", selected_file);
settings_save(); settings_save();
@ -1375,10 +1634,10 @@ MAKE_ONPLAYMENU( wps_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE),
#ifdef HAVE_TAGCACHE #ifdef HAVE_TAGCACHE
&rating_item, &rating_item,
#endif #endif
&bookmark_menu, &bookmark_menu,
#ifdef HAVE_PICTUREFLOW_INTEGRATION #ifdef HAVE_PICTUREFLOW_INTEGRATION
&pictureflow_item, &pictureflow_item,
#endif #endif
&browse_id3_item, &list_viewers_item, &browse_id3_item, &list_viewers_item,
&delete_file_item, &view_cue_item, &delete_file_item, &view_cue_item,
#ifdef HAVE_PITCHCONTROL #ifdef HAVE_PITCHCONTROL
@ -1451,7 +1710,7 @@ static int playlist_insert_shuffled(void)
playlist_insert_func((intptr_t*)PLAYLIST_INSERT_SHUFFLED); playlist_insert_func((intptr_t*)PLAYLIST_INSERT_SHUFFLED);
return ONPLAY_START_PLAY; return ONPLAY_START_PLAY;
} }
return ONPLAY_RELOAD_DIR; return ONPLAY_RELOAD_DIR;
} }
@ -1465,7 +1724,7 @@ struct hotkey_assignment {
#define HOTKEY_FUNC(func, param) {{(void *)func}, param} #define HOTKEY_FUNC(func, param) {{(void *)func}, param}
/* Any desired hotkey functions go here, in the enum in onplay.h, /* Any desired hotkey functions go here, in the enum in onplay.h,
and in the settings menu in settings_list.c. The order here and in the settings menu in settings_list.c. The order here
is not important. */ is not important. */
static struct hotkey_assignment hotkey_items[] = { static struct hotkey_assignment hotkey_items[] = {
{ HOTKEY_VIEW_PLAYLIST, LANG_VIEW_DYNAMIC_PLAYLIST, { HOTKEY_VIEW_PLAYLIST, LANG_VIEW_DYNAMIC_PLAYLIST,
@ -1510,7 +1769,7 @@ int get_hotkey_lang_id(int action)
if (hotkey_items[i].action == action) if (hotkey_items[i].action == action)
return hotkey_items[i].lang_id; return hotkey_items[i].lang_id;
} }
return LANG_OFF; return LANG_OFF;
} }
@ -1521,7 +1780,7 @@ static int execute_hotkey(bool is_wps)
struct hotkey_assignment *this_item; struct hotkey_assignment *this_item;
const int action = (is_wps ? global_settings.hotkey_wps : const int action = (is_wps ? global_settings.hotkey_wps :
global_settings.hotkey_tree); global_settings.hotkey_tree);
/* search assignment struct for a match for the hotkey setting */ /* search assignment struct for a match for the hotkey setting */
while (i--) while (i--)
{ {
@ -1546,7 +1805,7 @@ static int execute_hotkey(bool is_wps)
return return_code; return return_code;
} }
} }
/* no valid hotkey set, ignore hotkey */ /* no valid hotkey set, ignore hotkey */
return ONPLAY_RELOAD_DIR; return ONPLAY_RELOAD_DIR;
} }

View file

@ -673,6 +673,8 @@ Malik Enes Şafak
Roman Levkin-Taymenev Roman Levkin-Taymenev
Nathan Follens Nathan Follens
Gergely Békési Gergely Békési
Stephane Doyon
Alex Wallis
The libmad team The libmad team
The wavpack team The wavpack team

View file

@ -213,20 +213,20 @@ your WPS (While Playing Screen).
\end{description} \end{description}
} }
\subsection{\label{sec:contextmenu}The WPS Context Menu} \subsection{\label{sec:contextmenu}The WPS Context Menu}
Like the context menu for the \setting{File Browser}, the \setting{WPS Context Menu} Like the context menu for the \setting{File Browser}, the \setting{WPS Context Menu}
allows you quick access to some often used functions. allows you quick access to some often used functions.
\subsubsection{Playlist} \subsubsection{Playlist}
The \setting{Playlist} submenu allows you to view, save, search and The \setting{Playlist} submenu allows you to view, save, search, reshuffle,
reshuffle the current playlist. These and other operations are detailed in and display the play time of the current playlist. These and other operations
\reference{ref:working_with_playlists}. To change settings for the are detailed in \reference{ref:working_with_playlists}. To change settings for
\setting{Playlist Viewer} press \ActionStdContext{} while viewing the current the \setting{Playlist Viewer} press \ActionStdContext{} while viewing the
playlist to bring up the \setting{Playlist Viewer Menu}. In this menu, you current playlist to bring up the \setting{Playlist Viewer Menu}. In this
can find the \setting{Playlist Viewer Settings}. menu, you can find the \setting{Playlist Viewer Settings}.
\paragraph{Playlist Viewer Settings} \paragraph{Playlist Viewer Settings}
\begin{description} \begin{description}
\item[Show Icons.] This toggles display of the icon for the currently \item[Show Icons.] This toggles display of the icon for the currently
selected playlist entry and the icon for moving a playlist entry selected playlist entry and the icon for moving a playlist entry
\item[Show Indices.] This toggles display of the line numbering for \item[Show Indices.] This toggles display of the line numbering for
the playlist the playlist

View file

@ -137,10 +137,11 @@ playlist.
Dynamic playlists are saved so resume will restore them exactly as they Dynamic playlists are saved so resume will restore them exactly as they
were before shutdown. were before shutdown.
\note{To view, save or reshuffle the current dynamic playlist use the \note{To view, save, reshuffle, or display the play time of the current
dynamic playlist use the
\setting{Playlist} sub menu in the WPS context menu or in the \setting{Playlist} sub menu in the WPS context menu or in the
\setting{Main Menu}.} \setting{Main Menu}.}
\subsection{Modifying playlists} \subsection{Modifying playlists}
\subsubsection{Reshuffling} \subsubsection{Reshuffling}
Reshuffling the current playlist is easily done from the \setting{Playlist} Reshuffling the current playlist is easily done from the \setting{Playlist}