summaryrefslogtreecommitdiff
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-cb3a6877fcbb001a8ee9afd3e4a25579ac40f805.tar.gz
rockbox-cb3a6877fcbb001a8ee9afd3e4a25579ac40f805.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,
200 return action; 200 return action;
201} 201}
202 202
203enum ePT_SECS {
204 ePT_SECS_TTL = 0,
205 ePT_SECS_BEF,
206 ePT_SECS_AFT,
207 ePT_SECS_COUNT
208};
209
210enum ePT_KBS {
211 /* Note: Order matters (voicing order of LANG_PLAYTIME_STORAGE) */
212 ePT_KBS_TTL = 0,
213 ePT_KBS_BEF,
214 ePT_KBS_AFT,
215 ePT_KBS_COUNT
216};
217
218/* playing_time screen context */
219struct playing_time_info {
220 int curr_playing; /* index of currently playing track in playlist */
221 int nb_tracks; /* how many tracks in playlist */
222 /* seconds total, before, and after current position. Datatype
223 allows for values up to 68years. If I had kept it in ms
224 though, it would have overflowed at 24days, which takes
225 something like 8.5GB at 32kbps, and so we could conceivably
226 have playlists lasting longer than that. */
227 long secs[ePT_SECS_COUNT];
228 long trk_secs[ePT_SECS_COUNT];
229 /* kilobytes played total, before, and after current pos.
230 Kilobytes because bytes would overflow. Data type range is up
231 to 2TB. */
232 long kbs[ePT_KBS_COUNT];
233};
234
235/* list callback for playing_time screen */
236static const char * playing_time_get_or_speak_info(int selected_item, void * data,
237 char *buf, size_t buffer_len,
238 bool say_it)
239{
240 long elapsed_pct; /* percentage of duration elapsed */
241 struct playing_time_info *pti = (struct playing_time_info *)data;
242 switch(selected_item) {
243 case 0: { /* elapsed and total time */
244 char timestr1[25], timestr2[25];
245 format_time_auto(timestr1, sizeof(timestr1),
246 pti->secs[ePT_SECS_BEF], UNIT_SEC, false);
247
248 format_time_auto(timestr2, sizeof(timestr2),
249 pti->secs[ePT_SECS_TTL], UNIT_SEC, false);
250
251 if (pti->secs[ePT_SECS_TTL] == 0)
252 elapsed_pct = 0;
253 else if (pti->secs[ePT_SECS_TTL] <= 0xFFFFFF)
254 {
255 elapsed_pct = (pti->secs[ePT_SECS_BEF] * 100
256 / pti->secs[ePT_SECS_TTL]);
257 }
258 else /* sacrifice some precision to avoid overflow */
259 {
260 elapsed_pct = (pti->secs[ePT_SECS_BEF] >> 7) * 100
261 / (pti->secs[ePT_SECS_TTL] >> 7);
262 }
263 snprintf(buf, buffer_len, str(LANG_PLAYTIME_ELAPSED),
264 timestr1, timestr2, elapsed_pct);
265
266 if (say_it)
267 talk_ids(false, LANG_PLAYTIME_ELAPSED,
268 TALK_ID(pti->secs[ePT_SECS_BEF], UNIT_TIME),
269 VOICE_OF,
270 TALK_ID(pti->secs[ePT_SECS_TTL], UNIT_TIME),
271 VOICE_PAUSE,
272 TALK_ID(elapsed_pct, UNIT_PERCENT));
273 break;
274 }
275 case 1: { /* playlist remaining time */
276 char timestr[25];
277 format_time_auto(timestr, sizeof(timestr), pti->secs[ePT_SECS_AFT],
278 UNIT_SEC, false);
279 snprintf(buf, buffer_len, str(LANG_PLAYTIME_REMAINING), timestr);
280
281 if (say_it)
282 talk_ids(false, LANG_PLAYTIME_REMAINING,
283 TALK_ID(pti->secs[ePT_SECS_AFT], UNIT_TIME));
284 break;
285 }
286 case 2: { /* track elapsed and duration */
287 char timestr1[25], timestr2[25];
288
289 format_time_auto(timestr1, sizeof(timestr1), pti->trk_secs[ePT_SECS_BEF],
290 UNIT_SEC, false);
291 format_time_auto(timestr2, sizeof(timestr2), pti->trk_secs[ePT_SECS_TTL],
292 UNIT_SEC, false);
293
294 if (pti->trk_secs[ePT_SECS_TTL] == 0)
295 elapsed_pct = 0;
296 else if (pti->trk_secs[ePT_SECS_TTL] <= 0xFFFFFF)
297 {
298 elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] * 100
299 / pti->trk_secs[ePT_SECS_TTL]);
300 }
301 else /* sacrifice some precision to avoid overflow */
302 {
303 elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] >> 7) * 100
304 / (pti->trk_secs[ePT_SECS_TTL] >> 7);
305 }
306 snprintf(buf, buffer_len, str(LANG_PLAYTIME_TRK_ELAPSED),
307 timestr1, timestr2, elapsed_pct);
308
309 if (say_it)
310 talk_ids(false, LANG_PLAYTIME_TRK_ELAPSED,
311 TALK_ID(pti->trk_secs[ePT_SECS_BEF], UNIT_TIME),
312 VOICE_OF,
313 TALK_ID(pti->trk_secs[ePT_SECS_TTL], UNIT_TIME),
314 VOICE_PAUSE,
315 TALK_ID(elapsed_pct, UNIT_PERCENT));
316 break;
317 }
318 case 3: { /* track remaining time */
319 char timestr[25];
320 format_time_auto(timestr, sizeof(timestr), pti->trk_secs[ePT_SECS_AFT],
321 UNIT_SEC, false);
322 snprintf(buf, buffer_len, str(LANG_PLAYTIME_TRK_REMAINING), timestr);
323
324 if (say_it)
325 talk_ids(false, LANG_PLAYTIME_TRK_REMAINING,
326 TALK_ID(pti->trk_secs[ePT_SECS_AFT], UNIT_TIME));
327 break;
328 }
329 case 4: { /* track index */
330 int track_pct = (pti->curr_playing + 1) * 100 / pti->nb_tracks;
331 snprintf(buf, buffer_len, str(LANG_PLAYTIME_TRACK),
332 pti->curr_playing + 1, pti->nb_tracks, track_pct);
333
334 if (say_it)
335 talk_ids(false, LANG_PLAYTIME_TRACK,
336 TALK_ID(pti->curr_playing + 1, UNIT_INT),
337 VOICE_OF,
338 TALK_ID(pti->nb_tracks, UNIT_INT),
339 VOICE_PAUSE,
340 TALK_ID(track_pct, UNIT_PERCENT));
341 break;
342 }
343 case 5: { /* storage size */
344 int i;
345 char kbstr[ePT_KBS_COUNT][10];
346 203
347 for (i = 0; i < ePT_KBS_COUNT; i++) {
348 output_dyn_value(kbstr[i], sizeof(kbstr[i]),
349 pti->kbs[i], kibyte_units, 3, true);
350 }
351 snprintf(buf, buffer_len, str(LANG_PLAYTIME_STORAGE),
352 kbstr[ePT_KBS_TTL], kbstr[ePT_KBS_BEF],kbstr[ePT_KBS_AFT]);
353
354 if (say_it) {
355 int32_t voice_ids[ePT_KBS_COUNT];
356 voice_ids[ePT_KBS_TTL] = LANG_PLAYTIME_STORAGE;
357 voice_ids[ePT_KBS_BEF] = VOICE_PLAYTIME_DONE;
358 voice_ids[ePT_KBS_AFT] = LANG_PLAYTIME_REMAINING;
359
360 for (i = 0; i < ePT_KBS_COUNT; i++) {
361 talk_ids(i > 0, VOICE_PAUSE, voice_ids[i]);
362 output_dyn_value(NULL, 0, pti->kbs[i], kibyte_units, 3, true);
363 }
364 }
365 break;
366 }
367 case 6: { /* Average track file size */
368 char str[10];
369 long avg_track_size = pti->kbs[ePT_KBS_TTL] / pti->nb_tracks;
370 output_dyn_value(str, sizeof(str), avg_track_size, kibyte_units, 3, true);
371 snprintf(buf, buffer_len, str(LANG_PLAYTIME_AVG_TRACK_SIZE), str);
372
373 if (say_it) {
374 talk_id(LANG_PLAYTIME_AVG_TRACK_SIZE, false);
375 output_dyn_value(NULL, 0, avg_track_size, kibyte_units, 3, true);
376 }
377 break;
378 }
379 case 7: { /* Average bitrate */
380 /* Convert power of 2 kilobytes to power of 10 kilobits */
381 long avg_bitrate = (pti->kbs[ePT_KBS_TTL] / pti->secs[ePT_SECS_TTL]
382 * 1024 * 8 / 1000);
383 snprintf(buf, buffer_len, str(LANG_PLAYTIME_AVG_BITRATE), avg_bitrate);
384
385 if (say_it)
386 talk_ids(false, LANG_PLAYTIME_AVG_BITRATE,
387 TALK_ID(avg_bitrate, UNIT_KBIT));
388 break;
389 }
390 }
391 return buf;
392}
393
394static const char * playing_time_get_info(int selected_item, void * data,
395 char *buffer, size_t buffer_len)
396{
397 return playing_time_get_or_speak_info(selected_item, data,
398 buffer, buffer_len, false);
399}
400
401static int playing_time_speak_info(int selected_item, void * data)
402{
403 static char buffer[MAX_PATH];
404 playing_time_get_or_speak_info(selected_item, data,
405 buffer, MAX_PATH, true);
406 return 0;
407}
408
409/* playing time screen: shows total and elapsed playlist duration and
410 other stats */
411static bool playing_time(void)
412{
413 int error_count = 0;
414 unsigned long talked_tick = current_tick;
415 struct playing_time_info pti;
416 struct playlist_track_info pltrack;
417 struct mp3entry id3;
418 int i, fd;
419
420 pti.nb_tracks = playlist_amount();
421 playlist_get_resume_info(&pti.curr_playing);
422 struct mp3entry *curr_id3 = audio_current_track();
423 if (pti.curr_playing == -1 || !curr_id3)
424 return false;
425 pti.secs[ePT_SECS_BEF] = pti.trk_secs[ePT_SECS_BEF] = curr_id3->elapsed / 1000;
426 pti.secs[ePT_SECS_AFT] = pti.trk_secs[ePT_SECS_AFT]
427 = (curr_id3->length -curr_id3->elapsed) / 1000;
428 pti.kbs[ePT_KBS_BEF] = curr_id3->offset / 1024;
429 pti.kbs[ePT_KBS_AFT] = (curr_id3->filesize -curr_id3->offset) / 1024;
430
431 splash(0, ID2P(LANG_WAIT));
432 splash_progress_set_delay(5 * HZ);
433 /* Go through each file in the playlist and get its stats. For
434 huge playlists this can take a while... The reference position
435 is the position at the moment this function was invoked,
436 although playback continues forward. */
437 for (i = 0; i < pti.nb_tracks; i++) {
438 /* Show a splash while we are loading. */
439 splash_progress(i, pti.nb_tracks,
440 "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT));
441
442 /* Voice equivalent */
443 if (TIME_AFTER(current_tick, talked_tick + 5 * HZ)) {
444 talked_tick = current_tick;
445 talk_ids(false, LANG_LOADING_PERCENT,
446 TALK_ID(i * 100 / pti.nb_tracks, UNIT_PERCENT));
447 }
448 if (action_userabort(TIMEOUT_NOBLOCK))
449 goto exit;
450
451 if (i == pti.curr_playing)
452 continue;
453
454 if (playlist_get_track_info(NULL, i, &pltrack) >= 0)
455 {
456 bool ret = false;
457 if ((fd = open(pltrack.filename, O_RDONLY)) >= 0)
458 {
459 ret = get_metadata(&id3, fd, pltrack.filename);
460 close(fd);
461 if (ret)
462 {
463 if (i < pti.curr_playing) {
464 pti.secs[ePT_SECS_BEF] += id3.length / 1000;
465 pti.kbs[ePT_KBS_BEF] += id3.filesize / 1024;
466 } else {
467 pti.secs[ePT_SECS_AFT] += id3.length / 1000;
468 pti.kbs[ePT_KBS_AFT] += id3.filesize / 1024;
469 }
470 }
471 }
472
473 if (!ret)
474 {
475 error_count++;
476 continue;
477 }
478 }
479 else
480 {
481 error_count++;
482 break;
483 }
484 }
485
486 if (error_count > 0)
487 {
488 splash(HZ, ID2P(LANG_PLAYTIME_ERROR));
489 }
490
491 pti.nb_tracks -= error_count;
492 pti.secs[ePT_SECS_TTL] = pti.secs[ePT_SECS_BEF] + pti.secs[ePT_SECS_AFT];
493 pti.trk_secs[ePT_SECS_TTL] = pti.trk_secs[ePT_SECS_BEF] + pti.trk_secs[ePT_SECS_AFT];
494 pti.kbs[ePT_KBS_TTL] = pti.kbs[ePT_KBS_BEF] + pti.kbs[ePT_KBS_AFT];
495
496 struct gui_synclist pt_lists;
497 int key;
498
499 gui_synclist_init(&pt_lists, &playing_time_get_info, &pti, true, 1, NULL);
500 if (global_settings.talk_menu)
501 gui_synclist_set_voice_callback(&pt_lists, playing_time_speak_info);
502 gui_synclist_set_nb_items(&pt_lists, 8);
503 gui_synclist_set_title(&pt_lists, str(LANG_PLAYING_TIME), NOICON);
504 gui_synclist_draw(&pt_lists);
505 gui_synclist_speak_item(&pt_lists);
506 while (true) {
507 if (list_do_action(CONTEXT_LIST, HZ/2, &pt_lists, &key) == 0
508 && key!=ACTION_NONE && key!=ACTION_UNKNOWN)
509 {
510 talk_force_shutup();
511 return(default_event_handler(key) == SYS_USB_CONNECTED);
512 }
513
514 }
515
516 exit:
517 return false;
518}
519 204
520/* CONTEXT_WPS playlist options */ 205/* CONTEXT_WPS playlist options */
521static bool shuffle_playlist(void) 206static bool shuffle_playlist(void)
@@ -545,6 +230,11 @@ static int wps_view_cur_playlist(void)
545 return 0; 230 return 0;
546} 231}
547 232
233static void playing_time(void)
234{
235 plugin_load(PLUGIN_APPS_DIR"/playing_time.rock", NULL);
236}
237
548MENUITEM_FUNCTION(wps_view_cur_playlist_item, 0, ID2P(LANG_VIEW_DYNAMIC_PLAYLIST), 238MENUITEM_FUNCTION(wps_view_cur_playlist_item, 0, ID2P(LANG_VIEW_DYNAMIC_PLAYLIST),
549 wps_view_cur_playlist, NULL, Icon_NOICON); 239 wps_view_cur_playlist, NULL, Icon_NOICON);
550MENUITEM_FUNCTION(search_playlist_item, 0, ID2P(LANG_SEARCH_IN_PLAYLIST), 240MENUITEM_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 = {
827 /* new stuff at the end, sort into place next time 827 /* new stuff at the end, sort into place next time
828 the API gets incompatible */ 828 the API gets incompatible */
829 829
830 format_time_auto,
831 output_dyn_value,
832 playlist_get_resume_info,
833 playlist_get_track_info,
834 list_do_action,
835 talk_idarray,
836
830}; 837};
831 838
832static int plugin_buffer_handle; 839static 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 {
958 /* new stuff at the end, sort into place next time 958 /* new stuff at the end, sort into place next time
959 the API gets incompatible */ 959 the API gets incompatible */
960 960
961 const char* (*format_time_auto)(char *buffer, int buf_len, long value,
962 int unit_idx, bool supress_unit);
963 char* (*output_dyn_value)(char *buf, int buf_size, int value,
964 const unsigned char * const *units,
965 unsigned int unit_count, bool binary_scale);
966 int (*playlist_get_resume_info)(int *resume_index);
967 int (*playlist_get_track_info)(struct playlist_info* playlist, int index,
968 struct playlist_track_info* info);
969 bool (*list_do_action)(int context, int timeout,
970 struct gui_synclist *lists, int *action);
971 int (*talk_idarray)(const long *idarray, bool enqueue);
961}; 972};
962 973
963/* plugin header */ 974/* 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
84pitch_screen,viewers 84pitch_screen,viewers
85pixel-painter,games 85pixel-painter,games
86plasma,demos 86plasma,demos
87playing_time,apps
87png,viewers 88png,viewers
88gif,viewers 89gif,viewers
89pong,games 90pong,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
18lrcplayer.c 18lrcplayer.c
19mosaique.c 19mosaique.c
20main_menu_config.c 20main_menu_config.c
21playing_time.c
21properties.c 22properties.c
22random_folder_advance_config.c 23random_folder_advance_config.c
23rb_info.c 24rb_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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20
21#include "plugin.h"
22
23#define rb_talk_ids(enqueue, ids...) rb->talk_idarray(TALK_IDARRAY(ids), enqueue)
24
25/* units used with output_dyn_value */
26const unsigned char * const byte_units[] =
27{
28 ID2P(LANG_BYTE),
29 ID2P(LANG_KIBIBYTE),
30 ID2P(LANG_MEBIBYTE),
31 ID2P(LANG_GIBIBYTE)
32};
33
34const unsigned char * const * const kibyte_units = &byte_units[1];
35
36enum ePT_SECS {
37 ePT_SECS_TTL = 0,
38 ePT_SECS_BEF,
39 ePT_SECS_AFT,
40 ePT_SECS_COUNT
41};
42
43enum ePT_KBS {
44 /* Note: Order matters (voicing order of LANG_PLAYTIME_STORAGE) */
45 ePT_KBS_TTL = 0,
46 ePT_KBS_BEF,
47 ePT_KBS_AFT,
48 ePT_KBS_COUNT
49};
50
51/* playing_time screen context */
52struct playing_time_info {
53 int curr_playing; /* index of currently playing track in playlist */
54 int nb_tracks; /* how many tracks in playlist */
55 /* seconds total, before, and after current position. Datatype
56 allows for values up to 68years. If I had kept it in ms
57 though, it would have overflowed at 24days, which takes
58 something like 8.5GB at 32kbps, and so we could conceivably
59 have playlists lasting longer than that. */
60 long secs[ePT_SECS_COUNT];
61 long trk_secs[ePT_SECS_COUNT];
62 /* kilobytes played total, before, and after current pos.
63 Kilobytes because bytes would overflow. Data type range is up
64 to 2TB. */
65 long kbs[ePT_KBS_COUNT];
66};
67
68/* list callback for playing_time screen */
69static const char * playing_time_get_or_speak_info(int selected_item, void * data,
70 char *buf, size_t buffer_len,
71 bool say_it)
72{
73 long elapsed_pct; /* percentage of duration elapsed */
74 struct playing_time_info *pti = (struct playing_time_info *)data;
75 switch(selected_item) {
76 case 0: { /* elapsed and total time */
77 char timestr1[25], timestr2[25];
78 rb->format_time_auto(timestr1, sizeof(timestr1),
79 pti->secs[ePT_SECS_BEF], UNIT_SEC, false);
80
81 rb->format_time_auto(timestr2, sizeof(timestr2),
82 pti->secs[ePT_SECS_TTL], UNIT_SEC, false);
83
84 if (pti->secs[ePT_SECS_TTL] == 0)
85 elapsed_pct = 0;
86 else if (pti->secs[ePT_SECS_TTL] <= 0xFFFFFF)
87 {
88 elapsed_pct = (pti->secs[ePT_SECS_BEF] * 100
89 / pti->secs[ePT_SECS_TTL]);
90 }
91 else /* sacrifice some precision to avoid overflow */
92 {
93 elapsed_pct = (pti->secs[ePT_SECS_BEF] >> 7) * 100
94 / (pti->secs[ePT_SECS_TTL] >> 7);
95 }
96 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_ELAPSED),
97 timestr1, timestr2, elapsed_pct);
98
99 if (say_it)
100 rb_talk_ids(false, LANG_PLAYTIME_ELAPSED,
101 TALK_ID(pti->secs[ePT_SECS_BEF], UNIT_TIME),
102 VOICE_OF,
103 TALK_ID(pti->secs[ePT_SECS_TTL], UNIT_TIME),
104 VOICE_PAUSE,
105 TALK_ID(elapsed_pct, UNIT_PERCENT));
106 break;
107 }
108 case 1: { /* playlist remaining time */
109 char timestr[25];
110 rb->format_time_auto(timestr, sizeof(timestr), pti->secs[ePT_SECS_AFT],
111 UNIT_SEC, false);
112 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_REMAINING), timestr);
113
114 if (say_it)
115 rb_talk_ids(false, LANG_PLAYTIME_REMAINING,
116 TALK_ID(pti->secs[ePT_SECS_AFT], UNIT_TIME));
117 break;
118 }
119 case 2: { /* track elapsed and duration */
120 char timestr1[25], timestr2[25];
121
122 rb->format_time_auto(timestr1, sizeof(timestr1), pti->trk_secs[ePT_SECS_BEF],
123 UNIT_SEC, false);
124 rb->format_time_auto(timestr2, sizeof(timestr2), pti->trk_secs[ePT_SECS_TTL],
125 UNIT_SEC, false);
126
127 if (pti->trk_secs[ePT_SECS_TTL] == 0)
128 elapsed_pct = 0;
129 else if (pti->trk_secs[ePT_SECS_TTL] <= 0xFFFFFF)
130 {
131 elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] * 100
132 / pti->trk_secs[ePT_SECS_TTL]);
133 }
134 else /* sacrifice some precision to avoid overflow */
135 {
136 elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] >> 7) * 100
137 / (pti->trk_secs[ePT_SECS_TTL] >> 7);
138 }
139 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRK_ELAPSED),
140 timestr1, timestr2, elapsed_pct);
141
142 if (say_it)
143 rb_talk_ids(false, LANG_PLAYTIME_TRK_ELAPSED,
144 TALK_ID(pti->trk_secs[ePT_SECS_BEF], UNIT_TIME),
145 VOICE_OF,
146 TALK_ID(pti->trk_secs[ePT_SECS_TTL], UNIT_TIME),
147 VOICE_PAUSE,
148 TALK_ID(elapsed_pct, UNIT_PERCENT));
149 break;
150 }
151 case 3: { /* track remaining time */
152 char timestr[25];
153 rb->format_time_auto(timestr, sizeof(timestr), pti->trk_secs[ePT_SECS_AFT],
154 UNIT_SEC, false);
155 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRK_REMAINING), timestr);
156
157 if (say_it)
158 rb_talk_ids(false, LANG_PLAYTIME_TRK_REMAINING,
159 TALK_ID(pti->trk_secs[ePT_SECS_AFT], UNIT_TIME));
160 break;
161 }
162 case 4: { /* track index */
163 int track_pct = (pti->curr_playing + 1) * 100 / pti->nb_tracks;
164 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRACK),
165 pti->curr_playing + 1, pti->nb_tracks, track_pct);
166
167 if (say_it)
168 rb_talk_ids(false, LANG_PLAYTIME_TRACK,
169 TALK_ID(pti->curr_playing + 1, UNIT_INT),
170 VOICE_OF,
171 TALK_ID(pti->nb_tracks, UNIT_INT),
172 VOICE_PAUSE,
173 TALK_ID(track_pct, UNIT_PERCENT));
174 break;
175 }
176 case 5: { /* storage size */
177 int i;
178 char kbstr[ePT_KBS_COUNT][10];
179
180 for (i = 0; i < ePT_KBS_COUNT; i++) {
181 rb->output_dyn_value(kbstr[i], sizeof(kbstr[i]),
182 pti->kbs[i], kibyte_units, 3, true);
183 }
184 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_STORAGE),
185 kbstr[ePT_KBS_TTL], kbstr[ePT_KBS_BEF],kbstr[ePT_KBS_AFT]);
186
187 if (say_it) {
188 int32_t voice_ids[ePT_KBS_COUNT];
189 voice_ids[ePT_KBS_TTL] = LANG_PLAYTIME_STORAGE;
190 voice_ids[ePT_KBS_BEF] = VOICE_PLAYTIME_DONE;
191 voice_ids[ePT_KBS_AFT] = LANG_PLAYTIME_REMAINING;
192
193 for (i = 0; i < ePT_KBS_COUNT; i++) {
194 rb_talk_ids(i > 0, VOICE_PAUSE, voice_ids[i]);
195 rb->output_dyn_value(NULL, 0, pti->kbs[i], kibyte_units, 3, true);
196 }
197 }
198 break;
199 }
200 case 6: { /* Average track file size */
201 char str[10];
202 long avg_track_size = pti->kbs[ePT_KBS_TTL] / pti->nb_tracks;
203 rb->output_dyn_value(str, sizeof(str), avg_track_size, kibyte_units, 3, true);
204 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_AVG_TRACK_SIZE), str);
205
206 if (say_it) {
207 rb->talk_id(LANG_PLAYTIME_AVG_TRACK_SIZE, false);
208 rb->output_dyn_value(NULL, 0, avg_track_size, kibyte_units, 3, true);
209 }
210 break;
211 }
212 case 7: { /* Average bitrate */
213 /* Convert power of 2 kilobytes to power of 10 kilobits */
214 long avg_bitrate = (pti->kbs[ePT_KBS_TTL] / pti->secs[ePT_SECS_TTL]
215 * 1024 * 8 / 1000);
216 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_AVG_BITRATE), avg_bitrate);
217
218 if (say_it)
219 rb_talk_ids(false, LANG_PLAYTIME_AVG_BITRATE,
220 TALK_ID(avg_bitrate, UNIT_KBIT));
221 break;
222 }
223 }
224 return buf;
225}
226
227static const char * playing_time_get_info(int selected_item, void * data,
228 char *buffer, size_t buffer_len)
229{
230 return playing_time_get_or_speak_info(selected_item, data,
231 buffer, buffer_len, false);
232}
233
234static int playing_time_speak_info(int selected_item, void * data)
235{
236 static char buffer[MAX_PATH];
237 playing_time_get_or_speak_info(selected_item, data,
238 buffer, MAX_PATH, true);
239 return 0;
240}
241
242/* playing time screen: shows total and elapsed playlist duration and
243 other stats */
244static bool playing_time(void)
245{
246 int error_count = 0;
247 unsigned long talked_tick = *rb->current_tick;
248 struct playing_time_info pti;
249 struct playlist_track_info pltrack;
250 struct mp3entry id3;
251 int i, fd;
252
253 pti.nb_tracks = rb->playlist_amount();
254 rb->playlist_get_resume_info(&pti.curr_playing);
255 struct mp3entry *curr_id3 = rb->audio_current_track();
256 if (pti.curr_playing == -1 || !curr_id3)
257 return false;
258 pti.secs[ePT_SECS_BEF] = pti.trk_secs[ePT_SECS_BEF] = curr_id3->elapsed / 1000;
259 pti.secs[ePT_SECS_AFT] = pti.trk_secs[ePT_SECS_AFT]
260 = (curr_id3->length -curr_id3->elapsed) / 1000;
261 pti.kbs[ePT_KBS_BEF] = curr_id3->offset / 1024;
262 pti.kbs[ePT_KBS_AFT] = (curr_id3->filesize -curr_id3->offset) / 1024;
263
264 rb->splash(0, ID2P(LANG_WAIT));
265 rb->splash_progress_set_delay(5 * HZ);
266 /* Go through each file in the playlist and get its stats. For
267 huge playlists this can take a while... The reference position
268 is the position at the moment this function was invoked,
269 although playback continues forward. */
270 for (i = 0; i < pti.nb_tracks; i++) {
271 /* Show a splash while we are loading. */
272 rb->splash_progress(i, pti.nb_tracks,
273 "%s (%s)", rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT));
274
275 /* Voice equivalent */
276 if (TIME_AFTER(*rb->current_tick, talked_tick + 5 * HZ)) {
277 talked_tick = *rb->current_tick;
278 rb_talk_ids(false, LANG_LOADING_PERCENT,
279 TALK_ID(i * 100 / pti.nb_tracks, UNIT_PERCENT));
280 }
281 if (rb->action_userabort(TIMEOUT_NOBLOCK))
282 goto exit;
283
284 if (i == pti.curr_playing)
285 continue;
286
287 if (rb->playlist_get_track_info(NULL, i, &pltrack) >= 0)
288 {
289 bool ret = false;
290 if ((fd = rb->open(pltrack.filename, O_RDONLY)) >= 0)
291 {
292 ret = rb->get_metadata(&id3, fd, pltrack.filename);
293 rb->close(fd);
294 if (ret)
295 {
296 if (i < pti.curr_playing) {
297 pti.secs[ePT_SECS_BEF] += id3.length / 1000;
298 pti.kbs[ePT_KBS_BEF] += id3.filesize / 1024;
299 } else {
300 pti.secs[ePT_SECS_AFT] += id3.length / 1000;
301 pti.kbs[ePT_KBS_AFT] += id3.filesize / 1024;
302 }
303 }
304 }
305
306 if (!ret)
307 {
308 error_count++;
309 continue;
310 }
311 }
312 else
313 {
314 error_count++;
315 break;
316 }
317 }
318
319 if (error_count > 0)
320 {
321 rb->splash(HZ, ID2P(LANG_PLAYTIME_ERROR));
322 }
323
324 pti.nb_tracks -= error_count;
325 pti.secs[ePT_SECS_TTL] = pti.secs[ePT_SECS_BEF] + pti.secs[ePT_SECS_AFT];
326 pti.trk_secs[ePT_SECS_TTL] = pti.trk_secs[ePT_SECS_BEF] + pti.trk_secs[ePT_SECS_AFT];
327 pti.kbs[ePT_KBS_TTL] = pti.kbs[ePT_KBS_BEF] + pti.kbs[ePT_KBS_AFT];
328
329 struct gui_synclist pt_lists;
330 int key;
331
332 rb->gui_synclist_init(&pt_lists, &playing_time_get_info, &pti, true, 1, NULL);
333 if (rb->global_settings->talk_menu)
334 rb->gui_synclist_set_voice_callback(&pt_lists, playing_time_speak_info);
335 rb->gui_synclist_set_nb_items(&pt_lists, 8);
336 rb->gui_synclist_set_title(&pt_lists, rb->str(LANG_PLAYING_TIME), NOICON);
337 rb->gui_synclist_draw(&pt_lists);
338 rb->gui_synclist_speak_item(&pt_lists);
339 while (true) {
340 if (rb->list_do_action(CONTEXT_LIST, HZ/2, &pt_lists, &key) == 0
341 && key!=ACTION_NONE && key!=ACTION_UNKNOWN)
342 {
343 rb->talk_force_shutup();
344 return(rb->default_event_handler(key) == SYS_USB_CONNECTED);
345 }
346
347 }
348
349 exit:
350 return false;
351}
352
353/* this is the plugin entry point */
354enum plugin_status plugin_start(const void* parameter)
355{
356 enum plugin_status status = PLUGIN_OK;
357
358 (void)parameter;
359
360 if (!rb->audio_status())
361 {
362 rb->splash(HZ*2, "Nothing Playing");
363 return status;
364 }
365
366 FOR_NB_SCREENS(i)
367 rb->viewportmanager_theme_enable(i, true, NULL);
368
369 if (playing_time())
370 status = PLUGIN_USB_CONNECTED;
371
372 FOR_NB_SCREENS(i)
373 rb->viewportmanager_theme_undo(i, false);
374
375 return status;
376}