From 9e4262081b4ab5bad2e2708ea064643cf828685c Mon Sep 17 00:00:00 2001 From: Hardeep Sidhu Date: Tue, 1 Jul 2003 21:05:43 +0000 Subject: Added dynamic playlists. ON+PLAY->Playlist on a track, directory, or playlist from file browser to see available options. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@3796 a1c6a512-1295-4272-9138-f99709370657 --- apps/playlist.c | 2115 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 1576 insertions(+), 539 deletions(-) (limited to 'apps/playlist.c') 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,756 +92,1742 @@ 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; - - 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; - } + int insert_position = position; + unsigned int flags = PLAYLIST_INSERT_TYPE_INSERT; + int i; - if (steps >= 0 && playlist.num_queued > 0 && - playlist.num_queued - steps > 0) - *queue = true; - else + if (playlist.amount >= playlist.max_playlist_size) { - *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; - } - } + display_buffer_full(); + return -1; } - switch (global_settings.repeat_mode) + switch (position) { - case REPEAT_OFF: - if (*queue) - next_index = (playlist.queue_index+steps) % MAX_QUEUED_FILES; + 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 - { - 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; - } + 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; - 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 (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 - { - next_index = current_index; - *queue = false; - } + insert_position = playlist.amount; + + flags = PLAYLIST_INSERT_TYPE_APPEND; break; + } + + if (queue) + flags |= PLAYLIST_QUEUED; - case REPEAT_ALL: - default: - if (*queue) - next_index = (playlist.queue_index+steps) % MAX_QUEUED_FILES; - 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) + { + 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) { - next_index = (current_index+steps) % playlist.amount; - while (next_index < 0) - next_index += playlist.amount; + /* 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; } - break; + } + + mutex_unlock(&playlist.control_mutex); + + if (result < 0) + { + splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + return result; + } } - return next_index; -} + playlist.indices[insert_position] = flags | seek_pos; -int playlist_amount(void) -{ - return playlist.amount + playlist.num_queued; -} + playlist.amount++; -int playlist_first_index(void) -{ - return playlist.first_index; + return insert_position; } -/* 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) +/* + * 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; - *resume_index = playlist.index; + /* 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); - if (playlist.num_queued > 0) + for (i=0; i= 0) + *position = insert_pos + 1; - if (0 == buf[0]) - return NULL; + if ((*count%PLAYLIST_DISPLAY_COUNT) == 0) + { + display_playlist_count(*count, count_str); - /* Remove extension */ - sep = strrchr(buf, '.'); - if (NULL != sep) - *sep = 0; - - return buf; -} + if (*count == PLAYLIST_DISPLAY_COUNT) + mpeg_flush_and_reload_tracks(); + } + + /* let the other threads work */ + yield(); + } + } -void playlist_clear(void) -{ - playlist_end_pos = 0; - playlist_buffer[0] = 0; + return result; } -int playlist_add(char *filename) +/* + * remove track at specified position + */ +static int remove_track_from_playlist(int position, bool write) { - int len = strlen(filename); + int i; - 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; -} + /* shift indices now that track has been removed */ + for (i=position; i= MAX_QUEUED_FILES) - return -1; + /* update stored indices if needed */ + if (position < playlist.index) + playlist.index--; - fd = open(QUEUE_FILE, O_WRONLY); - if (fd < 0) - return -1; + if (position < playlist.first_index) + playlist.first_index--; - seek = lseek(fd, 0, SEEK_END); - if (seek < 0) - { - close(fd); - return -1; - } + if (position <= playlist.last_insert_pos) + playlist.last_insert_pos--; - /* 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, "D:%d\n", position) > 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) +/* + * 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) { - bool queue; - int index = get_next_index(steps, &queue); + int count; + int candidate; + int store; + unsigned int current = playlist.indices[playlist.index]; + + /* seed with the given seed */ + srand(seed); - if (queue) + /* randomise entire indices list */ + for(count = playlist.amount - 1; count >= 0; count--) { - /* 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; + /* the rand is from 0 to RAND_MAX, so adjust to our value range */ + candidate = rand() % (count + 1); - playlist.num_queued -= queue_diff; - playlist.queue_index = index; - playlist.start_queue = 0; + /* now swap the values at the 'count' and 'candidate' positions */ + store = playlist.indices[candidate]; + playlist.indices[candidate] = playlist.indices[count]; + playlist.indices[count] = store; } - else + + 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) { - playlist.index = index; - if (playlist.num_queued > 0 && !playlist.start_queue) + int result = -1; + + mutex_lock(&playlist.control_mutex); + + if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) { - if (steps >= 0) + if (fprintf(playlist.control_fd, "S:%d:%d\n", seed, + playlist.first_index) > 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; + 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; + return 0; } -/* 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); +/* + * 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; } -char* playlist_peek(int steps) +/* + * 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 (playlist.amount <= 0) + return -1; + + switch (global_settings.repeat_mode) + { + case REPEAT_OFF: + { + /* 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) + { + int i, index; + + index = next_index; + next_index = -1; + + /* second time around so skip the queued files */ + for (i=0; i MAX_PATH+1) + buf_length = MAX_PATH+1; - if (queue) + 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 { - seek = playlist.queue_indices[index]; - fd = open(QUEUE_FILE, O_RDONLY); + 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) { - buf = dir_buf; + if (control_file) + mutex_lock(&playlist.control_mutex); + lseek(fd, seek, SEEK_SET); - max = read(fd, buf, MAX_PATH); - close(fd); + 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) { - seek = playlist.indices[index]; - - if(playlist.in_ram) - { - buf = playlist_buffer + seek; - max = playlist_end_pos - seek; - } - else + /* 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') { - if(-1 == playlist.fd) - playlist.fd = open(playlist.filename, O_RDONLY); + int insert_pos; - if(-1 != playlist.fd) + /* we need to format so that relative paths are correctly + handled */ + if (format_track_path(trackname, temp_buf, sizeof(trackname), max, + dir) < 0) { - buf = playlist_buffer; - lseek(playlist.fd, seek, SEEK_SET); - max = read(playlist.fd, buf, MAX_PATH); + result = -1; + break; } - else - return NULL; + + 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(); } - /* Zero-terminate the file name */ - seek=0; - while((buf[seek] != '\n') && - (buf[seek] != '\r') && - (seek < max)) - seek++; + close(fd); + fsync(playlist.control_fd); - /* Now work back killing white space */ - while((buf[seek-1] == ' ') || - (buf[seek-1] == '\t')) - seek--; + *temp_ptr = '/'; - buf[seek]=0; - - /* replace backslashes with forward slashes */ - for ( i=0; i= 0 && global_settings.play_selected) { - while (buf) - { - fd = open(buf, O_RDONLY); - if (fd >= 0) - { - close(fd); - break; - } - - buf = strchr(buf+1, '/'); - } + /* 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)); - 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; - } + randomise_playlist(random_seed, start_current, true); - return buf; + return playlist.index; } -/* - * 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 */ +/* shuffle currently playing playlist */ +int playlist_randomise(unsigned int seed, bool start_current) { - char *sep=""; - int dirlen; - empty_playlist(queue_resume); + int result = randomise_playlist(seed, start_current, true); - playlist.index = start_index; - playlist.first_index = first_index; + if (result != -1) + mpeg_flush_and_reload_tracks(); -#ifdef HAVE_LCD_BITMAP - if(global_settings.statusbar) - lcd_setmargins(0, STATUSBAR_HEIGHT); - else - lcd_setmargins(0, 0); -#endif + return result; +} - /* 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; - } +/* sort currently playing playlist */ +int playlist_sort(bool start_current) +{ + int result = sort_playlist(start_current, true); - dirlen = strlen(dir); + if (result != -1) + mpeg_flush_and_reload_tracks(); - /* 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; + return result; +} - snprintf(playlist.filename, sizeof(playlist.filename), - "%s%s%s", - dir, sep, file); +/* start playing current playlist at specified index/offset */ +int playlist_start(int start_index, int offset) +{ + playlist.index = start_index; + mpeg_play(offset); - /* 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; + return 0; +} - /* store the seek position before the shuffle */ - int seek_pos = playlist.indices[start_index]; +/* Returns false if 'steps' is out of bounds, else true */ +bool playlist_check(int steps) +{ + int index = get_next_index(steps); + return (index >= 0); +} - /* now shuffle around the indices */ - randomise_playlist( random_seed ); +/* 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; - 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. */ + index = get_next_index(steps); + if (index < 0) + return NULL; - 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 */ - } - } - } + control_file = playlist.indices[index] & PLAYLIST_INSERT_TYPE_MASK; + seek = playlist.indices[index] & PLAYLIST_SEEK_MASK; - if (queue_resume) - { - /* update the queue indices */ - add_indices_to_queuelist(queue_resume_index); + if (get_filename(seek, control_file, now_playing, MAX_PATH+1) < 0) + return NULL; + + temp_ptr = now_playing; - if (queue_resume == QUEUE_BEGIN_PLAYLIST) + 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) { - /* begin with current playlist index */ - playlist.start_queue = 1; - playlist.index++; /* so we begin at the correct track */ + /* 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; } } - /* also make the first song get playing */ - mpeg_play(start_offset); - - return playlist.index; + return temp_ptr; } /* - * calculate track offsets within a playlist file + * Update indices as track has changed */ -void add_indices_to_playlist(void) +int playlist_next(int steps) { - 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 index; - 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 + if (steps > 0) + { + int i, j; + + /* We need to delete all the queued songs */ + for (i=0, j=steps; i= 0) { - if(playlist.in_ram) { - nread = playlist_end_pos; - } else { - nread = read(playlist.fd, buffer, buflen); - /* Terminate on EOF */ - if(nread <= 0) - break; - } - - p = buffer; + /* check to see if we've gone beyond the last inserted track */ + int rot_index = index; + int rot_last_pos = playlist.last_insert_pos; - for(count=0; count < nread; count++,p++) { + rot_index -= playlist.first_index; + if (rot_index < 0) + rot_index += playlist.amount; - /* Are we on a new line? */ - if((*p == '\n') || (*p == '\r')) - { - store_index = true; - } - else if(store_index) + 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