A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1166 lines 37 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2002 Jerome Kuptz 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21#include <stdio.h> 22#include <string.h> 23#include <stdlib.h> 24#include "config.h" 25 26#include "system.h" 27#include "file.h" 28#include "lcd.h" 29#include "font.h" 30#include "backlight.h" 31#include "action.h" 32#include "kernel.h" 33#include "filetypes.h" 34#include "settings.h" 35#include "skin_engine/skin_engine.h" 36#include "audio.h" 37#include "usb.h" 38#include "status.h" 39#include "storage.h" 40#include "screens.h" 41#include "playlist.h" 42#include "icons.h" 43#include "lang.h" 44#include "bookmark.h" 45#include "misc.h" 46#include "sound.h" 47#include "onplay.h" 48#include "abrepeat.h" 49#include "playback.h" 50#include "splash.h" 51#include "cuesheet.h" 52#include "ata_idle_notify.h" 53#include "root_menu.h" 54#include "backdrop.h" 55#include "quickscreen.h" 56#include "shortcuts.h" 57#include "pitchscreen.h" 58#include "appevents.h" 59#include "viewport.h" 60#include "pcmbuf.h" 61#include "option_select.h" 62#include "playlist_viewer.h" 63#include "wps.h" 64#include "statusbar-skinned.h" 65#include "skin_engine/wps_internals.h" 66#include "open_plugin.h" 67 68#ifdef USB_ENABLE_AUDIO 69#include "usbstack/usb_audio.h" 70#endif 71 72#define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */ 73 /* 3% of 30min file == 54s step size */ 74#define MIN_FF_REWIND_STEP 500 75 76static struct wps_state wps_state; 77 78/* initial setup of wps_data */ 79static void wps_state_init(void); 80static void track_info_callback(unsigned short id, void *param); 81 82#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps" 83#ifdef HAVE_REMOTE_LCD 84#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps" 85#define DEFAULT_WPS(screen) ((screen) == SCREEN_MAIN ? \ 86 WPS_DEFAULTCFG:RWPS_DEFAULTCFG) 87#else 88#define DEFAULT_WPS(screen) (WPS_DEFAULTCFG) 89#endif 90 91char* wps_default_skin(enum screen_type screen) 92{ 93 static char *skin_buf[NB_SCREENS] = { 94#if LCD_DEPTH > 1 95 "%X(d)\n" 96#endif 97 "%s%?it<%?in<%in. |>%it|%fn>\n" 98 "%s%?ia<%ia|%?d(2)<%d(2)|%(root%)>>\n" 99 "%s%?id<%id|%?d(1)<%d(1)|%(root%)>> %?iy<%(%iy%)|>\n\n" 100 "%al%pc/%pt%ar[%pp:%pe]\n" 101 "%fbkBit %?fv<avg|> %?iv<%(id3v%iv%)|%(no id3%)>\n" 102 "%pb\n%pm\n", 103#ifdef HAVE_REMOTE_LCD 104#if LCD_REMOTE_DEPTH > 1 105 "%X(d)\n" 106#endif 107 "%s%?ia<%ia|%?d(2)<%d(2)|%(root%)>>\n" 108 "%s%?it<%?in<%in. |>%it|%fn>\n" 109 "%al%pc/%pt%ar[%pp:%pe]\n" 110 "%fbkBit %?fv<avg|> %?iv<%(id3v%iv%)|%(no id3%)>\n" 111 "%pb\n", 112#endif 113 }; 114 return skin_buf[screen]; 115} 116 117static void update_non_static(void) 118{ 119 FOR_NB_SCREENS(i) 120 skin_update(WPS, i, SKIN_REFRESH_NON_STATIC); 121} 122 123void wps_do_action(enum wps_do_action_type action, bool updatewps) 124{ 125 if (action == WPS_PLAYPAUSE) /* toggle the status */ 126 { 127 struct wps_state *state = get_wps_state(); 128 if (state->paused) 129 action = WPS_PLAY; 130 131 state->paused = !state->paused; 132 } 133 134 if (action == WPS_PLAY) /* unpause_action */ 135 { 136 audio_resume(); 137 } 138 else /* WPS_PAUSE pause_action */ 139 { 140 audio_pause(); 141 142 if (global_settings.pause_rewind) { 143 unsigned long elapsed = audio_current_track()->elapsed; 144 long newpos = elapsed - (global_settings.pause_rewind * 1000); 145 146 audio_pre_ff_rewind(); 147 audio_ff_rewind(newpos > 0 ? newpos : 0); 148 } 149 if (action == WPS_PLAYPAUSE) 150 { 151 settings_save(); 152 #if !defined(HAVE_SW_POWEROFF) 153 call_storage_idle_notifys(true); /* make sure resume info is saved */ 154 #endif 155 } 156 } 157 158 /* Bugfix only do a skin refresh if in one of the below screens */ 159 enum current_activity act = get_current_activity(); 160 161 bool refresh = (act == ACTIVITY_FM || 162 act == ACTIVITY_WPS || 163 act == ACTIVITY_RECORDING); 164 165 if (updatewps && refresh) 166 update_non_static(); 167} 168 169#ifdef HAVE_TOUCHSCREEN 170static int skintouch_to_wps(void) 171{ 172 int offset = 0; 173 struct wps_state *gstate = get_wps_state(); 174 struct gui_wps *gwps = skin_get_gwps(WPS, SCREEN_MAIN); 175 int button = skin_get_touchaction(gwps, &offset); 176 switch (button) 177 { 178 case ACTION_STD_PREV: 179 return ACTION_WPS_SKIPPREV; 180 case ACTION_STD_PREVREPEAT: 181 return ACTION_WPS_SEEKBACK; 182 case ACTION_STD_NEXT: 183 return ACTION_WPS_SKIPNEXT; 184 case ACTION_STD_NEXTREPEAT: 185 return ACTION_WPS_SEEKFWD; 186 case ACTION_STD_MENU: 187 return ACTION_WPS_MENU; 188 case ACTION_STD_CONTEXT: 189 return ACTION_WPS_CONTEXT; 190 case ACTION_STD_QUICKSCREEN: 191 return ACTION_WPS_QUICKSCREEN; 192#ifdef HAVE_HOTKEY 193 case ACTION_STD_HOTKEY: 194 return ACTION_WPS_HOTKEY; 195#endif 196 case ACTION_TOUCH_SCROLLBAR: 197 gstate->id3->elapsed = gstate->id3->length*offset/1000; 198 audio_pre_ff_rewind(); 199 audio_ff_rewind(gstate->id3->elapsed); 200 return ACTION_TOUCHSCREEN; 201 case ACTION_TOUCH_VOLUME: 202 { 203 const int min_vol = sound_min(SOUND_VOLUME); 204 const int max_vol = sound_max(SOUND_VOLUME); 205 const int step_vol = sound_steps(SOUND_VOLUME); 206 207 global_status.volume = from_normalized_volume(offset, min_vol, max_vol, 1000); 208 global_status.volume -= (global_status.volume % step_vol); 209 setvol(); 210 } 211 return ACTION_TOUCHSCREEN; 212 } 213 return button; 214} 215#endif /* HAVE_TOUCHSCREEN */ 216 217static bool ffwd_rew(int button, bool seek_from_end) 218{ 219 unsigned int step = 0; /* current ff/rewind step */ 220 unsigned int max_step = 0; /* maximum ff/rewind step */ 221 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */ 222 int direction = -1; /* forward=1 or backward=-1 */ 223 bool exit = false; 224 bool usb = false; 225 bool ff_rewind = false; 226 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3); 227 struct wps_state *gstate = get_wps_state(); 228 struct mp3entry *old_id3 = gstate->id3; 229 230 if (button == ACTION_NONE) 231 { 232 status_set_ffmode(0); 233 return usb; 234 } 235 while (!exit) 236 { 237 struct mp3entry *id3 = gstate->id3; 238 if (id3 != old_id3) 239 { 240 ff_rewind = false; 241 ff_rewind_count = 0; 242 old_id3 = id3; 243 } 244 if (id3 && seek_from_end) 245 id3->elapsed = id3->length; 246 247 switch ( button ) 248 { 249 case ACTION_WPS_SEEKFWD: 250 direction = 1; 251 /* Fallthrough */ 252 case ACTION_WPS_SEEKBACK: 253 if (ff_rewind) 254 { 255 if (direction == 1) 256 { 257 /* fast forwarding, calc max step relative to end */ 258 max_step = (id3->length - 259 (id3->elapsed + 260 ff_rewind_count)) * 261 FF_REWIND_MAX_PERCENT / 100; 262 } 263 else 264 { 265 /* rewinding, calc max step relative to start */ 266 max_step = (id3->elapsed + ff_rewind_count) * 267 FF_REWIND_MAX_PERCENT / 100; 268 } 269 270 max_step = MAX(max_step, MIN_FF_REWIND_STEP); 271 272 if (step > max_step) 273 step = max_step; 274 275 ff_rewind_count += step * direction; 276 277 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */ 278 step += step >> ff_rw_accel; 279 } 280 else 281 { 282 if ((audio_status() & AUDIO_STATUS_PLAY) && id3 && id3->length ) 283 { 284 audio_pre_ff_rewind(); 285 if (direction > 0) 286 status_set_ffmode(STATUS_FASTFORWARD); 287 else 288 status_set_ffmode(STATUS_FASTBACKWARD); 289 290 ff_rewind = true; 291 292 step = 1000 * global_settings.ff_rewind_min_step; 293 } 294 else 295 break; 296 } 297 298 if (direction > 0) { 299 if ((id3->elapsed + ff_rewind_count) > id3->length) 300 ff_rewind_count = id3->length - id3->elapsed; 301 } 302 else { 303 if ((int)(id3->elapsed + ff_rewind_count) < 0) 304 ff_rewind_count = -id3->elapsed; 305 } 306 307 /* set the wps state ff_rewind_count so the progess info 308 displays corectly */ 309 gstate->ff_rewind_count = ff_rewind_count; 310 311 FOR_NB_SCREENS(i) 312 { 313 skin_update(WPS, i, 314 SKIN_REFRESH_PLAYER_PROGRESS | 315 SKIN_REFRESH_DYNAMIC); 316 } 317 318 break; 319 320 case ACTION_WPS_STOPSEEK: 321 id3->elapsed = id3->elapsed + ff_rewind_count; 322 audio_ff_rewind(id3->elapsed); 323 gstate->ff_rewind_count = 0; 324 ff_rewind = false; 325 status_set_ffmode(0); 326 exit = true; 327 break; 328 329 default: 330 if(default_event_handler(button) == SYS_USB_CONNECTED) { 331 status_set_ffmode(0); 332 usb = true; 333 exit = true; 334 } 335 break; 336 } 337 if (!exit) 338 { 339 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK); 340#ifdef HAVE_TOUCHSCREEN 341 if (button == ACTION_TOUCHSCREEN) 342 button = skintouch_to_wps(); 343#endif 344 if (button != ACTION_WPS_SEEKFWD 345 && button != ACTION_WPS_SEEKBACK 346 && button != 0 && !IS_SYSEVENT(button)) 347 button = ACTION_WPS_STOPSEEK; 348 } 349 } 350 return usb; 351} 352 353static void gwps_caption_backlight(struct wps_state *state) 354{ 355#if defined(HAVE_BACKLIGHT) || defined(HAVE_REMOTE_LCD) 356 if (state->id3) 357 { 358#ifdef HAVE_BACKLIGHT 359 if (global_settings.caption_backlight) 360 { 361 /* turn on backlight n seconds before track ends, and turn it off n 362 seconds into the new track. n == backlight_timeout, or 5s */ 363 int n = global_settings.backlight_timeout * 1000; 364 365 if ( n < 1000 ) 366 n = 5000; /* use 5s if backlight is always on or off */ 367 368 if (((state->id3->elapsed < 1000) || 369 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && 370 (state->paused == false)) 371 backlight_on(); 372 } 373#endif 374#ifdef HAVE_REMOTE_LCD 375 if (global_settings.remote_caption_backlight) 376 { 377 /* turn on remote backlight n seconds before track ends, and turn it 378 off n seconds into the new track. n == remote_backlight_timeout, 379 or 5s */ 380 int n = global_settings.remote_backlight_timeout * 1000; 381 382 if ( n < 1000 ) 383 n = 5000; /* use 5s if backlight is always on or off */ 384 385 if (((state->id3->elapsed < 1000) || 386 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && 387 (state->paused == false)) 388 remote_backlight_on(); 389 } 390#endif 391 } 392#else 393 (void) state; 394#endif /* def HAVE_BACKLIGHT || def HAVE_REMOTE_LCD */ 395} 396 397static void change_dir(int direction) 398{ 399 if (global_settings.prevent_skip) 400 return; 401 402 if (direction < 0) 403 audio_prev_dir(); 404 else if (direction > 0) 405 audio_next_dir(); 406 /* prevent the next dir to immediatly start being ffw'd */ 407 action_wait_for_release(); 408} 409 410static void prev_track(unsigned long skip_thresh) 411{ 412 struct wps_state *state = get_wps_state(); 413 if (state->id3->elapsed < skip_thresh) 414 { 415 audio_prev(); 416 return; 417 } 418 else 419 { 420 if (state->id3->cuesheet) 421 { 422 curr_cuesheet_skip(state->id3->cuesheet, -1, state->id3->elapsed); 423 return; 424 } 425 426 audio_pre_ff_rewind(); 427 audio_ff_rewind(0); 428 } 429} 430 431static void next_track(void) 432{ 433 struct wps_state *state = get_wps_state(); 434 /* take care of if we're playing a cuesheet */ 435 if (state->id3->cuesheet) 436 { 437 if (curr_cuesheet_skip(state->id3->cuesheet, 1, state->id3->elapsed)) 438 { 439 /* if the result was false, then we really want 440 to skip to the next track */ 441 return; 442 } 443 } 444 445 audio_next(); 446} 447 448static void play_hop(int direction) 449{ 450 struct wps_state *state = get_wps_state(); 451 struct cuesheet *cue = state->id3->cuesheet; 452 long step = global_settings.skip_length*1000; 453 long elapsed = state->id3->elapsed; 454 long remaining = state->id3->length - elapsed; 455 456 /* if cuesheet is active, then we want the current tracks end instead of 457 * the total end */ 458 if (cue && (cue->curr_track_idx+1 < cue->track_count)) 459 { 460 int next = cue->curr_track_idx+1; 461 struct cue_track_info *t = &cue->tracks[next]; 462 remaining = t->offset - elapsed; 463 } 464 465 if (step < 0) 466 { 467 if (direction < 0) 468 { 469 prev_track(DEFAULT_SKIP_THRESH); 470 return; 471 } 472 else if (remaining < DEFAULT_SKIP_THRESH*2) 473 { 474 next_track(); 475 return; 476 } 477 else 478 elapsed += (remaining - DEFAULT_SKIP_THRESH*2); 479 } 480 else if (!global_settings.prevent_skip && 481 (!step || 482 (direction > 0 && step >= remaining) || 483 (direction < 0 && elapsed < DEFAULT_SKIP_THRESH))) 484 { /* Do normal track skipping */ 485 if (direction > 0) 486 next_track(); 487 else if (direction < 0) 488 { 489 if (step > 0 && global_settings.rewind_across_tracks && elapsed < DEFAULT_SKIP_THRESH && playlist_check(-1)) 490 { 491 bool audio_paused = (audio_status() & AUDIO_STATUS_PAUSE)?true:false; 492 if (!audio_paused) 493 audio_pause(); 494 audio_prev(); 495 audio_ff_rewind(-step); 496 if (!audio_paused) 497 audio_resume(); 498 return; 499 } 500 501 prev_track(DEFAULT_SKIP_THRESH); 502 } 503 return; 504 } 505 else if (direction == 1 && step >= remaining) 506 { 507 system_sound_play(SOUND_TRACK_NO_MORE); 508 return; 509 } 510 else if (direction == -1 && elapsed < step) 511 { 512 elapsed = 0; 513 } 514 else 515 { 516 elapsed += step * direction; 517 } 518 if(audio_status() & AUDIO_STATUS_PLAY) 519 { 520 audio_pre_ff_rewind(); 521 } 522 523 audio_ff_rewind(elapsed); 524} 525 526#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) 527/* 528 * If the user is unable to see the wps, because the display is deactivated, 529 * we suppress updates until the wps is activated again (the lcd driver will 530 * call this hook to issue an instant update) 531 * */ 532static void wps_lcd_activation_hook(unsigned short id, void *param) 533{ 534 (void)id; 535 (void)param; 536 skin_request_full_update(WPS); 537 /* force timeout in wps main loop, so that the update is instantly */ 538 button_queue_post(BUTTON_NONE, 0); 539} 540#endif 541 542static void gwps_leave_wps(bool theme_enabled) 543{ 544 FOR_NB_SCREENS(i) 545 { 546 struct gui_wps *gwps = skin_get_gwps(WPS, i); 547 gwps->display->scroll_stop(); 548 if (theme_enabled) 549 { 550#ifdef HAVE_BACKDROP_IMAGE 551 skin_backdrop_show(sb_get_backdrop(i)); 552 553 /* The following is supposed to erase any traces of %VB 554 viewports drawn by the WPS. May need further thought... */ 555 struct wps_data *sbs = skin_get_gwps(CUSTOM_STATUSBAR, i)->data; 556 if (gwps->data->use_extra_framebuffer && sbs->use_extra_framebuffer) 557 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); 558#endif 559 viewportmanager_theme_undo(i, skin_has_sbs(gwps)); 560 } 561 } 562 563#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) 564 /* Play safe and unregister the hook */ 565 remove_event(LCD_EVENT_ACTIVATION, wps_lcd_activation_hook); 566#endif 567 /* unhandle statusbar update delay */ 568 sb_skin_set_update_delay(DEFAULT_UPDATE_DELAY); 569#ifdef HAVE_TOUCHSCREEN 570 touchscreen_set_mode(global_settings.touch_mode); 571#endif 572} 573 574static void restore_theme(void) 575{ 576 FOR_NB_SCREENS(i) 577 { 578 struct gui_wps *gwps = skin_get_gwps(WPS, i); 579 struct screen *display = gwps->display; 580 display->scroll_stop(); 581 viewportmanager_theme_enable(i, skin_has_sbs(gwps), NULL); 582 } 583} 584 585/* 586 * display the wps on entering or restoring */ 587static void gwps_enter_wps(bool theme_enabled) 588{ 589 struct gui_wps *gwps; 590 struct screen *display; 591 if (theme_enabled) 592 restore_theme(); 593 FOR_NB_SCREENS(i) 594 { 595 gwps = skin_get_gwps(WPS, i); 596 display = gwps->display; 597 display->scroll_stop(); 598 /* Update the values in the first (default) viewport - in case the user 599 has modified the statusbar or colour settings */ 600#if LCD_DEPTH > 1 601 if (display->depth > 1) 602 { 603 struct skin_viewport *svp = skin_find_item(VP_DEFAULT_LABEL_STRING, 604 SKIN_FIND_VP, gwps->data); 605 if (svp) 606 { 607 struct viewport *vp = &svp->vp; 608 vp->fg_pattern = display->get_foreground(); 609 vp->bg_pattern = display->get_background(); 610 } 611 } 612#endif 613 /* make the backdrop actually take effect */ 614#ifdef HAVE_BACKDROP_IMAGE 615 skin_backdrop_show(gwps->data->backdrop_id); 616#endif 617 display->clear_display(); 618 skin_update(WPS, i, SKIN_REFRESH_ALL); 619 620 } 621#ifdef HAVE_TOUCHSCREEN 622 gwps = skin_get_gwps(WPS, SCREEN_MAIN); 623 skin_disarm_touchregions(gwps); 624 if (gwps->data->touchregions < 0) 625 touchscreen_set_mode(TOUCHSCREEN_BUTTON); 626#endif 627 /* force statusbar/skin update since we just cleared the whole screen */ 628 send_event(GUI_EVENT_ACTIONUPDATE, (void*)1); 629} 630 631static long do_wps_exit(long action, bool bookmark) 632{ 633 audio_pause(); 634 update_non_static(); 635 if (bookmark) 636 bookmark_autobookmark(true); 637 audio_stop(); 638 639 ab_reset_markers(); 640 641 gwps_leave_wps(true); 642#ifdef HAVE_RECORDING 643 if (action == ACTION_WPS_REC) 644 return GO_TO_RECSCREEN; 645#else 646 (void)action; 647#endif 648 if (global_settings.browse_current) 649 return GO_TO_PREVIOUS_BROWSER; 650 return GO_TO_PREVIOUS; 651} 652 653static long do_party_mode(long action) 654{ 655 if (global_settings.party_mode) 656 { 657 switch (action) 658 { 659#ifdef ACTION_WPSAB_SINGLE 660 case ACTION_WPSAB_SINGLE: 661 if (!ab_repeat_mode_enabled()) 662 break; 663 /* Note: currently all targets use ACTION_WPS_BROWSE 664 * if mapped to any of below actions this will cause problems */ 665#endif 666 case ACTION_WPS_PLAY: 667 case ACTION_WPS_SEEKFWD: 668 case ACTION_WPS_SEEKBACK: 669 case ACTION_WPS_SKIPPREV: 670 case ACTION_WPS_SKIPNEXT: 671 case ACTION_WPS_ABSETB_NEXTDIR: 672 case ACTION_WPS_ABSETA_PREVDIR: 673 case ACTION_WPS_STOP: 674 return ACTION_NONE; 675 break; 676 default: 677 break; 678 } 679 } 680 return action; 681} 682 683static inline int action_wpsab_single(long button) 684{ 685/* The iPods/X5/M5 use a single button for the A-B mode markers, 686 defined as ACTION_WPSAB_SINGLE in their config files. */ 687#ifdef ACTION_WPSAB_SINGLE 688 static int wps_ab_state = 0; 689 if (button == ACTION_WPSAB_SINGLE && ab_repeat_mode_enabled()) 690 { 691 switch (wps_ab_state) 692 { 693 case 0: /* set the A spot */ 694 button = ACTION_WPS_ABSETA_PREVDIR; 695 break; 696 case 1: /* set the B spot */ 697 button = ACTION_WPS_ABSETB_NEXTDIR; 698 break; 699 case 2: 700 button = ACTION_WPS_ABRESET; 701 break; 702 } 703 wps_ab_state = (wps_ab_state+1) % 3; 704 } 705#endif /* def ACTION_WPSAB_SINGLE */ 706 return button; 707} 708 709/* The WPS can be left in two ways: 710 * a) call a function, which draws over the wps. In this case, the wps 711 * will be still active (i.e. the below function didn't return) 712 * b) return with a value evaluated by root_menu.c, in this case the wps 713 * is really left, and root_menu will handle the next screen 714 * 715 * In either way, call gwps_leave_wps(true), in order to restore the correct 716 * "main screen" backdrops and statusbars 717 */ 718long gui_wps_show(void) 719{ 720/* NOTE: if USBAudio ever gets its own DSP channel, this block can go away! */ 721#ifdef USB_ENABLE_AUDIO 722 if (usb_audio_get_active()) 723 { 724 splash(HZ*2, ID2P(LANG_USB_DAC_ACTIVE)); 725 } 726#endif 727 long button = 0; 728 bool restore = true; 729 bool exit = false; 730 bool bookmark = false; 731 bool update = false; 732 bool theme_enabled = true; 733 long last_left = 0, last_right = 0; 734 struct wps_state *state = get_wps_state(); 735 736 ab_reset_markers(); 737 738 wps_state_init(); 739 while ( 1 ) 740 { 741 bool hotkey = false; 742 bool audio_paused = (audio_status() & AUDIO_STATUS_PAUSE)?true:false; 743 /* did someone else (i.e power thread) change audio pause mode? */ 744 if (state->paused != audio_paused) { 745 state->paused = audio_paused; 746 747 /* if another thread paused audio, we are probably in car mode, 748 about to shut down. lets save the settings. */ 749 if (state->paused) { 750 settings_save(); 751#if !defined(HAVE_SW_POWEROFF) 752 call_storage_idle_notifys(true); 753#endif 754 } 755 } 756 757 if (restore) 758 { 759 restore = false; 760#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) 761 add_event(LCD_EVENT_ACTIVATION, wps_lcd_activation_hook); 762#endif 763 /* we remove the update delay since it's not very usable in the wps, 764 * e.g. during volume changing or ffwd/rewind */ 765 sb_skin_set_update_delay(0); 766 skin_request_full_update(WPS); 767 update = true; 768 gwps_enter_wps(theme_enabled); 769 theme_enabled = true; 770 } 771 else 772 { 773 gwps_caption_backlight(state); 774 775 FOR_NB_SCREENS(i) 776 { 777#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) 778 /* currently, all remotes are readable without backlight 779 * so still update those */ 780 if (lcd_active() || (i != SCREEN_MAIN)) 781#endif 782 { 783 bool full_update = skin_do_full_update(WPS, i); 784 if (update || full_update) 785 { 786 skin_update(WPS, i, full_update ? 787 SKIN_REFRESH_ALL : SKIN_REFRESH_NON_STATIC); 788 } 789 } 790 } 791 update = false; 792 } 793 794 if (exit) 795 { 796 return do_wps_exit(button, bookmark); 797 } 798 799 if (button && !IS_SYSEVENT(button) ) 800 storage_spin(); 801 802 button = skin_wait_for_action(WPS, CONTEXT_WPS|ALLOW_SOFTLOCK, HZ/5); 803 804 /* Exit if audio has stopped playing. This happens e.g. at end of 805 playlist or if using the sleep timer. */ 806 if (!(audio_status() & AUDIO_STATUS_PLAY)) 807 exit = true; 808#ifdef HAVE_TOUCHSCREEN 809 if (button == ACTION_TOUCHSCREEN) 810 button = skintouch_to_wps(); 811#endif 812 button = do_party_mode(button); /* block select actions in party mode */ 813 814 button = action_wpsab_single(button); /* iPods/X5/M5 */ 815 816 switch(button) 817 { 818#ifdef HAVE_HOTKEY 819 case ACTION_WPS_HOTKEY: 820 { 821 hotkey = true; 822 if (!global_settings.hotkey_wps) 823 break; 824 if (get_hotkey(global_settings.hotkey_wps)->flags & HOTKEY_FLAG_NOSBS) 825 { 826 /* leave WPS without re-enabling theme */ 827 theme_enabled = false; 828 gwps_leave_wps(theme_enabled); 829 onplay(state->id3->path, 830 FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey, ONPLAY_NO_CUSTOMACTION); 831 if (!audio_status()) 832 { 833 /* re-enable theme since we're returning to SBS */ 834 gwps_leave_wps(true); 835 return GO_TO_ROOT; 836 } 837 restore = true; 838 break; 839 } 840 } 841 /* fall through */ 842#endif /* def HAVE_HOTKEY */ 843 case ACTION_WPS_CONTEXT: 844 { 845 gwps_leave_wps(true); 846 int retval = onplay(state->id3->path, 847 FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey, ONPLAY_NO_CUSTOMACTION); 848 /* if music is stopped in the context menu we want to exit the wps */ 849 if (retval == ONPLAY_MAINMENU 850 || !audio_status()) 851 return GO_TO_ROOT; 852 else if (retval == ONPLAY_PLAYLIST) 853 return GO_TO_PLAYLIST_VIEWER; 854 else if (retval == ONPLAY_PLUGIN) 855 { 856 restore_theme(); 857 theme_enabled = false; 858 open_plugin_run(ID2P(LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN)); 859 } 860 861 restore = true; 862 } 863 break; 864 865 case ACTION_WPS_BROWSE: 866 gwps_leave_wps(true); 867 return GO_TO_PREVIOUS_BROWSER; 868 break; 869 870 /* play/pause */ 871 case ACTION_WPS_PLAY: 872 wps_do_action(WPS_PLAYPAUSE, true); 873 break; 874 875 case ACTION_WPS_VOLUP: /* fall through */ 876 case ACTION_WPS_VOLDOWN: 877 if (button == ACTION_WPS_VOLUP) 878 adjust_volume(1); 879 else 880 adjust_volume(-1); 881 882 setvol(); 883 FOR_NB_SCREENS(i) 884 { 885 skin_update(WPS, i, SKIN_REFRESH_NON_STATIC); 886 } 887 update = false; 888 break; 889 /* fast forward 890 OR next dir if this is straight after ACTION_WPS_SKIPNEXT */ 891 case ACTION_WPS_SEEKFWD: 892 if (current_tick -last_right < HZ) 893 { 894 if (state->id3->cuesheet && playlist_check(1)) 895 { 896 audio_next(); 897 } 898 else 899 { 900 change_dir(1); 901 } 902 } 903 else 904 ffwd_rew(ACTION_WPS_SEEKFWD, false); 905 last_right = last_left = 0; 906 break; 907 /* fast rewind 908 OR prev dir if this is straight after ACTION_WPS_SKIPPREV,*/ 909 case ACTION_WPS_SEEKBACK: 910 if (current_tick - last_left < HZ) 911 { 912 if (state->id3->cuesheet && playlist_check(-1)) 913 { 914 audio_prev(); 915 } 916 else 917 { 918 change_dir(-1); 919 } 920 } else if (global_settings.rewind_across_tracks 921 && get_wps_state()->id3->elapsed < DEFAULT_SKIP_THRESH 922 && playlist_check(-1)) 923 { 924 if (!audio_paused) 925 audio_pause(); 926 audio_prev(); 927 ffwd_rew(ACTION_WPS_SEEKBACK, true); 928 if (!audio_paused) 929 audio_resume(); 930 } 931 else 932 ffwd_rew(ACTION_WPS_SEEKBACK, false); 933 last_left = last_right = 0; 934 break; 935 936 /* prev / restart */ 937 case ACTION_WPS_SKIPPREV: 938 last_left = current_tick; 939 940 /* if we're in A/B repeat mode and the current position 941 is past the A marker, jump back to the A marker... */ 942 if ( ab_repeat_mode_enabled() && ab_after_A_marker(state->id3->elapsed) ) 943 { 944 ab_jump_to_A_marker(); 945 break; 946 } 947 else /* ...otherwise, do it normally */ 948 play_hop(-1); 949 break; 950 951 /* next 952 OR if skip length set, hop by predetermined amount. */ 953 case ACTION_WPS_SKIPNEXT: 954 last_right = current_tick; 955 956 /* if we're in A/B repeat mode and the current position is 957 before the A marker, jump to the A marker... */ 958 if ( ab_repeat_mode_enabled() ) 959 { 960 if ( ab_before_A_marker(state->id3->elapsed) ) 961 { 962 ab_jump_to_A_marker(); 963 break; 964 } 965 } 966 else /* ...otherwise, do it normally */ 967 play_hop(1); 968 break; 969 /* next / prev directories */ 970 /* and set A-B markers if in a-b mode */ 971 case ACTION_WPS_ABSETB_NEXTDIR: 972 if (ab_repeat_mode_enabled()) 973 { 974 ab_set_B_marker(state->id3->elapsed); 975 ab_jump_to_A_marker(); 976 } 977 else 978 { 979 change_dir(1); 980 } 981 break; 982 case ACTION_WPS_ABSETA_PREVDIR: 983 if (ab_repeat_mode_enabled()) 984 ab_set_A_marker(state->id3->elapsed); 985 else 986 { 987 change_dir(-1); 988 } 989 break; 990 /* menu key functions */ 991 case ACTION_WPS_MENU: 992 gwps_leave_wps(true); 993 return GO_TO_ROOT; 994 break; 995 996 997#ifdef HAVE_QUICKSCREEN 998 case ACTION_WPS_QUICKSCREEN: 999 { 1000 gwps_leave_wps(true); 1001 bool enter_shortcuts_menu = global_settings.shortcuts_replaces_qs; 1002 if (!enter_shortcuts_menu) 1003 { 1004 int ret = quick_screen_quick(button); 1005 if (ret == QUICKSCREEN_IN_USB) 1006 return GO_TO_ROOT; 1007 else if (ret == QUICKSCREEN_GOTO_SHORTCUTS_MENU) 1008 enter_shortcuts_menu = true; 1009 else 1010 restore = true; 1011 } 1012 1013 if (enter_shortcuts_menu) /* enter_shortcuts_menu */ 1014 { 1015 global_status.last_screen = GO_TO_SHORTCUTMENU; 1016 int ret = do_shortcut_menu(NULL); 1017 return (ret == GO_TO_PREVIOUS ? GO_TO_WPS : ret); 1018 } 1019 } 1020 break; 1021#endif /* HAVE_QUICKSCREEN */ 1022 1023 /* screen settings */ 1024 1025 /* pitch screen */ 1026#ifdef HAVE_PITCHCONTROL 1027 case ACTION_WPS_PITCHSCREEN: 1028 { 1029 gwps_leave_wps(true); 1030 if (1 == gui_syncpitchscreen_run()) 1031 return GO_TO_ROOT; 1032 restore = true; 1033 } 1034 break; 1035#endif /* HAVE_PITCHCONTROL */ 1036 1037 /* reset A&B markers */ 1038 case ACTION_WPS_ABRESET: 1039 if (ab_repeat_mode_enabled()) 1040 { 1041 ab_reset_markers(); 1042 update = true; 1043 } 1044 break; 1045 1046 /* stop and exit wps */ 1047 case ACTION_WPS_STOP: 1048 bookmark = true; 1049 exit = true; 1050 break; 1051 1052 case ACTION_WPS_LIST_BOOKMARKS: 1053 gwps_leave_wps(true); 1054 if (bookmark_load_menu() == BOOKMARK_USB_CONNECTED) 1055 { 1056 return GO_TO_ROOT; 1057 } 1058 restore = true; 1059 break; 1060 1061 case ACTION_WPS_CREATE_BOOKMARK: 1062 gwps_leave_wps(true); 1063 bookmark_create_menu(); 1064 restore = true; 1065 break; 1066 1067 case ACTION_WPS_ID3SCREEN: 1068 { 1069 gwps_leave_wps(true); 1070 if (browse_id3(audio_current_track(), 1071 playlist_get_display_index(), 1072 playlist_amount(), NULL, 1, NULL)) 1073 return GO_TO_ROOT; 1074 restore = true; 1075 } 1076 break; 1077 /* this case is used by the softlock feature 1078 * it requests a full update here */ 1079 case ACTION_REDRAW: 1080 skin_request_full_update(WPS); 1081 skin_request_full_update(CUSTOM_STATUSBAR); /* if SBS is used */ 1082 break; 1083 case ACTION_NONE: /* Timeout, do a partial update */ 1084 update = true; 1085 ffwd_rew(button, false); /* hopefully fix the ffw/rwd bug */ 1086 break; 1087#ifdef HAVE_RECORDING 1088 case ACTION_WPS_REC: 1089 exit = true; 1090 break; 1091#endif 1092 case ACTION_WPS_VIEW_PLAYLIST: 1093 gwps_leave_wps(true); 1094 return GO_TO_PLAYLIST_VIEWER; 1095 break; 1096 default: 1097 switch(default_event_handler(button)) 1098 { /* music has been stopped by the default handler */ 1099 case SYS_USB_CONNECTED: 1100 case SYS_CALL_INCOMING: 1101 case BUTTON_MULTIMEDIA_STOP: 1102 gwps_leave_wps(true); 1103 return GO_TO_ROOT; 1104 } 1105 update = true; 1106 break; 1107 } 1108 } 1109 return GO_TO_ROOT; /* unreachable - just to reduce compiler warnings */ 1110} 1111 1112struct wps_state *get_wps_state(void) 1113{ 1114 return &wps_state; 1115} 1116 1117/* this is called from the playback thread so NO DRAWING! */ 1118static void track_info_callback(unsigned short id, void *param) 1119{ 1120 struct wps_state *state = get_wps_state(); 1121 1122 if (id == PLAYBACK_EVENT_TRACK_CHANGE || id == PLAYBACK_EVENT_CUR_TRACK_READY) 1123 { 1124 state->id3 = ((struct track_event *)param)->id3; 1125 if (state->id3->cuesheet) 1126 { 1127 cue_find_current_track(state->id3->cuesheet, state->id3->elapsed); 1128 } 1129 } 1130#ifdef AUDIO_FAST_SKIP_PREVIEW 1131 else if (id == PLAYBACK_EVENT_TRACK_SKIP) 1132 { 1133 state->id3 = audio_current_track(); 1134 } 1135#endif 1136 if (id == PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE) 1137 state->nid3 = audio_next_track(); 1138 skin_request_full_update(WPS); 1139} 1140 1141static void wps_state_init(void) 1142{ 1143 struct wps_state *state = get_wps_state(); 1144 state->paused = false; 1145 if(audio_status() & AUDIO_STATUS_PLAY) 1146 { 1147 state->id3 = audio_current_track(); 1148 state->nid3 = audio_next_track(); 1149 } 1150 else 1151 { 1152 state->id3 = NULL; 1153 state->nid3 = NULL; 1154 } 1155 /* We'll be updating due to restore initialized with true */ 1156 skin_request_full_update(WPS); 1157 /* add the WPS track event callbacks */ 1158 add_event(PLAYBACK_EVENT_TRACK_CHANGE, track_info_callback); 1159 add_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, track_info_callback); 1160 /* Use the same callback as ..._TRACK_CHANGE for when remaining handles have 1161 finished */ 1162 add_event(PLAYBACK_EVENT_CUR_TRACK_READY, track_info_callback); 1163#ifdef AUDIO_FAST_SKIP_PREVIEW 1164 add_event(PLAYBACK_EVENT_TRACK_SKIP, track_info_callback); 1165#endif 1166}