diff --git a/apps/lang/english.lang b/apps/lang/english.lang index ed1cadfb9e..1f5afa0a50 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -237,7 +237,7 @@ *: "Loading... %d%% done (%s)" - *: "" + *: "Loading" @@ -13829,3 +13829,157 @@ *: "Never" + + id: LANG_PLAYTIME_ELAPSED + desc: playing time screen + user: core + + *: "Playlist elapsed: %s / %s %ld%%" + + + *: "Playlist elapsed: %s / %s %ld%%" + + + *: "Playlist elapsed" + + + + id: LANG_PLAYTIME_TRK_ELAPSED + desc: playing time screen + user: core + + *: "Track elapsed: %s / %s %ld%%" + + + *: "Track elapsed: %s / %s %ld%%" + + + *: "Track elapsed" + + + + id: LANG_PLAYTIME_REMAINING + desc: playing time screen + user: core + + *: "Playlist remaining: %s" + + + *: "Playlist remaining: %s" + + + *: "Playlist remaining" + + + + id: LANG_PLAYTIME_TRK_REMAINING + desc: playing time screen + user: core + + *: "Track remaining: %s" + + + *: "Track remaining: %s" + + + *: "Track remaining" + + + + id: LANG_PLAYTIME_TRACK + desc: playing time screen + user: core + + *: "Track %d / %d %d%%" + + + *: "Track %d / %d %d%%" + + + *: "Track" + + + + id: LANG_PLAYTIME_STORAGE + desc: playing time screen + user: core + + *: "Storage: %s (done %s, remaining %s)" + + + *: "Storage: %s (done %s, remaining %s)" + + + *: "Storage" + + + + id: VOICE_PLAYTIME_DONE + desc: playing time screen + user: core + + *: "" + + + *: "" + + + *: "Done" + + + + id: LANG_PLAYTIME_AVG_TRACK_SIZE + desc: playing time screen + user: core + + *: "Average track size: %s" + + + *: "Average track size: %s" + + + *: "Average track size" + + + + id: LANG_PLAYTIME_AVG_BITRATE + desc: playing time screen + user: core + + *: "Average bitrate: %ld kbps" + + + *: "Average bitrate: %ld kbps" + + + *: "Average bit rate" + + + + id: LANG_PLAYTIME_ERROR + desc: playing time screen + user: core + + *: "Error while gathering info" + + + *: "Error while gathering info" + + + *: "Error while gathering info" + + + + id: LANG_PLAYING_TIME + desc: onplay menu + user: core + + *: "Playing time" + + + *: "Playing time" + + + *: "Playing time" + + diff --git a/apps/onplay.c b/apps/onplay.c index 7c4c83a991..092d23406b 100644 --- a/apps/onplay.c +++ b/apps/onplay.c @@ -192,6 +192,263 @@ static int bookmark_menu_callback(int 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 */ 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); MENUITEM_FUNCTION(reshuffle_item, 0, ID2P(LANG_SHUFFLE_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), NULL, Icon_Playlist, &view_cur_playlist, &search_playlist_item, - &playlist_save_item, &reshuffle_item + &playlist_save_item, &reshuffle_item, &playing_time_item ); /* 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), treeplaylist_callback, Icon_Playlist, - + /* view */ &view_playlist_item, - + /* insert */ &i_pl_item, &i_first_pl_item, &i_last_pl_item, &i_shuf_pl_item, &i_last_shuf_pl_item, /* queue */ - + &q_pl_item, &q_first_pl_item, &q_last_pl_item, &q_shuf_pl_item, &q_last_shuf_pl_item, - + /* replace */ &replace_pl_item ); @@ -941,7 +1200,7 @@ static int clipboard_pastedirectory(struct dirrecurse_params *src, a move instead */ flags |= PASTE_EXDEV; break; - } + } #endif /* HAVE_MULTIVOLUME */ } } @@ -999,7 +1258,7 @@ static int clipboard_pastedirectory(struct dirrecurse_params *src, rc = OPRC_CANCELLED; break; } - + DEBUGF("Copy %s to %s\n", src->path, target->path); 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), set_rating_inline, NULL, ratingitem_callback, Icon_Questionmark); -#endif -#ifdef HAVE_PICTUREFLOW_INTEGRATION -MENUITEM_RETURNVALUE(pictureflow_item, ID2P(LANG_ONPLAY_PICTUREFLOW), - GO_TO_PICTUREFLOW, NULL, Icon_NOICON); +#endif +#ifdef HAVE_PICTUREFLOW_INTEGRATION +MENUITEM_RETURNVALUE(pictureflow_item, ID2P(LANG_ONPLAY_PICTUREFLOW), + GO_TO_PICTUREFLOW, NULL, Icon_NOICON); #endif static bool view_cue(void) { struct mp3entry* id3 = audio_current_track(); - if(id3 && id3->cuesheet) + if (id3 && id3->cuesheet) { browse_cuesheet(id3->cuesheet); } @@ -1274,7 +1533,7 @@ MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_SET_AS_REC_DIR), #endif static bool set_startdir(void) { - snprintf(global_settings.start_directory, + snprintf(global_settings.start_directory, sizeof(global_settings.start_directory), "%s/", selected_file); settings_save(); @@ -1375,10 +1634,10 @@ MAKE_ONPLAYMENU( wps_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE), #ifdef HAVE_TAGCACHE &rating_item, #endif - &bookmark_menu, + &bookmark_menu, #ifdef HAVE_PICTUREFLOW_INTEGRATION &pictureflow_item, -#endif +#endif &browse_id3_item, &list_viewers_item, &delete_file_item, &view_cue_item, #ifdef HAVE_PITCHCONTROL @@ -1451,7 +1710,7 @@ static int playlist_insert_shuffled(void) playlist_insert_func((intptr_t*)PLAYLIST_INSERT_SHUFFLED); return ONPLAY_START_PLAY; } - + return ONPLAY_RELOAD_DIR; } @@ -1465,7 +1724,7 @@ struct hotkey_assignment { #define HOTKEY_FUNC(func, param) {{(void *)func}, param} /* 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. */ static struct hotkey_assignment hotkey_items[] = { { HOTKEY_VIEW_PLAYLIST, LANG_VIEW_DYNAMIC_PLAYLIST, @@ -1510,7 +1769,7 @@ int get_hotkey_lang_id(int action) if (hotkey_items[i].action == action) return hotkey_items[i].lang_id; } - + return LANG_OFF; } @@ -1521,7 +1780,7 @@ static int execute_hotkey(bool is_wps) struct hotkey_assignment *this_item; const int action = (is_wps ? global_settings.hotkey_wps : global_settings.hotkey_tree); - + /* search assignment struct for a match for the hotkey setting */ while (i--) { @@ -1546,7 +1805,7 @@ static int execute_hotkey(bool is_wps) return return_code; } } - + /* no valid hotkey set, ignore hotkey */ return ONPLAY_RELOAD_DIR; } diff --git a/docs/CREDITS b/docs/CREDITS index 0fcf8c66ae..6df7e58bca 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -673,6 +673,8 @@ Malik Enes Şafak Roman Levkin-Taymenev Nathan Follens Gergely Békési +Stephane Doyon +Alex Wallis The libmad team The wavpack team diff --git a/manual/rockbox_interface/wps.tex b/manual/rockbox_interface/wps.tex index af594d0cd3..c82a9cb9f4 100644 --- a/manual/rockbox_interface/wps.tex +++ b/manual/rockbox_interface/wps.tex @@ -213,20 +213,20 @@ your WPS (While Playing Screen). \end{description} } \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. \subsubsection{Playlist} -The \setting{Playlist} submenu allows you to view, save, search and -reshuffle the current playlist. These and other operations are detailed in -\reference{ref:working_with_playlists}. To change settings for the -\setting{Playlist Viewer} press \ActionStdContext{} while viewing the current -playlist to bring up the \setting{Playlist Viewer Menu}. In this menu, you -can find the \setting{Playlist Viewer Settings}. +The \setting{Playlist} submenu allows you to view, save, search, reshuffle, +and display the play time of the current playlist. These and other operations +are detailed in \reference{ref:working_with_playlists}. To change settings for +the \setting{Playlist Viewer} press \ActionStdContext{} while viewing the +current playlist to bring up the \setting{Playlist Viewer Menu}. In this +menu, you can find the \setting{Playlist Viewer Settings}. \paragraph{Playlist Viewer Settings} \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 \item[Show Indices.] This toggles display of the line numbering for the playlist diff --git a/manual/working_with_playlists/main.tex b/manual/working_with_playlists/main.tex index 412f77a234..a7c2bc1299 100644 --- a/manual/working_with_playlists/main.tex +++ b/manual/working_with_playlists/main.tex @@ -137,10 +137,11 @@ playlist. Dynamic playlists are saved so resume will restore them exactly as they 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{Main Menu}.} - + \subsection{Modifying playlists} \subsubsection{Reshuffling} Reshuffling the current playlist is easily done from the \setting{Playlist}