From 8dcd781291a5fe8d8605a0e5aab2dc0dd371b33b Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Fri, 2 Aug 2024 22:02:05 +0200 Subject: plugins: Playing Time: Add 'Remaining' and 'Single Mode' options 1) Remaining: Sometimes, you may only be interested in how much time is left in a playlist, but don't care about the progress already made. The new option will disregard already played tracks, which can be much faster, depending on your position in the playlist. 2) Single Mode: When 'Single Mode' is enabled (and set to something other than 'Track'), you now have the option to see the time elapsed and remaining specifically for that section of the playlist (e.g. the playing album). Change-Id: I786eaababc537f565ab3fa4de1c46b7d5f3d1101 --- apps/lang/english.lang | 22 ++- apps/plugins/playing_time.c | 421 ++++++++++++++++++++++++++++++-------------- 2 files changed, 303 insertions(+), 140 deletions(-) (limited to 'apps') diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 0635f7b569..ada1129a43 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -12614,16 +12614,16 @@ id: LANG_PLAYTIME_REMAINING - desc: playing time screen + desc: deprecated user: core - *: "Playlist remaining:" + *: "" - *: "Playlist remaining:" + *: "" - *: "Playlist remaining" + *: "" @@ -16500,3 +16500,17 @@ *: "Choose File" + + id: LANG_REMAINING + desc: Playing Time + user: core + + *: "Remaining" + + + *: "Remaining" + + + *: "Remaining" + + diff --git a/apps/plugins/playing_time.c b/apps/plugins/playing_time.c index 5916e4e58f..54752e987a 100644 --- a/apps/plugins/playing_time.c +++ b/apps/plugins/playing_time.c @@ -32,10 +32,10 @@ const unsigned char * const byte_units[] = }; const int menu_items[] = { - LANG_PLAYTIME_ELAPSED, - LANG_PLAYTIME_REMAINING, - LANG_PLAYTIME_TRK_ELAPSED, + LANG_REMAINING, + LANG_ELAPSED, LANG_PLAYTIME_TRK_REMAINING, + LANG_PLAYTIME_TRK_ELAPSED, LANG_PLAYTIME_TRACK, LANG_PLAYTIME_STORAGE, LANG_PLAYTIME_AVG_TRACK_SIZE, @@ -54,14 +54,61 @@ enum ePT_SUM { }; struct playing_time_info { - int nb_tracks; /* number of tracks in playlist */ - int curr_track_index; /* index of currently playing track in playlist */ - int curr_display_index; /* display index of currently playing track */ - unsigned long curr_track_length[ePT_COUNT]; /* current track length */ - unsigned long long length[ePT_COUNT]; /* length of all tracks */ - unsigned long long size[ePT_COUNT]; /* file size of all tracks */ + char single_mode_tag[MAX_PATH]; /* Relevant tag when single mode enabled */ + unsigned long long size[ePT_COUNT]; /* File size of tracks */ + unsigned long long length[ePT_COUNT]; /* Length of tracks */ + unsigned long curr_track_length[ePT_COUNT]; /* Current track length */ + int curr_track_index; /* Index of currently playing track in playlist */ + int curr_display_index; /* Display index of currently playing track */ + int actual_index; /* Display index in actually counted tracks */ + int counted; /* Number of tracks already added up */ + int nb_tracks; /* Number of tracks in playlist */ + int error_count; /* Number of tracks whose data couldn't be retrieved */ + bool remaining_only; /* Whether to ignore elapsed tracks */ }; +static int32_t single_mode_lang(void) +{ + switch (rb->global_settings->single_mode) + { + case SINGLE_MODE_ALBUM: + return LANG_ID3_ALBUM; + case SINGLE_MODE_ALBUM_ARTIST: + return LANG_ID3_ALBUMARTIST; + case SINGLE_MODE_ARTIST: + return LANG_ID3_ARTIST; + case SINGLE_MODE_COMPOSER: + return LANG_ID3_COMPOSER; + case SINGLE_MODE_GROUPING: + return LANG_ID3_GROUPING; + case SINGLE_MODE_GENRE: + return LANG_ID3_GENRE; + case SINGLE_MODE_TRACK: + return LANG_TRACK; + } + return LANG_OFF; +} + +static char* single_mode_id3_tag(struct mp3entry *id3) +{ + switch (rb->global_settings->single_mode) + { + case SINGLE_MODE_ALBUM: + return id3->album; + case SINGLE_MODE_ALBUM_ARTIST: + return id3->albumartist; + case SINGLE_MODE_ARTIST: + return id3->artist; + case SINGLE_MODE_COMPOSER: + return id3->composer; + case SINGLE_MODE_GROUPING: + return id3->grouping; + case SINGLE_MODE_GENRE: + return id3->genre_string; + } + return NULL; +} + static char* get_percent_str(long percents) { static char val[10]; @@ -97,7 +144,18 @@ static const char * pt_get_or_speak_info(int selected_item, void * data, /* data */ switch(info_no) { - case 0: { /* elapsed and total time */ + case 0: { /* playlist remaining time */ + char timestr[25]; + rb->format_time_auto(timestr, sizeof(timestr), + pti->length[ePT_REMAINING], UNIT_SEC, false); + rb->snprintf(buf, buffer_len, "%s", timestr); + + if (say_it) + rb_talk_ids(false, menu_name_id, + TALK_ID(pti->length[ePT_REMAINING], UNIT_TIME)); + break; + } + case 1: { /* elapsed and total time */ char timestr1[25], timestr2[25]; rb->format_time_auto(timestr1, sizeof(timestr1), pti->length[ePT_ELAPSED], UNIT_SEC, true); @@ -128,18 +186,18 @@ static const char * pt_get_or_speak_info(int selected_item, void * data, TALK_ID(elapsed_pct, UNIT_PERCENT)); break; } - case 1: { /* playlist remaining time */ + case 2: { /* track remaining time */ char timestr[25]; rb->format_time_auto(timestr, sizeof(timestr), - pti->length[ePT_REMAINING], UNIT_SEC, false); + pti->curr_track_length[ePT_REMAINING], UNIT_SEC, false); rb->snprintf(buf, buffer_len, "%s", timestr); if (say_it) rb_talk_ids(false, menu_name_id, - TALK_ID(pti->length[ePT_REMAINING], UNIT_TIME)); + TALK_ID(pti->curr_track_length[ePT_REMAINING], UNIT_TIME)); break; } - case 2: { /* track elapsed and duration */ + case 3: { /* track elapsed and duration */ char timestr1[25], timestr2[25]; rb->format_time_auto(timestr1, sizeof(timestr1), @@ -170,32 +228,21 @@ static const char * pt_get_or_speak_info(int selected_item, void * data, TALK_ID(elapsed_pct, UNIT_PERCENT)); break; } - case 3: { /* track remaining time */ - char timestr[25]; - rb->format_time_auto(timestr, sizeof(timestr), - pti->curr_track_length[ePT_REMAINING], UNIT_SEC, false); - rb->snprintf(buf, buffer_len, "%s", timestr); - - if (say_it) - rb_talk_ids(false, menu_name_id, - TALK_ID(pti->curr_track_length[ePT_REMAINING], UNIT_TIME)); - break; - } case 4: { /* track index */ - int track_pct = pti->curr_display_index * 100 / pti->nb_tracks; + int track_pct = pti->actual_index * 100 / pti->counted; if (rb->lang_is_rtl()) rb->snprintf(buf, buffer_len, "%s %d / %d", get_percent_str(track_pct), - pti->nb_tracks, pti->curr_display_index); + pti->counted, pti->actual_index); else - rb->snprintf(buf, buffer_len, "%d / %d %s", pti->curr_display_index, - pti->nb_tracks, get_percent_str(track_pct)); + rb->snprintf(buf, buffer_len, "%d / %d %s", pti->actual_index, + pti->counted, get_percent_str(track_pct)); if (say_it) rb_talk_ids(false, menu_name_id, - TALK_ID(pti->curr_display_index, UNIT_INT), + TALK_ID(pti->actual_index, UNIT_INT), VOICE_OF, - TALK_ID(pti->nb_tracks, UNIT_INT), + TALK_ID(pti->counted, UNIT_INT), VOICE_PAUSE, TALK_ID(track_pct, UNIT_PERCENT)); break; @@ -215,7 +262,7 @@ static const char * pt_get_or_speak_info(int selected_item, void * data, int32_t voice_ids[ePT_COUNT]; voice_ids[ePT_TOTAL] = menu_name_id; voice_ids[ePT_ELAPSED] = VOICE_PLAYTIME_DONE; - voice_ids[ePT_REMAINING] = LANG_PLAYTIME_REMAINING; + voice_ids[ePT_REMAINING] = LANG_REMAINING; for (i = 0; i < ePT_COUNT; i++) { @@ -227,7 +274,7 @@ static const char * pt_get_or_speak_info(int selected_item, void * data, } case 6: { /* Average track file size */ char str[20]; - long avg_track_size = pti->size[ePT_TOTAL] / pti->nb_tracks; + long avg_track_size = pti->size[ePT_TOTAL] / pti->counted; rb->output_dyn_value(str, sizeof(str), avg_track_size, kibyte_units, 3, true); rb->snprintf(buf, buffer_len, "%s", str); @@ -266,138 +313,240 @@ static int pt_speak_info(int selected_item, void * data) return 0; } -/* playing time screen: shows total and elapsed playlist duration and - other stats */ -static bool playing_time(void) +static bool pt_display_stats(struct playing_time_info *pti) { - struct playing_time_info pti; - struct playlist_track_info pl_track; - struct mp3entry id3; - struct mp3entry *curr_id3; struct gui_synclist pt_lists; - unsigned long talked_tick = *rb->current_tick; - int action, i, index, section, error_count = 0; + rb->gui_synclist_init(&pt_lists, &pt_get_info, pti, true, 2, NULL); + if (rb->global_settings->talk_menu) + rb->gui_synclist_set_voice_callback(&pt_lists, pt_speak_info); + rb->gui_synclist_set_nb_items(&pt_lists, pti->remaining_only ? 2 : 8*2); + rb->gui_synclist_set_title(&pt_lists, *pti->single_mode_tag ? + rb->str(single_mode_lang()) : + rb->str(LANG_PLAYLIST), NOICON); + rb->gui_synclist_draw(&pt_lists); + rb->gui_synclist_speak_item(&pt_lists); + while (true) + { + int action = rb->get_action(CONTEXT_LIST, HZ/2); + if (rb->gui_synclist_do_button(&pt_lists, &action) == 0 + && action != ACTION_NONE && action != ACTION_UNKNOWN) + { + bool usb = rb->default_event_handler(action) == SYS_USB_CONNECTED; - pti.nb_tracks = rb->playlist_amount(); - rb->playlist_get_resume_info(&pti.curr_track_index); - curr_id3 = rb->audio_current_track(); + if (!usb && IS_SYSEVENT(action)) + continue; - if (pti.curr_track_index == -1 || !curr_id3) - return false; + rb->talk_force_shutup(); + return usb; + } + } + return false; +} - pti.curr_display_index = rb->playlist_get_display_index(); +static const char *pt_options_name(int selected_item, void * data, + char *buf, size_t buf_size) +{ + (void) data; + (void) buf; + (void) buf_size; + return selected_item == 0 ? rb->str(LANG_ALL) : + selected_item == 1 ? rb->str(LANG_REMAINING) : + rb->str(single_mode_lang()); +} - pti.length[ePT_ELAPSED] = pti.curr_track_length[ePT_ELAPSED] - = curr_id3->elapsed; - pti.length[ePT_REMAINING] = pti.curr_track_length[ePT_REMAINING] - = curr_id3->length - curr_id3->elapsed; +static int pt_options_speak(int selected_item, void * data) +{ + (void) data; + rb->talk_id(selected_item == 0 ? LANG_ALL : + selected_item == 1 ? LANG_REMAINING : + single_mode_lang(), false); + return 0; +} - pti.size[ePT_ELAPSED] = curr_id3->offset; - pti.size[ePT_REMAINING] = curr_id3->filesize - curr_id3->offset; +static int pt_options(struct playing_time_info *pti) +{ + struct gui_synclist pt_options; + rb->gui_synclist_init(&pt_options, &pt_options_name, NULL, true, 1, NULL); + if (rb->global_settings->talk_menu) + rb->gui_synclist_set_voice_callback(&pt_options, pt_options_speak); + rb->gui_synclist_set_nb_items(&pt_options, *pti->single_mode_tag ? 3 : 2); + rb->gui_synclist_set_title(&pt_options, rb->str(LANG_PLAYING_TIME), NOICON); + rb->gui_synclist_draw(&pt_options); + rb->gui_synclist_speak_item(&pt_options); - rb->splash_progress_set_delay(HZ/2); + while(true) + { + int button = rb->get_action(CONTEXT_LIST, HZ); + if (rb->gui_synclist_do_button(&pt_options, &button)) + continue; + switch(button) + { + case ACTION_STD_OK: + { + int sel = rb->gui_synclist_get_sel_pos(&pt_options); + if (sel < 2) + *pti->single_mode_tag = 0; + if (sel == 1) + pti->remaining_only = true; + return -1; + } + case ACTION_STD_CANCEL: + return 0; + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + return 1; + } + } +} -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(true); -#endif - /* 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. */ - index = rb->playlist_get_first_index(NULL); - for (i = 0; i < pti.nb_tracks; i++, index++) { +static void pt_store_converted_totals(struct playing_time_info *pti) +{ + /* convert units from ms to s */ + pti->length[ePT_ELAPSED] /= 1000; + pti->length[ePT_REMAINING] /= 1000; + pti->curr_track_length[ePT_ELAPSED] /= 1000; + pti->curr_track_length[ePT_REMAINING] /= 1000; + /* convert units from Bytes to KiB */ + pti->size[ePT_ELAPSED] >>= 10; + pti->size[ePT_REMAINING] >>= 10; - if (index == pti.nb_tracks) - index = 0; + pti->length[ePT_TOTAL] = pti->length[ePT_ELAPSED] + pti->length[ePT_REMAINING]; + pti->curr_track_length[ePT_TOTAL] = pti->curr_track_length[ePT_ELAPSED] + + pti->curr_track_length[ePT_REMAINING]; + pti->size[ePT_TOTAL] = pti->size[ePT_ELAPSED] + pti->size[ePT_REMAINING]; +} - rb->splash_progress(i, pti.nb_tracks, "%s (%s)", - rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT)); +static int pt_add_track(int i, enum ePT_SUM section, struct playing_time_info *pti) +{ + struct mp3entry id3; + struct playlist_track_info pl_track; + static unsigned long talked_tick; + int progress_total = pti->remaining_only ? + pti->nb_tracks - pti->curr_display_index : + pti->nb_tracks; - if (TIME_AFTER(*rb->current_tick, talked_tick + HZ*5)) - { - talked_tick = *rb->current_tick; - rb_talk_ids(false, LANG_LOADING_PERCENT, - TALK_ID(i * 100 / pti.nb_tracks, UNIT_PERCENT)); - } - if (rb->action_userabort(TIMEOUT_NOBLOCK)) - { -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(false); -#endif - goto exit; - } + rb->splash_progress(pti->counted, progress_total, "%s (%s)", + rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT)); - if (index == pti.curr_track_index) - continue; + if (TIME_AFTER(*rb->current_tick, talked_tick + HZ*5)) + { + talked_tick = *rb->current_tick; + rb_talk_ids(false, LANG_LOADING_PERCENT, + TALK_ID(pti->counted * 100 / progress_total, + UNIT_PERCENT)); + } - if (rb->playlist_get_track_info(NULL, index, &pl_track) < 0 - || !rb->get_metadata(&id3, -1, pl_track.filename)) - { - error_count++; - continue; - } + if (rb->action_userabort(TIMEOUT_NOBLOCK)) + return -1; + else if (rb->playlist_get_track_info(NULL, i, &pl_track) < 0 + || !rb->get_metadata(&id3, -1, pl_track.filename)) + { + pti->error_count++; + return -2; + } + else if(*pti->single_mode_tag && /* single mode tag doesn't match */ + rb->strcmp(pti->single_mode_tag, single_mode_id3_tag(&id3) ?: "")) + return 1; + + pti->length[section] += id3.length; + pti->size[section] += id3.filesize; + pti->counted++; + return 0; +} - section = pl_track.display_index < pti.curr_display_index ? - ePT_ELAPSED : ePT_REMAINING; - pti.length[section] += id3.length; - pti.size[section] += id3.filesize; +static bool pt_add_remaining(struct playing_time_info *pti) +{ + int display_index = pti->curr_display_index + 1; + for (int i = pti->curr_track_index + 1; display_index <= pti->nb_tracks; i++, display_index++) + { + if (i == pti->nb_tracks) + i = 0; + + int ret = pt_add_track(i, ePT_REMAINING, pti); + if (ret == 1) + break; + else if (ret == -1) + return false; } -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(false); -#endif + return true; +} - if (error_count > 0) - rb->splash(HZ, ID2P(LANG_PLAYTIME_ERROR)); +static bool pt_add_elapsed(struct playing_time_info *pti) +{ + int display_index = pti->curr_display_index - 1; + for (int i = pti->curr_track_index - 1; display_index > 0; i--, display_index--) + { + if (i < 0) + i = pti->nb_tracks - 1; + + int ret = pt_add_track(i, ePT_ELAPSED, pti); + if (ret == 1) + break; + else if (ret == -1) + return false; + else if (ret == 0) + pti->actual_index++; + } + return true; +} - pti.nb_tracks -= error_count; +static bool pt_add_curr_track(struct playing_time_info *pti) +{ + struct mp3entry *curr_id3 = rb->audio_current_track(); + rb->playlist_get_resume_info(&pti->curr_track_index); - /* convert units from ms to s */ - pti.length[ePT_ELAPSED] /= 1000; - pti.length[ePT_REMAINING] /= 1000; - pti.curr_track_length[ePT_ELAPSED] /= 1000; - pti.curr_track_length[ePT_REMAINING] /= 1000; - /* convert units from Bytes to KiB */ - pti.size[ePT_ELAPSED] >>= 10; - pti.size[ePT_REMAINING] >>= 10; + if (pti->curr_track_index == -1 || !curr_id3) + return false; - /* calculate totals */ - pti.length[ePT_TOTAL] = pti.length[ePT_ELAPSED] + pti.length[ePT_REMAINING]; - pti.curr_track_length[ePT_TOTAL] = pti.curr_track_length[ePT_ELAPSED] - + pti.curr_track_length[ePT_REMAINING]; - pti.size[ePT_TOTAL] = pti.size[ePT_ELAPSED] + pti.size[ePT_REMAINING]; + pti->curr_display_index = rb->playlist_get_display_index(); + pti->length[ePT_ELAPSED] = pti->curr_track_length[ePT_ELAPSED] + = curr_id3->elapsed; + pti->length[ePT_REMAINING] = pti->curr_track_length[ePT_REMAINING] + = curr_id3->length - curr_id3->elapsed; + pti->size[ePT_ELAPSED] = curr_id3->offset; + pti->size[ePT_REMAINING] = curr_id3->filesize - curr_id3->offset; + pti->actual_index = pti->counted = 1; + rb->strlcpy(pti->single_mode_tag, single_mode_id3_tag(curr_id3) ?: "", + sizeof(pti->single_mode_tag)); + return true; +} - rb->gui_synclist_init(&pt_lists, &pt_get_info, &pti, true, 2, NULL); - if (rb->global_settings->talk_menu) - rb->gui_synclist_set_voice_callback(&pt_lists, pt_speak_info); - rb->gui_synclist_set_nb_items(&pt_lists, 16); - rb->gui_synclist_set_title(&pt_lists, rb->str(LANG_PLAYING_TIME), NOICON); - rb->gui_synclist_draw(&pt_lists); - rb->gui_synclist_speak_item(&pt_lists); - while (true) - { - action = rb->get_action(CONTEXT_LIST, HZ/2); - if (rb->gui_synclist_do_button(&pt_lists, &action) == 0 - && action != ACTION_NONE && action != ACTION_UNKNOWN) - { - bool usb = rb->default_event_handler(action) == SYS_USB_CONNECTED; +/* playing time screen: shows total and elapsed playlist duration and + other stats */ +static bool playing_time(void) +{ + struct playing_time_info pti = {0}; - if (!usb && IS_SYSEVENT(action)) - continue; + if (!pt_add_curr_track(&pti)) + return false; - rb->talk_force_shutup(); - return usb; - } - } - exit: - return false; + int opt = pt_options(&pti); + if (opt > -1) + return opt; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif + rb->splash_progress_set_delay(HZ/2); + pti.nb_tracks = rb->playlist_amount(); + int success = (pti.remaining_only || pt_add_elapsed(&pti)) && pt_add_remaining(&pti); +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif + if (!success) + return false; + if (pti.error_count > 0) + rb->splash(HZ, ID2P(LANG_PLAYTIME_ERROR)); + + pt_store_converted_totals(&pti); + return pt_display_stats(&pti); } /* this is the plugin entry point */ enum plugin_status plugin_start(const void* parameter) { - enum plugin_status status = PLUGIN_OK; - (void)parameter; + enum plugin_status status = PLUGIN_OK; if (!rb->audio_status()) { -- cgit v1.2.3