diff --git a/apps/applimits.h b/apps/applimits.h index 6372dac924..a18544e87d 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 57bec0c656..dab8fa6bd4 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 b4d952ea5a..e356603653 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 3e0f5a365e..6c538f8d8a 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 8540aaf2c7..7b47479e4b 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 726e8a9ecb..05149d164f 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:::) + - 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:::) + - 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:) + - Delete track from specified position in the current playlist. + d. Shuffle playlist (S::) + - 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:) + - 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 #include #include #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,233 +92,1649 @@ 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 -static unsigned char *playlist_buffer; +/* + 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. -static int playlist_end_pos = 0; + 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 + +#define PLAYLIST_QUEUED 0x20000000 + +#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; - playlist.index = 0; - playlist.queue_index = 0; - playlist.last_queue_index = 0; - playlist.amount = 0; - playlist.num_queued = 0; - playlist.start_queue = 0; - if (!queue_resume) + 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.first_index = 0; + playlist.amount = 0; + playlist.last_insert_pos = -1; + + 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 insert_position = position; + unsigned int flags = PLAYLIST_INSERT_TYPE_INSERT; + int i; + + if (playlist.amount >= playlist.max_playlist_size) + { + display_buffer_full(); + return -1; + } + + switch (position) + { + 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; + + /* 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) + { + int result = -1; + + mutex_lock(&playlist.control_mutex); + + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) + { + if (fprintf(playlist.control_fd, "%c:%d:%d:", (queue?'Q':'A'), + position, playlist.last_insert_pos) > 0) + { + /* 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; + } + } + + 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) + { + 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= 0) + *position = insert_pos + 1; + + 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(); + } + } + + return result; +} + +/* + * remove track at specified position + */ +static int remove_track_from_playlist(int position, bool write) +{ + int i; + + if (playlist.amount <= 0) + return -1; + + /* shift indices now that track has been removed */ + for (i=position; i= 0) + { + int result = -1; + + mutex_lock(&playlist.control_mutex); + + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) + { + if (fprintf(playlist.control_fd, "D:%d\n", position) > 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; + } + } + + return 0; +} + +/* + * 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) +{ + int count; + int candidate; + int store; + unsigned int current = playlist.indices[playlist.index]; + + /* seed with the given seed */ + srand(seed); + + /* 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); + + /* now swap the values at the 'count' and 'candidate' positions */ + store = playlist.indices[candidate]; + playlist.indices[candidate] = playlist.indices[count]; + playlist.indices[count] = store; + } + + if (start_current) + find_and_set_playlist_index(current); + + /* indices have been moved so last insert position is no longer valid */ + playlist.last_insert_pos = -1; + + if (write && playlist.control_fd >= 0) + { + int result = -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; + } + } + + return 0; +} + +/* + * 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) +{ + unsigned int current = playlist.indices[playlist.index]; + + if (playlist.amount > 0) + qsort(playlist.indices, playlist.amount, sizeof(playlist.indices[0]), + compare); + + if (start_current) + find_and_set_playlist_index(current); + + /* indices have been moved so last insert position is no longer valid */ + playlist.last_insert_pos = -1; + + if (write && playlist.control_fd >= 0) + { + int result = -1; + + mutex_lock(&playlist.control_mutex); + + 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; + } + } + + mutex_unlock(&playlist.control_mutex); + + if (result < 0) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return result; + } + } + + return 0; +} + +/* + * returns the index of the track that is "steps" away from current playing + * track. + */ +static int get_next_index(int steps) { int current_index = playlist.index; int next_index = -1; - if (global_settings.repeat_mode == REPEAT_ONE) - { - /* this is needed for repeat one to work with queue mode */ - steps = 0; - } - else if (steps >= 0) - { - /* 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; - } - - if (steps >= 0 && playlist.num_queued > 0 && - playlist.num_queued - steps > 0) - *queue = true; - else - { - *queue = false; - if (playlist.num_queued) - { - if (steps >= 0) - { - /* skip the queued tracks */ - steps -= (playlist.num_queued - 1); - } - else if (!playlist.start_queue) - { - /* previous from queue list needs to go to current track in - playlist */ - steps += 1; - } - } - } + if (playlist.amount <= 0) + return -1; switch (global_settings.repeat_mode) { case REPEAT_OFF: - if (*queue) - next_index = (playlist.queue_index+steps) % MAX_QUEUED_FILES; + { + /* 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 - { - 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; - } + next_index = (next_index+playlist.first_index) % + playlist.amount; + 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; - else - { - next_index = current_index; - *queue = false; - } + next_index = current_index; break; case REPEAT_ALL: default: - if (*queue) - next_index = (playlist.queue_index+steps) % MAX_QUEUED_FILES; - else + { + next_index = (current_index+steps) % playlist.amount; + while (next_index < 0) + next_index += playlist.amount; + + if (steps >= playlist.amount) { - next_index = (current_index+steps) % playlist.amount; - while (next_index < 0) - next_index += playlist.amount; + int i, index; + + index = next_index; + next_index = -1; + + /* second time around so skip the queued files */ + for (i=0; i 0) + int i; + + /* Set the index to the current song */ + for (i=0; i MAX_PATH+1) + buf_length = MAX_PATH+1; + + if (playlist.in_ram && !control_file) + { + strncpy(tmp_buf, &playlist.buffer[seek], sizeof(tmp_buf)); + tmp_buf[MAX_PATH] = '\0'; + max = strlen(tmp_buf) + 1; + } + else + { + 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 (max < 0) + { + if (control_file) + splash(HZ*2, 0, true, + str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + else + 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 */ + while((src[i] != '\n') && + (src[i] != '\r') && + (i < max)) + i++; + + /* Now work back killing white space */ + while((src[i-1] == ' ') || + (src[i-1] == '\t')) + i--; + + src[i]=0; + + /* replace backslashes with forward slashes */ + for ( j=0; j 0) + fsync(playlist.control_fd); + else + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return -1; + } + + /* 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; + } + + /* read a small amount first to get the header */ + nread = read(playlist.control_fd, buffer, + PLAYLIST_COMMAND_SIZE playlist.buffer_size - playlist.buffer_end_pos) || + (playlist.amount >= playlist.max_playlist_size)) + { + display_buffer_full(); + return -1; + } + + playlist.indices[playlist.amount++] = playlist.buffer_end_pos; + + strcpy(&playlist.buffer[playlist.buffer_end_pos], filename); + playlist.buffer_end_pos += len; + playlist.buffer[playlist.buffer_end_pos++] = '\0'; + + return 0; +} + +/* + * Insert track into playlist at specified position (or one of the special + * positions). Returns position where track was inserted or -1 if error. + */ +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 count = 0; + int result; + char *count_str; + + 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(); + } + + close(fd); + fsync(playlist.control_fd); + + *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)); + + 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) + { + /* 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, '/'); + } + + 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; + } + } + + return temp_ptr; +} + +/* + * 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= 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) + { + int result = -1; + + mutex_lock(&playlist.control_mutex); + + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) + { + 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; + } + } + } + } + + return index; +} + +/* 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; +} + +/* 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 index = playlist.index; + + /* first_index should always be index 0 for display purposes */ + index -= playlist.first_index; + if (index < 0) + index += playlist.amount; + + return (index+1); +} + +/* returns number of tracks in playlist (includes queued/inserted tracks) */ +int playlist_amount(void) +{ + return playlist.amount; +} + +/* returns playlist name */ char *playlist_name(char *buf, int buf_size) { char *sep; @@ -285,512 +1752,82 @@ char *playlist_name(char *buf, int buf_size) return buf; } -void playlist_clear(void) +/* save the current dynamic playlist to specified file */ +int playlist_save(char *filename) { - playlist_end_pos = 0; - playlist_buffer[0] = 0; -} + int fd; + int i, index; + int count = 0; + char tmp_buf[MAX_PATH+1]; + int result = 0; -int playlist_add(char *filename) -{ - int len = strlen(filename); - - if(len+2 > playlist.buffer_size - playlist_end_pos) + if (playlist.amount <= 0) return -1; - 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) -{ - int fd, seek, result; - int len = strlen(filename); - - if(playlist.num_queued >= MAX_QUEUED_FILES) + /* 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(QUEUE_FILE, O_WRONLY); + fd = open(tmp_buf, O_CREAT|O_WRONLY|O_TRUNC); if (fd < 0) - return -1; - - seek = lseek(fd, 0, SEEK_END); - if (seek < 0) { - close(fd); + splash(HZ*2, 0, true, str(LANG_PLAYLIST_ACCESS_ERROR)); return -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) + display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT)); + + index = playlist.first_index; + for (i=0; i 0 && !playlist.start_queue) - { - if (steps >= 0) - { - /* done with queue list */ - playlist.queue_index = playlist.last_queue_index; - playlist.num_queued = 0; - } - else - { - /* user requested previous. however, don't forget about queue - list */ - playlist.start_queue = 1; - } - } - } - - return index; -} - -/* Returns false if 'steps' is out of bounds, else true */ -bool playlist_check(int steps) -{ - bool queue; - int index = get_next_index(steps, &queue); - return (index >= 0); -} - -char* playlist_peek(int steps) -{ - int seek; - int max; - int i; - int fd; - char *buf; - 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 (queue) - { - 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; - } - else - { - seek = playlist.indices[index]; - - if(playlist.in_ram) - { - buf = playlist_buffer + seek; - max = playlist_end_pos - seek; - } - else - { - if(-1 == playlist.fd) - playlist.fd = open(playlist.filename, O_RDONLY); - - if(-1 != playlist.fd) - { - buf = playlist_buffer; - lseek(playlist.fd, seek, SEEK_SET); - max = read(playlist.fd, buf, MAX_PATH); - } - else - return NULL; - } - } - - /* Zero-terminate the file name */ - seek=0; - while((buf[seek] != '\n') && - (buf[seek] != '\r') && - (seek < max)) - seek++; - - /* Now work back killing white space */ - while((buf[seek-1] == ' ') || - (buf[seek-1] == '\t')) - seek--; - - buf[seek]=0; - - /* replace backslashes with forward slashes */ - for ( i=0; i= 0) - { - close(fd); - break; - } - - buf = strchr(buf+1, '/'); - } - } - - 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; -} - -/* - * 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) - */ -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 */ -{ - char *sep=""; - int dirlen; - empty_playlist(queue_resume); - - playlist.index = start_index; - playlist.first_index = first_index; - -#ifdef HAVE_LCD_BITMAP - if(global_settings.statusbar) - lcd_setmargins(0, STATUSBAR_HEIGHT); - else - 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; - } - - 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); - - /* 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; - - /* store the seek position before the shuffle */ - int seek_pos = playlist.indices[start_index]; - - /* now shuffle around the indices */ - randomise_playlist( random_seed ); - - 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. */ - - 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 */ - } - } - /* 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 (queue_resume == QUEUE_BEGIN_PLAYLIST) - { - /* begin with current playlist index */ - playlist.start_queue = 1; - playlist.index++; /* so we begin at the correct track */ - } - } - - /* also make the first song get playing */ - mpeg_play(start_offset); - - return playlist.index; -} - -/* - * calculate track offsets within a playlist file - */ -void add_indices_to_playlist(void) -{ - int nread; - int i = 0; - int count = 0; - unsigned char* buffer = playlist_buffer; - int buflen = playlist.buffer_size; - bool store_index; - unsigned char *p; - - 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; -#endif - } - - store_index = true; - - mpeg_stop(); - - while(1) - { - if(playlist.in_ram) { - nread = playlist_end_pos; - } else { - nread = read(playlist.fd, buffer, buflen); - /* Terminate on EOF */ - if(nread <= 0) - break; - } - - 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; - - if(playlist.in_ram || (*p != '#')) - { - /* 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; - } - } - } - } - - i+= count; - - if(playlist.in_ram) - break; - } -} - -/* - * randomly rearrange the array of indices for the playlist - */ -void randomise_playlist( unsigned int seed ) -{ - int count; - int candidate; - int store; - - /* seed with the given seed */ - srand( seed ); - - /* 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); - - /* now swap the values at the 'count' and 'candidate' positions */ - store = playlist.indices[candidate]; - playlist.indices[candidate] = playlist.indices[count]; - playlist.indices[count] = store; - } - - mpeg_flush_and_reload_tracks(); -} - -static int compare(const void* p1, const void* p2) -{ - int* e1 = (int*) p1; - int* e2 = (int*) p2; - - return *e1 - *e2; -} - -/* - * 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) -{ - int i; - int current = playlist.indices[playlist.index]; - - if (playlist.amount > 0) - { - qsort(playlist.indices, playlist.amount, sizeof(playlist.indices[0]), compare); - } - - if (start_current) - { - /* Set the index to the current song */ - for (i=0; i #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 0000000000..3508240efe --- /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 + +#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 0000000000..e10fc81299 --- /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 6055be3a2d..eb31eff5cf 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 d6e555f03e..e5fbfb5e06 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