Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+401 -315
+5 -315
apps/onplay.c
··· 200 200 return action; 201 201 } 202 202 203 - enum ePT_SECS { 204 - ePT_SECS_TTL = 0, 205 - ePT_SECS_BEF, 206 - ePT_SECS_AFT, 207 - ePT_SECS_COUNT 208 - }; 209 203 210 - enum 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 */ 219 - struct 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 */ 236 - static 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 - 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 - 394 - static 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 - 401 - static 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 */ 411 - static 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 205 /* CONTEXT_WPS playlist options */ 521 206 static bool shuffle_playlist(void) ··· 543 228 playlist_viewer_ex(NULL, NULL); 544 229 545 230 return 0; 231 + } 232 + 233 + static void playing_time(void) 234 + { 235 + plugin_load(PLUGIN_APPS_DIR"/playing_time.rock", NULL); 546 236 } 547 237 548 238 MENUITEM_FUNCTION(wps_view_cur_playlist_item, 0, ID2P(LANG_VIEW_DYNAMIC_PLAYLIST),
+7
apps/plugin.c
··· 827 827 /* new stuff at the end, sort into place next time 828 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 832 839 static int plugin_buffer_handle;
+11
apps/plugin.h
··· 958 958 /* new stuff at the end, sort into place next time 959 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 974 /* plugin header */
+1
apps/plugins/CATEGORIES
··· 84 84 pitch_screen,viewers 85 85 pixel-painter,games 86 86 plasma,demos 87 + playing_time,apps 87 88 png,viewers 88 89 gif,viewers 89 90 pong,games
+1
apps/plugins/SOURCES
··· 18 18 lrcplayer.c 19 19 mosaique.c 20 20 main_menu_config.c 21 + playing_time.c 21 22 properties.c 22 23 random_folder_advance_config.c 23 24 rb_info.c
+376
apps/plugins/playing_time.c
··· 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 */ 26 + const unsigned char * const byte_units[] = 27 + { 28 + ID2P(LANG_BYTE), 29 + ID2P(LANG_KIBIBYTE), 30 + ID2P(LANG_MEBIBYTE), 31 + ID2P(LANG_GIBIBYTE) 32 + }; 33 + 34 + const unsigned char * const * const kibyte_units = &byte_units[1]; 35 + 36 + enum ePT_SECS { 37 + ePT_SECS_TTL = 0, 38 + ePT_SECS_BEF, 39 + ePT_SECS_AFT, 40 + ePT_SECS_COUNT 41 + }; 42 + 43 + enum 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 */ 52 + struct 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 */ 69 + static 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 + 227 + static 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 + 234 + static 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 */ 244 + static 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 */ 354 + enum 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 + }