Index: apps/plugins/CATEGORIES =================================================================== --- apps/plugins/CATEGORIES (revision 20614) +++ apps/plugins/CATEGORIES (working copy) @@ -36,6 +36,7 @@ keybox,apps lamp,apps logo,demos +lrcplayer,apps mandelbrot,demos matrix,demos maze,games Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (revision 20614) +++ apps/plugins/SOURCES (working copy) @@ -6,6 +6,7 @@ jackpot.c keybox.c logo.c +lrcplayer.c mosaique.c properties.c random_folder_advance_config.c Index: apps/plugins/viewers.config =================================================================== --- apps/plugins/viewers.config (revision 20614) +++ apps/plugins/viewers.config (working copy) @@ -1,6 +1,7 @@ ch8,viewers/chip8,0 txt,viewers/viewer,1 nfo,viewers/viewer,1 +lrc,apps/lrcplayer,1 txt,apps/text_editor,2 jpg,viewers/jpeg,2 jpe,viewers/jpeg,2 Index: apps/plugins/lrcplayer.c =================================================================== --- apps/plugins/lrcplayer.c (revision 0) +++ apps/plugins/lrcplayer.c (revision 0) @@ -0,0 +1,1759 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: $ + * + * Copyright (C) 2008 Teruaki Kawashima + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "plugin.h" +#include "lib/playback_control.h" +#include "lib/configfile.h" +#include "lib/helper.h" +#include + +PLUGIN_HEADER + +#define MAX_LINE_LEN 128 +#define MAX_CHARS 0x1000 /* 4 kiB */ +#define MAX_LRCS MAX_CHARS/32 +#define MAX_LRCLINES (MAX_LRCS)*4 + +struct lrc_display_line { + int count; + int width; +}; + +struct lrc { + long file_offset; /* offset of time tag in file */ + long time_start; + int width; + int nline[NB_SCREENS]; + struct lrc_display_line *lines[NB_SCREENS]; + unsigned char *pbuf; /* pointer to lrc_buffer */ +}; + +struct preferences { +#if LCD_DEPTH > 1 + unsigned active_color; + unsigned inactive_color; +#endif +#ifdef HAVE_LCD_BITMAP + bool wrap; + bool wipe; + bool active_one_line; + enum { + ALIGN_RIGHT, ALIGN_CENTER, ALIGN_LEFT, + } align; +#endif + char lrc_directry[64]; + enum codepages encoding; /* use NUM_ENCODING as `use default codepage' */ +}; + +static struct preferences prefs; +static unsigned char lrc_buffer[MAX_CHARS]; +static int lrc_buffer_used; +enum extention_types {LRC, LRC8, SNC, TXT, NUM_TYPES}; +static const char *extentions[NUM_TYPES] = { + "lrc", "lrc8", "snc", "txt", +}; +static struct lrc_info { + struct mp3entry *id3; + char mp3_file[MAX_PATH]; + char lrc_file[MAX_PATH]; + char title[MAX_LINE_LEN]; + char artist[MAX_LINE_LEN]; + enum extention_types type; + long offset; /* msec */ + int nlrcline; + int nlrc; + struct lrc_display_line lrclines[MAX_LRCLINES]; + struct lrc lrcs[MAX_LRCS+1]; + bool loaded_lrc; + bool too_many_lines; /* true if nlrcline >= MAX_LRCLINES after calc pos */ +} current; +static off_t file_offset_offset; +static char temp_buf[MAX_LINE_LEN]; +#ifdef HAVE_LCD_BITMAP +static int font_ui_height = 1; +#endif +static struct viewport vp_lyrics[NB_SCREENS]; + +/******************************* + * misc stuff + *******************************/ +static void reset_current_data(void) +{ + current.title[0] = 0; + current.artist[0] = 0; + current.offset = 0; + current.nlrcline = 0; + current.nlrc = 0; + current.loaded_lrc = false; + current.too_many_lines = false; + file_offset_offset = -1; + lrc_buffer_used = 0; +} + +/* sort lyrics by time */ +static void sort_lrcs(void) +{ + struct lrc *lrc, temp; + int i, j; + + /* sort by time_start. should be stable. */ + for(i=current.nlrc-1; i>0; i--) + { + for(j=0, lrc = ¤t.lrcs[0]; j lrc[1].time_start) + { + /* swap */ + rb->memcpy(&temp, &lrc[0], sizeof(struct lrc)); + rb->memcpy(&lrc[0], &lrc[1], sizeof(struct lrc)); + rb->memcpy(&lrc[1], &temp, sizeof(struct lrc)); + } + } + } +} + +/* find start of next line */ +static inline const char *lrc_skip_space(const char * str) +{ +#ifdef HAVE_LCD_BITMAP + if(prefs.wrap) + { + while(*str && *str != '\n' && isspace(*str)) + str++; + } +#endif + if(*str == '\n') + str++; + return str; +} + +/* calculate how many lines is need to display each line and store it. */ +static void calc_pos(int start_idx, int end_idx) +{ + struct lrc *lrc; + struct lrc_display_line *lrc_line; + int i, idx, width, count, sp_width, sp_count; + const char *str, *sp; + char ch[4]; + + if(start_idx < 0) start_idx = 0; + if(end_idx > current.nlrc) end_idx = current.nlrc; + + current.nlrcline = 0; + lrc_line = ¤t.lrclines[0]; + lrc = ¤t.lrcs[start_idx]; + for(idx=start_idx; idxpbuf; + lrc->width = 0; + lrc->nline[i] = 0; + lrc->lines[i] = lrc_line; + + do + { + lrc_line->count = 0; + lrc_line->width = 0; + sp_width = 0; + sp_count = 0; + sp = NULL; + + while (*str) + { + if(*str == '\n') + { + str++; + break; + } + count = rb->utf8seek(str, 1); + rb->strncpy(ch, str, count); + ch[count] = 0; + rb->lcd_getstringsize(ch, &width, NULL); +#ifdef HAVE_LCD_BITMAP + if(isspace(*str)) /* remember position of last space */ + { + sp_width = lrc_line->width; + sp_count = lrc_line->count; + sp = str; + } + if(lrc_line->count && + lrc_line->width+width > vp_lyrics[i].width) + { + if(prefs.wrap && sp != NULL && sp_count) /* wrap */ + { + lrc_line->width = sp_width; + lrc_line->count = sp_count; + str = lrc_skip_space(sp); + } + break; + } +#endif + lrc_line->count += count; + lrc_line->width += width; + str += count; + } + lrc->width += lrc_line->width; + lrc->nline[i]++; + current.nlrcline++; + lrc_line++; + /* use one line evne for empty line.. */ + } while (*str && current.nlrcline= MAX_LRCLINES) + current.too_many_lines = true; + + return; +} + +/******************************* + * Serch lrc file. + *******************************/ + +/* search in same or parent directries of playing file. + * assume playing file is /aaa/bbb/ccc/ddd.mp3, + * this function searchs lrc file following order. + * (base_dir)/aaa/bbb/ccc/ddd.lrc + * (base_dir)/aaa/bbb/ddd.lrc + * (base_dir)/aaa/ddd.lrc + * (base_dir)/bbb/ccc/ddd.lrc + * (base_dir)/bbb/ddd.lrc + * (base_dir)/ccc/ddd.lrc + * (base_dir)/ddd.lrc + */ +static bool find_lrc_file_helper(const char *base_dir) +{ + char temp_path[MAX_PATH]; + char *names[3] = {NULL, NULL, NULL}; + char *p, *q, *dir; + int i, len; + /* /aaa/bbb/ccc/ddd.mp3 + * dir p q names[0] + */ + + /* assuming file name starts with '/' */ + rb->strncpy(temp_path, current.mp3_file, MAX_PATH); + /* get file name and remove extension */ + names[0] = rb->strrchr(temp_path, '/')+1; + if((p = rb->strrchr(names[0], '.')) != NULL) + *p = 0; + if(current.id3->title != NULL && rb->strcmp(names[0], current.id3->title)) + names[1] = current.id3->title; + + if(base_dir == NULL) + base_dir = ""; + dir = temp_path; + do{ + q = p = names[0]-1; + do{ + *p = '/'; + *q = 0; + p = q; + DEBUGF("%s%s/%s.lrc\n", base_dir, dir, names[0]); + len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s", base_dir, dir); + for(current.type=0; current.typesnprintf(¤t.lrc_file[len], MAX_PATH-len, + "/%s.%s", names[i], extentions[current.type]); + if(rb->file_exists(current.lrc_file)) + return true; + } + } + }while((q = rb->strrchr(dir, '/')) != NULL && q != dir); + *p = '/'; + }while((dir = rb->strchr(dir+1, '/')) != NULL); + DEBUGF("%s\n", temp_path); + return false; +} + +/* return true if a lrc file is found */ +static bool find_lrc_file(void) +{ + if(rb->audio_status() & AUDIO_STATUS_PLAY) + { + /* if playing same file, skip to find lrc. */ + if(!rb->strncmp(current.mp3_file, current.id3->path, MAX_PATH)) + { + return (current.lrc_file[0] != 0); + } + else + { + rb->strncpy(current.mp3_file, current.id3->path, MAX_PATH); + current.mp3_file[MAX_PATH-1] = 0; + if(!rb->file_exists(current.mp3_file)) + current.mp3_file[0] = 0; + } + } + else + { + current.mp3_file[0] = 0; + } + reset_current_data(); + current.lrc_file[0] = 0; + if(current.mp3_file[0] == 0) return false; + + DEBUGF("find lrc file for `%s'\n", current.mp3_file); + /* find .lrc file */ + if(find_lrc_file_helper("")) return true; + if(rb->dir_exists(prefs.lrc_directry)) + { + if(find_lrc_file_helper(prefs.lrc_directry)) return true; + } + + current.lrc_file[0] = 0; + return false; +} + +/******************************* + * Load file. + *******************************/ +static off_t file_offset; +/* + * check tag format and calculate value of the tag. + * supported tag: ti, ar, offset + * supported format of time tag: [mm:ss], [mm:ss.xx] + */ +static long get_time_value(char *tag, bool read_id_tags) +{ + long time; + int len = rb->strlen(tag); + DEBUGF("parse tag:%s\n", tag); + if(read_id_tags) + { + if(!rb->strncmp(tag, "ti:", 3)) + { + rb->strncpy(current.title, &tag[3], MAX_LINE_LEN); + current.title[MAX_LINE_LEN-1] = 0; + return -1; + } + if(!rb->strncmp(tag, "ar:", 3)) + { + rb->strncpy(current.artist, &tag[3], MAX_LINE_LEN); + current.artist[MAX_LINE_LEN-1] = 0; + return -1; + } + if(!rb->strncmp(tag, "offset:", 7)) + { + current.offset = rb->atoi(&tag[7]); + file_offset_offset = file_offset; + return -1; + } + } + if(len != 5 && len != 8) return -1; + if(!isdigit(tag[0]) || !isdigit(tag[1]) || tag[2] != ':') + return -1; + if(!isdigit(tag[3]) || !isdigit(tag[4])) + return -1; + if(len == 8) + { + if(tag[5] != '.' || !isdigit(tag[6]) || !isdigit(tag[7])) + return -1; + } + + /* tag is [mm:ss...] */ + time = rb->atoi(&tag[0])*60000 + rb->atoi(&tag[3])*1000; + if(len == 8) + { + /* tag is [mm:ss.xx] */ + time += rb->atoi(&tag[6])*10; + } + + return time; +} + +static bool parse_lrc_line(char *line) +{ + struct lrc *lrc; + int repeat, size; + long time; + char *tag, *tagend, *str; + + str = line; + lrc = ¤t.lrcs[current.nlrc]; + DEBUGF("parse lrc line:%s\n", str); + for(repeat = 0; current.nlrc+repeat < MAX_LRCS; repeat++) + { + tag = str; + if (*tag != '[') + break; + tagend = rb->strchr(tag, ']'); + if (tagend == NULL) break; + *tagend = 0; + time = get_time_value(tag+1, !repeat); + if (time < 0) + { + *tagend = ']'; + break; + } + lrc->file_offset = file_offset + ((int)(str-line)); + lrc->time_start = time; + str = tagend+1; + lrc++; + } + if(!repeat) return true; + + if(current.nlrc+repeat >= MAX_LRCS) /* lrc is full */ + return false; + size = rb->strlen(str)+1; + if(lrc_buffer_used+size > MAX_CHARS) /* not enough buffer */ + return false; + + /* initialize for new lrc */ + lrc = ¤t.lrcs[current.nlrc++]; + lrc->pbuf = &lrc_buffer[lrc_buffer_used]; + rb->strncpy(lrc->pbuf, str, size); + lrc_buffer_used += size; + + DEBUGF("copy repeat line:%d\n", repeat); + /* copy pointer to buffer */ + while(--repeat) + { + lrc[1].pbuf = lrc[0].pbuf; + current.nlrc++; + lrc++; + } + + return true; +} + +/* borrowed from sncviewer, see http://www.rockbox.org/tracker/task/7432 */ +/* format: \xA2\xE2hhmmss..\xA2\xD0 */ +static bool parse_snc_line(char *line) +{ + const char *open_tag = "\xA2\xE2"; + const char *end_tag = "\xA2\xD0"; + + struct lrc *lrc; + int size; + + DEBUGF("parse snc line:%s\n", line); + + /* temp_buf contains native data */ + if(!rb->strncmp(temp_buf,open_tag,2)) /* time tag */ + { + const char *pos = temp_buf+2+2; /* skip hh */ + int mm,ss,xx; + + mm = (pos[0]-'0')*10+(pos[1]-'0'); + pos += 2; + ss = (pos[0]-'0')*10+(pos[1]-'0'); + pos += 2; + xx = (pos[0]-'0')*10+(pos[1]-'0'); + pos += 2; + + rb->strncmp(pos, end_tag, 2); + pos+=2; + + size = rb->strlen(pos)+1; + /* initialize for new lrc */ + lrc = ¤t.lrcs[current.nlrc++]; + if(lrc_buffer_used+size > MAX_CHARS) /* not enough buffer */ + return false; + lrc->pbuf = &lrc_buffer[lrc_buffer_used]; + rb->strncpy(lrc->pbuf, pos, size); + lrc->file_offset = file_offset+2; + lrc->time_start = mm*60000+ss*1000+xx*10; + lrc_buffer_used += size; + if(size>1 && lrc_buffer_used < MAX_CHARS) + { + lrc_buffer[lrc_buffer_used-1] = '\n'; + lrc_buffer[lrc_buffer_used++] = 0; + } + } + else if(current.nlrc) + { + size = rb->strlen(line)+1; + if(lrc_buffer_used+size > MAX_CHARS) /* not enough buffer */ + return false; + rb->snprintf(&lrc_buffer[lrc_buffer_used-1], size+1, "%s\n", line); + lrc_buffer_used += size; + } + return true; +} + +static bool parse_txt_line(char *line) +{ + struct lrc *lrc; + int size; + + DEBUGF("parse txt line:%s\n", line); + + size = rb->strlen(line)+1; + if(lrc_buffer_used+size > MAX_CHARS) /* not enough buffer */ + return false; + + /* initialize */ + lrc = ¤t.lrcs[current.nlrc++]; + lrc->pbuf = &lrc_buffer[lrc_buffer_used]; + rb->strncpy(lrc->pbuf, line, size); + lrc->file_offset = file_offset; + lrc->time_start = 0; + lrc_buffer_used += size; + + return true; +} + +static void load_lrc_file(void) +{ + char utf8line[MAX_LINE_LEN]; + int fd; + int encoding = prefs.encoding; + bool (*line_parser)(char *line) = NULL; + off_t readsize; + if(prefs.encoding == NUM_CODEPAGES) /* use default codepage */ + encoding = -1; + + current.nlrc = 0; + lrc_buffer_used = 0; + switch(current.type) + { + case LRC8: + encoding = UTF_8; /* force utf8 temporarily */ + /* fall through */ + case LRC: + line_parser = parse_lrc_line; + break; + case SNC: + line_parser = parse_snc_line; + break; + case TXT: + default: + line_parser = parse_txt_line; + break; + } + fd = rb->open(current.lrc_file, O_RDONLY); + if(fd < 0) return; + { + /* from apps/misc.c */ + #define BOM "\xef\xbb\xbf" + #define BOM_SIZE 3 + unsigned char bom[BOM_SIZE]; + rb->read(fd, bom, BOM_SIZE); + /* check for BOM */ + if(memcmp(bom, BOM, BOM_SIZE)) + rb->lseek(fd, 0, SEEK_SET); + } + file_offset = rb->lseek(fd, 0, SEEK_CUR); /* used in parse_lrc_line */ + while((readsize = rb->read_line(fd, temp_buf, MAX_LINE_LEN)) > 0) + { + /* note: parse_snc_line() uses temp_buf to read native data. */ + rb->iso_decode(temp_buf, utf8line, encoding, readsize+1); + if(!line_parser(utf8line)) + { + break; + } + file_offset += readsize; + } + rb->close(fd); + current.loaded_lrc = true; + + sort_lrcs(); + calc_pos(0, current.nlrc); + + if(current.type == TXT) + { + /* set time tag according to length of audio and total line + * count, so that scroll speed is almost constant. */ + long length = current.id3->length; + struct lrc *lrc = ¤t.lrcs[0]; + int i, idx = 0, line = 0; + while(idxtime_start = line * length / current.nlrcline; + if(linenline[i]; + } + idx++; + lrc++; + } + } + + return; +} + +/* stole from sncviewer, see http://www.rockbox.org/tracker/task/7432 */ +/* read lyrics from id3 */ +static unsigned long bytes2int(unsigned long b0, unsigned long b1, + unsigned long b2, unsigned long b3) +{ + return (((long)(b0 & 0xFF) << 24) | /* 3*8 */ + ((long)(b1 & 0xFF) << 16) | /* 2*8 */ + ((long)(b2 & 0xFF) << 8) | /* 1*8 */ + ((long)(b3 & 0xFF))); /* 0*8 */ +} + +static bool read_id3(void) +{ + if(current.id3->codectype != AFMT_MPA_L3 || current.id3->length>600000) + { + /* not mp3 or > 10 min */ + return false; + } + #define HEADER_SIZE 10 + char header[HEADER_SIZE]; + unsigned int pos = HEADER_SIZE; + unsigned long framelen = 0; + int header_size = 0; + enum {NOLT, SYLT, USLT} type = NOLT; + + int fd = rb->open(current.mp3_file, O_RDONLY); + if(fd < 0) return false; + rb->read(fd, header, HEADER_SIZE); + DEBUGF("ID3: %d, %d, %lu\n", header[3], + current.id3->id3version, current.id3->id3v2len); + while(pos < current.id3->id3v2len) + { + rb->read(fd, header, HEADER_SIZE); + framelen = bytes2int(header[4], header[5], header[6], header[7]); + if(!rb->memcmp(header,"SYLT",4)) + { + type = SYLT; + header_size = 6; + break; + } + else if(!rb->memcmp(header,"USLT",4)) + { + type = USLT; + header_size = 4; + break; + } + rb->lseek(fd, framelen, SEEK_CUR); /* skip content */ + pos += HEADER_SIZE; + } + if(type == NOLT) + { + rb->close(fd); + return false; + } + + unsigned char* (*utf_decode)(const unsigned char *, unsigned char *, int); + char *p; + int byte_size = 1, i, idx, line; + struct lrc *lrc; + + /* read */ + rb->read(fd, header, header_size); + framelen -= header_size; + utf_decode = NULL; + + if(!(header[0] == 1)) /* not unicode */ + { + byte_size = 1; + do { + rb->read(fd, temp_buf, 1); + framelen -= 1; + } while(temp_buf[0]); /* skip content descriptor */ + } + else + { + byte_size = 2; + rb->read(fd, temp_buf, 2); + framelen -= 2; + if(rb->strncmp(temp_buf, "\xFE\xFF", 2) == 0) + utf_decode = rb->utf16BEdecode; + else + utf_decode = rb->utf16LEdecode; + /* skip rest of content descriptor */ + while(temp_buf[0] || temp_buf[1]) + { + rb->read(fd, temp_buf, 2); + framelen -= 2; + } + /* skip them (tagrename) */ + do { + rb->read(fd, temp_buf, 2); + framelen -= 2; + } while(temp_buf[0] == (char)0xFF || temp_buf[0] == (char)0xFE); + rb->lseek(fd, -1, SEEK_CUR); + framelen++; + } + + current.nlrc = 0; + lrc_buffer_used = 0; + while(framelen>0 && current.nlrc < MAX_LRCS && + lrc_buffer_used < MAX_CHARS-MAX_LINE_LEN/2) + { + p = temp_buf; + do { + rb->read(fd, temp_buf, byte_size); + framelen -= byte_size; + p += byte_size; + if(type == USLT) + { + /* replace USLT endchar 0x0A and 0x0D with 0x00 */ + if((*(p-byte_size) == 0x0A || *(p-1) == 0x0A) || + (*(p-byte_size) == 0x0D || *(p-1) == 0x0D)) + { + *(p-byte_size) = 0; + *(p-1) = 0; + } + } + } while((*(p-byte_size) || *(p-1)) && + framelen>0 && p-temp_buf < MAX_LINE_LEN-2); + if(*(p-byte_size) && *(p-1)) + { + p += byte_size; + *p = 0; + *(p+1) = 0; + } + if(lrc_buffer_used+(p-temp_buf) > MAX_CHARS) /* not enough buffer */ + break; + lrc = ¤t.lrcs[current.nlrc++]; + lrc->pbuf = &lrc_buffer[lrc_buffer_used]; + if(!utf_decode) /* not unicode */ + rb->strcpy(lrc->pbuf, temp_buf); + else + utf_decode(temp_buf, lrc->pbuf, p-temp_buf); + lrc_buffer_used += rb->strlen(lrc->pbuf)+1; + if(type == SYLT) /* timestamp */ + { + rb->read(fd, temp_buf, 4); + framelen -= 4; + lrc->time_start = bytes2int(temp_buf[0], temp_buf[1], + temp_buf[2], temp_buf[3]); + } + } + + sort_lrcs(); + calc_pos(0, current.nlrc); + + if(type == USLT) + { + /* set time tag according to length of audio and total line + * count, so that scroll speed is almost constant. */ + long length = current.id3->length; + for(idx = 0, line = 0; idxtime_start = line * length / current.nlrcline; + if(linenline[i]; + } + idx++; + } + } + rb->close(fd); + + /* save to lrc8 file and treat as lrc8 */ + current.type = LRC8; + rb->strcpy(current.lrc_file, current.mp3_file); + p = rb->strrchr(current.lrc_file, '.')+1; + rb->strcpy(p, extentions[LRC8]); + fd = rb->creat(current.lrc_file); + if(fd < 0) return true; + + if(current.id3->title != NULL) + rb->fdprintf(fd, "[ti:%s]\n", current.id3->title); + if(current.id3->artist != NULL) + rb->fdprintf(fd, "[ar:%s]\n", current.id3->artist); + for(idx = 0; idx < current.nlrc; idx++) + { + lrc = ¤t.lrcs[idx]; + lrc->file_offset = rb->lseek(fd, 0, SEEK_CUR); + long time_start = lrc->time_start + current.offset; + if(time_start < 0) time_start = 0; + rb->fdprintf(fd, "[%02ld:%02ld.%02ld]%s\n", + time_start/60000, time_start%60000/1000, + time_start%1000/10, lrc->pbuf); + } + current.loaded_lrc = true; + rb->close(fd); + + return true; +} + +/******************************* + * Display information + *******************************/ +static void display_title_artist(void) +{ +#ifdef HAVE_LCD_BITMAP + int i; + char *title = NULL, *artist = NULL, *str = temp_buf; + + if(!(rb->audio_status() & AUDIO_STATUS_PLAY)) + { + str = "Audio Stopped"; + } + else + { + if(current.id3 != NULL) + { + if(current.id3->title != NULL) + title = current.id3->title; + else + title = rb->strrchr(current.id3->path, '/')+1; + + if(current.id3->artist != NULL) + artist = current.id3->artist; + } + if(current.title[0]) + title = current.title; + if(current.artist[0]) + artist = current.artist; + + if(artist != NULL) + rb->snprintf(temp_buf, MAX_LINE_LEN, "%s/%s", title, artist); + else if(title != NULL) + str = title; + else + str = "(no info)"; + } + + FOR_NB_SCREENS(i) + { + struct screen* display = rb->screens[i]; + display->clear_display(); + display->puts_scroll(0,0,str); + display->update(); + } + return; +#else + /* no space to display title for charcell */ +#endif /* HAVE_LCD_BITMAP */ +} + +static void display_progressbar(long length, long elapsed) +{ + rb->snprintf(temp_buf, MAX_LINE_LEN, "%d:%02d/%d:%02d", + (int) elapsed/60000, (int) elapsed%60000/1000, + (int) length/60000, (int) length%60000/1000); +#ifdef HAVE_LCD_BITMAP + int y = font_ui_height+SYSFONT_HEIGHT, i; + FOR_NB_SCREENS(i) + { + struct screen* display = rb->screens[i]; + display->setfont(FONT_SYSFIXED); + display->putsxy(0, font_ui_height, temp_buf); + rb->gui_scrollbar_draw(display, 0, y+1, display->getwidth(), + SYSFONT_HEIGHT-2, length, 0, elapsed, HORIZONTAL); + display->update_rect(0, font_ui_height, display->getwidth(), + SYSFONT_HEIGHT*2); + display->setfont(FONT_UI); + } +#else + rb->lcd_puts(0, 0, temp_buf); + rb->lcd_update(); +#endif /* HAVE_LCD_BITMAP */ +} + +/******************************* + * Display pharses. + *******************************/ +#ifdef HAVE_LCD_BITMAP +static inline bool dont_wipe(void) +{ + return (!prefs.wipe || current.type == TXT); +} + +static inline void set_to_active(struct screen *display) +{ +#if (LCD_DEPTH > 1) || (defined(LCD_REMOTE_DEPTH) && (LCD_REMOTE_DEPTH > 1)) + if(display->depth > 1) + display->set_foreground(prefs.active_color); + else +#endif + display->set_drawmode(DRMODE_INVERSEVID); +} +static inline void set_to_inactive(struct screen *display) +{ +#if (LCD_DEPTH > 1) || (defined(LCD_REMOTE_DEPTH) && (LCD_REMOTE_DEPTH > 1)) + if(display->depth > 1) + display->set_foreground(prefs.inactive_color); + else +#endif + display->set_drawmode(DRMODE_SOLID); +} + +static int display_lrc_lines(enum screen_type i, int ypos, int lrc_idx, + long elapsed, long length) +{ + struct screen *display = rb->screens[i]; + struct lrc *lrc = ¤t.lrcs[lrc_idx]; + struct lrc_display_line *lrc_line = lrc->lines[i]; + long time_start, time_end; + int xpos, height = ypos + lrc->nline[i] * font_ui_height; + const char *str = lrc->pbuf; + if(height > vp_lyrics[i].height) + height = vp_lyrics[i].height; + + current.lrcs[current.nlrc].time_start = length - current.offset; + + time_start = lrc[0].time_start + current.offset; + if(time_start < 0) time_start = 0; + time_end = lrc[1].time_start + current.offset; + if(time_end < 0) time_end = 0; + + if(time_start > elapsed || + (prefs.active_one_line && time_end <= elapsed)) /* inactive */ + elapsed = 0; + else if(dont_wipe() || time_end <= elapsed) /* active whole line */ + elapsed = lrc->width; + else + { + long rin = elapsed - time_start; + long len = time_end - time_start; + + if(len) + { + /* do not draw bar if interval is <3.5sec */ + if(lrc->width || len <= 3500) + elapsed = rin * lrc->width / len; + else + elapsed = rin * vp_lyrics[i].width / len; + } + } + + while(ypos <= -font_ui_height && ypos < height) + { + ypos += font_ui_height; + elapsed -= lrc_line->width; + str += lrc_line->count; + str = lrc_skip_space(str); + lrc_line++; + } + + set_to_active(display); + while(ypos < height) + { + switch(prefs.align) + { + case ALIGN_RIGHT: + xpos = vp_lyrics[i].width - lrc_line->width; + break; + case ALIGN_CENTER: + xpos = (vp_lyrics[i].width - lrc_line->width)/2; + break; + case ALIGN_LEFT: + default: + xpos = 0; + break; + } + + if(elapsed <= 0) + { +#if (LCD_DEPTH > 1) || (defined(LCD_REMOTE_DEPTH) && (LCD_REMOTE_DEPTH > 1)) + if(display->depth > 1) + display->set_foreground(prefs.inactive_color); +#endif + display->set_drawmode(DRMODE_SOLID); + } + else if(!lrc->width) /* draw bar during interval */ + { + display->drawrect(0, ypos+font_ui_height/4, + vp_lyrics[i].width, font_ui_height/2); + display->fillrect(1, ypos+font_ui_height/4+1, + elapsed-1, font_ui_height/2-2); + set_to_inactive(display); + display->fillrect(elapsed, ypos+font_ui_height/4+1, + vp_lyrics[i].width-elapsed-1, font_ui_height/2-2); + elapsed = 0; + } + else if(elapsed < lrc_line->width)/* wipe text */ + { + display->fillrect(xpos, ypos, elapsed, font_ui_height); + set_to_inactive(display); + display->fillrect(xpos+elapsed, ypos, + lrc_line->width-elapsed, font_ui_height); +#if (LCD_DEPTH > 1) || (defined(LCD_REMOTE_DEPTH) && (LCD_REMOTE_DEPTH > 1)) + if(display->depth > 1) + display->set_drawmode(DRMODE_BG); + else +#endif + display->set_drawmode(DRMODE_INVERSEVID); + } + rb->strncpy(temp_buf, str, lrc_line->count); + temp_buf[lrc_line->count] = 0; + display->putsxy(xpos, ypos, temp_buf); + + ypos += font_ui_height; + elapsed -= lrc_line->width; + str += lrc_line->count; + str = lrc_skip_space(str); + lrc_line++; + } +#if (LCD_DEPTH > 1) || (defined(LCD_REMOTE_DEPTH) && (LCD_REMOTE_DEPTH > 1)) + if(display->depth > 1) + display->set_foreground(prefs.active_color); +#endif + display->set_drawmode(DRMODE_SOLID); + return ypos; +} +#endif /* HAVE_LCD_BITMAP */ + +static void display_lrcs(long elapsed, long length) +{ + struct lrc *lrc; + int lrc_idx; + long time_start, time_end, rin, len; + + if(!current.nlrc) return; + + current.lrcs[current.nlrc].time_start = length - current.offset; + + elapsed -= current.offset; + lrc = ¤t.lrcs[0]; + lrc_idx = 0; + while(lrc[1].time_start <= elapsed) + { + lrc++; + lrc_idx++; + } + elapsed += current.offset; + + time_start = lrc[0].time_start + current.offset; + if(time_start < 0) time_start = 0; + time_end = lrc[1].time_start + current.offset; + if(time_end < 0) time_end = 0; + rin = elapsed - time_start; + len = time_end - time_start; + + struct screen *display; + int i; + FOR_NB_SCREENS(i) + { + display = rb->screens[i]; + /* display current line at the center of the viewport */ + display->set_viewport(&vp_lyrics[i]); + display->clear_viewport(); +#ifdef HAVE_LCD_BITMAP + struct lrc *_lrc = lrc; + int y, ypos = 0, _lrc_idx = lrc_idx; + y = (display->getnblines()-1)/2; + if(current.too_many_lines) + calc_pos(_lrc_idx-y, _lrc_idx+y+1); + if(len) + { + if(dont_wipe()) + ypos = - rin * _lrc->nline[i] * font_ui_height / len; + else + { + long _elapsed = rin * _lrc->width / len; + struct lrc_display_line *lrc_line = _lrc->lines[i]; + while(_elapsed > lrc_line->width) { + _elapsed -= lrc_line->width; + y--; + lrc_line++; + } + } + } + + /* find top line to display */ + while(_lrc_idx && y > 0) + { + _lrc--; + _lrc_idx--; + y -= _lrc->nline[i]; + } + + ypos += y*font_ui_height; + while(_lrc_idx < current.nlrc && ypos < vp_lyrics[i].height) + { + ypos = display_lrc_lines(i, ypos, _lrc_idx, elapsed, length); + _lrc_idx++; + } + if(_lrc_idx == current.nlrc && ypos < vp_lyrics[i].height) + display->putsxy(0, ypos, "[end]"); +#else + int x = vp_lyrics[i].width/2, y = 0; + if(len) + x -= rin * lrc->width / len; + if (x < 0) + display->puts(0, y, + lrc->pbuf + rb->utf8seek(lrc->pbuf, -x)); + else + display->puts(x, y, lrc->pbuf); + x += rb->utf8length(lrc->pbuf)+1; + if(lrc_idx+1 == current.nlrc && x < vp_lyrics[i].width) + display->puts(x, y, "[end]"); +#endif /* HAVE_LCD_BITMAP */ + display->update_viewport(); + display->set_viewport(NULL); + } +} + +/******************************* + * Browse and edit file. + *******************************/ +/* point playing line in lyrics */ +static enum themable_icons get_cursor(int selected, void * data) +{ + (void) data; + long t1, t2, elapsed; + if(selected < 0 || selected >= current.nlrc) + return Icon_NOICON; + t1 = current.lrcs[selected].time_start + current.offset; + t2 = current.lrcs[selected+1].time_start + current.offset; + elapsed = current.id3->elapsed; + if(t1 <= elapsed && t2 > elapsed) + return Icon_Moving; + return Icon_NOICON; +} +static char *get_lrc_line(int selected, void *data, + char *buffer, size_t buffer_len) +{ + (void) data; + struct lrc *lrc; + long t; + + if(selected < 0 || selected >= current.nlrc) + return NULL; + + lrc = ¤t.lrcs[selected]; + t = lrc->time_start + current.offset; + if(t<0) t=0; + rb->snprintf(buffer, buffer_len, "[%02ld:%02ld.%02ld]%s", + t/60000, t%60000/1000, t%1000/10, lrc->pbuf); + return buffer; +} + +static bool save_changes(const char *temp_path) +{ + int fd, fe, i, idx; + off_t curr, next, size, offset; + long time_start; + fd = rb->creat(current.lrc_file); + if(fd < 0) + return false; + fe = rb->open(temp_path, O_RDONLY); + if(fe < 0) + { + rb->close(fd); + return false; + } + size = rb->filesize(fe); + curr = -1; + while(curr curr && offset < next) + idx = i, next = offset; + } + offset = file_offset_offset; + if(offset > curr && offset < next) + idx = -1, next = offset; + if(curr == -1) curr = 0; + /* copy before the next tag */ + while(curr MAX_LINE_LEN) + count = MAX_LINE_LEN; + if(rb->read(fe, temp_buf, count)!=count) + break; + rb->write(fd, temp_buf, count); + curr += count; + } + if(curr=size) break; + /* write tag to src and skip tag in dst */ + if(i>=0) + { + current.lrcs[i].file_offset = rb->lseek(fd, 0, SEEK_CUR); + time_start = current.lrcs[idx].time_start + current.offset; + if(time_start < 0) time_start = 0; + if(current.type == LRC || current.type == LRC8 || + current.type == TXT) + { + rb->fdprintf(fd, "[%02ld:%02ld.%02ld]", + time_start/60000, time_start%60000/1000, + time_start%1000/10); + } + else if(current.type == SNC) + { + rb->fdprintf(fd, "00%02ld%02ld%02ld", + time_start/60000, time_start%60000/1000, + time_start%1000/10); + curr += rb->read(fe, temp_buf, 8); + } + } + if(current.type == LRC || current.type == LRC8) + { + while(curr++read(fe, temp_buf, 1)==1) + if(temp_buf[0]==']') break; + } + } + rb->close(fe); + rb->close(fd); + return (curr>=size); +} +static bool lrc_editor(void) +{ + struct gui_synclist gui_editor; + bool exit = false, changed = false; + int button, selected; + + if(!current.nlrc) + { + rb->splash(HZ, "No lyrics"); + return false; + } + + rb->gui_synclist_init(&gui_editor, &get_lrc_line, NULL, false, 1, NULL); + rb->gui_synclist_set_nb_items(&gui_editor, current.nlrc); + rb->gui_synclist_set_icon_callback(&gui_editor, get_cursor); + rb->gui_synclist_set_title(&gui_editor, "Lrc Editor", Icon_Menu_functioncall); + rb->gui_synclist_select_item(&gui_editor, 0); + rb->gui_synclist_draw(&gui_editor); + + while (!exit) + { + button = rb->get_action(CONTEXT_TREE, TIMEOUT_BLOCK); + if(rb->gui_synclist_do_button(&gui_editor, &button, + LIST_WRAP_UNLESS_HELD)) + continue; + + switch (button) + { + case ACTION_STD_OK: + selected = rb->gui_synclist_get_sel_pos(&gui_editor); + if(selected >= 0 && selected < current.nlrc) + { + current.lrcs[selected].time_start = + ((current.id3->elapsed - current.offset)/10)*10; + changed = true; + rb->gui_synclist_draw(&gui_editor); + } + break; + case ACTION_TREE_STOP: + case ACTION_STD_CANCEL: + exit = true; + break; + default: + if(rb->default_event_handler(button) == SYS_USB_CONNECTED) + return true; + break; + } + } + if(changed) + { + char temp_path[MAX_PATH]; + bool success = false; + sort_lrcs(); + rb->splash(HZ/2, "Saving changes.."); + if(current.type == TXT) + { + /* save changes to new .lrc file */ + char *p; + rb->strcpy(temp_path, current.lrc_file); + p = rb->strrchr(current.lrc_file, '.')+1; + rb->strcpy(p, extentions[LRC]); + } + else + { + /* create backup */ + rb->snprintf(temp_path, MAX_PATH, "%s~", current.lrc_file); + if(rb->file_exists(temp_path)) + rb->remove(temp_path); + rb->rename(current.lrc_file, temp_path); + } + success = save_changes(temp_path); + if(success) + { + if(current.type == TXT) + current.type = LRC; + } + else + { + if(current.type == TXT) + { + rb->remove(current.lrc_file); + rb->strcpy(current.lrc_file, temp_path); + } + else /* restore backup */ + { + rb->remove(current.lrc_file); + rb->rename(temp_path, current.lrc_file); + } + rb->splash(HZ, "Could not save changes."); + } + } + return false; +} + +/******************************* + * Settings. + *******************************/ +static bool settings_changed = false; +static void load_or_save_settings(bool save) +{ + static const char config_file[] = "lrcplayer.cfg"; + static struct configdata config[] = { +#ifdef HAVE_LCD_COLOR + { TYPE_INT, 0, 0xffffff, { .int_p = &prefs.inactive_color }, + "inactive color", NULL }, +#endif +#ifdef HAVE_LCD_BITMAP + { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wrap }, "wrap", NULL }, + { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wipe }, "wipe", NULL }, + { TYPE_BOOL, 0, 1, { .bool_p = &prefs.active_one_line }, + "active one line", NULL }, + { TYPE_INT, 0, 2, { .int_p = (int *) &prefs.align }, "align", NULL }, +#endif + { TYPE_STRING, 0, sizeof(prefs.lrc_directry), + { .string = prefs.lrc_directry}, "lrc directry", NULL }, + { TYPE_INT, 0, NUM_CODEPAGES, { .int_p = (int *) &prefs.encoding }, + "encoding", NULL }, + }; + + if(!save) + { +#if LCD_DEPTH > 1 + prefs.active_color = rb->lcd_get_foreground(); + prefs.inactive_color = LCD_LIGHTGRAY; +#endif +#ifdef HAVE_LCD_BITMAP + prefs.wrap = true; + prefs.wipe = true; + prefs.active_one_line = false; + prefs.align = ALIGN_CENTER; +#endif + rb->strcpy(prefs.lrc_directry, "/Lyrics"); + prefs.encoding = NUM_CODEPAGES; /* default is `default codepage' */ + + configfile_load(config_file, config, + sizeof(config) / sizeof(config[0]), 0); + } + else if(settings_changed) + { + configfile_save(config_file, config, + sizeof(config) / sizeof(config[0]), 0); + } + settings_changed = false; +} + +/* returns true if USB was inserted, false otherwise */ +static bool lrcplayer_menu(void) +{ + enum { +#ifdef HAVE_LCD_COLOR + MENU_INACTIVE_COLOR, +#endif +#ifdef HAVE_LCD_BITMAP + MENU_WRAP, + MENU_WIPE, + MENU_LINE_MODE, + MENU_ALIGN, +#endif + MENU_LRC_DIR, + MENU_ENCODING, + MENU_OFFSET, + MENU_LRC_EDITOR, + MENU_PLAYBACK, + }; + + MENUITEM_STRINGLIST(menu, "Lrcplayer Menu", NULL, +#ifdef HAVE_LCD_COLOR + "Inactive Color", +#endif +#ifdef HAVE_LCD_BITMAP + "Wrap", "Wipe", "Activate Only Current Line", "Align", +#endif + "Lrc Directry", "Encoding", + "offset (msec)", "Lrc Editor" "Audio Playback",); +#ifdef HAVE_LCD_BITMAP + struct opt_items align_names[] = { + {"Right", -1}, {"Center", -1}, {"Left", -1}, + }; +#endif + struct opt_items cp_names[NUM_CODEPAGES+1]; + unsigned old_val; + int selected = 0, idx; + bool exit = false, usb = false; + + for (idx=0; idx < NUM_CODEPAGES; idx++) + { + cp_names[idx].string = rb->get_codepage_name(idx); + cp_names[idx].voice_id = -1; + } + cp_names[idx].string = "Use default codepage"; + cp_names[idx].voice_id = -1; + + while(!exit && !usb) + { + switch(rb->do_menu(&menu, &selected, NULL, false)) + { +#ifdef HAVE_LCD_COLOR + case MENU_INACTIVE_COLOR: + old_val = prefs.inactive_color; + usb = rb->set_color(NULL, "Inactive Color", + &prefs.inactive_color, prefs.active_color); + settings_changed |= (prefs.inactive_color != old_val); + break; +#endif +#ifdef HAVE_LCD_BITMAP + case MENU_WRAP: + old_val = prefs.wrap; + usb = rb->set_bool("Wrap", &prefs.wrap); + if(prefs.wrap != old_val) + { + settings_changed = true; + calc_pos(0, current.nlrc); + } + break; + case MENU_WIPE: + old_val = prefs.wipe; + usb = rb->set_bool("Wipe", &prefs.wipe); + settings_changed |= (prefs.wipe != old_val); + break; + case MENU_LINE_MODE: + old_val = prefs.active_one_line; + usb = rb->set_bool("Activate Only Current Line", + &prefs.active_one_line); + settings_changed |= (prefs.active_one_line != old_val); + break; + case MENU_ALIGN: + old_val = prefs.align; + usb = rb->set_option("Align", &prefs.align, INT, align_names, + sizeof(align_names) / sizeof(align_names[0]), NULL); + settings_changed |= (prefs.align != old_val); + break; +#endif + case MENU_LRC_DIR: + rb->strcpy(temp_buf, prefs.lrc_directry); + if (!rb->kbd_input(temp_buf, sizeof(prefs.lrc_directry))) + { + settings_changed |= rb->strcmp(prefs.lrc_directry, temp_buf); + if(temp_buf[0] == '/') + rb->strcpy(prefs.lrc_directry, temp_buf); + else + rb->snprintf(prefs.lrc_directry, + sizeof(prefs.lrc_directry), "/%s", temp_buf); + } + break; + case MENU_ENCODING: + old_val = prefs.encoding; + usb = rb->set_option("Encoding", &prefs.encoding, INT, cp_names, + sizeof(cp_names) / sizeof(cp_names[0]), NULL); + if(prefs.encoding != old_val) + { + settings_changed = true; + /* let reload lrc file to apply encoding setting */ + reset_current_data(); + } + break; + case MENU_PLAYBACK: + return playback_control(NULL); + break; + case MENU_OFFSET: + old_val = (unsigned)current.offset; + rb->snprintf(temp_buf, sizeof(temp_buf), "%ld", current.offset); + if (!rb->kbd_input(temp_buf, 10)) + { + current.offset = rb->atoi(temp_buf); + settings_changed |= (current.offset != (signed)old_val); + } + break; + case MENU_LRC_EDITOR: + usb = lrc_editor(); + break; + case GO_TO_PREVIOUS: + exit = true; + break; + case MENU_ATTACHED_USB: + usb = true; + break; + } + } + return usb; +} + +/******************************* + * Main. + *******************************/ +static void ff_rewind(long time, bool resume) +{ + int audio_status = rb->audio_status(); + if(audio_status & AUDIO_STATUS_PLAY) + { + if(!(audio_status & AUDIO_STATUS_PAUSE)) + { + resume = true; + rb->audio_pause(); + } + rb->audio_ff_rewind(time); + rb->sleep(HZ/10); /* take affect seeking */ + if(resume) + rb->audio_resume(); + } +} + +/* this is the plugin entry point */ +enum plugin_status plugin_start(const void* parameter) +{ + int button=0; + bool found_lrc_file = false; + long tryagain_tick = 0; + long ff_rewind_time = -1, elapsed, length; + int temp, step = 0, audio_status; + bool update_display_state = true; + bool exit = false, usb = false; + +#ifdef HAVE_LCD_BITMAP + font_ui_height = rb->font_get(FONT_UI)->height; +#endif + FOR_NB_SCREENS(temp) + { + rb->viewport_set_defaults(&vp_lyrics[temp], temp); +#ifdef HAVE_LCD_BITMAP + /* title + time + progress bar */ + vp_lyrics[temp].y += font_ui_height + SYSFONT_HEIGHT*2; + vp_lyrics[temp].height -= font_ui_height + SYSFONT_HEIGHT*2; +#else + vp_lyrics[temp].y += 1; /* title & time */ + vp_lyrics[temp].height -= 1; +#endif + } + + /* call this function first to initialize settings. */ + load_or_save_settings(false); + + audio_status = rb->audio_status(); + if (!parameter) + { + current.id3 = NULL; + /* search and load lrc later */ + tryagain_tick = *rb->current_tick+HZ/10; + } + else + { + const char *ext; + tryagain_tick = 0; + if(!(audio_status&AUDIO_STATUS_PLAY)) + { + rb->splash(HZ, "Audio stopped"); + return PLUGIN_ERROR; + } + current.id3 = rb->audio_current_track(); + if(current.id3 == NULL || current.id3->path[0] == 0) + current.mp3_file[0] = 0; + else + rb->strncpy(current.mp3_file, current.id3->path, MAX_PATH); + /* use passed parameter as lrc file. */ + rb->strncpy(current.lrc_file, parameter, MAX_PATH); + current.lrc_file[MAX_PATH-1] = 0; + if(!rb->file_exists(current.lrc_file)) + { + rb->splash(HZ, "Specified file dose not exist."); + return PLUGIN_ERROR; + } + found_lrc_file = true; + + ext = rb->strrchr(current.lrc_file, '.'); + if(ext == NULL) + { + ext = rb->strrchr(current.lrc_file, '/')+1; + rb->splashf(HZ, "%s: Not supported file type", ext); + return PLUGIN_ERROR; + } + ext++; + for(current.type=0; current.typestrcmp(ext, extentions[current.type])) + break; + } + if(current.type==NUM_TYPES) + { + rb->splashf(HZ, "%s: Not supported file type", ext); + return PLUGIN_ERROR; + } + } + + /* backlight condition: + * force on while displaying lyrics. + * use setting for menu, editor. + * turn on if music is changed. */ + while (!exit && !usb) + { + if(rb->audio_has_changed_track() || + (tryagain_tick && TIME_AFTER(*rb->current_tick, tryagain_tick))) + { + if(!tryagain_tick) + rb->backlight_on(); + current.id3 = rb->audio_current_track(); + if(current.id3 == NULL || current.id3->path[0] == 0) + { + /* id3 does not seem to be valid. try again later. */ + tryagain_tick = *rb->current_tick+HZ; + } + else + { + tryagain_tick = 0; + update_display_state = true; + found_lrc_file = find_lrc_file(); + if(!found_lrc_file) + { + /* no lyrics file found. try to read from id3 tag. */ + found_lrc_file = read_id3(); + } + } + } + if(found_lrc_file && !current.loaded_lrc) + { + load_lrc_file(); + } + + if(ff_rewind_time == -1) + audio_status = rb->audio_status(); + if(current.id3 == NULL) + { + elapsed = 0; + length = 1; + } + else + { + elapsed = current.id3->elapsed; + length = current.id3->length; + } + if(ff_rewind_time > -1) + elapsed = ff_rewind_time; + if(update_display_state) + { + if(!(audio_status&AUDIO_STATUS_PLAY) || + !found_lrc_file || !current.nlrc) + backlight_use_settings(); + else + backlight_force_on(); + display_title_artist(); + update_display_state = false; + } + if((audio_status & AUDIO_STATUS_PLAY)) + { + display_progressbar(length, elapsed); + display_lrcs(elapsed, length); + } + + button = rb->get_action(CONTEXT_WPS, HZ/16); + switch(button) + { + case ACTION_WPS_BROWSE: + case ACTION_WPS_STOP: + exit = true; + break; + case ACTION_WPS_PLAY: + if (!audio_status && rb->global_status->resume_index != -1) + { + if (rb->playlist_resume() != -1) + { + rb->playlist_start(rb->global_status->resume_index, + rb->global_status->resume_offset); + } + } + else if (audio_status & AUDIO_STATUS_PAUSE) + rb->audio_resume(); + else + rb->audio_pause(); + break; + case ACTION_WPS_SEEKFWD: + case ACTION_WPS_SEEKBACK: + if(current.id3 == NULL || !(audio_status & AUDIO_STATUS_PLAY)) + break; + if(ff_rewind_time > -1) + { + if (button == ACTION_WPS_SEEKFWD) + /* fast forwarding, calc max step relative to end */ + temp = (length - ff_rewind_time) * 3 / 100; + else + /* rewinding, calc max step relative to start */ + temp = (ff_rewind_time) * 3 / 100; + temp = MAX(temp, 500); + + if (step > temp) + step = temp; + + if (button == ACTION_WPS_SEEKFWD) + ff_rewind_time += step; + else + ff_rewind_time -= step; + + if (ff_rewind_time > length) + ff_rewind_time = length; + if (ff_rewind_time < 0) + ff_rewind_time = 0; + + /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */ + step += step >> (rb->global_settings->ff_rewind_accel + 3); + } + else + { + ff_rewind_time = elapsed; + if(!(audio_status & AUDIO_STATUS_PAUSE)) + rb->audio_pause(); + step = 1000 * rb->global_settings->ff_rewind_min_step; + } + break; + case ACTION_WPS_STOPSEEK: + if(ff_rewind_time == -1) + break; + ff_rewind(ff_rewind_time, !(audio_status & AUDIO_STATUS_PAUSE)); + ff_rewind_time = -1; + break; + case ACTION_WPS_SKIPNEXT: + rb->audio_next(); + break; + case ACTION_WPS_SKIPPREV: + if(elapsed < 3000) + rb->audio_prev(); + else + ff_rewind(0, false); + break; + case ACTION_WPS_VOLDOWN: + temp = rb->sound_min(SOUND_VOLUME); + if(--rb->global_settings->volume < temp) + rb->global_settings->volume = temp; + rb->sound_set(SOUND_VOLUME, rb->global_settings->volume); + break; + case ACTION_WPS_VOLUP: + temp = rb->sound_max(SOUND_VOLUME); + if(++rb->global_settings->volume > temp) + rb->global_settings->volume = temp; + rb->sound_set(SOUND_VOLUME, rb->global_settings->volume); + break; + case ACTION_WPS_CONTEXT: + backlight_use_settings(); + usb = lrc_editor(); + update_display_state = true; + break; + case ACTION_WPS_MENU: + backlight_use_settings(); + usb = lrcplayer_menu(); + update_display_state = true; + break; + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + usb = true; + break; + } + } + + rb->lcd_stop_scroll(); + backlight_use_settings(); + load_or_save_settings(true); + return usb? PLUGIN_USB_CONNECTED: PLUGIN_OK; +}