diff options
| -rw-r--r-- | apps/applimits.h | 1 | ||||
| -rw-r--r-- | apps/lang/english.lang | 90 | ||||
| -rw-r--r-- | apps/main_menu.c | 3 | ||||
| -rw-r--r-- | apps/onplay.c | 116 | ||||
| -rw-r--r-- | apps/onplay.h | 6 | ||||
| -rw-r--r-- | apps/playlist.c | 2039 | ||||
| -rw-r--r-- | apps/playlist.h | 69 | ||||
| -rw-r--r-- | apps/playlist_menu.c | 71 | ||||
| -rw-r--r-- | apps/playlist_menu.h | 24 | ||||
| -rw-r--r-- | apps/screens.c | 4 | ||||
| -rw-r--r-- | apps/settings.c | 51 | ||||
| -rw-r--r-- | apps/settings.h | 10 | ||||
| -rw-r--r-- | apps/settings_menu.c | 4 | ||||
| -rw-r--r-- | apps/tree.c | 477 | ||||
| -rw-r--r-- | apps/tree.h | 11 | ||||
| -rw-r--r-- | apps/wps-display.c | 7 | ||||
| -rw-r--r-- | apps/wps.c | 13 | ||||
| -rw-r--r-- | firmware/common/file.c | 2 | ||||
| -rw-r--r-- | uisimulator/win32/Makefile | 5 | ||||
| -rw-r--r-- | uisimulator/win32/file.h | 2 | ||||
| -rw-r--r-- | uisimulator/win32/kernel.c | 12 | ||||
| -rw-r--r-- | uisimulator/win32/rockbox.dsp | 8 | ||||
| -rw-r--r-- | uisimulator/x11/Makefile | 5 | ||||
| -rw-r--r-- | uisimulator/x11/kernel.h | 3 |
24 files changed, 2169 insertions, 864 deletions
diff --git a/apps/applimits.h b/apps/applimits.h index 6372dac..a18544e 100644 --- a/apps/applimits.h +++ b/apps/applimits.h @@ -23,6 +23,5 @@ #define AVERAGE_FILENAME_LENGTH 40 #define MAX_DIR_LEVELS 10 #define MAX_PLAYLIST_SIZE 10000 -#define MAX_QUEUED_FILES 100 #endif diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 57bec0c..dab8fa6 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -1632,3 +1632,93 @@ id: LANG_STAR desc: in the games menu eng: "Star" new: + +id: LANG_QUEUE_LAST +desc: in onplay menu. queue a track/playlist at end of playlist. +eng: "Queue last" +new: + +id: LANG_INSERT +desc: in onplay menu. insert a track/playlist into dynamic playlist. +eng: "Insert" +new: + +id: LANG_INSERT_LAST +desc: in onplay menu. append a track/playlist into dynamic playlist. +eng: "Insert last" +new: + +id: LANG_QUEUE_FIRST +desc: in onplay menu. queue a track/playlist into dynamic playlist. +eng: "Queue first" +new: + +id: LANG_INSERT_FIRST +desc: in onplay menu. insert a track/playlist into dynamic playlist. +eng: "Insert first" +new: + +id: LANG_SAVE_DYNAMIC_PLAYLIST +desc: in playlist menu. +eng: "Save Dynamic Playlist" +new: + +id: LANG_PLAYLIST_MENU +desc: in main menu. +eng: "Playlist Options" +new: + +id: LANG_PLAYLIST_INSERT_COUNT +desc: splash number of tracks inserted +eng: "Inserted %d tracks (%s)" +new: + +id: LANG_PLAYLIST_QUEUE_COUNT +desc: splash number of tracks queued +eng: "Queued %d tracks (%s)" +new: + +id: LANG_PLAYLIST_SAVE_COUNT +desc: splash number of tracks saved +eng: "Saved %d tracks (%s)" +new: + +id: LANG_OFF_ABORT +desc: Used on recorder models +eng: "OFF to abort" +new: + +id: LANG_STOP_ABORT +desc: Used on player models +eng: "STOP to abort" +new: + +id: LANG_PLAYLIST_CONTROL_UPDATE_ERROR +desc: Playlist error +eng: "Error updating playlist control file" +new: + +id: LANG_PLAYLIST_ACCESS_ERROR +desc: Playlist error +eng: "Error accessing playlist file" +new: + +id: LANG_PLAYLIST_CONTROL_ACCESS_ERROR +desc: Playlist error +eng: "Error accessing playlist control file" +new: + +id: LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR +desc: Playlist error +eng: "Error accessing directory" +new: + +id: LANG_PLAYLIST_CONTROL_INVALID +desc: Playlist resume error +eng: "Playlist control file is invalid" +new: + +id: LANG_RECURSE_DIRECTORY +desc: In playlist menu +eng: "Recursively Insert Directories" +new: diff --git a/apps/main_menu.c b/apps/main_menu.c index b4d952e..e356603 100644 --- a/apps/main_menu.c +++ b/apps/main_menu.c @@ -43,6 +43,7 @@ #include "wps.h" #include "buffer.h" #include "screens.h" +#include "playlist_menu.h" #ifdef HAVE_FMRADIO #include "radio.h" #endif @@ -266,7 +267,7 @@ bool main_menu(void) { str(LANG_RECORDING), recording_screen }, { str(LANG_RECORDING_SETTINGS), recording_menu }, #endif - { str(LANG_CREATE_PLAYLIST), create_playlist }, + { str(LANG_PLAYLIST_MENU), playlist_menu }, { str(LANG_MENU_SHOW_ID3_INFO), browse_id3 }, { str(LANG_SLEEP_TIMER), sleeptimer_screen }, #ifdef HAVE_ALARM_MOD diff --git a/apps/onplay.c b/apps/onplay.c index 3e0f5a3..6c538f8 100644 --- a/apps/onplay.c +++ b/apps/onplay.c @@ -38,13 +38,103 @@ #include "screens.h" #include "tree.h" #include "buffer.h" +#include "settings.h" +#include "status.h" +#include "onplay.h" static char* selected_file = NULL; -static bool reload_dir = false; +static int selected_file_attr = 0; +static int onplay_result = ONPLAY_OK; -static bool queue_file(void) +/* For playlist options */ +struct playlist_args { + int position; + bool queue; +}; + +static bool add_to_playlist(int position, bool queue) { - queue_add(selected_file); + bool new_playlist = !(mpeg_status() & MPEG_STATUS_PLAY); + + if (new_playlist) + playlist_create(NULL, NULL); + + if (selected_file_attr & TREE_ATTR_MPA) + playlist_insert_track(selected_file, position, queue); + else if (selected_file_attr & ATTR_DIRECTORY) + playlist_insert_directory(selected_file, position, queue); + else if (selected_file_attr & TREE_ATTR_M3U) + playlist_insert_playlist(selected_file, position, queue); + + if (new_playlist && (playlist_amount() > 0)) + { + /* nothing is currently playing so begin playing what we just + inserted */ + if (global_settings.playlist_shuffle) + playlist_shuffle(current_tick, -1); + playlist_start(0,0); + status_set_playmode(STATUS_PLAY); + status_draw(false); + onplay_result = ONPLAY_START_PLAY; + } + + return false; +} + +/* Sub-menu for playlist options */ +static bool playlist_options(void) +{ + struct menu_items menu[6]; + struct playlist_args args[6]; /* increase these 2 if you add entries! */ + int m, i=0, result; + + if (mpeg_status() & MPEG_STATUS_PLAY) + { + menu[i].desc = str(LANG_INSERT); + args[i].position = PLAYLIST_INSERT; + args[i].queue = false; + i++; + + menu[i].desc = str(LANG_INSERT_FIRST); + args[i].position = PLAYLIST_INSERT_FIRST; + args[i].queue = false; + i++; + + menu[i].desc = str(LANG_INSERT_LAST); + args[i].position = PLAYLIST_INSERT_LAST; + args[i].queue = false; + i++; + + menu[i].desc = str(LANG_QUEUE); + args[i].position = PLAYLIST_INSERT; + args[i].queue = true; + i++; + + menu[i].desc = str(LANG_QUEUE_FIRST); + args[i].position = PLAYLIST_INSERT_FIRST; + args[i].queue = true; + i++; + + menu[i].desc = str(LANG_QUEUE_LAST); + args[i].position = PLAYLIST_INSERT_LAST; + args[i].queue = true; + i++; + } + else if ((selected_file_attr & TREE_ATTR_MPA) || + (selected_file_attr & ATTR_DIRECTORY)) + { + menu[i].desc = str(LANG_INSERT); + args[i].position = PLAYLIST_INSERT; + args[i].queue = false; + i++; + } + + m = menu_init( menu, i ); + result = menu_show(m); + if (result >= 0) + add_to_playlist(args[result].position, args[result].queue); + menu_exit(m); + return false; } @@ -68,7 +158,7 @@ static bool delete_file(void) switch (btn) { case BUTTON_PLAY: if (!remove(selected_file)) { - reload_dir = true; + onplay_result = ONPLAY_RELOAD_DIR; lcd_clear_display(); lcd_puts(0,0,str(LANG_DELETED)); lcd_puts_scroll(0,1,selected_file); @@ -104,7 +194,7 @@ static bool rename_file(void) sleep(HZ*2); } else - reload_dir = true; + onplay_result = ONPLAY_RELOAD_DIR; } return false; @@ -225,7 +315,7 @@ static bool vbr_fix(void) if(mpeg_status()) { splash(HZ*2, 0, true, str(LANG_VBRFIX_STOP_PLAY)); - return reload_dir; + return onplay_result; } lcd_clear_display(); @@ -356,12 +446,16 @@ int onplay(char* file, int attr) struct menu_items menu[5]; /* increase this if you add entries! */ int m, i=0, result; + onplay_result = ONPLAY_OK; + selected_file = file; - - if ((mpeg_status() & MPEG_STATUS_PLAY) && (attr & TREE_ATTR_MPA)) + selected_file_attr = attr; + + if ((attr & TREE_ATTR_MPA) || (attr & ATTR_DIRECTORY) || + (attr & TREE_ATTR_M3U)) { - menu[i].desc = str(LANG_QUEUE); - menu[i].function = queue_file; + menu[i].desc = str(LANG_PLAYINDICES_PLAYLIST); + menu[i].function = playlist_options; i++; } @@ -390,5 +484,5 @@ int onplay(char* file, int attr) menu[result].function(); menu_exit(m); - return reload_dir; + return onplay_result; } diff --git a/apps/onplay.h b/apps/onplay.h index 8540aaf..7b47479 100644 --- a/apps/onplay.h +++ b/apps/onplay.h @@ -21,4 +21,10 @@ int onplay(char* file, int attr); +enum { + ONPLAY_OK, + ONPLAY_RELOAD_DIR, + ONPLAY_START_PLAY +}; + #endif diff --git a/apps/playlist.c b/apps/playlist.c index 726e8a9..05149d1 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -17,11 +17,58 @@ * ****************************************************************************/ +/* + Dynamic playlist design (based on design originally proposed by ricII) + + There are two files associated with a dynamic playlist: + 1. Playlist file : This file contains the initial songs in the playlist. + The file is created by the user and stored on the hard + drive. NOTE: If we are playing the contents of a + directory, there will be no playlist file. + 2. Control file : This file is automatically created when a playlist is + started and contains all the commands done to it. + + The first non-comment line in a control file must begin with + "P:VERSION:DIR:FILE" where VERSION is the playlist control file version, + DIR is the directory where the playlist is located and FILE is the + playlist filename. For dirplay, FILE will be empty. An empty playlist + will have both entries as null. + + Control file commands: + a. Add track (A:<position>:<last position>:<path to track>) + - Insert a track at the specified position in the current + playlist. Last position is used to specify where last insertion + occurred. + b. Queue track (Q:<position>:<last position>:<path to track>) + - Queue a track at the specified position in the current + playlist. Queued tracks differ from added tracks in that they + are deleted from the playlist as soon as they are played and + they are not saved to disk as part of the playlist. + c. Delete track (D:<position>) + - Delete track from specified position in the current playlist. + d. Shuffle playlist (S:<seed>:<index>) + - Shuffle entire playlist with specified seed. The index + identifies the first index in the newly shuffled playlist + (needed for repeat mode). + e. Unshuffle playlist (U:<index>) + - Unshuffle entire playlist. The index identifies the first index + in the newly unshuffled playlist. + f. Reset last insert position (R) + - Needed so that insertions work properly after resume + + Resume: + The only resume info that needs to be saved is the current index in the + playlist and the position in the track. When resuming, all the commands + in the control file will be reapplied so that the playlist indices are + exactly the same as before shutdown. + */ + #include <stdio.h> #include <stdlib.h> #include <string.h> #include "playlist.h" #include "file.h" +#include "dir.h" #include "sprintf.h" #include "debug.h" #include "mpeg.h" @@ -32,6 +79,10 @@ #include "applimits.h" #include "screens.h" #include "buffer.h" +#include "atoi.h" +#include "misc.h" +#include "button.h" +#include "tree.h" #ifdef HAVE_LCD_BITMAP #include "icons.h" #include "widgets.h" @@ -41,526 +92,825 @@ static struct playlist_info playlist; -#define QUEUE_FILE ROCKBOX_DIR "/.queue_file" +#define PLAYLIST_CONTROL_FILE ROCKBOX_DIR "/.playlist_control" +#define PLAYLIST_CONTROL_FILE_VERSION 1 + +/* + Each playlist index has a flag associated with it which identifies what + type of track it is. These flags are stored in the 3 high order bits of + the index. + + NOTE: This limits the playlist file size to a max of 512K. + + Bits 31-30: + 00 = Playlist track + 01 = Track was prepended into playlist + 10 = Track was inserted into playlist + 11 = Track was appended into playlist + Bit 29: + 0 = Added track + 1 = Queued track + */ +#define PLAYLIST_SEEK_MASK 0x1FFFFFFF +#define PLAYLIST_INSERT_TYPE_MASK 0xC0000000 +#define PLAYLIST_QUEUE_MASK 0x20000000 + +#define PLAYLIST_INSERT_TYPE_PREPEND 0x40000000 +#define PLAYLIST_INSERT_TYPE_INSERT 0x80000000 +#define PLAYLIST_INSERT_TYPE_APPEND 0xC0000000 -static unsigned char *playlist_buffer; +#define PLAYLIST_QUEUED 0x20000000 -static int playlist_end_pos = 0; +#define PLAYLIST_DISPLAY_COUNT 10 static char now_playing[MAX_PATH+1]; -void playlist_init(void) -{ - playlist.fd = -1; - playlist.max_playlist_size = global_settings.max_files_in_playlist; - playlist.indices = buffer_alloc(playlist.max_playlist_size * sizeof(int)); - playlist.buffer_size = - AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir; - playlist_buffer = buffer_alloc(playlist.buffer_size); -} +static void empty_playlist(bool resume); +static void update_playlist_filename(char *dir, char *file); +static int add_indices_to_playlist(void); +static int add_track_to_playlist(char *filename, int position, bool queue, + int seek_pos); +static int add_directory_to_playlist(char *dirname, int *position, bool queue, + int *count); +static int remove_track_from_playlist(int position, bool write); +static int randomise_playlist(unsigned int seed, bool start_current, + bool write); +static int sort_playlist(bool start_current, bool write); +static int get_next_index(int steps); +static void find_and_set_playlist_index(unsigned int seek); +static int compare(const void* p1, const void* p2); +static int get_filename(int seek, bool control_file, char *buf, + int buf_length); +static int format_track_path(char *dest, char *src, int buf_length, int max, + char *dir); +static void display_playlist_count(int count, char *fmt); +static void display_buffer_full(void); /* * remove any files and indices associated with the playlist */ -static void empty_playlist(bool queue_resume) +static void empty_playlist(bool resume) { - int fd; - playlist.filename[0] = '\0'; + if(-1 != playlist.fd) /* If there is an already open playlist, close it. */ close(playlist.fd); playlist.fd = -1; + + if(-1 != playlist.control_fd) + close(playlist.control_fd); + playlist.control_fd = -1; + + playlist.in_ram = false; + playlist.buffer[0] = 0; + playlist.buffer_end_pos = 0; + playlist.index = 0; - playlist.queue_index = 0; - playlist.last_queue_index = 0; + playlist.first_index = 0; playlist.amount = 0; - playlist.num_queued = 0; - playlist.start_queue = 0; + playlist.last_insert_pos = -1; - if (!queue_resume) + if (!resume) { - /* start with fresh queue file when starting new playlist */ - remove(QUEUE_FILE); - fd = creat(QUEUE_FILE, 0); - if (fd > 0) + int fd; + + /* start with fresh playlist control file when starting new + playlist */ + fd = creat(PLAYLIST_CONTROL_FILE, 0000200); + if (fd >= 0) close(fd); } } -/* update queue list after resume */ -static void add_indices_to_queuelist(int seek) +/* + * store directory and name of playlist file + */ +static void update_playlist_filename(char *dir, char *file) { - int nread; - int fd = -1; - int i = seek; - int count = 0; + char *sep=""; + int dirlen = strlen(dir); + + /* If the dir does not end in trailing slash, we use a separator. + Otherwise we don't. */ + if('/' != dir[dirlen-1]) + { + sep="/"; + dirlen++; + } + + playlist.dirlen = dirlen; + + snprintf(playlist.filename, sizeof(playlist.filename), + "%s%s%s", + dir, sep, file); +} + +/* + * calculate track offsets within a playlist file + */ +static int add_indices_to_playlist(void) +{ + unsigned int nread; + unsigned int i = 0; + unsigned int count = 0; + int buflen; bool store_index; - char buf[MAX_PATH]; + char *buffer; + unsigned char *p; - unsigned char *p = buf; + if(-1 == playlist.fd) + playlist.fd = open(playlist.filename, O_RDONLY); + if(-1 == playlist.fd) + return -1; /* failure */ + +#ifdef HAVE_LCD_BITMAP + if(global_settings.statusbar) + lcd_setmargins(0, STATUSBAR_HEIGHT); + else + lcd_setmargins(0, 0); +#endif - fd = open(QUEUE_FILE, O_RDONLY); - if(fd < 0) - return; + splash(0, 0, true, str(LANG_PLAYLIST_LOAD)); - nread = lseek(fd, seek, SEEK_SET); - if (nread < 0) - return; + /* use mp3 buffer for maximum load speed */ + buflen = (mp3end - mp3buf); + buffer = mp3buf; store_index = true; + mpeg_stop(); + while(1) { - nread = read(fd, buf, MAX_PATH); + nread = read(playlist.fd, buffer, buflen); + /* Terminate on EOF */ if(nread <= 0) break; - - p = buf; - for(count=0; count < nread; count++,p++) { - if(*p == '\n') + p = buffer; + + for(count=0; count < nread; count++,p++) { + + /* Are we on a new line? */ + if((*p == '\n') || (*p == '\r')) + { store_index = true; + } else if(store_index) { store_index = false; - - playlist.queue_indices[playlist.last_queue_index] = i+count; - playlist.last_queue_index = - (playlist.last_queue_index + 1) % MAX_QUEUED_FILES; - playlist.num_queued++; + + if(*p != '#') + { + /* Store a new entry */ + playlist.indices[ playlist.amount ] = i+count; + playlist.amount++; + if ( playlist.amount >= playlist.max_playlist_size ) { + display_buffer_full(); + return -1; + } + } } } - - i += count; + + i+= count; } + + return 0; } -static int get_next_index(int steps, bool *queue) +/* + * Add track to playlist at specified position. There are three special + * positions that can be specified: + * PLAYLIST_PREPEND - Add track at beginning of playlist + * PLAYLIST_INSERT - Add track after current song. NOTE: If there + * are already inserted tracks then track is added + * to the end of the insertion list. + * PLAYLIST_INSERT_FIRST - Add track immediately after current song, no + * matter what other tracks have been inserted. + * PLAYLIST_INSERT_LAST - Add track to end of playlist + */ +static int add_track_to_playlist(char *filename, int position, bool queue, + int seek_pos) { - int current_index = playlist.index; - int next_index = -1; + int insert_position = position; + unsigned int flags = PLAYLIST_INSERT_TYPE_INSERT; + int i; - if (global_settings.repeat_mode == REPEAT_ONE) + if (playlist.amount >= playlist.max_playlist_size) { - /* this is needed for repeat one to work with queue mode */ - steps = 0; + display_buffer_full(); + return -1; } - else if (steps >= 0) + + switch (position) { - /* Queue index starts from 0 which needs to be accounted for. Also, - after resume, this handles case where we want to begin with - playlist */ - steps -= playlist.start_queue; + case PLAYLIST_PREPEND: + insert_position = playlist.first_index; + flags = PLAYLIST_INSERT_TYPE_PREPEND; + break; + case PLAYLIST_INSERT: + /* if there are already inserted tracks then add track to end of + insertion list else add after current playing track */ + if (playlist.last_insert_pos >= 0 && + playlist.last_insert_pos < playlist.amount && + (playlist.indices[playlist.last_insert_pos]& + PLAYLIST_INSERT_TYPE_MASK) == PLAYLIST_INSERT_TYPE_INSERT) + position = insert_position = playlist.last_insert_pos+1; + else if (playlist.amount > 0) + position = insert_position = playlist.index + 1; + else + position = insert_position = 0; + + playlist.last_insert_pos = position; + break; + case PLAYLIST_INSERT_FIRST: + if (playlist.amount > 0) + position = insert_position = playlist.index + 1; + else + position = insert_position = 0; + + if (playlist.last_insert_pos < 0) + playlist.last_insert_pos = position; + break; + case PLAYLIST_INSERT_LAST: + if (playlist.first_index > 0) + insert_position = playlist.first_index; + else + insert_position = playlist.amount; + + flags = PLAYLIST_INSERT_TYPE_APPEND; + break; } + + if (queue) + flags |= PLAYLIST_QUEUED; - if (steps >= 0 && playlist.num_queued > 0 && - playlist.num_queued - steps > 0) - *queue = true; - else + /* shift indices so that track can be added */ + for (i=playlist.amount; i>insert_position; i--) + playlist.indices[i] = playlist.indices[i-1]; + + /* update stored indices if needed */ + if (playlist.amount > 0 && insert_position <= playlist.index) + playlist.index++; + + if (playlist.amount > 0 && insert_position <= playlist.first_index && + position != PLAYLIST_PREPEND) + playlist.first_index++; + + if (insert_position < playlist.last_insert_pos || + (insert_position == playlist.last_insert_pos && position < 0)) + playlist.last_insert_pos++; + + if (seek_pos < 0 && playlist.control_fd >= 0) { - *queue = false; - if (playlist.num_queued) + int result = -1; + + mutex_lock(&playlist.control_mutex); + + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) { - if (steps >= 0) - { - /* skip the queued tracks */ - steps -= (playlist.num_queued - 1); - } - else if (!playlist.start_queue) + if (fprintf(playlist.control_fd, "%c:%d:%d:", (queue?'Q':'A'), + position, playlist.last_insert_pos) > 0) { - /* previous from queue list needs to go to current track in - playlist */ - steps += 1; + /* save the position in file where track name is written */ + seek_pos = lseek(playlist.control_fd, 0, SEEK_CUR); + + if (fprintf(playlist.control_fd, "%s\n", filename) > 0) + result = 0; } } + + mutex_unlock(&playlist.control_mutex); + + if (result < 0) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return result; + } } - switch (global_settings.repeat_mode) + playlist.indices[insert_position] = flags | seek_pos; + + playlist.amount++; + + return insert_position; +} + +/* + * Insert directory into playlist. May be called recursively. + */ +static int add_directory_to_playlist(char *dirname, int *position, bool queue, + int *count) +{ + char buf[MAX_PATH+1]; + char *count_str; + int result = 0; + int num_files = 0; + bool buffer_full = false; + int i; + struct entry *files; + + /* use the tree browser dircache to load files */ + files = load_and_sort_directory(dirname, SHOW_ALL, &num_files, + &buffer_full); + + if(!files) { - case REPEAT_OFF: - if (*queue) - next_index = (playlist.queue_index+steps) % MAX_QUEUED_FILES; - else - { - if (current_index < playlist.first_index) - current_index += playlist.amount; - current_index -= playlist.first_index; - - next_index = current_index+steps; - if ((next_index < 0) || (next_index >= playlist.amount)) - next_index = -1; - else - next_index = (next_index+playlist.first_index) % - playlist.amount; - } + splash(HZ*2, 0, true, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); + return 0; + } + + /* we've overwritten the dircache so tree browser will need to be + reloaded */ + reload_directory(); + + if (queue) + count_str = str(LANG_PLAYLIST_QUEUE_COUNT); + else + count_str = str(LANG_PLAYLIST_INSERT_COUNT); + + for (i=0; i<num_files; i++) + { + /* user abort */ +#ifdef HAVE_PLAYER_KEYPAD + if (button_get(false) == BUTTON_STOP) +#else + if (button_get(false) == BUTTON_OFF) +#endif + { + result = -1; break; + } - case REPEAT_ONE: - /* if we are still in playlist when repeat one is set, don't go to - queue list */ - if (*queue && !playlist.start_queue) - next_index = playlist.queue_index; + if (files[i].attr & ATTR_DIRECTORY) + { + if (global_settings.recursive_dir_insert) + { + /* recursively add directories */ + snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name); + result = add_directory_to_playlist(buf, position, queue, + count); + if (result < 0) + break; + + /* we now need to reload our current directory */ + files = load_and_sort_directory(dirname, SHOW_ALL, &num_files, + &buffer_full); + if (!files) + { + result = -1; + break; + } + } else + continue; + } + else if (files[i].attr & TREE_ATTR_MPA) + { + int insert_pos; + + snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name); + + insert_pos = add_track_to_playlist(buf, *position, queue, -1); + if (insert_pos < 0) { - next_index = current_index; - *queue = false; + result = -1; + break; } - break; - case REPEAT_ALL: - default: - if (*queue) - next_index = (playlist.queue_index+steps) % MAX_QUEUED_FILES; - else + (*count)++; + + /* Make sure tracks are inserted in correct order if user requests + INSERT_FIRST */ + if (*position == PLAYLIST_INSERT_FIRST || *position >= 0) + *position = insert_pos + 1; + + if ((*count%PLAYLIST_DISPLAY_COUNT) == 0) { - next_index = (current_index+steps) % playlist.amount; - while (next_index < 0) - next_index += playlist.amount; + display_playlist_count(*count, count_str); + + if (*count == PLAYLIST_DISPLAY_COUNT) + mpeg_flush_and_reload_tracks(); } - break; + + /* let the other threads work */ + yield(); + } } - return next_index; + return result; } -int playlist_amount(void) +/* + * remove track at specified position + */ +static int remove_track_from_playlist(int position, bool write) { - return playlist.amount + playlist.num_queued; -} + int i; -int playlist_first_index(void) -{ - return playlist.first_index; -} + if (playlist.amount <= 0) + return -1; -/* Get resume info for current playing song. If return value is -1 then - settings shouldn't be saved (eg. when playing queued song and save queue - disabled) */ -int playlist_get_resume_info(int *resume_index, int *queue_resume, - int *queue_resume_index) -{ - int result = 0; + /* shift indices now that track has been removed */ + for (i=position; i<playlist.amount; i++) + playlist.indices[i] = playlist.indices[i+1]; - *resume_index = playlist.index; + playlist.amount--; + + /* update stored indices if needed */ + if (position < playlist.index) + playlist.index--; + + if (position < playlist.first_index) + playlist.first_index--; - if (playlist.num_queued > 0) + if (position <= playlist.last_insert_pos) + playlist.last_insert_pos--; + + if (write && playlist.control_fd >= 0) { - if (global_settings.save_queue_resume) + int result = -1; + + mutex_lock(&playlist.control_mutex); + + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) { - *queue_resume_index = - playlist.queue_indices[playlist.queue_index]; - /* have we started playing the queue list yet? */ - if (playlist.start_queue) - *queue_resume = QUEUE_BEGIN_PLAYLIST; - else - *queue_resume = QUEUE_BEGIN_QUEUE; + if (fprintf(playlist.control_fd, "D:%d\n", position) > 0) + { + fsync(playlist.control_fd); + result = 0; + } } - else if (!playlist.start_queue) + + mutex_unlock(&playlist.control_mutex); + + if (result < 0) { - *queue_resume = QUEUE_OFF; - result = -1; + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return result; } } - else - *queue_resume = QUEUE_OFF; - return result; + return 0; } -char *playlist_name(char *buf, int buf_size) +/* + * randomly rearrange the array of indices for the playlist. If start_current + * is true then update the index to the new index of the current playing track + */ +static int randomise_playlist(unsigned int seed, bool start_current, bool write) { - char *sep; + int count; + int candidate; + int store; + unsigned int current = playlist.indices[playlist.index]; + + /* seed with the given seed */ + srand(seed); - snprintf(buf, buf_size, "%s", playlist.filename+playlist.dirlen); + /* randomise entire indices list */ + for(count = playlist.amount - 1; count >= 0; count--) + { + /* the rand is from 0 to RAND_MAX, so adjust to our value range */ + candidate = rand() % (count + 1); - if (0 == buf[0]) - return NULL; + /* now swap the values at the 'count' and 'candidate' positions */ + store = playlist.indices[candidate]; + playlist.indices[candidate] = playlist.indices[count]; + playlist.indices[count] = store; + } - /* Remove extension */ - sep = strrchr(buf, '.'); - if (NULL != sep) - *sep = 0; - - return buf; -} + if (start_current) + find_and_set_playlist_index(current); -void playlist_clear(void) -{ - playlist_end_pos = 0; - playlist_buffer[0] = 0; -} + /* indices have been moved so last insert position is no longer valid */ + playlist.last_insert_pos = -1; -int playlist_add(char *filename) -{ - int len = strlen(filename); + if (write && playlist.control_fd >= 0) + { + int result = -1; - if(len+2 > playlist.buffer_size - playlist_end_pos) - return -1; + mutex_lock(&playlist.control_mutex); + + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) + { + if (fprintf(playlist.control_fd, "S:%d:%d\n", seed, + playlist.first_index) > 0) + { + fsync(playlist.control_fd); + result = 0; + } + } + + mutex_unlock(&playlist.control_mutex); + + if (result < 0) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return result; + } + } - strcpy(&playlist_buffer[playlist_end_pos], filename); - playlist_end_pos += len; - playlist_buffer[playlist_end_pos++] = '\n'; - playlist_buffer[playlist_end_pos] = '\0'; return 0; } -/* Add track to queue file */ -int queue_add(char *filename) +/* + * Sort the array of indices for the playlist. If start_current is true then + * set the index to the new index of the current song. + */ +static int sort_playlist(bool start_current, bool write) { - int fd, seek, result; - int len = strlen(filename); + unsigned int current = playlist.indices[playlist.index]; - if(playlist.num_queued >= MAX_QUEUED_FILES) - return -1; + if (playlist.amount > 0) + qsort(playlist.indices, playlist.amount, sizeof(playlist.indices[0]), + compare); - fd = open(QUEUE_FILE, O_WRONLY); - if (fd < 0) - return -1; + if (start_current) + find_and_set_playlist_index(current); - seek = lseek(fd, 0, SEEK_END); - if (seek < 0) - { - close(fd); - return -1; - } + /* indices have been moved so last insert position is no longer valid */ + playlist.last_insert_pos = -1; - /* save the file name with a trailing \n. QUEUE_FILE can be used as a - playlist if desired */ - filename[len] = '\n'; - result = write(fd, filename, len+1); - if (result < 0) + if (write && playlist.control_fd >= 0) { - close(fd); - return -1; - } - filename[len] = '\0'; + int result = -1; - close(fd); + mutex_lock(&playlist.control_mutex); - if (playlist.num_queued <= 0) - playlist.start_queue = 1; + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) + { + if (fprintf(playlist.control_fd, "U:%d\n", playlist.first_index) > + 0) + { + fsync(playlist.control_fd); + result = 0; + } + } - playlist.queue_indices[playlist.last_queue_index] = seek; - playlist.last_queue_index = - (playlist.last_queue_index + 1) % MAX_QUEUED_FILES; - playlist.num_queued++; + mutex_unlock(&playlist.control_mutex); - /* the play order has changed */ - mpeg_flush_and_reload_tracks(); + if (result < 0) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return result; + } + } - return playlist.num_queued; + return 0; } -int playlist_next(int steps) +/* + * returns the index of the track that is "steps" away from current playing + * track. + */ +static int get_next_index(int steps) { - bool queue; - int index = get_next_index(steps, &queue); + int current_index = playlist.index; + int next_index = -1; - if (queue) - { - /* queue_diff accounts for bad songs in queue list */ - int queue_diff = index - playlist.queue_index; - if (queue_diff < 0) - queue_diff += MAX_QUEUED_FILES; + if (playlist.amount <= 0) + return -1; - playlist.num_queued -= queue_diff; - playlist.queue_index = index; - playlist.start_queue = 0; - } - else + switch (global_settings.repeat_mode) { - playlist.index = index; - if (playlist.num_queued > 0 && !playlist.start_queue) + case REPEAT_OFF: { - if (steps >= 0) - { - /* done with queue list */ - playlist.queue_index = playlist.last_queue_index; - playlist.num_queued = 0; - } + /* Rotate indices such that first_index is considered index 0 to + simplify next calculation */ + current_index -= playlist.first_index; + if (current_index < 0) + current_index += playlist.amount; + + next_index = current_index+steps; + if ((next_index < 0) || (next_index >= playlist.amount)) + next_index = -1; else + next_index = (next_index+playlist.first_index) % + playlist.amount; + + break; + } + + case REPEAT_ONE: + next_index = current_index; + break; + + case REPEAT_ALL: + default: + { + next_index = (current_index+steps) % playlist.amount; + while (next_index < 0) + next_index += playlist.amount; + + if (steps >= playlist.amount) { - /* user requested previous. however, don't forget about queue - list */ - playlist.start_queue = 1; + int i, index; + + index = next_index; + next_index = -1; + + /* second time around so skip the queued files */ + for (i=0; i<playlist.amount; i++) + { + if (playlist.indices[index] & PLAYLIST_QUEUE_MASK) + index = (index+1) % playlist.amount; + else + { + next_index = index; + break; + } + } } + break; } } - return index; + return next_index; } -/* Returns false if 'steps' is out of bounds, else true */ -bool playlist_check(int steps) +/* + * Search for the seek track and set appropriate indices. Used after shuffle + * to make sure the current index is still pointing to correct track. + */ +static void find_and_set_playlist_index(unsigned int seek) { - bool queue; - int index = get_next_index(steps, &queue); - return (index >= 0); + int i; + + /* Set the index to the current song */ + for (i=0; i<playlist.amount; i++) + { + if (playlist.indices[i] == seek) + { + playlist.index = playlist.first_index = i; + break; + } + } } -char* playlist_peek(int steps) +/* + * used to sort track indices. Sort order is as follows: + * 1. Prepended tracks (in prepend order) + * 2. Playlist/directory tracks (in playlist order) + * 3. Inserted/Appended tracks (in insert order) + */ +static int compare(const void* p1, const void* p2) +{ + unsigned int* e1 = (unsigned int*) p1; + unsigned int* e2 = (unsigned int*) p2; + unsigned int flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK; + unsigned int flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK; + + if (flags1 == flags2) + return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); + else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND || + flags2 == PLAYLIST_INSERT_TYPE_APPEND) + return -1; + else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND || + flags2 == PLAYLIST_INSERT_TYPE_PREPEND) + return 1; + else if (flags1 && flags2) + return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); + else + return *e1 - *e2; +} + +/* + * gets pathname for track at seek index + */ +static int get_filename(int seek, bool control_file, char *buf, + int buf_length) { - int seek; - int max; - int i; int fd; - char *buf; + int max = -1; + char tmp_buf[MAX_PATH+1]; char dir_buf[MAX_PATH+1]; - char *dir_end; - int index; - bool queue; - index = get_next_index(steps, &queue); - if (index < 0) - return NULL; + if (buf_length > MAX_PATH+1) + buf_length = MAX_PATH+1; - if (queue) + if (playlist.in_ram && !control_file) { - seek = playlist.queue_indices[index]; - fd = open(QUEUE_FILE, O_RDONLY); - if(-1 != fd) - { - buf = dir_buf; - lseek(fd, seek, SEEK_SET); - max = read(fd, buf, MAX_PATH); - close(fd); - } - else - return NULL; + strncpy(tmp_buf, &playlist.buffer[seek], sizeof(tmp_buf)); + tmp_buf[MAX_PATH] = '\0'; + max = strlen(tmp_buf) + 1; } else { - seek = playlist.indices[index]; - - if(playlist.in_ram) - { - buf = playlist_buffer + seek; - max = playlist_end_pos - seek; - } + if (control_file) + fd = playlist.control_fd; else { if(-1 == playlist.fd) playlist.fd = open(playlist.filename, O_RDONLY); + + fd = playlist.fd; + } + + if(-1 != fd) + { + if (control_file) + mutex_lock(&playlist.control_mutex); + + lseek(fd, seek, SEEK_SET); + max = read(fd, tmp_buf, buf_length); + + if (control_file) + mutex_unlock(&playlist.control_mutex); + } - if(-1 != playlist.fd) - { - buf = playlist_buffer; - lseek(playlist.fd, seek, SEEK_SET); - max = read(playlist.fd, buf, MAX_PATH); - } + if (max < 0) + { + if (control_file) + splash(HZ*2, 0, true, + str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); else - return NULL; + splash(HZ*2, 0, true, str(LANG_PLAYLIST_ACCESS_ERROR)); + + return max; } } + strncpy(dir_buf, playlist.filename, playlist.dirlen-1); + dir_buf[playlist.dirlen-1] = 0; + + return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf)); +} + +/* + * Returns absolute path of track + */ +static int format_track_path(char *dest, char *src, int buf_length, int max, + char *dir) +{ + int i = 0; + int j; + char *temp_ptr; + /* Zero-terminate the file name */ - seek=0; - while((buf[seek] != '\n') && - (buf[seek] != '\r') && - (seek < max)) - seek++; + while((src[i] != '\n') && + (src[i] != '\r') && + (i < max)) + i++; /* Now work back killing white space */ - while((buf[seek-1] == ' ') || - (buf[seek-1] == '\t')) - seek--; + while((src[i-1] == ' ') || + (src[i-1] == '\t')) + i--; - buf[seek]=0; + src[i]=0; /* replace backslashes with forward slashes */ - for ( i=0; i<seek; i++ ) - if ( buf[i] == '\\' ) - buf[i] = '/'; + for ( j=0; j<i; j++ ) + if ( src[j] == '\\' ) + src[j] = '/'; - if('/' == buf[0]) { - strcpy(now_playing, &buf[0]); + if('/' == src[0]) + { + strncpy(dest, src, buf_length); } - else { - strncpy(dir_buf, playlist.filename, playlist.dirlen-1); - dir_buf[playlist.dirlen-1] = 0; - + else + { /* handle dos style drive letter */ - if ( ':' == buf[1] ) { - strcpy(now_playing, &buf[2]); - } - else if ( '.' == buf[0] && '.' == buf[1] && '/' == buf[2] ) { + if (':' == src[1]) + strcpy(src, &dest[2]); + else if ('.' == src[0] && '.' == src[1] && '/' == src[2]) + { /* handle relative paths */ - seek=3; - while(buf[seek] == '.' && - buf[seek+1] == '.' && - buf[seek+2] == '/') - seek += 3; - for (i=0; i<seek/3; i++) { - dir_end = strrchr(dir_buf, '/'); - if (dir_end) - *dir_end = '\0'; + i=3; + while(src[i] == '.' && + src[i] == '.' && + src[i] == '/') + i += 3; + for (j=0; j<i/3; j++) { + temp_ptr = strrchr(dir, '/'); + if (temp_ptr) + *temp_ptr = '\0'; else break; } - snprintf(now_playing, MAX_PATH+1, "%s/%s", dir_buf, &buf[seek]); + snprintf(dest, buf_length, "%s/%s", dir, &src[i]); } - else if ( '.' == buf[0] && '/' == buf[1] ) { - snprintf(now_playing, MAX_PATH+1, "%s/%s", dir_buf, &buf[2]); + else if ( '.' == src[0] && '/' == src[1] ) { + snprintf(dest, buf_length, "%s/%s", dir, &src[2]); } else { - snprintf(now_playing, MAX_PATH+1, "%s/%s", dir_buf, buf); - } - } - - buf = now_playing; - - /* remove bogus dirs from beginning of path - (workaround for buggy playlist creation tools) */ - if(!playlist.in_ram) - { - while (buf) - { - fd = open(buf, O_RDONLY); - if (fd >= 0) - { - close(fd); - break; - } - - buf = strchr(buf+1, '/'); + snprintf(dest, buf_length, "%s/%s", dir, src); } } - - if (!buf) - { - /* Even though this is an invalid file, we still need to pass a file - name to the caller because NULL is used to indicate end of - playlist */ - return now_playing; - } - return buf; + return 0; } /* - * This function is called to start playback of a given playlist. This - * playlist may be stored in RAM (when using full-dir playback). - * - * Return: the new index (possibly different due to shuffle) + * Display splash message showing progress of playlist/directory insertion or + * save. */ -int play_list(char *dir, /* "current directory" */ - char *file, /* playlist */ - int start_index, /* index in the playlist */ - bool shuffled_index, /* if TRUE the specified index is for the - playlist AFTER the shuffle */ - int start_offset, /* offset in the file */ - int random_seed, /* used for shuffling */ - int first_index, /* first index of playlist */ - int queue_resume, /* resume queue list? */ - int queue_resume_index ) /* queue list seek pos */ +static void display_playlist_count(int count, char *fmt) { - char *sep=""; - int dirlen; - empty_playlist(queue_resume); - - playlist.index = start_index; - playlist.first_index = first_index; + lcd_clear_display(); #ifdef HAVE_LCD_BITMAP if(global_settings.statusbar) @@ -569,228 +919,915 @@ int play_list(char *dir, /* "current directory" */ lcd_setmargins(0, 0); #endif - /* If file is NULL, the list is in RAM */ - if(file) { - splash(0, 0, true, str(LANG_PLAYLIST_LOAD)); - playlist.in_ram = false; - } else { - /* Assign a dummy filename */ - file = ""; - playlist.in_ram = true; +#ifdef HAVE_PLAYER_KEYPAD + splash(0, 0, true, fmt, count, str(LANG_STOP_ABORT)); +#else + splash(0, 0, true, fmt, count, str(LANG_OFF_ABORT)); +#endif +} + +/* + * Display buffer full message + */ +static void display_buffer_full(void) +{ + lcd_clear_display(); + lcd_puts(0,0,str(LANG_PLAYINDICES_PLAYLIST)); + lcd_puts(0,1,str(LANG_PLAYINDICES_BUFFER)); + lcd_update(); + sleep(HZ*2); + lcd_clear_display(); +} + +/* + * Initialize playlist entries at startup + */ +void playlist_init(void) +{ + playlist.fd = -1; + playlist.control_fd = -1; + playlist.max_playlist_size = global_settings.max_files_in_playlist; + playlist.indices = buffer_alloc(playlist.max_playlist_size * sizeof(int)); + playlist.buffer_size = + AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir; + playlist.buffer = buffer_alloc(playlist.buffer_size); + mutex_init(&playlist.control_mutex); + empty_playlist(true); +} + +/* + * Create new playlist + */ +int playlist_create(char *dir, char *file) +{ + empty_playlist(false); + + playlist.control_fd = open(PLAYLIST_CONTROL_FILE, O_RDWR); + if (-1 == playlist.control_fd) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + return -1; } - dirlen = strlen(dir); + if (!file) + { + file = ""; - /* If the dir does not end in trailing slash, we use a separator. - Otherwise we don't. */ - if('/' != dir[dirlen-1]) { - sep="/"; - dirlen++; + if (dir) + playlist.in_ram = true; + else + dir = ""; /* empty playlist */ } - playlist.dirlen = dirlen; + update_playlist_filename(dir, file); - snprintf(playlist.filename, sizeof(playlist.filename), - "%s%s%s", - dir, sep, file); + if (fprintf(playlist.control_fd, "P:%d:%s:%s\n", + PLAYLIST_CONTROL_FILE_VERSION, dir, file) > 0) + fsync(playlist.control_fd); + else + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return -1; + } - /* add track indices to playlist data structure */ - add_indices_to_playlist(); - - if(global_settings.playlist_shuffle) { - if(!playlist.in_ram) { - splash(0, 0, true, str(LANG_PLAYLIST_SHUFFLE)); - randomise_playlist( random_seed ); - } - else { - int i; + /* load the playlist file */ + if (file[0] != '\0') + add_indices_to_playlist(); + + return 0; +} + +#define PLAYLIST_COMMAND_SIZE (MAX_PATH+12) + +/* + * Restore the playlist state based on control file commands. Called to + * resume playback after shutdown. + */ +int playlist_resume(void) +{ + char *buffer; + int buflen; + int nread; + int total_read = 0; + bool first = true; + + enum { + resume_playlist, + resume_add, + resume_queue, + resume_delete, + resume_shuffle, + resume_unshuffle, + resume_reset, + resume_comment + }; + + /* use mp3 buffer for maximum load speed */ + buflen = (mp3end - mp3buf); + buffer = mp3buf; + + empty_playlist(true); + + playlist.control_fd = open(PLAYLIST_CONTROL_FILE, O_RDWR); + if (-1 == playlist.control_fd) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + return -1; + } - /* store the seek position before the shuffle */ - int seek_pos = playlist.indices[start_index]; + /* read a small amount first to get the header */ + nread = read(playlist.control_fd, buffer, + PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); + if(nread <= 0) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + return -1; + } - /* now shuffle around the indices */ - randomise_playlist( random_seed ); + while (1) + { + int result = 0; + int count; + int current_command = resume_comment; + int last_newline = 0; + int str_count = -1; + bool newline = true; + bool exit_loop = false; + char *p = buffer; + char *str1 = NULL; + char *str2 = NULL; + char *str3 = NULL; + + for(count=0; count<nread && !exit_loop; count++,p++) + { + /* Are we on a new line? */ + if((*p == '\n') || (*p == '\r')) + { + *p = '\0'; - if(!shuffled_index && global_settings.play_selected) { - /* The given index was for the unshuffled list, so we need - to figure out the index AFTER the shuffle has been made. - We scan for the seek position we remmber from before. */ + /* save last_newline in case we need to load more data */ + last_newline = count; - for(i=0; i< playlist.amount; i++) { - if(seek_pos == playlist.indices[i]) { - /* here's the start song! yiepee! */ - playlist.index = i; - playlist.first_index = i; - break; /* now stop searching */ + switch (current_command) + { + case resume_playlist: + { + /* str1=version str2=dir str3=file */ + int version; + + if (!str1) + { + result = -1; + exit_loop = true; + break; + } + + if (!str2) + str2 = ""; + + if (!str3) + str3 = ""; + + version = atoi(str1); + + if (version != PLAYLIST_CONTROL_FILE_VERSION) + { + result = -1; + exit_loop = true; + break; + } + + update_playlist_filename(str2, str3); + + if (str3[0] != '\0') + { + /* NOTE: add_indices_to_playlist() overwrites the + mp3buf so we need to reload control file + data */ + add_indices_to_playlist(); + } + else if (str2[0] != '\0') + { + playlist.in_ram = true; + resume_directory(str2); + } + + /* load the rest of the data */ + first = false; + exit_loop = true; + + break; + } + case resume_add: + case resume_queue: + { + /* str1=position str2=last_position str3=file */ + int position, last_position; + bool queue; + + if (!str1 || !str2 || !str3) + { + result = -1; + exit_loop = true; + break; + } + + position = atoi(str1); + last_position = atoi(str2); + + queue = (current_command == resume_add)?false:true; + + /* seek position is based on str3's position in + buffer */ + if (add_track_to_playlist(str3, position, queue, + total_read+(str3-buffer)) < 0) + return -1; + + playlist.last_insert_pos = last_position; + + break; + } + case resume_delete: + { + /* str1=position */ + int position; + + if (!str1) + { + result = -1; + exit_loop = true; + break; + } + + position = atoi(str1); + + if (remove_track_from_playlist(position, + false) < 0) + return -1; + + break; + } + case resume_shuffle: + { + /* str1=seed str2=first_index */ + int seed; + + if (!str1 || !str2) + { + result = -1; + exit_loop = true; + break; + } + + seed = atoi(str1); + playlist.first_index = atoi(str2); + + if (randomise_playlist(seed, false, false) < 0) + return -1; + + break; + } + case resume_unshuffle: + { + /* str1=first_index */ + if (!str1) + { + result = -1; + exit_loop = true; + break; + } + + playlist.first_index = atoi(str1); + + if (sort_playlist(false, false) < 0) + return -1; + + break; + } + case resume_reset: + { + playlist.last_insert_pos = -1; + break; + } + case resume_comment: + default: + break; + } + + newline = true; + + /* to ignore any extra newlines */ + current_command = resume_comment; + } + else if(newline) + { + newline = false; + + /* first non-comment line must always specify playlist */ + if (first && *p != 'P' && *p != '#') + { + result = -1; + exit_loop = true; + break; + } + + switch (*p) + { + case 'P': + /* playlist can only be specified once */ + if (!first) + { + result = -1; + exit_loop = true; + break; + } + + current_command = resume_playlist; + break; + case 'A': + current_command = resume_add; + break; + case 'Q': + current_command = resume_queue; + break; + case 'D': + current_command = resume_delete; + break; + case 'S': + current_command = resume_shuffle; + break; + case 'U': + current_command = resume_unshuffle; + break; + case 'R': + current_command = resume_reset; + break; + case '#': + current_command = resume_comment; + break; + default: + result = -1; + exit_loop = true; + break; + } + + str_count = -1; + str1 = NULL; + str2 = NULL; + str3 = NULL; + } + else if(current_command != resume_comment) + { + /* all control file strings are separated with a colon. + Replace the colon with 0 to get proper strings that can be + used by commands above */ + if (*p == ':') + { + *p = '\0'; + str_count++; + + if ((count+1) < nread) + { + switch (str_count) + { + case 0: + str1 = p+1; + break; + case 1: + str2 = p+1; + break; + case 2: + str3 = p+1; + break; + default: + /* allow last string to contain colons */ + *p = ':'; + break; + } } } - /* if we for any reason wouldn't find the index again, it - won't set the index again and we should start at index 0 - instead */ } } - } - if (queue_resume) - { - /* update the queue indices */ - add_indices_to_queuelist(queue_resume_index); + if (result < 0) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_INVALID)); + return result; + } - if (queue_resume == QUEUE_BEGIN_PLAYLIST) + if (!newline || (exit_loop && count<nread)) { - /* begin with current playlist index */ - playlist.start_queue = 1; - playlist.index++; /* so we begin at the correct track */ + /* We didn't end on a newline or we exited loop prematurely. + Either way, re-read the remainder. + NOTE: because of this, control file must always end with a + newline */ + count = last_newline; + lseek(playlist.control_fd, total_read+count, SEEK_SET); } + + total_read += count; + + if (first) + /* still looking for header */ + nread = read(playlist.control_fd, buffer, + PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); + else + nread = read(playlist.control_fd, buffer, buflen); + + /* Terminate on EOF */ + if(nread <= 0) + break; + } + + return 0; +} + +/* + * Add track to in_ram playlist. Used when playing directories. + */ +int playlist_add(char *filename) +{ + int len = strlen(filename); + + if((len+1 > playlist.buffer_size - playlist.buffer_end_pos) || + (playlist.amount >= playlist.max_playlist_size)) + { + display_buffer_full(); + return -1; } - /* also make the first song get playing */ - mpeg_play(start_offset); + playlist.indices[playlist.amount++] = playlist.buffer_end_pos; - return playlist.index; + strcpy(&playlist.buffer[playlist.buffer_end_pos], filename); + playlist.buffer_end_pos += len; + playlist.buffer[playlist.buffer_end_pos++] = '\0'; + + return 0; } /* - * calculate track offsets within a playlist file + * Insert track into playlist at specified position (or one of the special + * positions). Returns position where track was inserted or -1 if error. */ -void add_indices_to_playlist(void) +int playlist_insert_track(char *filename, int position, bool queue) +{ + int result = add_track_to_playlist(filename, position, queue, -1); + + if (result != -1) + { + fsync(playlist.control_fd); + mpeg_flush_and_reload_tracks(); + } + + return result; +} + +/* + * Insert all tracks from specified directory into playlist. + */ +int playlist_insert_directory(char *dirname, int position, bool queue) { - int nread; - int i = 0; int count = 0; - unsigned char* buffer = playlist_buffer; - int buflen = playlist.buffer_size; - bool store_index; - unsigned char *p; + int result; + char *count_str; - if(!playlist.in_ram) { - if(-1 == playlist.fd) - playlist.fd = open(playlist.filename, O_RDONLY); - if(-1 == playlist.fd) - return; /* failure */ - -#ifndef SIMULATOR - /* use mp3 buffer for maximum load speed */ - buflen = (mp3end - mp3buf); - buffer = mp3buf; + if (queue) + count_str = str(LANG_PLAYLIST_QUEUE_COUNT); + else + count_str = str(LANG_PLAYLIST_INSERT_COUNT); + + display_playlist_count(count, count_str); + + result = add_directory_to_playlist(dirname, &position, queue, &count); + fsync(playlist.control_fd); + + display_playlist_count(count, count_str); + mpeg_flush_and_reload_tracks(); + + return result; +} + +/* + * Insert all tracks from specified playlist into dynamic playlist + */ +int playlist_insert_playlist(char *filename, int position, bool queue) +{ + int fd; + int max; + char *temp_ptr; + char *dir; + char *count_str; + char temp_buf[MAX_PATH+1]; + char trackname[MAX_PATH+1]; + int count = 0; + int result = 0; + + fd = open(filename, O_RDONLY); + if (fd < 0) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_ACCESS_ERROR)); + return -1; + } + + /* we need the directory name for formatting purposes */ + dir = filename; + + temp_ptr = strrchr(filename+1,'/'); + if (temp_ptr) + *temp_ptr = 0; + else + dir = "/"; + + if (queue) + count_str = str(LANG_PLAYLIST_QUEUE_COUNT); + else + count_str = str(LANG_PLAYLIST_INSERT_COUNT); + + display_playlist_count(count, count_str); + + while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0) + { + /* user abort */ +#ifdef HAVE_PLAYER_KEYPAD + if (button_get(false) == BUTTON_STOP) +#else + if (button_get(false) == BUTTON_OFF) #endif + break; + + if (temp_buf[0] != '#' || temp_buf[0] != '\0') + { + int insert_pos; + + /* we need to format so that relative paths are correctly + handled */ + if (format_track_path(trackname, temp_buf, sizeof(trackname), max, + dir) < 0) + { + result = -1; + break; + } + + insert_pos = add_track_to_playlist(trackname, position, queue, + -1); + + if (insert_pos < 0) + { + result = -1; + break; + } + + /* Make sure tracks are inserted in correct order if user + requests INSERT_FIRST */ + if (position == PLAYLIST_INSERT_FIRST || position >= 0) + position = insert_pos + 1; + + count++; + + if ((count%PLAYLIST_DISPLAY_COUNT) == 0) + { + display_playlist_count(count, count_str); + + if (count == PLAYLIST_DISPLAY_COUNT) + mpeg_flush_and_reload_tracks(); + } + } + + /* let the other threads work */ + yield(); } - store_index = true; + close(fd); + fsync(playlist.control_fd); - mpeg_stop(); + *temp_ptr = '/'; + + display_playlist_count(count, count_str); + mpeg_flush_and_reload_tracks(); + + return result; +} + +/* delete track at specified index */ +int playlist_delete(int index) +{ + int result = remove_track_from_playlist(index, true); + + if (result != -1) + mpeg_flush_and_reload_tracks(); + + return result; +} + +/* shuffle newly created playlist using random seed. */ +int playlist_shuffle(int random_seed, int start_index) +{ + unsigned int seek_pos = 0; + bool start_current = false; + + if (start_index >= 0 && global_settings.play_selected) + { + /* store the seek position before the shuffle */ + seek_pos = playlist.indices[start_index]; + playlist.index = playlist.first_index = start_index; + start_current = true; + } + + splash(0, 0, true, str(LANG_PLAYLIST_SHUFFLE)); - while(1) + randomise_playlist(random_seed, start_current, true); + + return playlist.index; +} + +/* shuffle currently playing playlist */ +int playlist_randomise(unsigned int seed, bool start_current) +{ + int result = randomise_playlist(seed, start_current, true); + + if (result != -1) + mpeg_flush_and_reload_tracks(); + + return result; +} + +/* sort currently playing playlist */ +int playlist_sort(bool start_current) +{ + int result = sort_playlist(start_current, true); + + if (result != -1) + mpeg_flush_and_reload_tracks(); + + return result; +} + +/* start playing current playlist at specified index/offset */ +int playlist_start(int start_index, int offset) +{ + playlist.index = start_index; + mpeg_play(offset); + + return 0; +} + +/* Returns false if 'steps' is out of bounds, else true */ +bool playlist_check(int steps) +{ + int index = get_next_index(steps); + return (index >= 0); +} + +/* get trackname of track that is "steps" away from current playing track. + NULL is used to identify end of playlist */ +char* playlist_peek(int steps) +{ + int seek; + int fd; + char *temp_ptr; + int index; + bool control_file; + + index = get_next_index(steps); + if (index < 0) + return NULL; + + control_file = playlist.indices[index] & PLAYLIST_INSERT_TYPE_MASK; + seek = playlist.indices[index] & PLAYLIST_SEEK_MASK; + + if (get_filename(seek, control_file, now_playing, MAX_PATH+1) < 0) + return NULL; + + temp_ptr = now_playing; + + if (!playlist.in_ram || control_file) { - if(playlist.in_ram) { - nread = playlist_end_pos; - } else { - nread = read(playlist.fd, buffer, buflen); - /* Terminate on EOF */ - if(nread <= 0) + /* remove bogus dirs from beginning of path + (workaround for buggy playlist creation tools) */ + while (temp_ptr) + { + fd = open(temp_ptr, O_RDONLY); + if (fd >= 0) + { + close(fd); break; + } + + temp_ptr = strchr(temp_ptr+1, '/'); } - p = buffer; + if (!temp_ptr) + { + /* Even though this is an invalid file, we still need to pass a + file name to the caller because NULL is used to indicate end + of playlist */ + return now_playing; + } + } - for(count=0; count < nread; count++,p++) { + return temp_ptr; +} - /* Are we on a new line? */ - if((*p == '\n') || (*p == '\r')) +/* + * Update indices as track has changed + */ +int playlist_next(int steps) +{ + int index; + + if (steps > 0) + { + int i, j; + + /* We need to delete all the queued songs */ + for (i=0, j=steps; i<j; i++) + { + index = get_next_index(i); + + if (playlist.indices[index] & PLAYLIST_QUEUE_MASK) { - store_index = true; - } - else if(store_index) + remove_track_from_playlist(index, true); + steps--; /* one less track */ + } + } + } + + index = get_next_index(steps); + playlist.index = index; + + if (playlist.last_insert_pos >= 0) + { + /* check to see if we've gone beyond the last inserted track */ + int rot_index = index; + int rot_last_pos = playlist.last_insert_pos; + + rot_index -= playlist.first_index; + if (rot_index < 0) + rot_index += playlist.amount; + + rot_last_pos -= playlist.first_index; + if (rot_last_pos < 0) + rot_last_pos += playlist.amount; + + if (rot_index > rot_last_pos) + { + /* reset last inserted track */ + playlist.last_insert_pos = -1; + + if (playlist.control_fd >= 0) { - store_index = false; + int result = -1; - if(playlist.in_ram || (*p != '#')) + mutex_lock(&playlist.control_mutex); + + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) { - /* Store a new entry */ - playlist.indices[ playlist.amount ] = i+count; - playlist.amount++; - if ( playlist.amount >= playlist.max_playlist_size ) { - lcd_clear_display(); - lcd_puts(0,0,str(LANG_PLAYINDICES_PLAYLIST)); - lcd_puts(0,1,str(LANG_PLAYINDICES_BUFFER)); - lcd_update(); - sleep(HZ*2); - lcd_clear_display(); - - return; + if (fprintf(playlist.control_fd, "R\n") > 0) + { + fsync(playlist.control_fd); + result = 0; } } + + mutex_unlock(&playlist.control_mutex); + + if (result < 0) + { + splash(HZ*2, 0, true, + str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return result; + } } } + } - i+= count; + return index; +} - if(playlist.in_ram) - break; - } +/* Get resume info for current playing song. If return value is -1 then + settings shouldn't be saved. */ +int playlist_get_resume_info(int *resume_index) +{ + *resume_index = playlist.index; + + return 0; } -/* - * randomly rearrange the array of indices for the playlist - */ -void randomise_playlist( unsigned int seed ) +/* Returns index of current playing track for display purposes. This value + should not be used for resume purposes as it doesn't represent the actual + index into the playlist */ +int playlist_get_display_index(void) { - int count; - int candidate; - int store; - - /* seed with the given seed */ - srand( seed ); + int index = playlist.index; - /* randomise entire indices list */ - for(count = playlist.amount - 1; count >= 0; count--) - { - /* the rand is from 0 to RAND_MAX, so adjust to our value range */ - candidate = rand() % (count + 1); + /* first_index should always be index 0 for display purposes */ + index -= playlist.first_index; + if (index < 0) + index += playlist.amount; - /* now swap the values at the 'count' and 'candidate' positions */ - store = playlist.indices[candidate]; - playlist.indices[candidate] = playlist.indices[count]; - playlist.indices[count] = store; - } + return (index+1); +} - mpeg_flush_and_reload_tracks(); +/* returns number of tracks in playlist (includes queued/inserted tracks) */ +int playlist_amount(void) +{ + return playlist.amount; } -static int compare(const void* p1, const void* p2) +/* returns playlist name */ +char *playlist_name(char *buf, int buf_size) { - int* e1 = (int*) p1; - int* e2 = (int*) p2; + char *sep; + + snprintf(buf, buf_size, "%s", playlist.filename+playlist.dirlen); + + if (0 == buf[0]) + return NULL; - return *e1 - *e2; + /* Remove extension */ + sep = strrchr(buf, '.'); + if (NULL != sep) + *sep = 0; + + return buf; } -/* - * Sort the array of indices for the playlist. If start_current is true then - * set the index to the new index of the current song. - */ -void sort_playlist(bool start_current) +/* save the current dynamic playlist to specified file */ +int playlist_save(char *filename) { - int i; - int current = playlist.indices[playlist.index]; + int fd; + int i, index; + int count = 0; + char tmp_buf[MAX_PATH+1]; + int result = 0; - if (playlist.amount > 0) + if (playlist.amount <= 0) + return -1; + + /* use current working directory as base for pathname */ + if (format_track_path(tmp_buf, filename, sizeof(tmp_buf), + strlen(filename)+1, getcwd(NULL, -1)) < 0) + return -1; + + fd = open(tmp_buf, O_CREAT|O_WRONLY|O_TRUNC); + if (fd < 0) { - qsort(playlist.indices, playlist.amount, sizeof(playlist.indices[0]), compare); + splash(HZ*2, 0, true, str(LANG_PLAYLIST_ACCESS_ERROR)); + return -1; } - if (start_current) + display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT)); + + index = playlist.first_index; + for (i=0; i<playlist.amount; i++) { - /* Set the index to the current song */ - for (i=0; i<playlist.amount; i++) + bool control_file; + bool queue; + int seek; + + /* user abort */ +#ifdef HAVE_PLAYER_KEYPAD + if (button_get(false) == BUTTON_STOP) +#else + if (button_get(false) == BUTTON_OFF) +#endif + break; + + control_file = playlist.indices[index] & PLAYLIST_INSERT_TYPE_MASK; + queue = playlist.indices[index] & PLAYLIST_QUEUE_MASK; + seek = playlist.indices[index] & PLAYLIST_SEEK_MASK; + + /* Don't save queued files */ + if (!queue) { - if (playlist.indices[i] == current) + if (get_filename(seek, control_file, tmp_buf, MAX_PATH+1) < 0) { - playlist.index = i; + result = -1; break; } + + if (fprintf(fd, "%s\n", tmp_buf) < 0) + { + splash(HZ*2, 0, true, + str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + result = -1; + break; + } + + count++; + + if ((count%PLAYLIST_DISPLAY_COUNT) == 0) + display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT)); + + yield(); } + + index = (index+1)%playlist.amount; } - mpeg_flush_and_reload_tracks(); + display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT)); + + close(fd); + + return result; } diff --git a/apps/playlist.h b/apps/playlist.h index 9ff5122..a5318cd 100644 --- a/apps/playlist.h +++ b/apps/playlist.h @@ -22,57 +22,56 @@ #include <stdbool.h> #include "file.h" -#include "applimits.h" +#include "kernel.h" /* playlist data */ struct playlist_info { char filename[MAX_PATH]; /* path name of m3u playlist on disk */ - int fd; /* file descriptor of the open playlist */ + int fd; /* descriptor of the open playlist file */ + int control_fd; /* descriptor of the open control file */ int dirlen; /* Length of the path to the playlist file */ - int *indices; /* array of indices */ - int max_playlist_size; /* Max number of files in playlist. Mirror of + unsigned int *indices; /* array of indices */ + int max_playlist_size; /* Max number of files in playlist. Mirror of global_settings.max_files_in_playlist */ - int buffer_size; /* Playlist buffer size */ + bool in_ram; /* playlist stored in ram (dirplay) */ + char *buffer; /* buffer for in-ram playlists */ + int buffer_size; /* size of buffer */ + int buffer_end_pos; /* last position where buffer was written */ int index; /* index of current playing track */ int first_index; /* index of first song in playlist */ - int seed; /* random seed */ int amount; /* number of tracks in the index */ - bool in_ram; /* True if the playlist is RAM-based */ - - /* Queue function */ - int queue_indices[MAX_QUEUED_FILES]; /* array of queue indices */ - int last_queue_index; /* index of last queued track */ - int queue_index; /* index of current playing queued track */ - int num_queued; /* number of songs queued */ - int start_queue; /* the first song was queued */ + int last_insert_pos; /* last position we inserted a track */ + struct mutex control_mutex; /* mutex for control file access */ }; -extern struct playlist_info playlist; -extern bool playlist_shuffle; - void playlist_init(void); -int play_list(char *dir, char *file, int start_index, - bool shuffled_index, int start_offset, - int random_seed, int first_index, int queue_resume, - int queue_resume_index); -char* playlist_peek(int steps); -char* playlist_name(char *name, int name_size); -int playlist_next(int steps); -bool playlist_check(int steps); -void randomise_playlist( unsigned int seed ); -void sort_playlist(bool start_current); -void add_indices_to_playlist(void); -void playlist_clear(void); +int playlist_create(char *dir, char *file); +int playlist_resume(void); int playlist_add(char *filename); -int queue_add(char *filename); +int playlist_insert_track(char *filename, int position, bool queue); +int playlist_insert_directory(char *dirname, int position, bool queue); +int playlist_insert_playlist(char *filename, int position, bool queue); +int playlist_delete(int index); +int playlist_shuffle(int random_seed, int start_index); +int playlist_randomise(unsigned int seed, bool start_current); +int playlist_sort(bool start_current); +int playlist_start(int start_index, int offset); +bool playlist_check(int steps); +char *playlist_peek(int steps); +int playlist_next(int steps); +int playlist_get_resume_info(int *resume_index); +int playlist_get_display_index(void); int playlist_amount(void); -int playlist_first_index(void); -int playlist_get_resume_info(int *resume_index, int *queue_resume, - int *queue_resume_index); +char *playlist_name(char *buf, int buf_size); +int playlist_save(char *filename); -enum { QUEUE_OFF, QUEUE_BEGIN_QUEUE, QUEUE_BEGIN_PLAYLIST, NUM_QUEUE_MODES }; +enum { + PLAYLIST_PREPEND = -1, + PLAYLIST_INSERT = -2, + PLAYLIST_INSERT_LAST = -3, + PLAYLIST_INSERT_FIRST = -4 +}; #endif /* __PLAYLIST_H__ */ - diff --git a/apps/playlist_menu.c b/apps/playlist_menu.c new file mode 100644 index 0000000..3508240 --- /dev/null +++ b/apps/playlist_menu.c @@ -0,0 +1,71 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 Björn Stenberg + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include <string.h> + +#include "menu.h" +#include "file.h" +#include "keyboard.h" +#include "playlist.h" +#include "tree.h" +#include "settings.h" + +#include "lang.h" + +#define DEFAULT_PLAYLIST_NAME "/dynamic.m3u" + +static bool save_playlist(void) +{ + char filename[MAX_PATH+1]; + + strncpy(filename, DEFAULT_PLAYLIST_NAME, sizeof(filename)); + + if (!kbd_input(filename, sizeof(filename))) + { + playlist_save(filename); + + /* reload in case playlist was saved to cwd */ + reload_directory(); + } + + return false; +} + +static bool recurse_directory(void) +{ + return (set_bool( str(LANG_RECURSE_DIRECTORY), + &global_settings.recursive_dir_insert)); +} + +bool playlist_menu(void) +{ + int m; + bool result; + + struct menu_items items[] = { + { str(LANG_CREATE_PLAYLIST), create_playlist }, + { str(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist }, + { str(LANG_RECURSE_DIRECTORY), recurse_directory }, + }; + + m = menu_init( items, sizeof items / sizeof(struct menu_items) ); + result = menu_run(m); + menu_exit(m); + return result; +} diff --git a/apps/playlist_menu.h b/apps/playlist_menu.h new file mode 100644 index 0000000..e10fc81 --- /dev/null +++ b/apps/playlist_menu.h @@ -0,0 +1,24 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 Björn Stenberg + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef _PLAYLIST_MENU_H +#define _PLAYLIST_MENU_H + +bool playlist_menu(void); + +#endif diff --git a/apps/screens.c b/apps/screens.c index 6055be3..eb31eff 100644 --- a/apps/screens.c +++ b/apps/screens.c @@ -341,9 +341,9 @@ bool f2_screen(void) if(mpeg_status() & MPEG_STATUS_PLAY) { if (global_settings.playlist_shuffle) - randomise_playlist(current_tick); + playlist_randomise(current_tick, true); else - sort_playlist(true); + playlist_sort(true); } used = true; break; diff --git a/apps/settings.c b/apps/settings.c index d6e555f..e5fbfb5 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -62,7 +62,7 @@ struct user_settings global_settings; char rockboxdir[] = ROCKBOX_DIR; /* config/font/data file directory */ -#define CONFIG_BLOCK_VERSION 6 +#define CONFIG_BLOCK_VERSION 7 #define CONFIG_BLOCK_SIZE 512 #define RTC_BLOCK_SIZE 44 @@ -98,10 +98,10 @@ offset abs 0x12 0x26 <(int) Resume playlist index, or -1 if no playlist resume> 0x16 0x2a <(int) Byte offset into resume file> 0x1a 0x2e <time until disk spindown> -0x1b 0x2f <browse current, play selected, queue_resume> +0x1b 0x2f <browse current, play selected, recursive dir insert> 0x1c 0x30 <peak meter hold timeout (bit 0-4), rec_editable (bit 7)> -0x1d 0x31 <(int) queue resume index> +0x1d 0x31 <unused> 0x21 0x35 <repeat mode (bit 0-1), rec. channels (bit 2), mic gain (bit 4-7)> 0x22 0x36 <rec. quality (bit 0-2), source (bit 3-4), frequency (bit 5-7)> @@ -144,9 +144,9 @@ Rest of config block, only saved to disk: 0xB8 (char[20]) WPS file 0xCC (char[20]) Lang file 0xE0 (char[20]) Font file -0xF4 (int) Playlist first index -0xF8 (int) Playlist shuffle seed -0xFC-0x1FF (char[260]) Resume playlist (path/to/dir or path/to/playlist.m3u) +0xF4 <unused> +0xF8 <unused> +0xFC <unused> *************************************/ @@ -343,18 +343,19 @@ int settings_save( void ) memcpy(&config_block[0x12], &global_settings.resume_index, 4); memcpy(&config_block[0x16], &global_settings.resume_offset, 4); + DEBUGF( "+Resume index %X offset %X\n", + global_settings.resume_index, + global_settings.resume_offset ); config_block[0x1a] = (unsigned char)global_settings.disk_spindown; config_block[0x1b] = (unsigned char) (((global_settings.browse_current & 1)) | ((global_settings.play_selected & 1) << 1) | - ((global_settings.queue_resume & 3) << 2)); + ((global_settings.recursive_dir_insert & 1) << 2)); config_block[0x1c] = (unsigned char)global_settings.peak_meter_hold | (global_settings.rec_editable?0x80:0); - memcpy(&config_block[0x1d], &global_settings.queue_resume_index, 4); - config_block[0x21] = (unsigned char) ((global_settings.repeat_mode & 3) | ((global_settings.rec_channels & 1) << 2) | @@ -415,17 +416,6 @@ int settings_save( void ) strncpy(&config_block[0xb8], global_settings.wps_file, MAX_FILENAME); strncpy(&config_block[0xcc], global_settings.lang_file, MAX_FILENAME); strncpy(&config_block[0xe0], global_settings.font_file, MAX_FILENAME); - memcpy(&config_block[0xF4], &global_settings.resume_first_index, 4); - memcpy(&config_block[0xF8], &global_settings.resume_seed, 4); - - strncpy(&config_block[0xFC], global_settings.resume_file, MAX_PATH); - DEBUGF( "+Resume file %s\n",global_settings.resume_file ); - DEBUGF( "+Resume index %X offset %X\n", - global_settings.resume_index, - global_settings.resume_offset ); - DEBUGF( "+Resume shuffle %s seed %X\n", - global_settings.playlist_shuffle?"on":"off", - global_settings.resume_seed ); if(save_config_buffer()) { @@ -655,7 +645,8 @@ void settings_load(void) if (config_block[0x1b] != 0xFF) { global_settings.browse_current = (config_block[0x1b]) & 1; global_settings.play_selected = (config_block[0x1b] >> 1) & 1; - global_settings.queue_resume = (config_block[0x1b] >> 2) & 3; + global_settings.recursive_dir_insert = + (config_block[0x1b] >> 2) & 1; } if (config_block[0x1c] != 0xFF) { @@ -664,10 +655,6 @@ void settings_load(void) (config_block[0x1c] & 0x80)?true:false; } - if (config_block[0x1d] != 0xFF) - memcpy(&global_settings.queue_resume_index, &config_block[0x1d], - 4); - if (config_block[0x21] != 0xFF) { global_settings.repeat_mode = config_block[0x21] & 3; @@ -745,14 +732,9 @@ void settings_load(void) global_settings.max_files_in_playlist = config_block[0xaa] | (config_block[0xab] << 8); - memcpy(&global_settings.resume_first_index, &config_block[0xF4], 4); - memcpy(&global_settings.resume_seed, &config_block[0xF8], 4); - strncpy(global_settings.wps_file, &config_block[0xb8], MAX_FILENAME); strncpy(global_settings.lang_file, &config_block[0xcc], MAX_FILENAME); strncpy(global_settings.font_file, &config_block[0xe0], MAX_FILENAME); - strncpy(global_settings.resume_file, &config_block[0xFC], MAX_PATH); - global_settings.resume_file[MAX_PATH]=0; #ifdef HAVE_LCD_CHARCELLS if (config_block[0xa8] != 0xff) global_settings.jump_scroll = config_block[0xa8]; @@ -1097,6 +1079,8 @@ bool settings_load_config(char* file) else if (!strcasecmp(name, "max files in playlist")) set_cfg_int(&global_settings.max_files_in_playlist, value, 1000, 20000); + else if (!strcasecmp(name, "recursive directory insert")) + set_cfg_bool(&global_settings.recursive_dir_insert, value); } close(fd); @@ -1385,6 +1369,9 @@ bool settings_save_config(void) fprintf(fd, "max files in playlist: %d\r\n", global_settings.max_files_in_playlist); + fprintf(fd, "recursive directory insert: %s\r\n", + boolopt[global_settings.recursive_dir_insert]); + close(fd); lcd_clear_display(); @@ -1450,9 +1437,6 @@ void settings_reset(void) { global_settings.ff_rewind_accel = DEFAULT_FF_REWIND_ACCEL_SETTING; global_settings.resume_index = -1; global_settings.resume_offset = -1; - global_settings.save_queue_resume = true; - global_settings.queue_resume = 0; - global_settings.queue_resume_index = -1; global_settings.disk_spindown = 5; global_settings.disk_poweroff = false; global_settings.buffer_margin = 0; @@ -1475,6 +1459,7 @@ void settings_reset(void) { global_settings.max_files_in_dir = 400; global_settings.max_files_in_playlist = 10000; global_settings.show_icons = true; + global_settings.recursive_dir_insert = false; } bool set_bool(char* string, bool* variable ) diff --git a/apps/settings.h b/apps/settings.h index 624b06e..25efd65 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -103,16 +103,7 @@ struct user_settings int resume; /* resume option: 0=off, 1=ask, 2=on */ int resume_index; /* index in playlist (-1 for no active resume) */ int resume_offset; /* byte offset in mp3 file */ - int resume_seed; /* random seed for playlist shuffle */ - int resume_first_index; /* first index of playlist */ - bool save_queue_resume; /* save queued songs for resume */ - int queue_resume; /* resume queue file?: 0 = no - 1 = resume at queue index - 2 = resume at playlist index */ - int queue_resume_index; /* queue index (seek point in queue file) */ - - unsigned char resume_file[MAX_PATH+1]; /* playlist name (or dir) */ unsigned char font_file[MAX_FILENAME+1]; /* last font */ unsigned char wps_file[MAX_FILENAME+1]; /* last wps */ unsigned char lang_file[MAX_FILENAME+1]; /* last language */ @@ -175,6 +166,7 @@ struct user_settings int max_files_in_dir; /* Max entries in directory (file browser) */ int max_files_in_playlist; /* Max entries in playlist */ bool show_icons; /* 0=hide 1=show */ + bool recursive_dir_insert;/* should directories be inserted recursively */ }; enum optiontype { INT, BOOL }; diff --git a/apps/settings_menu.c b/apps/settings_menu.c index 57ba181..f64ee17 100644 --- a/apps/settings_menu.c +++ b/apps/settings_menu.c @@ -724,11 +724,11 @@ static bool playback_settings_menu(void) { if (global_settings.playlist_shuffle) { - randomise_playlist(current_tick); + playlist_randomise(current_tick, true); } else { - sort_playlist(true); + playlist_sort(true); } } return result; diff --git a/apps/tree.c b/apps/tree.c index f7a83de..29ca4df 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -66,10 +66,6 @@ static int max_files_in_dir; static char *name_buffer; static int name_buffer_size; /* Size of allocated buffer */ static int name_buffer_length; /* Currently used amount */ -struct entry { - short attr; /* FAT attributes + file type flags */ - char *name; -}; static struct entry *dircache; @@ -87,6 +83,8 @@ static int boot_size = 0; static int boot_cluster; static bool boot_changed = false; +static bool dirbrowse(char *root); + void browse_root(void) { #ifndef SIMULATOR @@ -158,14 +156,13 @@ static int build_playlist(int start_index) int i; int start=start_index; - playlist_clear(); - for(i = 0;i < filesindir;i++) { if(dircache[i].attr & TREE_ATTR_MPA) { DEBUGF("Adding %s\n", dircache[i].name); - playlist_add(dircache[i].name); + if (playlist_add(dircache[i].name) < 0) + break; } else { @@ -237,6 +234,133 @@ static void showfileline(int line, int direntry, bool scroll) } } +/* load sorted directory into dircache. returns NULL on failure. */ +struct entry* load_and_sort_directory(char *dirname, int dirfilter, + int *num_files, bool *buffer_full) +{ + int i; + + DIR *dir = opendir(dirname); + if(!dir) + return NULL; /* not a directory */ + + name_buffer_length = 0; + *buffer_full = false; + + for ( i=0; i < max_files_in_dir; i++ ) { + int len; + struct dirent *entry = readdir(dir); + struct entry* dptr = &dircache[i]; + if (!entry) + break; + + len = strlen(entry->d_name); + + /* skip directories . and .. */ + if ((entry->attribute & ATTR_DIRECTORY) && + (((len == 1) && + (!strncmp(entry->d_name, ".", 1))) || + ((len == 2) && + (!strncmp(entry->d_name, "..", 2))))) { + i--; + continue; + } + + /* Skip FAT volume ID */ + if (entry->attribute & ATTR_VOLUME_ID) { + i--; + continue; + } + + /* filter out dotfiles and hidden files */ + if (dirfilter != SHOW_ALL && + ((entry->d_name[0]=='.') || + (entry->attribute & ATTR_HIDDEN))) { + i--; + continue; + } + + dptr->attr = entry->attribute; + + /* mark mp? and m3u files as such */ + if ( !(dptr->attr & ATTR_DIRECTORY) && (len > 4) ) { + if (!strcasecmp(&entry->d_name[len-4], ".mp3") || + (!strcasecmp(&entry->d_name[len-4], ".mp2")) || + (!strcasecmp(&entry->d_name[len-4], ".mpa"))) + dptr->attr |= TREE_ATTR_MPA; + else if (!strcasecmp(&entry->d_name[len-4], ".m3u")) + dptr->attr |= TREE_ATTR_M3U; + else if (!strcasecmp(&entry->d_name[len-4], ".cfg")) + dptr->attr |= TREE_ATTR_CFG; + else if (!strcasecmp(&entry->d_name[len-4], ".wps")) + dptr->attr |= TREE_ATTR_WPS; + else if (!strcasecmp(&entry->d_name[len-4], ".txt")) + dptr->attr |= TREE_ATTR_TXT; + else if (!strcasecmp(&entry->d_name[len-4], ".lng")) + dptr->attr |= TREE_ATTR_LNG; +#ifdef HAVE_RECORDER_KEYPAD + else if (!strcasecmp(&entry->d_name[len-4], ".fnt")) + dptr->attr |= TREE_ATTR_FONT; + else if (!strcasecmp(&entry->d_name[len-4], ".ajz")) +#else + else if (!strcasecmp(&entry->d_name[len-4], ".mod")) +#endif + dptr->attr |= TREE_ATTR_MOD; + else if (!strcasecmp(&entry->d_name[len-5], ".rock")) + dptr->attr |= TREE_ATTR_ROCK; + } + + /* memorize/compare details about the boot file */ + if ((currdir[1] == 0) && !strcmp(entry->d_name, BOOTFILE)) { + if (boot_size) { + if ((entry->size != boot_size) || + (entry->startcluster != boot_cluster)) + boot_changed = true; + } + boot_size = entry->size; + boot_cluster = entry->startcluster; + } + + /* filter out all non-playlist files */ + if ( dirfilter == SHOW_PLAYLIST && + (!(dptr->attr & + (ATTR_DIRECTORY|TREE_ATTR_M3U))) ) { + i--; + continue; + } + + /* filter out non-music files */ + if ( dirfilter == SHOW_MUSIC && + (!(dptr->attr & + (ATTR_DIRECTORY|TREE_ATTR_MPA|TREE_ATTR_M3U))) ) { + i--; + continue; + } + + /* filter out non-supported files */ + if ( dirfilter == SHOW_SUPPORTED && + (!(dptr->attr & TREE_ATTR_MASK)) ) { + i--; + continue; + } + + if (len > name_buffer_size - name_buffer_length - 1) { + /* Tell the world that we ran out of buffer space */ + *buffer_full = true; + break; + } + dptr->name = &name_buffer[name_buffer_length]; + strcpy(dptr->name,entry->d_name); + name_buffer_length += len + 1; + } + *num_files = i; + closedir(dir); + strncpy(lastdir,dirname,sizeof(lastdir)); + lastdir[sizeof(lastdir)-1] = 0; + qsort(dircache,i,sizeof(struct entry),compare); + + return dircache; +} static int showdir(char *path, int start) { @@ -258,124 +382,9 @@ static int showdir(char *path, int start) /* new dir? cache it */ if (strncmp(path,lastdir,sizeof(lastdir)) || reload_dir) { - DIR *dir = opendir(path); - if(!dir) - return -1; /* not a directory */ - - name_buffer_length = 0; - dir_buffer_full = false; - - for ( i=0; i < max_files_in_dir; i++ ) { - int len; - struct dirent *entry = readdir(dir); - struct entry* dptr = &dircache[i]; - if (!entry) - break; - - len = strlen(entry->d_name); - - /* skip directories . and .. */ - if ((entry->attribute & ATTR_DIRECTORY) && - (((len == 1) && - (!strncmp(entry->d_name, ".", 1))) || - ((len == 2) && - (!strncmp(entry->d_name, "..", 2))))) { - i--; - continue; - } - - /* Skip FAT volume ID */ - if (entry->attribute & ATTR_VOLUME_ID) { - i--; - continue; - } - - /* filter out dotfiles and hidden files */ - if (global_settings.dirfilter != SHOW_ALL && - ((entry->d_name[0]=='.') || - (entry->attribute & ATTR_HIDDEN))) { - i--; - continue; - } - - dptr->attr = entry->attribute; - - /* mark mp? and m3u files as such */ - if ( !(dptr->attr & ATTR_DIRECTORY) && (len > 4) ) { - if (!strcasecmp(&entry->d_name[len-4], ".mp3") || - (!strcasecmp(&entry->d_name[len-4], ".mp2")) || - (!strcasecmp(&entry->d_name[len-4], ".mpa"))) - dptr->attr |= TREE_ATTR_MPA; - else if (!strcasecmp(&entry->d_name[len-4], ".m3u")) - dptr->attr |= TREE_ATTR_M3U; - else if (!strcasecmp(&entry->d_name[len-4], ".cfg")) - dptr->attr |= TREE_ATTR_CFG; - else if (!strcasecmp(&entry->d_name[len-4], ".wps")) - dptr->attr |= TREE_ATTR_WPS; - else if (!strcasecmp(&entry->d_name[len-4], ".txt")) - dptr->attr |= TREE_ATTR_TXT; - else if (!strcasecmp(&entry->d_name[len-4], ".lng")) - dptr->attr |= TREE_ATTR_LNG; -#ifdef HAVE_RECORDER_KEYPAD - else if (!strcasecmp(&entry->d_name[len-4], ".fnt")) - dptr->attr |= TREE_ATTR_FONT; - else if (!strcasecmp(&entry->d_name[len-4], ".ajz")) -#else - else if (!strcasecmp(&entry->d_name[len-4], ".mod")) -#endif - dptr->attr |= TREE_ATTR_MOD; - else if (!strcasecmp(&entry->d_name[len-5], ".rock")) - dptr->attr |= TREE_ATTR_ROCK; - } - - /* memorize/compare details about the boot file */ - if ((currdir[1] == 0) && !strcmp(entry->d_name, BOOTFILE)) { - if (boot_size) { - if ((entry->size != boot_size) || - (entry->startcluster != boot_cluster)) - boot_changed = true; - } - boot_size = entry->size; - boot_cluster = entry->startcluster; - } - - /* filter out all non-playlist files */ - if ( global_settings.dirfilter == SHOW_PLAYLIST && - (!(dptr->attr & - (ATTR_DIRECTORY|TREE_ATTR_M3U))) ) { - i--; - continue; - } - - /* filter out non-music files */ - if ( global_settings.dirfilter == SHOW_MUSIC && - (!(dptr->attr & - (ATTR_DIRECTORY|TREE_ATTR_MPA|TREE_ATTR_M3U))) ) { - i--; - continue; - } - - /* filter out non-supported files */ - if ( global_settings.dirfilter == SHOW_SUPPORTED && - (!(dptr->attr & TREE_ATTR_MASK)) ) { - i--; - continue; - } - - if (len > name_buffer_size - name_buffer_length - 1) { - /* Tell the world that we ran out of buffer space */ - dir_buffer_full = true; - break; - } - dptr->name = &name_buffer[name_buffer_length]; - strcpy(dptr->name,entry->d_name); - name_buffer_length += len + 1; - } - filesindir = i; - closedir(dir); - strncpy(lastdir,path,sizeof(lastdir)); - lastdir[sizeof(lastdir)-1] = 0; - qsort(dircache,filesindir,sizeof(struct entry),compare); + if (!load_and_sort_directory(path, global_settings.dirfilter, + &filesindir, &dir_buffer_full)) + return -1; if ( dir_buffer_full || filesindir == max_files_in_dir ) { #ifdef HAVE_LCD_CHARCELLS @@ -531,7 +540,7 @@ static int showdir(char *path, int start) return filesindir; } -bool ask_resume(void) +static bool ask_resume(void) { #ifdef HAVE_LCD_CHARCELLS lcd_double_height(false); @@ -570,92 +579,62 @@ bool ask_resume(void) return false; } -void start_resume(void) +/* load tracks from specified directory to resume play */ +void resume_directory(char *dir) +{ + bool buffer_full; + + if (!load_and_sort_directory(dir, global_settings.dirfilter, &filesindir, + &buffer_full)) + return; + lastdir[0] = 0; + + build_playlist(0); +} + +/* Returns the current working directory and also writes cwd to buf if + non-NULL. In case of error, returns NULL. */ +char *getcwd(char *buf, int size) +{ + if (!buf) + return currdir; + else if (size > 0) + { + strncpy(buf, currdir, size); + return buf; + } + else + return NULL; +} + +/* Force a reload of the directory next time directory browser is called */ +void reload_directory(void) +{ + reload_dir = true; +} + +static void start_resume(void) { if ( global_settings.resume && global_settings.resume_index != -1 ) { - int len = strlen(global_settings.resume_file); - - DEBUGF("Resume file %s\n",global_settings.resume_file); DEBUGF("Resume index %X offset %X\n", global_settings.resume_index, global_settings.resume_offset); - DEBUGF("Resume shuffle %s seed %X\n", - global_settings.playlist_shuffle?"on":"off", - global_settings.resume_seed); - - /* playlist? */ - if (!strcasecmp(&global_settings.resume_file[len-4], ".m3u")) { - char* slash; - - /* check that the file exists */ - int fd = open(global_settings.resume_file, O_RDONLY); - if(fd<0) - return; - close(fd); - - if (!ask_resume()) - return; - - slash = strrchr(global_settings.resume_file,'/'); - if (slash) { - *slash=0; - play_list(global_settings.resume_file, - slash+1, - global_settings.resume_index, - true, /* the index is AFTER shuffle */ - global_settings.resume_offset, - global_settings.resume_seed, - global_settings.resume_first_index, - global_settings.queue_resume, - global_settings.queue_resume_index); - *slash='/'; - } - else { - /* check that the dir exists */ - DIR* dir = opendir(global_settings.resume_file); - if(!dir) - return; - closedir(dir); - - if (!ask_resume()) - return; - - play_list("/", - global_settings.resume_file, - global_settings.resume_index, - true, - global_settings.resume_offset, - global_settings.resume_seed, - global_settings.resume_first_index, - global_settings.queue_resume, - global_settings.queue_resume_index); - } - } - else { - if (!ask_resume()) - return; - - if (showdir(global_settings.resume_file, 0) < 0 ) - return; - - lastdir[0] = '\0'; - - build_playlist(global_settings.resume_index); - play_list(global_settings.resume_file, - NULL, - global_settings.resume_index, - true, - global_settings.resume_offset, - global_settings.resume_seed, - global_settings.resume_first_index, - global_settings.queue_resume, - global_settings.queue_resume_index); - } - status_set_playmode(STATUS_PLAY); - status_draw(true); - wps_show(); + if (!ask_resume()) + return; + + if (playlist_resume() != -1) + { + playlist_start(global_settings.resume_index, + global_settings.resume_offset); + + status_set_playmode(STATUS_PLAY); + status_draw(true); + wps_show(); + } + else + return; } } @@ -751,19 +730,33 @@ static bool handle_on(int* ds, int* dc, int numentries, int tree_max_on_screen) case BUTTON_PLAY: case BUTTON_RC_PLAY: - case BUTTON_ON | BUTTON_PLAY: + case BUTTON_ON | BUTTON_PLAY: { + int onplay_result; + if (currdir[1]) snprintf(buf, sizeof buf, "%s/%s", currdir, dircache[dircursor+dirstart].name); else snprintf(buf, sizeof buf, "/%s", dircache[dircursor+dirstart].name); - if (onplay(buf, dircache[dircursor+dirstart].attr)) - reload_dir = 1; + onplay_result = onplay(buf, + dircache[dircursor+dirstart].attr); + switch (onplay_result) + { + case ONPLAY_OK: + used = true; + break; + case ONPLAY_RELOAD_DIR: + reload_dir = 1; + used = true; + break; + case ONPLAY_START_PLAY: + used = false; /* this will enable the wps */ + break; + } exit = true; - used = true; break; - + } case BUTTON_ON | BUTTON_REL: case BUTTON_ON | TREE_PREV | BUTTON_REL: case BUTTON_ON | TREE_NEXT | BUTTON_REL: @@ -793,7 +786,7 @@ static bool handle_on(int* ds, int* dc, int numentries, int tree_max_on_screen) return used; } -bool dirbrowse(char *root) +static bool dirbrowse(char *root) { int numentries=0; char buf[MAX_PATH]; @@ -934,41 +927,36 @@ bool dirbrowse(char *root) lcd_stop_scroll(); switch ( file->attr & TREE_ATTR_MASK ) { case TREE_ATTR_M3U: - if ( global_settings.resume ) { - if (currdir[1]) - snprintf(global_settings.resume_file, - MAX_PATH, "%s/%s", - currdir, file->name); - else - snprintf(global_settings.resume_file, - MAX_PATH, "/%s", file->name); + if (playlist_create(currdir, file->name) != -1) + { + if (global_settings.playlist_shuffle) + playlist_shuffle(seed, -1); + start_index = 0; + playlist_start(start_index,0); + play = true; } - play_list(currdir, file->name, 0, false, 0, - seed, 0, 0, -1); - start_index = 0; - play = true; break; - + case TREE_ATTR_MPA: - if ( global_settings.resume ) - strncpy(global_settings.resume_file, - currdir, MAX_PATH); - - start_index = - build_playlist(dircursor+dirstart); - - /* when shuffling dir.: play all files even if the - file selected by user is not the first one */ - if (global_settings.playlist_shuffle - && !global_settings.play_selected) - start_index = 0; - - /* it is important that we get back the index - in the (shuffled) list and store that */ - start_index = play_list(currdir, NULL, - start_index, false, - 0, seed, 0, 0, -1); - play = true; + if (playlist_create(currdir, NULL) != -1) + { + start_index = + build_playlist(dircursor+dirstart); + if (global_settings.playlist_shuffle) + { + start_index = + playlist_shuffle(seed,start_index); + + /* when shuffling dir.: play all files + even if the file selected by user is + not the first one */ + if (!global_settings.play_selected) + start_index = 0; + } + + playlist_start(start_index, 0); + play = true; + } break; /* wps config file */ @@ -1055,9 +1043,6 @@ bool dirbrowse(char *root) shuffled list in case shuffle is enabled */ global_settings.resume_index = start_index; global_settings.resume_offset = 0; - global_settings.resume_first_index = - playlist_first_index(); - global_settings.resume_seed = seed; settings_save(); } diff --git a/apps/tree.h b/apps/tree.h index aa8f212..66e83bc 100644 --- a/apps/tree.h +++ b/apps/tree.h @@ -21,6 +21,11 @@ #include <stdbool.h> +struct entry { + short attr; /* FAT attributes + file type flags */ + char *name; +}; + /* using attribute not used by FAT */ #define TREE_ATTR_MPA 0x40 /* mpeg audio file */ #define TREE_ATTR_M3U 0x80 /* playlist */ @@ -36,7 +41,11 @@ void tree_init(void); void browse_root(void); void set_current_file(char *path); -bool dirbrowse(char *root); bool create_playlist(void); +void resume_directory(char *dir); +char *getcwd(char *buf, int size); +void reload_directory(void); +struct entry* load_and_sort_directory(char *dirname, int dirfilter, + int *num_files, bool *buffer_full); #endif diff --git a/apps/wps-display.c b/apps/wps-display.c index 71ba4c5..d3c2613 100644 --- a/apps/wps-display.c +++ b/apps/wps-display.c @@ -418,12 +418,7 @@ static char* get_tag(struct mp3entry* id3, #endif case 'p': /* Playlist Position */ *flags |= WPS_REFRESH_STATIC; - { - int index = id3->index - playlist_first_index(); - if (index < 0) - index += playlist_amount(); - snprintf(buf, buf_size, "%d", index + 1); - } + snprintf(buf, buf_size, "%d", playlist_get_display_index()); return buf; case 'n': /* Playlist Name (without path) */ @@ -223,13 +223,8 @@ bool browse_id3(void) case 7: lcd_puts(0, 0, str(LANG_ID3_PLAYLIST)); - { - int index = id3->index - playlist_first_index(); - if (index < 0) - index += playlist_amount(); - snprintf(scroll_text,sizeof(scroll_text), "%d/%d", - index + 1, playlist_amount()); - } + snprintf(scroll_text,sizeof(scroll_text), "%d/%d", + playlist_get_display_index(), playlist_amount()); lcd_puts_scroll(0, 1, scroll_text); break; @@ -455,9 +450,7 @@ static bool update(void) DEBUGF("R%X,%X (%X)\n", global_settings.resume_offset, id3->offset,id3); - if (!playlist_get_resume_info(&global_settings.resume_index, - &global_settings.queue_resume, - &global_settings.queue_resume_index)) + if (!playlist_get_resume_info(&global_settings.resume_index)) { global_settings.resume_offset = id3->offset; settings_save(); diff --git a/firmware/common/file.c b/firmware/common/file.c index 6f86b2f..b4b879f 100644 --- a/firmware/common/file.c +++ b/firmware/common/file.c @@ -33,7 +33,7 @@ The penalty is the RAM used for the cache and slightly more complex code. */ -#define MAX_OPEN_FILES 4 +#define MAX_OPEN_FILES 8 struct filedesc { unsigned char cache[SECTOR_SIZE]; diff --git a/uisimulator/win32/Makefile b/uisimulator/win32/Makefile index d66fa6b..42388b7 100644 --- a/uisimulator/win32/Makefile +++ b/uisimulator/win32/Makefile @@ -100,7 +100,7 @@ APPS = main.c tree.c menu.c credits.c main_menu.c icons.c language.c \ screens.c peakmeter.c sleeptimer.c keyboard.c onplay.c\ misc.c plugin.c -MENUS = games_menu.c demo_menu.c settings_menu.c sound_menu.c +MENUS = games_menu.c demo_menu.c settings_menu.c sound_menu.c playlist_menu.c ifeq ($(DISPLAY),-DHAVE_LCD_BITMAP) APPS += bmp.c widgets.c @@ -160,6 +160,9 @@ $(OBJDIR)/demo_menu.o: $(APPDIR)/demo_menu.c $(OBJDIR)/settings_menu.o: $(APPDIR)/settings_menu.c $(CC) $(APPCFLAGS) -c $< -o $@ +$(OBJDIR)/playlist_menu.o: $(APPDIR)/playlist_menu.c + $(CC) $(APPCFLAGS) -c $< -o $@ + $(OBJDIR)/icons.o: $(MACHINEDIR)/icons.c $(CC) $(APPCFLAGS) -c $< -o $@ diff --git a/uisimulator/win32/file.h b/uisimulator/win32/file.h index 4e13c81..1ba4e22 100644 --- a/uisimulator/win32/file.h +++ b/uisimulator/win32/file.h @@ -29,7 +29,7 @@ int win32_filesize(int fd); #define rename(x,y) win32_rename(x,y) #define filesize(x) win32_filesize(x) -#define flush(x) _commit(x) +#define fsync(x) _commit(x) #include "../../firmware/include/file.h" diff --git a/uisimulator/win32/kernel.c b/uisimulator/win32/kernel.c index 567ed9e..f2a9dbb 100644 --- a/uisimulator/win32/kernel.c +++ b/uisimulator/win32/kernel.c @@ -103,3 +103,15 @@ int set_irq_level (int level) static int _lv = 0; return (_lv = level); } + +void mutex_init(struct mutex *m) +{ +} + +void mutex_lock(struct mutex *m) +{ +} + +void mutex_unlock(struct mutex *m) +{ +} diff --git a/uisimulator/win32/rockbox.dsp b/uisimulator/win32/rockbox.dsp index 558c256..2834046 100644 --- a/uisimulator/win32/rockbox.dsp +++ b/uisimulator/win32/rockbox.dsp @@ -43,7 +43,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../../firmware/export" /I "../../firmware/drivers" /I "../../firmware/common" /I "../common" /I "../win32" /I "../../apps" /I "../../apps/recorder" /D "HAVE_LCD_BITMAP" /D "HAVE_RECORDER_KEYPAD" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "HAVE_CONFIG_H" /D "GETTIMEOFDAY_TWO_ARGS" /D "SIMULATOR" /D APPSVERSION=\"WIN32SIM\" /FR /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../../firmware/export" /I "../../firmware/drivers" /I "../../firmware/common" /I "../common" /I "../win32" /I "../../apps" /I "../../apps/recorder" /D "HAVE_LCD_BITMAP" /D "HAVE_RECORDER_KEYPAD" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "HAVE_CONFIG_H" /D "GETTIMEOFDAY_TWO_ARGS" /D "SIMULATOR" /D APPSVERSION=\"WIN32SIM\" /FR /YX /FD /GZ /c # ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x407 /d "_DEBUG" @@ -69,7 +69,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../../firmware/export" /I "../../firmware/drivers" /I "../../firmware/common" /I "../common" /I "../win32" /I "../../apps" /I "../../apps/player" /D "HAVE_LCD_CHARCELLS" /D "HAVE_PLAYER_KEYPAD" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "HAVE_CONFIG_H" /D "GETTIMEOFDAY_TWO_ARGS" /D "SIMULATOR" /D APPSVERSION=\"WIN32SIM\" /FR /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../../firmware/export" /I "../../firmware/drivers" /I "../../firmware/common" /I "../common" /I "../win32" /I "../../apps" /I "../../apps/player" /D "HAVE_LCD_CHARCELLS" /D "HAVE_PLAYER_KEYPAD" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "HAVE_CONFIG_H" /D "GETTIMEOFDAY_TWO_ARGS" /D "SIMULATOR" /D APPSVERSION=\"WIN32SIM\" /FR /YX /FD /GZ /c # ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x407 /d "_DEBUG" @@ -369,6 +369,10 @@ SOURCE=..\..\apps\playlist.c # End Source File # Begin Source File +SOURCE=..\..\apps\playlist_menu.c +# End Source File +# Begin Source File + SOURCE=..\..\apps\plugin.c # End Source File # Begin Source File diff --git a/uisimulator/x11/Makefile b/uisimulator/x11/Makefile index b501ea2..05116ed 100644 --- a/uisimulator/x11/Makefile +++ b/uisimulator/x11/Makefile @@ -95,7 +95,7 @@ APPS = main.c tree.c menu.c credits.c main_menu.c language.c\ screens.c peakmeter.c sleeptimer.c keyboard.c onplay.c\ misc.c plugin.c -MENUS = games_menu.c demo_menu.c settings_menu.c sound_menu.c +MENUS = games_menu.c demo_menu.c settings_menu.c sound_menu.c playlist_menu.c ifeq ($(DISPLAY),-DHAVE_LCD_BITMAP) APPS += bmp.c widgets.c @@ -156,6 +156,9 @@ $(OBJDIR)/demo_menu.o: $(APPDIR)/demo_menu.c $(OBJDIR)/settings_menu.o: $(APPDIR)/settings_menu.c $(CC) $(APPCFLAGS) -c $< -o $@ +$(OBJDIR)/playlist_menu.o: $(APPDIR)/playlist_menu.c + $(CC) $(APPCFLAGS) -c $< -o $@ + $(OBJDIR)/icons.o: $(MACHINEDIR)/icons.c $(CC) $(APPCFLAGS) -c $< -o $@ diff --git a/uisimulator/x11/kernel.h b/uisimulator/x11/kernel.h index 7ec2979..a045a3f 100644 --- a/uisimulator/x11/kernel.h +++ b/uisimulator/x11/kernel.h @@ -22,6 +22,9 @@ #ifndef NO_REDEFINES_PLEASE #define sleep(x) x11_sleep(x) +#define mutex_init(x) (void)x +#define mutex_lock(x) (void)x +#define mutex_unlock(x) (void)x #endif |