diff options
| author | Hardeep Sidhu <dyp@pobox.com> | 2003-07-01 21:05:43 +0000 |
|---|---|---|
| committer | Hardeep Sidhu <dyp@pobox.com> | 2003-07-01 21:05:43 +0000 |
| commit | 9e4262081b4ab5bad2e2708ea064643cf828685c (patch) | |
| tree | bd809cc4616a2ed61bdbff217d26a13fd78b6609 /apps/playlist.c | |
| parent | 928a09e3f464dc62e2863f8d77e766578788ba13 (diff) | |
| download | rockbox-9e4262081b4ab5bad2e2708ea064643cf828685c.zip rockbox-9e4262081b4ab5bad2e2708ea064643cf828685c.tar.gz rockbox-9e4262081b4ab5bad2e2708ea064643cf828685c.tar.bz2 rockbox-9e4262081b4ab5bad2e2708ea064643cf828685c.tar.xz | |
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
Diffstat (limited to 'apps/playlist.c')
| -rw-r--r-- | apps/playlist.c | 2039 |
1 files changed, 1538 insertions, 501 deletions
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; } |