summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Soffke <christian.soffke@gmail.com>2023-04-13 15:14:58 +0200
committerChristian Soffke <christian.soffke@gmail.com>2023-09-20 23:57:39 -0400
commitcb3a6877fcbb001a8ee9afd3e4a25579ac40f805 (patch)
tree0bb67d281ec320e27fa6757f2d085029bda954d1
parent6ac55adc88cdacdc78e17b3e8c75fedc1d5972ef (diff)
downloadrockbox-cb3a6877fc.tar.gz
rockbox-cb3a6877fc.zip
RFC: Turn Playing Time function into plugin
Since this function already requires hitting the disk, it may make sense to turn it into a plugin. A minor advantage (apart from cleaning up onplay.c and saving RAM) is that you can now access the menu not just from the WPS context menu, but also from the Shortcuts Menu or using the WPS plugin shortcut. On the other hand, TSR plugins would have to be terminated when Playing Time is launched, as is already the case for other plugins such as PictureFlow. Change-Id: Iea85229486887463ffc52f05e33e2340437f69a4
-rw-r--r--apps/onplay.c320
-rw-r--r--apps/plugin.c7
-rw-r--r--apps/plugin.h11
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/playing_time.c376
6 files changed, 401 insertions, 315 deletions
diff --git a/apps/onplay.c b/apps/onplay.c
index 26a2614b56..5f8af77fca 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -200,322 +200,7 @@ static int bookmark_menu_callback(int action,
return action;
}
-enum ePT_SECS {
- ePT_SECS_TTL = 0,
- ePT_SECS_BEF,
- ePT_SECS_AFT,
- ePT_SECS_COUNT
-};
-
-enum ePT_KBS {
- /* Note: Order matters (voicing order of LANG_PLAYTIME_STORAGE) */
- ePT_KBS_TTL = 0,
- ePT_KBS_BEF,
- ePT_KBS_AFT,
- ePT_KBS_COUNT
-};
-
-/* 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 total, before, and after current position. 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[ePT_SECS_COUNT];
- long trk_secs[ePT_SECS_COUNT];
- /* kilobytes played total, before, and after current pos.
- Kilobytes because bytes would overflow. Data type range is up
- to 2TB. */
- long kbs[ePT_KBS_COUNT];
-};
-
-/* 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)
-{
- long elapsed_pct; /* percentage of duration elapsed */
- 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[ePT_SECS_BEF], UNIT_SEC, false);
-
- format_time_auto(timestr2, sizeof(timestr2),
- pti->secs[ePT_SECS_TTL], UNIT_SEC, false);
-
- if (pti->secs[ePT_SECS_TTL] == 0)
- elapsed_pct = 0;
- else if (pti->secs[ePT_SECS_TTL] <= 0xFFFFFF)
- {
- elapsed_pct = (pti->secs[ePT_SECS_BEF] * 100
- / pti->secs[ePT_SECS_TTL]);
- }
- else /* sacrifice some precision to avoid overflow */
- {
- elapsed_pct = (pti->secs[ePT_SECS_BEF] >> 7) * 100
- / (pti->secs[ePT_SECS_TTL] >> 7);
- }
- snprintf(buf, buffer_len, str(LANG_PLAYTIME_ELAPSED),
- timestr1, timestr2, elapsed_pct);
-
- if (say_it)
- talk_ids(false, LANG_PLAYTIME_ELAPSED,
- TALK_ID(pti->secs[ePT_SECS_BEF], UNIT_TIME),
- VOICE_OF,
- TALK_ID(pti->secs[ePT_SECS_TTL], UNIT_TIME),
- VOICE_PAUSE,
- TALK_ID(elapsed_pct, UNIT_PERCENT));
- break;
- }
- case 1: { /* playlist remaining time */
- char timestr[25];
- format_time_auto(timestr, sizeof(timestr), pti->secs[ePT_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[ePT_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[ePT_SECS_BEF],
- UNIT_SEC, false);
- format_time_auto(timestr2, sizeof(timestr2), pti->trk_secs[ePT_SECS_TTL],
- UNIT_SEC, false);
-
- if (pti->trk_secs[ePT_SECS_TTL] == 0)
- elapsed_pct = 0;
- else if (pti->trk_secs[ePT_SECS_TTL] <= 0xFFFFFF)
- {
- elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] * 100
- / pti->trk_secs[ePT_SECS_TTL]);
- }
- else /* sacrifice some precision to avoid overflow */
- {
- elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] >> 7) * 100
- / (pti->trk_secs[ePT_SECS_TTL] >> 7);
- }
- snprintf(buf, buffer_len, str(LANG_PLAYTIME_TRK_ELAPSED),
- timestr1, timestr2, elapsed_pct);
-
- if (say_it)
- talk_ids(false, LANG_PLAYTIME_TRK_ELAPSED,
- TALK_ID(pti->trk_secs[ePT_SECS_BEF], UNIT_TIME),
- VOICE_OF,
- TALK_ID(pti->trk_secs[ePT_SECS_TTL], UNIT_TIME),
- VOICE_PAUSE,
- TALK_ID(elapsed_pct, UNIT_PERCENT));
- break;
- }
- case 3: { /* track remaining time */
- char timestr[25];
- format_time_auto(timestr, sizeof(timestr), pti->trk_secs[ePT_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[ePT_SECS_AFT], UNIT_TIME));
- break;
- }
- case 4: { /* track index */
- int track_pct = (pti->curr_playing + 1) * 100 / pti->nb_tracks;
- snprintf(buf, buffer_len, str(LANG_PLAYTIME_TRACK),
- pti->curr_playing + 1, pti->nb_tracks, track_pct);
-
- 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_pct, UNIT_PERCENT));
- break;
- }
- case 5: { /* storage size */
- int i;
- char kbstr[ePT_KBS_COUNT][10];
- for (i = 0; i < ePT_KBS_COUNT; i++) {
- output_dyn_value(kbstr[i], sizeof(kbstr[i]),
- pti->kbs[i], kibyte_units, 3, true);
- }
- snprintf(buf, buffer_len, str(LANG_PLAYTIME_STORAGE),
- kbstr[ePT_KBS_TTL], kbstr[ePT_KBS_BEF],kbstr[ePT_KBS_AFT]);
-
- if (say_it) {
- int32_t voice_ids[ePT_KBS_COUNT];
- voice_ids[ePT_KBS_TTL] = LANG_PLAYTIME_STORAGE;
- voice_ids[ePT_KBS_BEF] = VOICE_PLAYTIME_DONE;
- voice_ids[ePT_KBS_AFT] = LANG_PLAYTIME_REMAINING;
-
- for (i = 0; i < ePT_KBS_COUNT; i++) {
- talk_ids(i > 0, VOICE_PAUSE, voice_ids[i]);
- output_dyn_value(NULL, 0, pti->kbs[i], kibyte_units, 3, true);
- }
- }
- break;
- }
- case 6: { /* Average track file size */
- char str[10];
- long avg_track_size = pti->kbs[ePT_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[ePT_KBS_TTL] / pti->secs[ePT_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)
-{
- int error_count = 0;
- unsigned long talked_tick = current_tick;
- struct playing_time_info pti;
- struct playlist_track_info pltrack;
- struct mp3entry id3;
- int i, fd;
-
- 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[ePT_SECS_BEF] = pti.trk_secs[ePT_SECS_BEF] = curr_id3->elapsed / 1000;
- pti.secs[ePT_SECS_AFT] = pti.trk_secs[ePT_SECS_AFT]
- = (curr_id3->length -curr_id3->elapsed) / 1000;
- pti.kbs[ePT_KBS_BEF] = curr_id3->offset / 1024;
- pti.kbs[ePT_KBS_AFT] = (curr_id3->filesize -curr_id3->offset) / 1024;
-
- splash(0, ID2P(LANG_WAIT));
- splash_progress_set_delay(5 * HZ);
- /* 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. */
- splash_progress(i, pti.nb_tracks,
- "%s (%s)", str(LANG_WAIT), 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)
- {
- bool ret = false;
- if ((fd = open(pltrack.filename, O_RDONLY)) >= 0)
- {
- ret = get_metadata(&id3, fd, pltrack.filename);
- close(fd);
- if (ret)
- {
- if (i < pti.curr_playing) {
- pti.secs[ePT_SECS_BEF] += id3.length / 1000;
- pti.kbs[ePT_KBS_BEF] += id3.filesize / 1024;
- } else {
- pti.secs[ePT_SECS_AFT] += id3.length / 1000;
- pti.kbs[ePT_KBS_AFT] += id3.filesize / 1024;
- }
- }
- }
-
- if (!ret)
- {
- error_count++;
- continue;
- }
- }
- else
- {
- error_count++;
- break;
- }
- }
-
- if (error_count > 0)
- {
- splash(HZ, ID2P(LANG_PLAYTIME_ERROR));
- }
-
- pti.nb_tracks -= error_count;
- pti.secs[ePT_SECS_TTL] = pti.secs[ePT_SECS_BEF] + pti.secs[ePT_SECS_AFT];
- pti.trk_secs[ePT_SECS_TTL] = pti.trk_secs[ePT_SECS_BEF] + pti.trk_secs[ePT_SECS_AFT];
- pti.kbs[ePT_KBS_TTL] = pti.kbs[ePT_KBS_BEF] + pti.kbs[ePT_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_set_title(&pt_lists, str(LANG_PLAYING_TIME), NOICON);
- gui_synclist_draw(&pt_lists);
- gui_synclist_speak_item(&pt_lists);
- while (true) {
- if (list_do_action(CONTEXT_LIST, HZ/2, &pt_lists, &key) == 0
- && key!=ACTION_NONE && key!=ACTION_UNKNOWN)
- {
- talk_force_shutup();
- return(default_event_handler(key) == SYS_USB_CONNECTED);
- }
-
- }
-
- exit:
- return false;
-}
/* CONTEXT_WPS playlist options */
static bool shuffle_playlist(void)
@@ -545,6 +230,11 @@ static int wps_view_cur_playlist(void)
return 0;
}
+static void playing_time(void)
+{
+ plugin_load(PLUGIN_APPS_DIR"/playing_time.rock", NULL);
+}
+
MENUITEM_FUNCTION(wps_view_cur_playlist_item, 0, ID2P(LANG_VIEW_DYNAMIC_PLAYLIST),
wps_view_cur_playlist, NULL, Icon_NOICON);
MENUITEM_FUNCTION(search_playlist_item, 0, ID2P(LANG_SEARCH_IN_PLAYLIST),
diff --git a/apps/plugin.c b/apps/plugin.c
index 13b2fc7769..da4d3432f5 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -827,6 +827,13 @@ static const struct plugin_api rockbox_api = {
/* new stuff at the end, sort into place next time
the API gets incompatible */
+ format_time_auto,
+ output_dyn_value,
+ playlist_get_resume_info,
+ playlist_get_track_info,
+ list_do_action,
+ talk_idarray,
+
};
static int plugin_buffer_handle;
diff --git a/apps/plugin.h b/apps/plugin.h
index 007552d120..b6dff14b1f 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -958,6 +958,17 @@ struct plugin_api {
/* new stuff at the end, sort into place next time
the API gets incompatible */
+ const char* (*format_time_auto)(char *buffer, int buf_len, long value,
+ int unit_idx, bool supress_unit);
+ char* (*output_dyn_value)(char *buf, int buf_size, int value,
+ const unsigned char * const *units,
+ unsigned int unit_count, bool binary_scale);
+ int (*playlist_get_resume_info)(int *resume_index);
+ int (*playlist_get_track_info)(struct playlist_info* playlist, int index,
+ struct playlist_track_info* info);
+ bool (*list_do_action)(int context, int timeout,
+ struct gui_synclist *lists, int *action);
+ int (*talk_idarray)(const long *idarray, bool enqueue);
};
/* plugin header */
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index cb0e407d31..9ec38ab76b 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -84,6 +84,7 @@ pitch_detector,apps
pitch_screen,viewers
pixel-painter,games
plasma,demos
+playing_time,apps
png,viewers
gif,viewers
pong,games
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 28a4bc38f5..7edc8a3c51 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -18,6 +18,7 @@ logo.c
lrcplayer.c
mosaique.c
main_menu_config.c
+playing_time.c
properties.c
random_folder_advance_config.c
rb_info.c
diff --git a/apps/plugins/playing_time.c b/apps/plugins/playing_time.c
new file mode 100644
index 0000000000..148da67df8
--- /dev/null
+++ b/apps/plugins/playing_time.c
@@ -0,0 +1,376 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#include "plugin.h"
+
+#define rb_talk_ids(enqueue, ids...) rb->talk_idarray(TALK_IDARRAY(ids), enqueue)
+
+/* units used with output_dyn_value */
+const unsigned char * const byte_units[] =
+{
+ ID2P(LANG_BYTE),
+ ID2P(LANG_KIBIBYTE),
+ ID2P(LANG_MEBIBYTE),
+ ID2P(LANG_GIBIBYTE)
+};
+
+const unsigned char * const * const kibyte_units = &byte_units[1];
+
+enum ePT_SECS {
+ ePT_SECS_TTL = 0,
+ ePT_SECS_BEF,
+ ePT_SECS_AFT,
+ ePT_SECS_COUNT
+};
+
+enum ePT_KBS {
+ /* Note: Order matters (voicing order of LANG_PLAYTIME_STORAGE) */
+ ePT_KBS_TTL = 0,
+ ePT_KBS_BEF,
+ ePT_KBS_AFT,
+ ePT_KBS_COUNT
+};
+
+/* 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 total, before, and after current position. 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[ePT_SECS_COUNT];
+ long trk_secs[ePT_SECS_COUNT];
+ /* kilobytes played total, before, and after current pos.
+ Kilobytes because bytes would overflow. Data type range is up
+ to 2TB. */
+ long kbs[ePT_KBS_COUNT];
+};
+
+/* 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)
+{
+ long elapsed_pct; /* percentage of duration elapsed */
+ struct playing_time_info *pti = (struct playing_time_info *)data;
+ switch(selected_item) {
+ case 0: { /* elapsed and total time */
+ char timestr1[25], timestr2[25];
+ rb->format_time_auto(timestr1, sizeof(timestr1),
+ pti->secs[ePT_SECS_BEF], UNIT_SEC, false);
+
+ rb->format_time_auto(timestr2, sizeof(timestr2),
+ pti->secs[ePT_SECS_TTL], UNIT_SEC, false);
+
+ if (pti->secs[ePT_SECS_TTL] == 0)
+ elapsed_pct = 0;
+ else if (pti->secs[ePT_SECS_TTL] <= 0xFFFFFF)
+ {
+ elapsed_pct = (pti->secs[ePT_SECS_BEF] * 100
+ / pti->secs[ePT_SECS_TTL]);
+ }
+ else /* sacrifice some precision to avoid overflow */
+ {
+ elapsed_pct = (pti->secs[ePT_SECS_BEF] >> 7) * 100
+ / (pti->secs[ePT_SECS_TTL] >> 7);
+ }
+ rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_ELAPSED),
+ timestr1, timestr2, elapsed_pct);
+
+ if (say_it)
+ rb_talk_ids(false, LANG_PLAYTIME_ELAPSED,
+ TALK_ID(pti->secs[ePT_SECS_BEF], UNIT_TIME),
+ VOICE_OF,
+ TALK_ID(pti->secs[ePT_SECS_TTL], UNIT_TIME),
+ VOICE_PAUSE,
+ TALK_ID(elapsed_pct, UNIT_PERCENT));
+ break;
+ }
+ case 1: { /* playlist remaining time */
+ char timestr[25];
+ rb->format_time_auto(timestr, sizeof(timestr), pti->secs[ePT_SECS_AFT],
+ UNIT_SEC, false);
+ rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_REMAINING), timestr);
+
+ if (say_it)
+ rb_talk_ids(false, LANG_PLAYTIME_REMAINING,
+ TALK_ID(pti->secs[ePT_SECS_AFT], UNIT_TIME));
+ break;
+ }
+ case 2: { /* track elapsed and duration */
+ char timestr1[25], timestr2[25];
+
+ rb->format_time_auto(timestr1, sizeof(timestr1), pti->trk_secs[ePT_SECS_BEF],
+ UNIT_SEC, false);
+ rb->format_time_auto(timestr2, sizeof(timestr2), pti->trk_secs[ePT_SECS_TTL],
+ UNIT_SEC, false);
+
+ if (pti->trk_secs[ePT_SECS_TTL] == 0)
+ elapsed_pct = 0;
+ else if (pti->trk_secs[ePT_SECS_TTL] <= 0xFFFFFF)
+ {
+ elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] * 100
+ / pti->trk_secs[ePT_SECS_TTL]);
+ }
+ else /* sacrifice some precision to avoid overflow */
+ {
+ elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] >> 7) * 100
+ / (pti->trk_secs[ePT_SECS_TTL] >> 7);
+ }
+ rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRK_ELAPSED),
+ timestr1, timestr2, elapsed_pct);
+
+ if (say_it)
+ rb_talk_ids(false, LANG_PLAYTIME_TRK_ELAPSED,
+ TALK_ID(pti->trk_secs[ePT_SECS_BEF], UNIT_TIME),
+ VOICE_OF,
+ TALK_ID(pti->trk_secs[ePT_SECS_TTL], UNIT_TIME),
+ VOICE_PAUSE,
+ TALK_ID(elapsed_pct, UNIT_PERCENT));
+ break;
+ }
+ case 3: { /* track remaining time */
+ char timestr[25];
+ rb->format_time_auto(timestr, sizeof(timestr), pti->trk_secs[ePT_SECS_AFT],
+ UNIT_SEC, false);
+ rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRK_REMAINING), timestr);
+
+ if (say_it)
+ rb_talk_ids(false, LANG_PLAYTIME_TRK_REMAINING,
+ TALK_ID(pti->trk_secs[ePT_SECS_AFT], UNIT_TIME));
+ break;
+ }
+ case 4: { /* track index */
+ int track_pct = (pti->curr_playing + 1) * 100 / pti->nb_tracks;
+ rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRACK),
+ pti->curr_playing + 1, pti->nb_tracks, track_pct);
+
+ if (say_it)
+ rb_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_pct, UNIT_PERCENT));
+ break;
+ }
+ case 5: { /* storage size */
+ int i;
+ char kbstr[ePT_KBS_COUNT][10];
+
+ for (i = 0; i < ePT_KBS_COUNT; i++) {
+ rb->output_dyn_value(kbstr[i], sizeof(kbstr[i]),
+ pti->kbs[i], kibyte_units, 3, true);
+ }
+ rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_STORAGE),
+ kbstr[ePT_KBS_TTL], kbstr[ePT_KBS_BEF],kbstr[ePT_KBS_AFT]);
+
+ if (say_it) {
+ int32_t voice_ids[ePT_KBS_COUNT];
+ voice_ids[ePT_KBS_TTL] = LANG_PLAYTIME_STORAGE;
+ voice_ids[ePT_KBS_BEF] = VOICE_PLAYTIME_DONE;
+ voice_ids[ePT_KBS_AFT] = LANG_PLAYTIME_REMAINING;
+
+ for (i = 0; i < ePT_KBS_COUNT; i++) {
+ rb_talk_ids(i > 0, VOICE_PAUSE, voice_ids[i]);
+ rb->output_dyn_value(NULL, 0, pti->kbs[i], kibyte_units, 3, true);
+ }
+ }
+ break;
+ }
+ case 6: { /* Average track file size */
+ char str[10];
+ long avg_track_size = pti->kbs[ePT_KBS_TTL] / pti->nb_tracks;
+ rb->output_dyn_value(str, sizeof(str), avg_track_size, kibyte_units, 3, true);
+ rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_AVG_TRACK_SIZE), str);
+
+ if (say_it) {
+ rb->talk_id(LANG_PLAYTIME_AVG_TRACK_SIZE, false);
+ rb->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[ePT_KBS_TTL] / pti->secs[ePT_SECS_TTL]
+ * 1024 * 8 / 1000);
+ rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_AVG_BITRATE), avg_bitrate);
+
+ if (say_it)
+ rb_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)
+{
+ int error_count = 0;
+ unsigned long talked_tick = *rb->current_tick;
+ struct playing_time_info pti;
+ struct playlist_track_info pltrack;
+ struct mp3entry id3;
+ int i, fd;
+
+ pti.nb_tracks = rb->playlist_amount();
+ rb->playlist_get_resume_info(&pti.curr_playing);
+ struct mp3entry *curr_id3 = rb->audio_current_track();
+ if (pti.curr_playing == -1 || !curr_id3)
+ return false;
+ pti.secs[ePT_SECS_BEF] = pti.trk_secs[ePT_SECS_BEF] = curr_id3->elapsed / 1000;
+ pti.secs[ePT_SECS_AFT] = pti.trk_secs[ePT_SECS_AFT]
+ = (curr_id3->length -curr_id3->elapsed) / 1000;
+ pti.kbs[ePT_KBS_BEF] = curr_id3->offset / 1024;
+ pti.kbs[ePT_KBS_AFT] = (curr_id3->filesize -curr_id3->offset) / 1024;
+
+ rb->splash(0, ID2P(LANG_WAIT));
+ rb->splash_progress_set_delay(5 * HZ);
+ /* 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. */
+ rb->splash_progress(i, pti.nb_tracks,
+ "%s (%s)", rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT));
+
+ /* Voice equivalent */
+ if (TIME_AFTER(*rb->current_tick, talked_tick + 5 * HZ)) {
+ 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))
+ goto exit;
+
+ if (i == pti.curr_playing)
+ continue;
+
+ if (rb->playlist_get_track_info(NULL, i, &pltrack) >= 0)
+ {
+ bool ret = false;
+ if ((fd = rb->open(pltrack.filename, O_RDONLY)) >= 0)
+ {
+ ret = rb->get_metadata(&id3, fd, pltrack.filename);
+ rb->close(fd);
+ if (ret)
+ {
+ if (i < pti.curr_playing) {
+ pti.secs[ePT_SECS_BEF] += id3.length / 1000;
+ pti.kbs[ePT_KBS_BEF] += id3.filesize / 1024;
+ } else {
+ pti.secs[ePT_SECS_AFT] += id3.length / 1000;
+ pti.kbs[ePT_KBS_AFT] += id3.filesize / 1024;
+ }
+ }
+ }
+
+ if (!ret)
+ {
+ error_count++;
+ continue;
+ }
+ }
+ else
+ {
+ error_count++;
+ break;
+ }
+ }
+
+ if (error_count > 0)
+ {
+ rb->splash(HZ, ID2P(LANG_PLAYTIME_ERROR));
+ }
+
+ pti.nb_tracks -= error_count;
+ pti.secs[ePT_SECS_TTL] = pti.secs[ePT_SECS_BEF] + pti.secs[ePT_SECS_AFT];
+ pti.trk_secs[ePT_SECS_TTL] = pti.trk_secs[ePT_SECS_BEF] + pti.trk_secs[ePT_SECS_AFT];
+ pti.kbs[ePT_KBS_TTL] = pti.kbs[ePT_KBS_BEF] + pti.kbs[ePT_KBS_AFT];
+
+ struct gui_synclist pt_lists;
+ int key;
+
+ rb->gui_synclist_init(&pt_lists, &playing_time_get_info, &pti, true, 1, NULL);
+ if (rb->global_settings->talk_menu)
+ rb->gui_synclist_set_voice_callback(&pt_lists, playing_time_speak_info);
+ rb->gui_synclist_set_nb_items(&pt_lists, 8);
+ 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) {
+ if (rb->list_do_action(CONTEXT_LIST, HZ/2, &pt_lists, &key) == 0
+ && key!=ACTION_NONE && key!=ACTION_UNKNOWN)
+ {
+ rb->talk_force_shutup();
+ return(rb->default_event_handler(key) == SYS_USB_CONNECTED);
+ }
+
+ }
+
+ exit:
+ return false;
+}
+
+/* this is the plugin entry point */
+enum plugin_status plugin_start(const void* parameter)
+{
+ enum plugin_status status = PLUGIN_OK;
+
+ (void)parameter;
+
+ if (!rb->audio_status())
+ {
+ rb->splash(HZ*2, "Nothing Playing");
+ return status;
+ }
+
+ FOR_NB_SCREENS(i)
+ rb->viewportmanager_theme_enable(i, true, NULL);
+
+ if (playing_time())
+ status = PLUGIN_USB_CONNECTED;
+
+ FOR_NB_SCREENS(i)
+ rb->viewportmanager_theme_undo(i, false);
+
+ return status;
+}