A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 560 lines 19 kB view raw
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 int menu_items[] = { 35 LANG_REMAINING, 36 LANG_ELAPSED, 37 LANG_PLAYTIME_TRK_REMAINING, 38 LANG_PLAYTIME_TRK_ELAPSED, 39 LANG_PLAYTIME_TRACK, 40 LANG_PLAYTIME_STORAGE, 41 LANG_PLAYTIME_AVG_TRACK_SIZE, 42 LANG_PLAYTIME_AVG_BITRATE, 43}; 44 45const unsigned char * const * const kibyte_units = &byte_units[1]; 46 47enum ePT_SUM { 48 /* Note: Order matters (voicing order of LANG_PLAYTIME_STORAGE) */ 49 ePT_TOTAL = 0, 50 ePT_ELAPSED, 51 ePT_REMAINING, 52 53 ePT_COUNT 54}; 55 56struct playing_time_info { 57 char single_mode_tag[MAX_PATH]; /* Relevant tag when single mode enabled */ 58 unsigned long long size[ePT_COUNT]; /* File size of tracks */ 59 unsigned long long length[ePT_COUNT]; /* Length of tracks */ 60 unsigned long curr_track_length[ePT_COUNT]; /* Current track length */ 61 int curr_track_index; /* Index of currently playing track in playlist */ 62 int curr_display_index; /* Display index of currently playing track */ 63 int actual_index; /* Display index in actually counted tracks */ 64 int counted; /* Number of tracks already added up */ 65 int nb_tracks; /* Number of tracks in playlist */ 66 int error_count; /* Number of tracks whose data couldn't be retrieved */ 67 bool remaining_only; /* Whether to ignore elapsed tracks */ 68}; 69 70static int32_t single_mode_lang(void) 71{ 72 switch (rb->global_settings->single_mode) 73 { 74 case SINGLE_MODE_ALBUM: 75 return LANG_ID3_ALBUM; 76 case SINGLE_MODE_ALBUM_ARTIST: 77 return LANG_ID3_ALBUMARTIST; 78 case SINGLE_MODE_ARTIST: 79 return LANG_ID3_ARTIST; 80 case SINGLE_MODE_COMPOSER: 81 return LANG_ID3_COMPOSER; 82 case SINGLE_MODE_GROUPING: 83 return LANG_ID3_GROUPING; 84 case SINGLE_MODE_GENRE: 85 return LANG_ID3_GENRE; 86 case SINGLE_MODE_TRACK: 87 return LANG_TRACK; 88 } 89 return LANG_OFF; 90} 91 92static char* single_mode_id3_tag(struct mp3entry *id3) 93{ 94 switch (rb->global_settings->single_mode) 95 { 96 case SINGLE_MODE_ALBUM: 97 return id3->album; 98 case SINGLE_MODE_ALBUM_ARTIST: 99 return id3->albumartist; 100 case SINGLE_MODE_ARTIST: 101 return id3->artist; 102 case SINGLE_MODE_COMPOSER: 103 return id3->composer; 104 case SINGLE_MODE_GROUPING: 105 return id3->grouping; 106 case SINGLE_MODE_GENRE: 107 return id3->genre_string; 108 } 109 return NULL; 110} 111 112static char* get_percent_str(long percents) 113{ 114 static char val[10]; 115 rb->snprintf(val, sizeof(val), rb->str(LANG_PERCENT_FORMAT), percents); 116 return val; 117} 118 119static inline void prepare_time_string(char *buf, size_t buffer_len, 120 long elapsed_pct, const char *timestr1, 121 const char *timestr2) 122{ 123 if (rb->lang_is_rtl()) 124 rb->snprintf(buf, buffer_len, "%s %s / %s", 125 get_percent_str(elapsed_pct), timestr2, timestr1); 126 else 127 rb->snprintf(buf, buffer_len, "%s / %s %s", 128 timestr1, timestr2, get_percent_str(elapsed_pct)); 129} 130 131/* list callback for playing_time screen */ 132static const char * pt_get_or_speak_info(int selected_item, void * data, 133 char *buf, size_t buffer_len, 134 bool say_it) 135{ 136 long elapsed_pct; /* percentage of duration elapsed */ 137 struct playing_time_info *pti = (struct playing_time_info *)data; 138 int info_no = selected_item/2; 139 const int menu_name_id = menu_items[info_no]; 140 141 /* header */ 142 if (!say_it && !(selected_item % 2)) 143 return rb->str(menu_name_id); 144 145 /* data */ 146 switch(info_no) { 147 case 0: { /* playlist remaining time */ 148 char timestr[25]; 149 rb->format_time_auto(timestr, sizeof(timestr), 150 pti->length[ePT_REMAINING], UNIT_SEC, false); 151 rb->snprintf(buf, buffer_len, "%s", timestr); 152 153 if (say_it) 154 rb_talk_ids(false, menu_name_id, 155 TALK_ID(pti->length[ePT_REMAINING], UNIT_TIME)); 156 break; 157 } 158 case 1: { /* elapsed and total time */ 159 char timestr1[25], timestr2[25]; 160 rb->format_time_auto(timestr1, sizeof(timestr1), 161 pti->length[ePT_ELAPSED], UNIT_SEC, true); 162 163 rb->format_time_auto(timestr2, sizeof(timestr2), 164 pti->length[ePT_TOTAL], UNIT_SEC, true); 165 166 if (pti->length[ePT_TOTAL] == 0) 167 elapsed_pct = 0; 168 else if (pti->length[ePT_TOTAL] <= 0xFFFFFF) 169 { 170 elapsed_pct = (pti->length[ePT_ELAPSED] * 100 171 / pti->length[ePT_TOTAL]); 172 } 173 else /* sacrifice some precision to avoid overflow */ 174 { 175 elapsed_pct = (pti->length[ePT_ELAPSED] >> 7) * 100 176 / (pti->length[ePT_TOTAL] >> 7); 177 } 178 prepare_time_string(buf, buffer_len, elapsed_pct, timestr1, timestr2); 179 180 if (say_it) 181 rb_talk_ids(false, menu_name_id, 182 TALK_ID(pti->length[ePT_ELAPSED], UNIT_TIME), 183 VOICE_OF, 184 TALK_ID(pti->length[ePT_TOTAL], UNIT_TIME), 185 VOICE_PAUSE, 186 TALK_ID(elapsed_pct, UNIT_PERCENT)); 187 break; 188 } 189 case 2: { /* track remaining time */ 190 char timestr[25]; 191 rb->format_time_auto(timestr, sizeof(timestr), 192 pti->curr_track_length[ePT_REMAINING], UNIT_SEC, false); 193 rb->snprintf(buf, buffer_len, "%s", timestr); 194 195 if (say_it) 196 rb_talk_ids(false, menu_name_id, 197 TALK_ID(pti->curr_track_length[ePT_REMAINING], UNIT_TIME)); 198 break; 199 } 200 case 3: { /* track elapsed and duration */ 201 char timestr1[25], timestr2[25]; 202 203 rb->format_time_auto(timestr1, sizeof(timestr1), 204 pti->curr_track_length[ePT_ELAPSED], UNIT_SEC, true); 205 rb->format_time_auto(timestr2, sizeof(timestr2), 206 pti->curr_track_length[ePT_TOTAL], UNIT_SEC, true); 207 208 if (pti->curr_track_length[ePT_TOTAL] == 0) 209 elapsed_pct = 0; 210 else if (pti->curr_track_length[ePT_TOTAL] <= 0xFFFFFF) 211 { 212 elapsed_pct = (pti->curr_track_length[ePT_ELAPSED] * 100 213 / pti->curr_track_length[ePT_TOTAL]); 214 } 215 else /* sacrifice some precision to avoid overflow */ 216 { 217 elapsed_pct = (pti->curr_track_length[ePT_ELAPSED] >> 7) * 100 218 / (pti->curr_track_length[ePT_TOTAL] >> 7); 219 } 220 prepare_time_string(buf, buffer_len, elapsed_pct, timestr1, timestr2); 221 222 if (say_it) 223 rb_talk_ids(false, menu_name_id, 224 TALK_ID(pti->curr_track_length[ePT_ELAPSED], UNIT_TIME), 225 VOICE_OF, 226 TALK_ID(pti->curr_track_length[ePT_TOTAL], UNIT_TIME), 227 VOICE_PAUSE, 228 TALK_ID(elapsed_pct, UNIT_PERCENT)); 229 break; 230 } 231 case 4: { /* track index */ 232 int track_pct = pti->actual_index * 100 / pti->counted; 233 234 if (rb->lang_is_rtl()) 235 rb->snprintf(buf, buffer_len, "%s %d / %d", get_percent_str(track_pct), 236 pti->counted, pti->actual_index); 237 else 238 rb->snprintf(buf, buffer_len, "%d / %d %s", pti->actual_index, 239 pti->counted, get_percent_str(track_pct)); 240 241 if (say_it) 242 rb_talk_ids(false, menu_name_id, 243 TALK_ID(pti->actual_index, UNIT_INT), 244 VOICE_OF, 245 TALK_ID(pti->counted, UNIT_INT), 246 VOICE_PAUSE, 247 TALK_ID(track_pct, UNIT_PERCENT)); 248 break; 249 } 250 case 5: { /* storage size */ 251 int i; 252 char kbstr[ePT_COUNT][20]; 253 254 for (i = 0; i < ePT_COUNT; i++) { 255 rb->output_dyn_value(kbstr[i], sizeof(kbstr[i]), 256 pti->size[i], kibyte_units, 3, true); 257 } 258 rb->snprintf(buf, buffer_len, "%s (%s / %s)", kbstr[ePT_TOTAL], 259 kbstr[ePT_ELAPSED], kbstr[ePT_REMAINING]); 260 261 if (say_it) { 262 int32_t voice_ids[ePT_COUNT]; 263 voice_ids[ePT_TOTAL] = menu_name_id; 264 voice_ids[ePT_ELAPSED] = VOICE_PLAYTIME_DONE; 265 voice_ids[ePT_REMAINING] = LANG_REMAINING; 266 267 for (i = 0; i < ePT_COUNT; i++) 268 { 269 rb_talk_ids(i > 0, VOICE_PAUSE, voice_ids[i]); 270 rb->output_dyn_value(NULL, 0, pti->size[i], kibyte_units, 3, true); 271 } 272 } 273 break; 274 } 275 case 6: { /* Average track file size */ 276 char str[20]; 277 long avg_track_size = pti->size[ePT_TOTAL] / pti->counted; 278 rb->output_dyn_value(str, sizeof(str), avg_track_size, kibyte_units, 3, true); 279 rb->snprintf(buf, buffer_len, "%s", str); 280 281 if (say_it) { 282 rb->talk_id(menu_name_id, false); 283 rb->output_dyn_value(NULL, 0, avg_track_size, kibyte_units, 3, true); 284 } 285 break; 286 } 287 case 7: { /* Average bitrate */ 288 /* Convert power of 2 kilobytes to power of 10 kilobits */ 289 long avg_bitrate = (pti->size[ePT_TOTAL] / pti->length[ePT_TOTAL] 290 * 1024 * 8 / 1000); 291 rb->snprintf(buf, buffer_len, "%ld kbps", avg_bitrate); 292 293 if (say_it) 294 rb_talk_ids(false, menu_name_id, 295 TALK_ID(avg_bitrate, UNIT_KBIT)); 296 break; 297 } 298 } 299 return buf; 300} 301 302static const char * pt_get_info(int selected_item, void * data, 303 char *buffer, size_t buffer_len) 304{ 305 return pt_get_or_speak_info(selected_item, data, 306 buffer, buffer_len, false); 307} 308 309static int pt_speak_info(int selected_item, void * data) 310{ 311 static char buffer[MAX_PATH]; 312 pt_get_or_speak_info(selected_item, data, buffer, sizeof(buffer), true); 313 return 0; 314} 315 316static bool pt_display_stats(struct playing_time_info *pti) 317{ 318 struct gui_synclist pt_lists; 319 rb->gui_synclist_init(&pt_lists, &pt_get_info, pti, true, 2, NULL); 320 if (rb->global_settings->talk_menu) 321 rb->gui_synclist_set_voice_callback(&pt_lists, pt_speak_info); 322 rb->gui_synclist_set_nb_items(&pt_lists, pti->remaining_only ? 2 : 8*2); 323 rb->gui_synclist_set_title(&pt_lists, *pti->single_mode_tag ? 324 rb->str(single_mode_lang()) : 325 rb->str(LANG_PLAYLIST), NOICON); 326 rb->gui_synclist_draw(&pt_lists); 327 rb->gui_synclist_speak_item(&pt_lists); 328 while (true) 329 { 330 int action = rb->get_action(CONTEXT_LIST, HZ/2); 331 if (rb->gui_synclist_do_button(&pt_lists, &action) == 0 332 && action != ACTION_NONE && action != ACTION_UNKNOWN) 333 { 334 bool usb = rb->default_event_handler(action) == SYS_USB_CONNECTED; 335 336 if (!usb && IS_SYSEVENT(action)) 337 continue; 338 339 rb->talk_force_shutup(); 340 return usb; 341 } 342 } 343 return false; 344} 345 346static const char *pt_options_name(int selected_item, void * data, 347 char *buf, size_t buf_size) 348{ 349 (void) data; 350 (void) buf; 351 (void) buf_size; 352 return selected_item == 0 ? rb->str(LANG_ALL) : 353 selected_item == 1 ? rb->str(LANG_REMAINING) : 354 rb->str(single_mode_lang()); 355} 356 357static int pt_options_speak(int selected_item, void * data) 358{ 359 (void) data; 360 rb->talk_id(selected_item == 0 ? LANG_ALL : 361 selected_item == 1 ? LANG_REMAINING : 362 single_mode_lang(), false); 363 return 0; 364} 365 366static int pt_options(struct playing_time_info *pti) 367{ 368 struct gui_synclist pt_options; 369 rb->gui_synclist_init(&pt_options, &pt_options_name, NULL, true, 1, NULL); 370 if (rb->global_settings->talk_menu) 371 rb->gui_synclist_set_voice_callback(&pt_options, pt_options_speak); 372 rb->gui_synclist_set_nb_items(&pt_options, *pti->single_mode_tag ? 3 : 2); 373 rb->gui_synclist_set_title(&pt_options, rb->str(LANG_PLAYING_TIME), NOICON); 374 rb->gui_synclist_draw(&pt_options); 375 rb->gui_synclist_speak_item(&pt_options); 376 377 while(true) 378 { 379 int button = rb->get_action(CONTEXT_LIST, HZ); 380 if (rb->gui_synclist_do_button(&pt_options, &button)) 381 continue; 382 switch(button) 383 { 384 case ACTION_STD_OK: 385 { 386 int sel = rb->gui_synclist_get_sel_pos(&pt_options); 387 if (sel < 2) 388 *pti->single_mode_tag = 0; 389 if (sel == 1) 390 pti->remaining_only = true; 391 return -1; 392 } 393 case ACTION_STD_CANCEL: 394 return 0; 395 default: 396 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) 397 return 1; 398 } 399 } 400} 401 402static void pt_store_converted_totals(struct playing_time_info *pti) 403{ 404 /* convert units from ms to s */ 405 pti->length[ePT_ELAPSED] /= 1000; 406 pti->length[ePT_REMAINING] /= 1000; 407 pti->curr_track_length[ePT_ELAPSED] /= 1000; 408 pti->curr_track_length[ePT_REMAINING] /= 1000; 409 /* convert units from Bytes to KiB */ 410 pti->size[ePT_ELAPSED] >>= 10; 411 pti->size[ePT_REMAINING] >>= 10; 412 413 pti->length[ePT_TOTAL] = pti->length[ePT_ELAPSED] + pti->length[ePT_REMAINING]; 414 pti->curr_track_length[ePT_TOTAL] = pti->curr_track_length[ePT_ELAPSED] 415 + pti->curr_track_length[ePT_REMAINING]; 416 pti->size[ePT_TOTAL] = pti->size[ePT_ELAPSED] + pti->size[ePT_REMAINING]; 417} 418 419static int pt_add_track(int i, enum ePT_SUM section, struct playing_time_info *pti) 420{ 421 static struct mp3entry id3; 422 static struct playlist_track_info pl_track; 423 int progress_total = pti->remaining_only ? 424 (pti->nb_tracks - pti->curr_display_index) + 1 : 425 pti->nb_tracks; 426 427 /* (voiced) */ 428 rb->splash_progress(pti->counted, progress_total, "%s (%s)", 429 rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT)); 430 431 if (rb->action_userabort(TIMEOUT_NOBLOCK)) 432 return -1; 433 else if (rb->playlist_get_track_info(NULL, i, &pl_track) < 0 434 || !rb->get_metadata(&id3, -1, pl_track.filename)) 435 { 436 pti->error_count++; 437 return -2; 438 } 439 else if(*pti->single_mode_tag && /* single mode tag doesn't match */ 440 rb->strcmp(pti->single_mode_tag, single_mode_id3_tag(&id3) ?: "")) 441 return 1; 442 443 pti->length[section] += id3.length; 444 pti->size[section] += id3.filesize; 445 pti->counted++; 446 return 0; 447} 448 449static bool pt_add_remaining(struct playing_time_info *pti) 450{ 451 int display_index = pti->curr_display_index + 1; 452 for (int i = pti->curr_track_index + 1; display_index <= pti->nb_tracks; i++, display_index++) 453 { 454 if (i == pti->nb_tracks) 455 i = 0; 456 457 int ret = pt_add_track(i, ePT_REMAINING, pti); 458 if (ret == 1) 459 break; 460 else if (ret == -1) 461 return false; 462 } 463 return true; 464} 465 466static bool pt_add_elapsed(struct playing_time_info *pti) 467{ 468 int display_index = pti->curr_display_index - 1; 469 for (int i = pti->curr_track_index - 1; display_index > 0; i--, display_index--) 470 { 471 if (i < 0) 472 i = pti->nb_tracks - 1; 473 474 int ret = pt_add_track(i, ePT_ELAPSED, pti); 475 if (ret == 1) 476 break; 477 else if (ret == -1) 478 return false; 479 else if (ret == 0) 480 pti->actual_index++; 481 } 482 return true; 483} 484 485static bool pt_add_curr_track(struct playing_time_info *pti) 486{ 487 struct mp3entry *curr_id3 = rb->audio_current_track(); 488 rb->playlist_get_resume_info(&pti->curr_track_index); 489 490 if (pti->curr_track_index == -1 || !curr_id3) 491 return false; 492 493 pti->curr_display_index = rb->playlist_get_display_index(); 494 pti->length[ePT_ELAPSED] = pti->curr_track_length[ePT_ELAPSED] 495 = curr_id3->elapsed; 496 pti->length[ePT_REMAINING] = pti->curr_track_length[ePT_REMAINING] 497 = curr_id3->length - curr_id3->elapsed; 498 pti->size[ePT_ELAPSED] = curr_id3->offset; 499 pti->size[ePT_REMAINING] = curr_id3->filesize - curr_id3->offset; 500 pti->actual_index = pti->counted = 1; 501 rb->strlcpy(pti->single_mode_tag, single_mode_id3_tag(curr_id3) ?: "", 502 sizeof(pti->single_mode_tag)); 503 return true; 504} 505 506/* playing time screen: shows total and elapsed playlist duration and 507 other stats */ 508static bool playing_time(void) 509{ 510 struct playing_time_info pti; 511 rb->memset(&pti, 0, sizeof(struct playing_time_info)); 512 513 if (!pt_add_curr_track(&pti)) 514 return false; 515 516 int opt = pt_options(&pti); 517 if (opt > -1) 518 return opt; 519 520#ifdef HAVE_ADJUSTABLE_CPU_FREQ 521 rb->cpu_boost(true); 522#endif 523 rb->splash_progress_set_delay(HZ/2); 524 pti.nb_tracks = rb->playlist_amount(); 525 int success = (pti.remaining_only || pt_add_elapsed(&pti)) && pt_add_remaining(&pti); 526#ifdef HAVE_ADJUSTABLE_CPU_FREQ 527 rb->cpu_boost(false); 528#endif 529 if (!success) 530 return false; 531 if (pti.error_count > 0) 532 rb->splash(HZ, ID2P(LANG_PLAYTIME_ERROR)); 533 534 pt_store_converted_totals(&pti); 535 return pt_display_stats(&pti); 536} 537 538/* this is the plugin entry point */ 539enum plugin_status plugin_start(const void* parameter) 540{ 541 (void)parameter; 542 enum plugin_status status = PLUGIN_OK; 543 544 if (!rb->audio_status()) 545 { 546 rb->splash(HZ*2, "Nothing Playing"); 547 return status; 548 } 549 550 FOR_NB_SCREENS(i) 551 rb->viewportmanager_theme_enable(i, true, NULL); 552 553 if (playing_time()) 554 status = PLUGIN_USB_CONNECTED; 555 556 FOR_NB_SCREENS(i) 557 rb->viewportmanager_theme_undo(i, false); 558 559 return status; 560}