Gigabeat: Add timer functionality. Rework tick timer setup to be exactly 100Hz. Metro...
[Rockbox.git] / apps / playlist.c
blob7b1b91e123a4d978a40efb010aa453387bb5a5c8
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 by wavey@wavey.org
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
21 Dynamic playlist design (based on design originally proposed by ricII)
23 There are two files associated with a dynamic playlist:
24 1. Playlist file : This file contains the initial songs in the playlist.
25 The file is created by the user and stored on the hard
26 drive. NOTE: If we are playing the contents of a
27 directory, there will be no playlist file.
28 2. Control file : This file is automatically created when a playlist is
29 started and contains all the commands done to it.
31 The first non-comment line in a control file must begin with
32 "P:VERSION:DIR:FILE" where VERSION is the playlist control file version,
33 DIR is the directory where the playlist is located and FILE is the
34 playlist filename. For dirplay, FILE will be empty. An empty playlist
35 will have both entries as null.
37 Control file commands:
38 a. Add track (A:<position>:<last position>:<path to track>)
39 - Insert a track at the specified position in the current
40 playlist. Last position is used to specify where last insertion
41 occurred.
42 b. Queue track (Q:<position>:<last position>:<path to track>)
43 - Queue a track at the specified position in the current
44 playlist. Queued tracks differ from added tracks in that they
45 are deleted from the playlist as soon as they are played and
46 they are not saved to disk as part of the playlist.
47 c. Delete track (D:<position>)
48 - Delete track from specified position in the current playlist.
49 d. Shuffle playlist (S:<seed>:<index>)
50 - Shuffle entire playlist with specified seed. The index
51 identifies the first index in the newly shuffled playlist
52 (needed for repeat mode).
53 e. Unshuffle playlist (U:<index>)
54 - Unshuffle entire playlist. The index identifies the first index
55 in the newly unshuffled playlist.
56 f. Reset last insert position (R)
57 - Needed so that insertions work properly after resume
59 Resume:
60 The only resume info that needs to be saved is the current index in the
61 playlist and the position in the track. When resuming, all the commands
62 in the control file will be reapplied so that the playlist indices are
63 exactly the same as before shutdown. To avoid unnecessary disk
64 accesses, the shuffle mode settings are also saved in settings and only
65 flushed to disk when required.
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
71 #include <ctype.h>
72 #include "playlist.h"
73 #include "file.h"
74 #include "action.h"
75 #include "dir.h"
76 #include "sprintf.h"
77 #include "debug.h"
78 #include "audio.h"
79 #include "lcd.h"
80 #include "kernel.h"
81 #include "settings.h"
82 #include "status.h"
83 #include "applimits.h"
84 #include "screens.h"
85 #include "buffer.h"
86 #include "atoi.h"
87 #include "misc.h"
88 #include "button.h"
89 #include "filetree.h"
90 #include "abrepeat.h"
91 #include "dircache.h"
92 #include "thread.h"
93 #include "usb.h"
94 #include "filetypes.h"
95 #ifdef HAVE_LCD_BITMAP
96 #include "icons.h"
97 #endif
99 #include "lang.h"
100 #include "talk.h"
101 #include "splash.h"
102 #include "rbunicode.h"
104 #define PLAYLIST_CONTROL_FILE ROCKBOX_DIR "/.playlist_control"
105 #define PLAYLIST_CONTROL_FILE_VERSION 2
108 Each playlist index has a flag associated with it which identifies what
109 type of track it is. These flags are stored in the 4 high order bits of
110 the index.
112 NOTE: This limits the playlist file size to a max of 256M.
114 Bits 31-30:
115 00 = Playlist track
116 01 = Track was prepended into playlist
117 10 = Track was inserted into playlist
118 11 = Track was appended into playlist
119 Bit 29:
120 0 = Added track
121 1 = Queued track
122 Bit 28:
123 0 = Track entry is valid
124 1 = Track does not exist on disk and should be skipped
126 #define PLAYLIST_SEEK_MASK 0x0FFFFFFF
127 #define PLAYLIST_INSERT_TYPE_MASK 0xC0000000
128 #define PLAYLIST_QUEUE_MASK 0x20000000
130 #define PLAYLIST_INSERT_TYPE_PREPEND 0x40000000
131 #define PLAYLIST_INSERT_TYPE_INSERT 0x80000000
132 #define PLAYLIST_INSERT_TYPE_APPEND 0xC0000000
134 #define PLAYLIST_QUEUED 0x20000000
135 #define PLAYLIST_SKIPPED 0x10000000
137 #define PLAYLIST_DISPLAY_COUNT 10
139 struct directory_search_context {
140 struct playlist_info* playlist;
141 int position;
142 bool queue;
143 int count;
146 static bool changing_dir = false;
148 static struct playlist_info current_playlist;
149 static char now_playing[MAX_PATH+1];
151 static void empty_playlist(struct playlist_info* playlist, bool resume);
152 static void new_playlist(struct playlist_info* playlist, const char *dir,
153 const char *file);
154 static void create_control(struct playlist_info* playlist);
155 static int check_control(struct playlist_info* playlist);
156 static int recreate_control(struct playlist_info* playlist);
157 static void update_playlist_filename(struct playlist_info* playlist,
158 const char *dir, const char *file);
159 static int add_indices_to_playlist(struct playlist_info* playlist,
160 char* buffer, size_t buflen);
161 static int add_track_to_playlist(struct playlist_info* playlist,
162 const char *filename, int position,
163 bool queue, int seek_pos);
164 static int directory_search_callback(char* filename, void* context);
165 static int remove_track_from_playlist(struct playlist_info* playlist,
166 int position, bool write);
167 static int randomise_playlist(struct playlist_info* playlist,
168 unsigned int seed, bool start_current,
169 bool write);
170 static int sort_playlist(struct playlist_info* playlist, bool start_current,
171 bool write);
172 static int get_next_index(const struct playlist_info* playlist, int steps,
173 int repeat_mode);
174 static void find_and_set_playlist_index(struct playlist_info* playlist,
175 unsigned int seek);
176 static int compare(const void* p1, const void* p2);
177 static int get_filename(struct playlist_info* playlist, int index, int seek,
178 bool control_file, char *buf, int buf_length);
179 static int get_next_directory(char *dir);
180 static int get_next_dir(char *dir, bool is_forward, bool recursion);
181 static int get_previous_directory(char *dir);
182 static int check_subdir_for_music(char *dir, char *subdir);
183 static int format_track_path(char *dest, char *src, int buf_length, int max,
184 char *dir);
185 static void display_playlist_count(int count, const unsigned char *fmt);
186 static void display_buffer_full(void);
187 static int flush_cached_control(struct playlist_info* playlist);
188 static int update_control(struct playlist_info* playlist,
189 enum playlist_command command, int i1, int i2,
190 const char* s1, const char* s2, void* data);
191 static void sync_control(struct playlist_info* playlist, bool force);
192 static int rotate_index(const struct playlist_info* playlist, int index);
194 #ifdef HAVE_DIRCACHE
195 #define PLAYLIST_LOAD_POINTERS 1
197 static struct event_queue playlist_queue;
198 static long playlist_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)];
199 static const char playlist_thread_name[] = "playlist cachectrl";
200 #endif
203 * remove any files and indices associated with the playlist
205 static void empty_playlist(struct playlist_info* playlist, bool resume)
207 playlist->filename[0] = '\0';
208 playlist->utf8 = true;
210 if(playlist->fd >= 0)
211 /* If there is an already open playlist, close it. */
212 close(playlist->fd);
213 playlist->fd = -1;
215 if(playlist->control_fd >= 0)
216 close(playlist->control_fd);
217 playlist->control_fd = -1;
218 playlist->control_created = false;
220 playlist->in_ram = false;
222 if (playlist->buffer)
223 playlist->buffer[0] = 0;
225 playlist->buffer_end_pos = 0;
227 playlist->index = 0;
228 playlist->first_index = 0;
229 playlist->amount = 0;
230 playlist->last_insert_pos = -1;
231 playlist->seed = 0;
232 playlist->shuffle_modified = false;
233 playlist->deleted = false;
234 playlist->num_inserted_tracks = 0;
235 playlist->started = false;
237 playlist->num_cached = 0;
238 playlist->pending_control_sync = false;
240 if (!resume && playlist->current)
242 /* start with fresh playlist control file when starting new
243 playlist */
244 create_control(playlist);
246 /* Reset resume settings */
247 global_status.resume_first_index = 0;
248 global_status.resume_seed = -1;
253 * Initialize a new playlist for viewing/editing/playing. dir is the
254 * directory where the playlist is located and file is the filename.
256 static void new_playlist(struct playlist_info* playlist, const char *dir,
257 const char *file)
259 empty_playlist(playlist, false);
261 if (!file)
263 file = "";
265 if (dir && playlist->current) /* !current cannot be in_ram */
266 playlist->in_ram = true;
267 else
268 dir = ""; /* empty playlist */
271 update_playlist_filename(playlist, dir, file);
273 if (playlist->control_fd >= 0)
275 update_control(playlist, PLAYLIST_COMMAND_PLAYLIST,
276 PLAYLIST_CONTROL_FILE_VERSION, -1, dir, file, NULL);
277 sync_control(playlist, false);
282 * create control file for playlist
284 static void create_control(struct playlist_info* playlist)
286 playlist->control_fd = open(playlist->control_filename,
287 O_CREAT|O_RDWR|O_TRUNC);
288 if (playlist->control_fd < 0)
290 if (check_rockboxdir())
292 gui_syncsplash(HZ*2, (unsigned char *)"%s (%d)",
293 str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR),
294 playlist->control_fd);
296 playlist->control_created = false;
298 else
300 playlist->control_created = true;
305 * validate the control file. This may include creating/initializing it if
306 * necessary;
308 static int check_control(struct playlist_info* playlist)
310 if (!playlist->control_created)
312 create_control(playlist);
314 if (playlist->control_fd >= 0)
316 char* dir = playlist->filename;
317 char* file = playlist->filename+playlist->dirlen;
318 char c = playlist->filename[playlist->dirlen-1];
320 playlist->filename[playlist->dirlen-1] = '\0';
322 update_control(playlist, PLAYLIST_COMMAND_PLAYLIST,
323 PLAYLIST_CONTROL_FILE_VERSION, -1, dir, file, NULL);
324 sync_control(playlist, false);
325 playlist->filename[playlist->dirlen-1] = c;
329 if (playlist->control_fd < 0)
330 return -1;
332 return 0;
336 * recreate the control file based on current playlist entries
338 static int recreate_control(struct playlist_info* playlist)
340 char temp_file[MAX_PATH+1];
341 int temp_fd = -1;
342 int i;
343 int result = 0;
345 if(playlist->control_fd >= 0)
347 char* dir = playlist->filename;
348 char* file = playlist->filename+playlist->dirlen;
349 char c = playlist->filename[playlist->dirlen-1];
351 close(playlist->control_fd);
353 snprintf(temp_file, sizeof(temp_file), "%s_temp",
354 playlist->control_filename);
356 if (rename(playlist->control_filename, temp_file) < 0)
357 return -1;
359 temp_fd = open(temp_file, O_RDONLY);
360 if (temp_fd < 0)
361 return -1;
363 playlist->control_fd = open(playlist->control_filename,
364 O_CREAT|O_RDWR|O_TRUNC);
365 if (playlist->control_fd < 0)
366 return -1;
368 playlist->filename[playlist->dirlen-1] = '\0';
370 /* cannot call update_control() because of mutex */
371 result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n",
372 PLAYLIST_CONTROL_FILE_VERSION, dir, file);
374 playlist->filename[playlist->dirlen-1] = c;
376 if (result < 0)
378 close(temp_fd);
379 return result;
383 playlist->seed = 0;
384 playlist->shuffle_modified = false;
385 playlist->deleted = false;
386 playlist->num_inserted_tracks = 0;
388 if (playlist->current)
390 global_status.resume_seed = -1;
391 status_save();
394 for (i=0; i<playlist->amount; i++)
396 if (playlist->indices[i] & PLAYLIST_INSERT_TYPE_MASK)
398 bool queue = playlist->indices[i] & PLAYLIST_QUEUE_MASK;
399 char inserted_file[MAX_PATH+1];
401 lseek(temp_fd, playlist->indices[i] & PLAYLIST_SEEK_MASK,
402 SEEK_SET);
403 read_line(temp_fd, inserted_file, sizeof(inserted_file));
405 result = fdprintf(playlist->control_fd, "%c:%d:%d:",
406 queue?'Q':'A', i, playlist->last_insert_pos);
407 if (result > 0)
409 /* save the position in file where name is written */
410 int seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR);
412 result = fdprintf(playlist->control_fd, "%s\n",
413 inserted_file);
415 playlist->indices[i] =
416 (playlist->indices[i] & ~PLAYLIST_SEEK_MASK) | seek_pos;
419 if (result < 0)
420 break;
422 playlist->num_inserted_tracks++;
426 close(temp_fd);
427 remove(temp_file);
428 fsync(playlist->control_fd);
430 if (result < 0)
431 return result;
433 return 0;
437 * store directory and name of playlist file
439 static void update_playlist_filename(struct playlist_info* playlist,
440 const char *dir, const char *file)
442 char *sep="";
443 int dirlen = strlen(dir);
444 int filelen = strlen(file);
446 /* Default to utf8 unless explicitly told otherwise. */
447 playlist->utf8 = !(filelen > 4 && strcasecmp(&file[filelen - 4], ".m3u") == 0);
449 /* If the dir does not end in trailing slash, we use a separator.
450 Otherwise we don't. */
451 if('/' != dir[dirlen-1])
453 sep="/";
454 dirlen++;
457 playlist->dirlen = dirlen;
459 snprintf(playlist->filename, sizeof(playlist->filename),
460 "%s%s%s", dir, sep, file);
464 * calculate track offsets within a playlist file
466 static int add_indices_to_playlist(struct playlist_info* playlist,
467 char* buffer, size_t buflen)
469 unsigned int nread;
470 unsigned int i = 0;
471 unsigned int count = 0;
472 bool store_index;
473 unsigned char *p;
474 int result = 0;
476 if(-1 == playlist->fd)
477 playlist->fd = open(playlist->filename, O_RDONLY);
478 if(playlist->fd < 0)
479 return -1; /* failure */
481 #ifdef HAVE_LCD_BITMAP
482 if(global_settings.statusbar)
483 lcd_setmargins(0, STATUSBAR_HEIGHT);
484 else
485 lcd_setmargins(0, 0);
486 #endif
488 gui_syncsplash(0, str(LANG_PLAYLIST_LOAD));
490 if (!buffer)
492 /* use mp3 buffer for maximum load speed */
493 audio_stop();
494 #if CONFIG_CODEC != SWCODEC
495 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
496 buflen = (audiobufend - audiobuf);
497 buffer = (char *)audiobuf;
498 #else
499 buffer = (char *)audio_get_buffer(false, &buflen);
500 #endif
503 store_index = true;
505 while(1)
507 nread = read(playlist->fd, buffer, buflen);
508 /* Terminate on EOF */
509 if(nread <= 0)
510 break;
512 p = (unsigned char *)buffer;
514 /* utf8 BOM at beginning of file? */
515 if(i == 0 && nread > 3
516 && *p == 0xef && *(p+1) == 0xbb && *(p+2) == 0xbf) {
517 nread -= 3;
518 p += 3;
519 i += 3;
520 playlist->utf8 = true; /* Override any earlier indication. */
523 for(count=0; count < nread; count++,p++) {
525 /* Are we on a new line? */
526 if((*p == '\n') || (*p == '\r'))
528 store_index = true;
530 else if(store_index)
532 store_index = false;
534 if(*p != '#')
536 if ( playlist->amount >= playlist->max_playlist_size ) {
537 display_buffer_full();
538 result = -1;
539 goto exit;
542 /* Store a new entry */
543 playlist->indices[ playlist->amount ] = i+count;
544 #ifdef HAVE_DIRCACHE
545 if (playlist->filenames)
546 playlist->filenames[ playlist->amount ] = NULL;
547 #endif
548 playlist->amount++;
553 i+= count;
556 exit:
557 #ifdef HAVE_DIRCACHE
558 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
559 #endif
561 return result;
565 * Removes all tracks, from the playlist, leaving the presently playing
566 * track queued.
568 int remove_all_tracks(struct playlist_info *playlist)
570 int result;
572 if (playlist == NULL)
573 playlist = &current_playlist;
575 while (playlist->index > 0)
576 if ((result = remove_track_from_playlist(playlist, 0, true)) < 0)
577 return result;
579 while (playlist->amount > 1)
580 if ((result = remove_track_from_playlist(playlist, 1, true)) < 0)
581 return result;
583 if (playlist->amount == 1) {
584 playlist->indices[0] |= PLAYLIST_QUEUED;
587 return 0;
592 * Add track to playlist at specified position. There are five special
593 * positions that can be specified:
594 * PLAYLIST_PREPEND - Add track at beginning of playlist
595 * PLAYLIST_INSERT - Add track after current song. NOTE: If
596 * there are already inserted tracks then track
597 * is added to the end of the insertion list
598 * PLAYLIST_INSERT_FIRST - Add track immediately after current song, no
599 * matter what other tracks have been inserted
600 * PLAYLIST_INSERT_LAST - Add track to end of playlist
601 * PLAYLIST_INSERT_SHUFFLED - Add track at some random point between the
602 * current playing track and end of playlist
603 * PLAYLIST_REPLACE - Erase current playlist, Cue the current track
604 * and inster this track at the end.
606 static int add_track_to_playlist(struct playlist_info* playlist,
607 const char *filename, int position,
608 bool queue, int seek_pos)
610 int insert_position, orig_position;
611 unsigned long flags = PLAYLIST_INSERT_TYPE_INSERT;
612 int i;
614 insert_position = orig_position = position;
616 if (playlist->amount >= playlist->max_playlist_size)
618 display_buffer_full();
619 return -1;
622 switch (position)
624 case PLAYLIST_PREPEND:
625 position = insert_position = playlist->first_index;
626 break;
627 case PLAYLIST_INSERT:
628 /* if there are already inserted tracks then add track to end of
629 insertion list else add after current playing track */
630 if (playlist->last_insert_pos >= 0 &&
631 playlist->last_insert_pos < playlist->amount &&
632 (playlist->indices[playlist->last_insert_pos]&
633 PLAYLIST_INSERT_TYPE_MASK) == PLAYLIST_INSERT_TYPE_INSERT)
634 position = insert_position = playlist->last_insert_pos+1;
635 else if (playlist->amount > 0)
636 position = insert_position = playlist->index + 1;
637 else
638 position = insert_position = 0;
640 if (playlist->started)
641 playlist->last_insert_pos = position;
642 break;
643 case PLAYLIST_INSERT_FIRST:
644 if (playlist->amount > 0)
645 position = insert_position = playlist->index + 1;
646 else
647 position = insert_position = 0;
649 if (playlist->last_insert_pos < 0 && playlist->started)
650 playlist->last_insert_pos = position;
651 break;
652 case PLAYLIST_INSERT_LAST:
653 if (playlist->first_index > 0)
654 position = insert_position = playlist->first_index;
655 else
656 position = insert_position = playlist->amount;
657 break;
658 case PLAYLIST_INSERT_SHUFFLED:
660 if (playlist->started)
662 int offset;
663 int n = playlist->amount -
664 rotate_index(playlist, playlist->index);
666 if (n > 0)
667 offset = rand() % n;
668 else
669 offset = 0;
671 position = playlist->index + offset + 1;
672 if (position >= playlist->amount)
673 position -= playlist->amount;
675 insert_position = position;
677 else
678 position = insert_position = (rand() % (playlist->amount+1));
679 break;
681 case PLAYLIST_REPLACE:
682 if (remove_all_tracks(playlist) < 0)
683 return -1;
685 position = insert_position = playlist->index + 1;
686 break;
689 if (queue)
690 flags |= PLAYLIST_QUEUED;
692 /* shift indices so that track can be added */
693 for (i=playlist->amount; i>insert_position; i--)
695 playlist->indices[i] = playlist->indices[i-1];
696 #ifdef HAVE_DIRCACHE
697 if (playlist->filenames)
698 playlist->filenames[i] = playlist->filenames[i-1];
699 #endif
702 /* update stored indices if needed */
703 if (playlist->amount > 0 && insert_position <= playlist->index &&
704 playlist->started)
705 playlist->index++;
707 if (playlist->amount > 0 && insert_position <= playlist->first_index &&
708 orig_position != PLAYLIST_PREPEND && playlist->started)
710 playlist->first_index++;
712 if (seek_pos < 0 && playlist->current)
714 global_status.resume_first_index = playlist->first_index;
715 status_save();
719 if (insert_position < playlist->last_insert_pos ||
720 (insert_position == playlist->last_insert_pos && position < 0))
721 playlist->last_insert_pos++;
723 if (seek_pos < 0 && playlist->control_fd >= 0)
725 int result = update_control(playlist,
726 (queue?PLAYLIST_COMMAND_QUEUE:PLAYLIST_COMMAND_ADD), position,
727 playlist->last_insert_pos, filename, NULL, &seek_pos);
729 if (result < 0)
730 return result;
733 playlist->indices[insert_position] = flags | seek_pos;
735 #ifdef HAVE_DIRCACHE
736 if (playlist->filenames)
737 playlist->filenames[insert_position] = NULL;
738 #endif
740 playlist->amount++;
741 playlist->num_inserted_tracks++;
743 return insert_position;
747 * Callback for playlist_directory_tracksearch to insert track into
748 * playlist.
750 static int directory_search_callback(char* filename, void* context)
752 struct directory_search_context* c =
753 (struct directory_search_context*) context;
754 int insert_pos;
756 insert_pos = add_track_to_playlist(c->playlist, filename, c->position,
757 c->queue, -1);
759 if (insert_pos < 0)
760 return -1;
762 (c->count)++;
764 /* Make sure tracks are inserted in correct order if user requests
765 INSERT_FIRST */
766 if (c->position == PLAYLIST_INSERT_FIRST || c->position >= 0)
767 c->position = insert_pos + 1;
769 if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0)
771 unsigned char* count_str;
773 if (c->queue)
774 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
775 else
776 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
778 display_playlist_count(c->count, count_str);
780 if ((c->count) == PLAYLIST_DISPLAY_COUNT &&
781 (audio_status() & AUDIO_STATUS_PLAY) &&
782 c->playlist->started)
783 audio_flush_and_reload_tracks();
786 return 0;
790 * remove track at specified position
792 static int remove_track_from_playlist(struct playlist_info* playlist,
793 int position, bool write)
795 int i;
796 bool inserted;
798 if (playlist->amount <= 0)
799 return -1;
801 inserted = playlist->indices[position] & PLAYLIST_INSERT_TYPE_MASK;
803 /* shift indices now that track has been removed */
804 for (i=position; i<playlist->amount; i++)
806 playlist->indices[i] = playlist->indices[i+1];
807 #ifdef HAVE_DIRCACHE
808 if (playlist->filenames)
809 playlist->filenames[i] = playlist->filenames[i+1];
810 #endif
813 playlist->amount--;
815 if (inserted)
816 playlist->num_inserted_tracks--;
817 else
818 playlist->deleted = true;
820 /* update stored indices if needed */
821 if (position < playlist->index)
822 playlist->index--;
824 if (position < playlist->first_index)
826 playlist->first_index--;
828 if (write)
830 global_status.resume_first_index = playlist->first_index;
831 status_save();
835 if (position <= playlist->last_insert_pos)
836 playlist->last_insert_pos--;
838 if (write && playlist->control_fd >= 0)
840 int result = update_control(playlist, PLAYLIST_COMMAND_DELETE,
841 position, -1, NULL, NULL, NULL);
843 if (result < 0)
844 return result;
846 sync_control(playlist, false);
849 return 0;
853 * randomly rearrange the array of indices for the playlist. If start_current
854 * is true then update the index to the new index of the current playing track
856 static int randomise_playlist(struct playlist_info* playlist,
857 unsigned int seed, bool start_current,
858 bool write)
860 int count;
861 int candidate;
862 long store;
863 unsigned int current = playlist->indices[playlist->index];
865 /* seed 0 is used to identify sorted playlist for resume purposes */
866 if (seed == 0)
867 seed = 1;
869 /* seed with the given seed */
870 srand(seed);
872 /* randomise entire indices list */
873 for(count = playlist->amount - 1; count >= 0; count--)
875 /* the rand is from 0 to RAND_MAX, so adjust to our value range */
876 candidate = rand() % (count + 1);
878 /* now swap the values at the 'count' and 'candidate' positions */
879 store = playlist->indices[candidate];
880 playlist->indices[candidate] = playlist->indices[count];
881 playlist->indices[count] = store;
882 #ifdef HAVE_DIRCACHE
883 if (playlist->filenames)
885 store = (long)playlist->filenames[candidate];
886 playlist->filenames[candidate] = playlist->filenames[count];
887 playlist->filenames[count] = (struct dircache_entry *)store;
889 #endif
892 if (start_current)
893 find_and_set_playlist_index(playlist, current);
895 /* indices have been moved so last insert position is no longer valid */
896 playlist->last_insert_pos = -1;
898 playlist->seed = seed;
899 if (playlist->num_inserted_tracks > 0 || playlist->deleted)
900 playlist->shuffle_modified = true;
902 if (write)
904 update_control(playlist, PLAYLIST_COMMAND_SHUFFLE, seed,
905 playlist->first_index, NULL, NULL, NULL);
906 global_status.resume_seed = seed;
907 status_save();
910 return 0;
914 * Sort the array of indices for the playlist. If start_current is true then
915 * set the index to the new index of the current song.
917 static int sort_playlist(struct playlist_info* playlist, bool start_current,
918 bool write)
920 unsigned int current = playlist->indices[playlist->index];
922 if (playlist->amount > 0)
923 qsort(playlist->indices, playlist->amount,
924 sizeof(playlist->indices[0]), compare);
926 #ifdef HAVE_DIRCACHE
927 /** We need to re-check the song names from disk because qsort can't
928 * sort two arrays at once :/
929 * FIXME: Please implement a better way to do this. */
930 memset(playlist->filenames, 0, playlist->max_playlist_size * sizeof(int));
931 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
932 #endif
934 if (start_current)
935 find_and_set_playlist_index(playlist, current);
937 /* indices have been moved so last insert position is no longer valid */
938 playlist->last_insert_pos = -1;
940 if (!playlist->num_inserted_tracks && !playlist->deleted)
941 playlist->shuffle_modified = false;
942 if (write && playlist->control_fd >= 0)
944 update_control(playlist, PLAYLIST_COMMAND_UNSHUFFLE,
945 playlist->first_index, -1, NULL, NULL, NULL);
946 global_status.resume_seed = 0;
947 status_save();
950 return 0;
953 /* Calculate how many steps we have to really step when skipping entries
954 * marked as bad.
956 static int calculate_step_count(const struct playlist_info *playlist, int steps)
958 int i, count, direction;
959 int index;
960 int stepped_count = 0;
962 if (steps < 0)
964 direction = -1;
965 count = -steps;
967 else
969 direction = 1;
970 count = steps;
973 index = playlist->index;
974 i = 0;
975 do {
976 /* Boundary check */
977 if (index < 0)
978 index += playlist->amount;
979 if (index >= playlist->amount)
980 index -= playlist->amount;
982 /* Check if we found a bad entry. */
983 if (playlist->indices[index] & PLAYLIST_SKIPPED)
985 steps += direction;
986 /* Are all entries bad? */
987 if (stepped_count++ > playlist->amount)
988 break ;
990 else
991 i++;
993 index += direction;
994 } while (i <= count);
996 return steps;
999 /* Marks the index of the track to be skipped that is "steps" away from
1000 * current playing track.
1002 void playlist_skip_entry(struct playlist_info *playlist, int steps)
1004 int index;
1006 if (playlist == NULL)
1007 playlist = &current_playlist;
1009 /* need to account for already skipped tracks */
1010 steps = calculate_step_count(playlist, steps);
1012 index = playlist->index + steps;
1013 if (index < 0)
1014 index += playlist->amount;
1015 else if (index >= playlist->amount)
1016 index -= playlist->amount;
1018 playlist->indices[index] |= PLAYLIST_SKIPPED;
1022 * returns the index of the track that is "steps" away from current playing
1023 * track.
1025 static int get_next_index(const struct playlist_info* playlist, int steps,
1026 int repeat_mode)
1028 int current_index = playlist->index;
1029 int next_index = -1;
1031 if (playlist->amount <= 0)
1032 return -1;
1034 if (repeat_mode == -1)
1035 repeat_mode = global_settings.repeat_mode;
1037 if (repeat_mode == REPEAT_SHUFFLE && playlist->amount <= 1)
1038 repeat_mode = REPEAT_ALL;
1040 steps = calculate_step_count(playlist, steps);
1041 switch (repeat_mode)
1043 case REPEAT_SHUFFLE:
1044 /* Treat repeat shuffle just like repeat off. At end of playlist,
1045 play will be resumed in playlist_next() */
1046 case REPEAT_OFF:
1048 current_index = rotate_index(playlist, current_index);
1049 next_index = current_index+steps;
1050 if ((next_index < 0) || (next_index >= playlist->amount))
1051 next_index = -1;
1052 else
1053 next_index = (next_index+playlist->first_index) %
1054 playlist->amount;
1056 break;
1059 case REPEAT_ONE:
1060 #ifdef AB_REPEAT_ENABLE
1061 case REPEAT_AB:
1062 #endif
1063 next_index = current_index;
1064 break;
1066 case REPEAT_ALL:
1067 default:
1069 next_index = (current_index+steps) % playlist->amount;
1070 while (next_index < 0)
1071 next_index += playlist->amount;
1073 if (steps >= playlist->amount)
1075 int i, index;
1077 index = next_index;
1078 next_index = -1;
1080 /* second time around so skip the queued files */
1081 for (i=0; i<playlist->amount; i++)
1083 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
1084 index = (index+1) % playlist->amount;
1085 else
1087 next_index = index;
1088 break;
1092 break;
1096 /* No luck if the whole playlist was bad. */
1097 if (playlist->indices[next_index] & PLAYLIST_SKIPPED)
1098 return -1;
1100 return next_index;
1104 * Search for the seek track and set appropriate indices. Used after shuffle
1105 * to make sure the current index is still pointing to correct track.
1107 static void find_and_set_playlist_index(struct playlist_info* playlist,
1108 unsigned int seek)
1110 int i;
1112 /* Set the index to the current song */
1113 for (i=0; i<playlist->amount; i++)
1115 if (playlist->indices[i] == seek)
1117 playlist->index = playlist->first_index = i;
1119 if (playlist->current)
1121 global_status.resume_first_index = i;
1122 status_save();
1125 break;
1131 * used to sort track indices. Sort order is as follows:
1132 * 1. Prepended tracks (in prepend order)
1133 * 2. Playlist/directory tracks (in playlist order)
1134 * 3. Inserted/Appended tracks (in insert order)
1136 static int compare(const void* p1, const void* p2)
1138 unsigned long* e1 = (unsigned long*) p1;
1139 unsigned long* e2 = (unsigned long*) p2;
1140 unsigned long flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK;
1141 unsigned long flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK;
1143 if (flags1 == flags2)
1144 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
1145 else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND ||
1146 flags2 == PLAYLIST_INSERT_TYPE_APPEND)
1147 return -1;
1148 else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND ||
1149 flags2 == PLAYLIST_INSERT_TYPE_PREPEND)
1150 return 1;
1151 else if (flags1 && flags2)
1152 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
1153 else
1154 return *e1 - *e2;
1157 #ifdef HAVE_DIRCACHE
1159 * Thread to update filename pointers to dircache on background
1160 * without affecting playlist load up performance. This thread also flushes
1161 * any pending control commands when the disk spins up.
1163 static void playlist_thread(void)
1165 struct event ev;
1166 bool dirty_pointers = false;
1167 static char tmp[MAX_PATH+1];
1169 struct playlist_info *playlist;
1170 int index;
1171 int seek;
1172 bool control_file;
1174 int sleep_time = 5;
1176 if (global_settings.disk_spindown > 1 &&
1177 global_settings.disk_spindown <= 5)
1178 sleep_time = global_settings.disk_spindown - 1;
1180 while (1)
1182 queue_wait_w_tmo(&playlist_queue, &ev, HZ*sleep_time);
1184 switch (ev.id)
1186 case PLAYLIST_LOAD_POINTERS:
1187 dirty_pointers = true;
1188 break ;
1190 /* Start the background scanning after either the disk spindown
1191 timeout or 5s, whichever is less */
1192 case SYS_TIMEOUT:
1193 playlist = &current_playlist;
1195 if (playlist->control_fd >= 0
1196 # ifndef SIMULATOR
1197 && ata_disk_is_active()
1198 # endif
1201 if (playlist->num_cached > 0)
1203 mutex_lock(&playlist->control_mutex);
1204 flush_cached_control(playlist);
1205 mutex_unlock(&playlist->control_mutex);
1208 sync_control(playlist, true);
1211 if (!dirty_pointers)
1212 break ;
1214 if (!dircache_is_enabled() || !playlist->filenames
1215 || playlist->amount <= 0)
1216 break ;
1218 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1219 cpu_boost(true);
1220 #endif
1221 for (index = 0; index < playlist->amount
1222 && queue_empty(&playlist_queue); index++)
1224 /* Process only pointers that are not already loaded. */
1225 if (playlist->filenames[index])
1226 continue ;
1228 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
1229 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
1231 /* Load the filename from playlist file. */
1232 if (get_filename(playlist, index, seek, control_file, tmp,
1233 sizeof(tmp)) < 0)
1234 break ;
1236 /* Set the dircache entry pointer. */
1237 playlist->filenames[index] = dircache_get_entry_ptr(tmp);
1239 /* And be on background so user doesn't notice any delays. */
1240 yield();
1243 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1244 cpu_boost(false);
1245 #endif
1246 dirty_pointers = false;
1247 break ;
1249 #ifndef SIMULATOR
1250 case SYS_USB_CONNECTED:
1251 usb_acknowledge(SYS_USB_CONNECTED_ACK);
1252 usb_wait_for_disconnect(&playlist_queue);
1253 break ;
1254 #endif
1258 #endif
1261 * gets pathname for track at seek index
1263 static int get_filename(struct playlist_info* playlist, int index, int seek,
1264 bool control_file, char *buf, int buf_length)
1266 int fd;
1267 int max = -1;
1268 char tmp_buf[MAX_PATH+1];
1269 char dir_buf[MAX_PATH+1];
1271 if (buf_length > MAX_PATH+1)
1272 buf_length = MAX_PATH+1;
1274 #ifdef HAVE_DIRCACHE
1275 if (dircache_is_enabled() && playlist->filenames)
1277 if (playlist->filenames[index] != NULL)
1279 dircache_copy_path(playlist->filenames[index], tmp_buf, sizeof(tmp_buf)-1);
1280 max = strlen(tmp_buf) + 1;
1283 #else
1284 (void)index;
1285 #endif
1287 if (playlist->in_ram && !control_file && max < 0)
1289 strncpy(tmp_buf, &playlist->buffer[seek], sizeof(tmp_buf));
1290 tmp_buf[MAX_PATH] = '\0';
1291 max = strlen(tmp_buf) + 1;
1293 else if (max < 0)
1295 mutex_lock(&playlist->control_mutex);
1297 if (control_file)
1298 fd = playlist->control_fd;
1299 else
1301 if(-1 == playlist->fd)
1302 playlist->fd = open(playlist->filename, O_RDONLY);
1304 fd = playlist->fd;
1307 if(-1 != fd)
1310 if (lseek(fd, seek, SEEK_SET) != seek)
1311 max = -1;
1312 else
1314 max = read(fd, tmp_buf, MIN((size_t) buf_length, sizeof(tmp_buf)));
1316 if ((max > 0) && !playlist->utf8)
1318 char* end;
1319 int i = 0;
1321 /* Locate EOL. */
1322 while ((tmp_buf[i] != '\n') && (tmp_buf[i] != '\r')
1323 && (i < max))
1325 i++;
1328 /* Now work back killing white space. */
1329 while ((i > 0) && isspace(tmp_buf[i - 1]))
1331 i--;
1334 /* Borrow dir_buf a little... */
1335 /* TODO: iso_decode can overflow dir_buf; it really
1336 * should take a dest size argument.
1338 end = iso_decode(tmp_buf, dir_buf, -1, i);
1339 *end = 0;
1340 strncpy(tmp_buf, dir_buf, sizeof(tmp_buf));
1341 tmp_buf[sizeof(tmp_buf) - 1] = 0;
1342 max = strlen(tmp_buf);
1347 mutex_unlock(&playlist->control_mutex);
1349 if (max < 0)
1351 if (control_file)
1352 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1353 else
1354 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
1356 return max;
1360 strncpy(dir_buf, playlist->filename, playlist->dirlen-1);
1361 dir_buf[playlist->dirlen-1] = 0;
1363 return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf));
1366 static int get_next_directory(char *dir){
1367 return get_next_dir(dir,true,false);
1370 static int get_previous_directory(char *dir){
1371 return get_next_dir(dir,false,false);
1375 * search through all the directories (starting with the current) to find
1376 * one that has tracks to play
1378 static int get_next_dir(char *dir, bool is_forward, bool recursion)
1380 struct playlist_info* playlist = &current_playlist;
1381 int result = -1;
1382 int sort_dir = global_settings.sort_dir;
1383 char *start_dir = NULL;
1384 bool exit = false;
1385 struct tree_context* tc = tree_get_context();
1386 int dirfilter = *(tc->dirfilter);
1388 if (global_settings.next_folder == FOLDER_ADVANCE_RANDOM)
1390 int fd = open(ROCKBOX_DIR "/folder_advance_list.dat",O_RDONLY);
1391 char buffer[MAX_PATH];
1392 int folder_count = 0,i;
1393 srand(current_tick);
1394 *(tc->dirfilter) = SHOW_MUSIC;
1395 if (fd >= 0)
1397 read(fd,&folder_count,sizeof(int));
1398 while (!exit)
1400 i = rand()%folder_count;
1401 lseek(fd,sizeof(int) + (MAX_PATH*i),SEEK_SET);
1402 read(fd,buffer,MAX_PATH);
1403 if (check_subdir_for_music(buffer,"") ==0)
1404 exit = true;
1406 strcpy(dir,buffer);
1407 close(fd);
1408 *(tc->dirfilter) = dirfilter;
1409 reload_directory();
1410 return 0;
1413 /* not random folder advance */
1414 if (recursion){
1415 /* start with root */
1416 dir[0] = '\0';
1418 else{
1419 /* start with current directory */
1420 strncpy(dir, playlist->filename, playlist->dirlen-1);
1421 dir[playlist->dirlen-1] = '\0';
1424 /* use the tree browser dircache to load files */
1425 *(tc->dirfilter) = SHOW_ALL;
1427 /* sort in another direction if previous dir is requested */
1428 if(!is_forward){
1429 if ((global_settings.sort_dir == 0) || (global_settings.sort_dir == 3))
1430 global_settings.sort_dir = 4;
1431 else if (global_settings.sort_dir == 1)
1432 global_settings.sort_dir = 2;
1433 else if (global_settings.sort_dir == 2)
1434 global_settings.sort_dir = 1;
1435 else if (global_settings.sort_dir == 4)
1436 global_settings.sort_dir = 0;
1439 while (!exit)
1441 struct entry *files;
1442 int num_files = 0;
1443 int i;
1445 if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0)
1447 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
1448 exit = true;
1449 result = -1;
1450 break;
1453 files = (struct entry*) tc->dircache;
1454 num_files = tc->filesindir;
1456 for (i=0; i<num_files; i++)
1458 /* user abort */
1459 if (action_userabort(TIMEOUT_NOBLOCK))
1461 result = -1;
1462 exit = true;
1463 break;
1466 if (files[i].attr & ATTR_DIRECTORY)
1468 if (!start_dir)
1470 result = check_subdir_for_music(dir, files[i].name);
1471 if (result != -1)
1473 exit = true;
1474 break;
1477 else if (!strcmp(start_dir, files[i].name))
1478 start_dir = NULL;
1482 if (!exit)
1484 /* move down to parent directory. current directory name is
1485 stored as the starting point for the search in parent */
1486 start_dir = strrchr(dir, '/');
1487 if (start_dir)
1489 *start_dir = '\0';
1490 start_dir++;
1492 else
1493 break;
1497 /* we've overwritten the dircache so tree browser will need to be
1498 reloaded */
1499 reload_directory();
1501 /* restore dirfilter & sort_dir */
1502 *(tc->dirfilter) = dirfilter;
1503 global_settings.sort_dir = sort_dir;
1505 /* special case if nothing found: try start searching again from root */
1506 if (result == -1 && !recursion){
1507 result = get_next_dir(dir,is_forward, true);
1510 return result;
1514 * Checks if there are any music files in the dir or any of its
1515 * subdirectories. May be called recursively.
1517 static int check_subdir_for_music(char *dir, char *subdir)
1519 int result = -1;
1520 int dirlen = strlen(dir);
1521 int num_files = 0;
1522 int i;
1523 struct entry *files;
1524 bool has_music = false;
1525 bool has_subdir = false;
1526 struct tree_context* tc = tree_get_context();
1528 snprintf(dir+dirlen, MAX_PATH-dirlen, "/%s", subdir);
1530 if (ft_load(tc, dir) < 0)
1532 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
1533 return -2;
1536 files = (struct entry*) tc->dircache;
1537 num_files = tc->filesindir;
1539 for (i=0; i<num_files; i++)
1541 if (files[i].attr & ATTR_DIRECTORY)
1542 has_subdir = true;
1543 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
1545 has_music = true;
1546 break;
1550 if (has_music)
1551 return 0;
1553 if (has_subdir)
1555 for (i=0; i<num_files; i++)
1557 if (action_userabort(TIMEOUT_NOBLOCK))
1559 result = -2;
1560 break;
1563 if (files[i].attr & ATTR_DIRECTORY)
1565 result = check_subdir_for_music(dir, files[i].name);
1566 if (!result)
1567 break;
1572 if (result < 0)
1574 if (dirlen)
1576 dir[dirlen] = '\0';
1578 else
1580 strcpy(dir, "/");
1583 /* we now need to reload our current directory */
1584 if(ft_load(tc, dir) < 0)
1585 gui_syncsplash(HZ*2,
1586 str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
1589 return result;
1593 * Returns absolute path of track
1595 static int format_track_path(char *dest, char *src, int buf_length, int max,
1596 char *dir)
1598 int i = 0;
1599 int j;
1600 char *temp_ptr;
1602 /* Zero-terminate the file name */
1603 while((src[i] != '\n') &&
1604 (src[i] != '\r') &&
1605 (i < max))
1606 i++;
1608 /* Now work back killing white space */
1609 while((src[i-1] == ' ') ||
1610 (src[i-1] == '\t'))
1611 i--;
1613 src[i]=0;
1615 /* replace backslashes with forward slashes */
1616 for ( j=0; j<i; j++ )
1617 if ( src[j] == '\\' )
1618 src[j] = '/';
1620 if('/' == src[0])
1622 strncpy(dest, src, buf_length);
1624 else
1626 /* handle dos style drive letter */
1627 if (':' == src[1])
1628 strncpy(dest, &src[2], buf_length);
1629 else if (!strncmp(src, "../", 3))
1631 /* handle relative paths */
1632 i=3;
1633 while(!strncmp(&src[i], "../", 3))
1634 i += 3;
1635 for (j=0; j<i/3; j++) {
1636 temp_ptr = strrchr(dir, '/');
1637 if (temp_ptr)
1638 *temp_ptr = '\0';
1639 else
1640 break;
1642 snprintf(dest, buf_length, "%s/%s", dir, &src[i]);
1644 else if ( '.' == src[0] && '/' == src[1] ) {
1645 snprintf(dest, buf_length, "%s/%s", dir, &src[2]);
1647 else {
1648 snprintf(dest, buf_length, "%s/%s", dir, src);
1652 return 0;
1656 * Display splash message showing progress of playlist/directory insertion or
1657 * save.
1659 static void display_playlist_count(int count, const unsigned char *fmt)
1661 lcd_clear_display();
1663 #ifdef HAVE_LCD_BITMAP
1664 if(global_settings.statusbar)
1665 lcd_setmargins(0, STATUSBAR_HEIGHT);
1666 else
1667 lcd_setmargins(0, 0);
1668 #endif
1670 gui_syncsplash(0, fmt, count,
1671 #if CONFIG_KEYPAD == PLAYER_PAD
1672 str(LANG_STOP_ABORT)
1673 #else
1674 str(LANG_OFF_ABORT)
1675 #endif
1680 * Display buffer full message
1682 static void display_buffer_full(void)
1684 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_BUFFER_FULL));
1688 * Flush any cached control commands to disk. Called when playlist is being
1689 * modified. Returns 0 on success and -1 on failure.
1691 static int flush_cached_control(struct playlist_info* playlist)
1693 int result = 0;
1694 int i;
1696 if (!playlist->num_cached)
1697 return 0;
1699 lseek(playlist->control_fd, 0, SEEK_END);
1701 for (i=0; i<playlist->num_cached; i++)
1703 struct playlist_control_cache* cache =
1704 &(playlist->control_cache[i]);
1706 switch (cache->command)
1708 case PLAYLIST_COMMAND_PLAYLIST:
1709 result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n",
1710 cache->i1, cache->s1, cache->s2);
1711 break;
1712 case PLAYLIST_COMMAND_ADD:
1713 case PLAYLIST_COMMAND_QUEUE:
1714 result = fdprintf(playlist->control_fd, "%c:%d:%d:",
1715 (cache->command == PLAYLIST_COMMAND_ADD)?'A':'Q',
1716 cache->i1, cache->i2);
1717 if (result > 0)
1719 /* save the position in file where name is written */
1720 int* seek_pos = (int *)cache->data;
1721 *seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR);
1722 result = fdprintf(playlist->control_fd, "%s\n",
1723 cache->s1);
1725 break;
1726 case PLAYLIST_COMMAND_DELETE:
1727 result = fdprintf(playlist->control_fd, "D:%d\n", cache->i1);
1728 break;
1729 case PLAYLIST_COMMAND_SHUFFLE:
1730 result = fdprintf(playlist->control_fd, "S:%d:%d\n",
1731 cache->i1, cache->i2);
1732 break;
1733 case PLAYLIST_COMMAND_UNSHUFFLE:
1734 result = fdprintf(playlist->control_fd, "U:%d\n", cache->i1);
1735 break;
1736 case PLAYLIST_COMMAND_RESET:
1737 result = fdprintf(playlist->control_fd, "R\n");
1738 break;
1739 default:
1740 break;
1743 if (result <= 0)
1744 break;
1747 if (result > 0)
1749 if (global_status.resume_seed >= 0)
1751 global_status.resume_seed = -1;
1752 status_save();
1755 playlist->num_cached = 0;
1756 playlist->pending_control_sync = true;
1758 result = 0;
1760 else
1762 result = -1;
1763 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
1766 return result;
1770 * Update control data with new command. Depending on the command, it may be
1771 * cached or flushed to disk.
1773 static int update_control(struct playlist_info* playlist,
1774 enum playlist_command command, int i1, int i2,
1775 const char* s1, const char* s2, void* data)
1777 int result = 0;
1778 struct playlist_control_cache* cache;
1779 bool flush = false;
1781 mutex_lock(&playlist->control_mutex);
1783 cache = &(playlist->control_cache[playlist->num_cached++]);
1785 cache->command = command;
1786 cache->i1 = i1;
1787 cache->i2 = i2;
1788 cache->s1 = s1;
1789 cache->s2 = s2;
1790 cache->data = data;
1792 switch (command)
1794 case PLAYLIST_COMMAND_PLAYLIST:
1795 case PLAYLIST_COMMAND_ADD:
1796 case PLAYLIST_COMMAND_QUEUE:
1797 #ifndef HAVE_DIRCACHE
1798 case PLAYLIST_COMMAND_DELETE:
1799 case PLAYLIST_COMMAND_RESET:
1800 #endif
1801 flush = true;
1802 break;
1803 case PLAYLIST_COMMAND_SHUFFLE:
1804 case PLAYLIST_COMMAND_UNSHUFFLE:
1805 default:
1806 /* only flush when needed */
1807 break;
1810 if (flush || playlist->num_cached == PLAYLIST_MAX_CACHE)
1811 result = flush_cached_control(playlist);
1813 mutex_unlock(&playlist->control_mutex);
1815 return result;
1819 * sync control file to disk
1821 static void sync_control(struct playlist_info* playlist, bool force)
1823 #ifdef HAVE_DIRCACHE
1824 if (playlist->started && force)
1825 #else
1826 (void) force;
1828 if (playlist->started)
1829 #endif
1831 if (playlist->pending_control_sync)
1833 mutex_lock(&playlist->control_mutex);
1834 fsync(playlist->control_fd);
1835 playlist->pending_control_sync = false;
1836 mutex_unlock(&playlist->control_mutex);
1842 * Rotate indices such that first_index is index 0
1844 static int rotate_index(const struct playlist_info* playlist, int index)
1846 index -= playlist->first_index;
1847 if (index < 0)
1848 index += playlist->amount;
1850 return index;
1854 * Initialize playlist entries at startup
1856 void playlist_init(void)
1858 struct playlist_info* playlist = &current_playlist;
1860 playlist->current = true;
1861 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
1862 "%s", PLAYLIST_CONTROL_FILE);
1863 playlist->fd = -1;
1864 playlist->control_fd = -1;
1865 playlist->max_playlist_size = global_settings.max_files_in_playlist;
1866 playlist->indices = buffer_alloc(
1867 playlist->max_playlist_size * sizeof(int));
1868 playlist->buffer_size =
1869 AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir;
1870 playlist->buffer = buffer_alloc(playlist->buffer_size);
1871 mutex_init(&playlist->control_mutex);
1872 empty_playlist(playlist, true);
1874 #ifdef HAVE_DIRCACHE
1875 playlist->filenames = buffer_alloc(
1876 playlist->max_playlist_size * sizeof(int));
1877 memset(playlist->filenames, 0,
1878 playlist->max_playlist_size * sizeof(int));
1879 create_thread(playlist_thread, playlist_stack, sizeof(playlist_stack),
1880 playlist_thread_name IF_PRIO(, PRIORITY_BACKGROUND)
1881 IF_COP(, CPU, false));
1882 queue_init(&playlist_queue, true);
1883 #endif
1887 * Clean playlist at shutdown
1889 void playlist_shutdown(void)
1891 struct playlist_info* playlist = &current_playlist;
1893 if (playlist->control_fd >= 0)
1895 mutex_lock(&playlist->control_mutex);
1897 if (playlist->num_cached > 0)
1898 flush_cached_control(playlist);
1900 close(playlist->control_fd);
1902 mutex_unlock(&playlist->control_mutex);
1907 * Create new playlist
1909 int playlist_create(const char *dir, const char *file)
1911 struct playlist_info* playlist = &current_playlist;
1913 new_playlist(playlist, dir, file);
1915 if (file)
1916 /* load the playlist file */
1917 add_indices_to_playlist(playlist, NULL, 0);
1919 return 0;
1922 #define PLAYLIST_COMMAND_SIZE (MAX_PATH+12)
1925 * Restore the playlist state based on control file commands. Called to
1926 * resume playback after shutdown.
1928 int playlist_resume(void)
1930 struct playlist_info* playlist = &current_playlist;
1931 char *buffer;
1932 size_t buflen;
1933 int nread;
1934 int total_read = 0;
1935 int control_file_size = 0;
1936 bool first = true;
1937 bool sorted = true;
1939 /* use mp3 buffer for maximum load speed */
1940 #if CONFIG_CODEC != SWCODEC
1941 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
1942 buflen = (audiobufend - audiobuf);
1943 buffer = (char *)audiobuf;
1944 #else
1945 buffer = (char *)audio_get_buffer(false, &buflen);
1946 #endif
1948 empty_playlist(playlist, true);
1950 gui_syncsplash(0, str(LANG_WAIT));
1951 playlist->control_fd = open(playlist->control_filename, O_RDWR);
1952 if (playlist->control_fd < 0)
1954 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1955 return -1;
1957 playlist->control_created = true;
1959 control_file_size = filesize(playlist->control_fd);
1960 if (control_file_size <= 0)
1962 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1963 return -1;
1966 /* read a small amount first to get the header */
1967 nread = read(playlist->control_fd, buffer,
1968 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
1969 if(nread <= 0)
1971 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1972 return -1;
1975 playlist->started = true;
1977 while (1)
1979 int result = 0;
1980 int count;
1981 enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT;
1982 int last_newline = 0;
1983 int str_count = -1;
1984 bool newline = true;
1985 bool exit_loop = false;
1986 char *p = buffer;
1987 char *str1 = NULL;
1988 char *str2 = NULL;
1989 char *str3 = NULL;
1990 unsigned long last_tick = current_tick;
1992 for(count=0; count<nread && !exit_loop; count++,p++)
1994 /* So a splash while we are loading. */
1995 if (current_tick - last_tick > HZ/4)
1997 gui_syncsplash(0, str(LANG_LOADING_PERCENT),
1998 (total_read+count)*100/control_file_size,
1999 #if CONFIG_KEYPAD == PLAYER_PAD
2000 str(LANG_STOP_ABORT)
2001 #else
2002 str(LANG_OFF_ABORT)
2003 #endif
2005 if (action_userabort(TIMEOUT_NOBLOCK))
2007 /* FIXME:
2008 * Not sure how to implement this, somebody more familiar
2009 * with the code, please fix this. */
2011 last_tick = current_tick;
2014 /* Are we on a new line? */
2015 if((*p == '\n') || (*p == '\r'))
2017 *p = '\0';
2019 /* save last_newline in case we need to load more data */
2020 last_newline = count;
2022 switch (current_command)
2024 case PLAYLIST_COMMAND_PLAYLIST:
2026 /* str1=version str2=dir str3=file */
2027 int version;
2029 if (!str1)
2031 result = -1;
2032 exit_loop = true;
2033 break;
2036 if (!str2)
2037 str2 = "";
2039 if (!str3)
2040 str3 = "";
2042 version = atoi(str1);
2044 if (version != PLAYLIST_CONTROL_FILE_VERSION)
2045 return -1;
2047 update_playlist_filename(playlist, str2, str3);
2049 if (str3[0] != '\0')
2051 /* NOTE: add_indices_to_playlist() overwrites the
2052 audiobuf so we need to reload control file
2053 data */
2054 add_indices_to_playlist(playlist, NULL, 0);
2056 else if (str2[0] != '\0')
2058 playlist->in_ram = true;
2059 resume_directory(str2);
2062 /* load the rest of the data */
2063 first = false;
2064 exit_loop = true;
2066 break;
2068 case PLAYLIST_COMMAND_ADD:
2069 case PLAYLIST_COMMAND_QUEUE:
2071 /* str1=position str2=last_position str3=file */
2072 int position, last_position;
2073 bool queue;
2075 if (!str1 || !str2 || !str3)
2077 result = -1;
2078 exit_loop = true;
2079 break;
2082 position = atoi(str1);
2083 last_position = atoi(str2);
2085 queue = (current_command == PLAYLIST_COMMAND_ADD)?
2086 false:true;
2088 /* seek position is based on str3's position in
2089 buffer */
2090 if (add_track_to_playlist(playlist, str3, position,
2091 queue, total_read+(str3-buffer)) < 0)
2092 return -1;
2094 playlist->last_insert_pos = last_position;
2096 break;
2098 case PLAYLIST_COMMAND_DELETE:
2100 /* str1=position */
2101 int position;
2103 if (!str1)
2105 result = -1;
2106 exit_loop = true;
2107 break;
2110 position = atoi(str1);
2112 if (remove_track_from_playlist(playlist, position,
2113 false) < 0)
2114 return -1;
2116 break;
2118 case PLAYLIST_COMMAND_SHUFFLE:
2120 /* str1=seed str2=first_index */
2121 int seed;
2123 if (!str1 || !str2)
2125 result = -1;
2126 exit_loop = true;
2127 break;
2130 if (!sorted)
2132 /* Always sort list before shuffling */
2133 sort_playlist(playlist, false, false);
2136 seed = atoi(str1);
2137 playlist->first_index = atoi(str2);
2139 if (randomise_playlist(playlist, seed, false,
2140 false) < 0)
2141 return -1;
2143 sorted = false;
2144 break;
2146 case PLAYLIST_COMMAND_UNSHUFFLE:
2148 /* str1=first_index */
2149 if (!str1)
2151 result = -1;
2152 exit_loop = true;
2153 break;
2156 playlist->first_index = atoi(str1);
2158 if (sort_playlist(playlist, false, false) < 0)
2159 return -1;
2161 sorted = true;
2162 break;
2164 case PLAYLIST_COMMAND_RESET:
2166 playlist->last_insert_pos = -1;
2167 break;
2169 case PLAYLIST_COMMAND_COMMENT:
2170 default:
2171 break;
2174 newline = true;
2176 /* to ignore any extra newlines */
2177 current_command = PLAYLIST_COMMAND_COMMENT;
2179 else if(newline)
2181 newline = false;
2183 /* first non-comment line must always specify playlist */
2184 if (first && *p != 'P' && *p != '#')
2186 result = -1;
2187 exit_loop = true;
2188 break;
2191 switch (*p)
2193 case 'P':
2194 /* playlist can only be specified once */
2195 if (!first)
2197 result = -1;
2198 exit_loop = true;
2199 break;
2202 current_command = PLAYLIST_COMMAND_PLAYLIST;
2203 break;
2204 case 'A':
2205 current_command = PLAYLIST_COMMAND_ADD;
2206 break;
2207 case 'Q':
2208 current_command = PLAYLIST_COMMAND_QUEUE;
2209 break;
2210 case 'D':
2211 current_command = PLAYLIST_COMMAND_DELETE;
2212 break;
2213 case 'S':
2214 current_command = PLAYLIST_COMMAND_SHUFFLE;
2215 break;
2216 case 'U':
2217 current_command = PLAYLIST_COMMAND_UNSHUFFLE;
2218 break;
2219 case 'R':
2220 current_command = PLAYLIST_COMMAND_RESET;
2221 break;
2222 case '#':
2223 current_command = PLAYLIST_COMMAND_COMMENT;
2224 break;
2225 default:
2226 result = -1;
2227 exit_loop = true;
2228 break;
2231 str_count = -1;
2232 str1 = NULL;
2233 str2 = NULL;
2234 str3 = NULL;
2236 else if(current_command != PLAYLIST_COMMAND_COMMENT)
2238 /* all control file strings are separated with a colon.
2239 Replace the colon with 0 to get proper strings that can be
2240 used by commands above */
2241 if (*p == ':')
2243 *p = '\0';
2244 str_count++;
2246 if ((count+1) < nread)
2248 switch (str_count)
2250 case 0:
2251 str1 = p+1;
2252 break;
2253 case 1:
2254 str2 = p+1;
2255 break;
2256 case 2:
2257 str3 = p+1;
2258 break;
2259 default:
2260 /* allow last string to contain colons */
2261 *p = ':';
2262 break;
2269 if (result < 0)
2271 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_INVALID));
2272 return result;
2275 if (!newline || (exit_loop && count<nread))
2277 if ((total_read + count) >= control_file_size)
2279 /* no newline at end of control file */
2280 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_INVALID));
2281 return -1;
2284 /* We didn't end on a newline or we exited loop prematurely.
2285 Either way, re-read the remainder. */
2286 count = last_newline;
2287 lseek(playlist->control_fd, total_read+count, SEEK_SET);
2290 total_read += count;
2292 if (first)
2293 /* still looking for header */
2294 nread = read(playlist->control_fd, buffer,
2295 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
2296 else
2297 nread = read(playlist->control_fd, buffer, buflen);
2299 /* Terminate on EOF */
2300 if(nread <= 0)
2302 if (global_status.resume_seed >= 0)
2304 /* Apply shuffle command saved in settings */
2305 if (global_status.resume_seed == 0)
2306 sort_playlist(playlist, false, true);
2307 else
2309 if (!sorted)
2310 sort_playlist(playlist, false, false);
2312 randomise_playlist(playlist, global_status.resume_seed,
2313 false, true);
2317 playlist->first_index = global_status.resume_first_index;
2318 break;
2322 #ifdef HAVE_DIRCACHE
2323 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2324 #endif
2326 return 0;
2330 * Add track to in_ram playlist. Used when playing directories.
2332 int playlist_add(const char *filename)
2334 struct playlist_info* playlist = &current_playlist;
2335 int len = strlen(filename);
2337 if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) ||
2338 (playlist->amount >= playlist->max_playlist_size))
2340 display_buffer_full();
2341 return -1;
2344 playlist->indices[playlist->amount] = playlist->buffer_end_pos;
2345 #ifdef HAVE_DIRCACHE
2346 playlist->filenames[playlist->amount] = NULL;
2347 #endif
2348 playlist->amount++;
2350 strcpy(&playlist->buffer[playlist->buffer_end_pos], filename);
2351 playlist->buffer_end_pos += len;
2352 playlist->buffer[playlist->buffer_end_pos++] = '\0';
2354 return 0;
2357 /* shuffle newly created playlist using random seed. */
2358 int playlist_shuffle(int random_seed, int start_index)
2360 struct playlist_info* playlist = &current_playlist;
2362 unsigned int seek_pos = 0;
2363 bool start_current = false;
2365 if (start_index >= 0 && global_settings.play_selected)
2367 /* store the seek position before the shuffle */
2368 seek_pos = playlist->indices[start_index];
2369 playlist->index = global_status.resume_first_index =
2370 playlist->first_index = start_index;
2371 start_current = true;
2374 gui_syncsplash(0, str(LANG_PLAYLIST_SHUFFLE));
2376 randomise_playlist(playlist, random_seed, start_current, true);
2378 return playlist->index;
2381 /* start playing current playlist at specified index/offset */
2382 int playlist_start(int start_index, int offset)
2384 struct playlist_info* playlist = &current_playlist;
2386 playlist->index = start_index;
2388 #if CONFIG_CODEC != SWCODEC
2389 talk_buffer_steal(); /* will use the mp3 buffer */
2390 #endif
2392 playlist->started = true;
2393 sync_control(playlist, false);
2394 audio_play(offset);
2396 return 0;
2399 /* Returns false if 'steps' is out of bounds, else true */
2400 bool playlist_check(int steps)
2402 struct playlist_info* playlist = &current_playlist;
2404 /* always allow folder navigation */
2405 if (global_settings.next_folder && playlist->in_ram)
2406 return true;
2408 int index = get_next_index(playlist, steps, -1);
2410 if (index < 0 && steps >= 0 && global_settings.repeat_mode == REPEAT_SHUFFLE)
2411 index = get_next_index(playlist, steps, REPEAT_ALL);
2413 return (index >= 0);
2416 /* get trackname of track that is "steps" away from current playing track.
2417 NULL is used to identify end of playlist */
2418 char* playlist_peek(int steps)
2420 struct playlist_info* playlist = &current_playlist;
2421 int seek;
2422 int fd;
2423 char *temp_ptr;
2424 int index;
2425 bool control_file;
2427 index = get_next_index(playlist, steps, -1);
2428 if (index < 0)
2429 return NULL;
2431 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2432 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2434 if (get_filename(playlist, index, seek, control_file, now_playing,
2435 MAX_PATH+1) < 0)
2436 return NULL;
2438 temp_ptr = now_playing;
2440 if (!playlist->in_ram || control_file)
2442 /* remove bogus dirs from beginning of path
2443 (workaround for buggy playlist creation tools) */
2444 while (temp_ptr)
2446 #ifdef HAVE_DIRCACHE
2447 if (dircache_is_enabled())
2449 if (dircache_get_entry_ptr(temp_ptr))
2450 break;
2452 else
2453 #endif
2455 fd = open(temp_ptr, O_RDONLY);
2456 if (fd >= 0)
2458 close(fd);
2459 break;
2463 temp_ptr = strchr(temp_ptr+1, '/');
2466 if (!temp_ptr)
2468 /* Even though this is an invalid file, we still need to pass a
2469 file name to the caller because NULL is used to indicate end
2470 of playlist */
2471 return now_playing;
2475 return temp_ptr;
2479 * Update indices as track has changed
2481 int playlist_next(int steps)
2483 struct playlist_info* playlist = &current_playlist;
2484 int index;
2486 if ( (steps > 0)
2487 #ifdef AB_REPEAT_ENABLE
2488 && (global_settings.repeat_mode != REPEAT_AB)
2489 #endif
2490 && (global_settings.repeat_mode != REPEAT_ONE) )
2492 int i, j;
2494 /* We need to delete all the queued songs */
2495 for (i=0, j=steps; i<j; i++)
2497 index = get_next_index(playlist, i, -1);
2499 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
2501 remove_track_from_playlist(playlist, index, true);
2502 steps--; /* one less track */
2507 index = get_next_index(playlist, steps, -1);
2509 if (index < 0)
2511 /* end of playlist... or is it */
2512 if (global_settings.repeat_mode == REPEAT_SHUFFLE &&
2513 playlist->amount > 1)
2515 /* Repeat shuffle mode. Re-shuffle playlist and resume play */
2516 playlist->first_index = global_status.resume_first_index = 0;
2517 sort_playlist(playlist, false, false);
2518 randomise_playlist(playlist, current_tick, false, true);
2519 #if CONFIG_CODEC != SWCODEC
2520 playlist_start(0, 0);
2521 #endif
2522 playlist->index = 0;
2523 index = 0;
2525 else if (playlist->in_ram && global_settings.next_folder)
2527 char dir[MAX_PATH+1];
2529 changing_dir = true;
2530 if (steps > 0)
2532 if (!get_next_directory(dir))
2534 /* start playing next directory */
2535 if (playlist_create(dir, NULL) != -1)
2537 ft_build_playlist(tree_get_context(), 0);
2538 if (global_settings.playlist_shuffle)
2539 playlist_shuffle(current_tick, -1);
2540 #if CONFIG_CODEC != SWCODEC
2541 playlist_start(0, 0);
2542 #endif
2543 playlist->index = index = 0;
2547 else
2549 if (!get_previous_directory(dir))
2551 /* start playing previous directory */
2552 if (playlist_create(dir, NULL) != -1)
2554 ft_build_playlist(tree_get_context(), 0);
2555 if (global_settings.playlist_shuffle)
2556 playlist_shuffle(current_tick, -1);
2557 #if CONFIG_CODEC != SWCODEC
2558 playlist_start(current_playlist.amount-1, 0);
2559 #endif
2560 playlist->index = index = current_playlist.amount - 1;
2564 changing_dir = false;
2567 return index;
2570 playlist->index = index;
2572 if (playlist->last_insert_pos >= 0 && steps > 0)
2574 /* check to see if we've gone beyond the last inserted track */
2575 int cur = rotate_index(playlist, index);
2576 int last_pos = rotate_index(playlist, playlist->last_insert_pos);
2578 if (cur > last_pos)
2580 /* reset last inserted track */
2581 playlist->last_insert_pos = -1;
2583 if (playlist->control_fd >= 0)
2585 int result = update_control(playlist, PLAYLIST_COMMAND_RESET,
2586 -1, -1, NULL, NULL, NULL);
2588 if (result < 0)
2589 return result;
2591 sync_control(playlist, false);
2596 return index;
2599 /* try playing next or previous folder */
2600 bool playlist_next_dir(int direction)
2602 char dir[MAX_PATH+1];
2603 bool result;
2604 int res;
2606 /* not to mess up real playlists */
2607 if(!current_playlist.in_ram)
2608 return false;
2610 if(changing_dir)
2611 return false;
2613 changing_dir = true;
2614 if(direction > 0)
2615 res = get_next_directory(dir);
2616 else
2617 res = get_previous_directory(dir);
2618 if (!res)
2620 if (playlist_create(dir, NULL) != -1)
2622 ft_build_playlist(tree_get_context(), 0);
2623 if (global_settings.playlist_shuffle)
2624 playlist_shuffle(current_tick, -1);
2625 #if (CONFIG_CODEC != SWCODEC)
2626 playlist_start(0,0);
2627 #endif
2628 result = true;
2630 else
2631 result = false;
2633 else
2634 result = false;
2636 changing_dir = false;
2638 return result;
2641 /* Get resume info for current playing song. If return value is -1 then
2642 settings shouldn't be saved. */
2643 int playlist_get_resume_info(int *resume_index)
2645 struct playlist_info* playlist = &current_playlist;
2647 *resume_index = playlist->index;
2649 return 0;
2652 /* Update resume info for current playing song. Returns -1 on error. */
2653 int playlist_update_resume_info(const struct mp3entry* id3)
2655 struct playlist_info* playlist = &current_playlist;
2657 if (id3)
2659 if (global_status.resume_index != playlist->index ||
2660 global_status.resume_offset != id3->offset)
2662 global_status.resume_index = playlist->index;
2663 global_status.resume_offset = id3->offset;
2664 status_save();
2667 else
2669 global_status.resume_index = -1;
2670 global_status.resume_offset = -1;
2671 status_save();
2674 return 0;
2677 /* Returns index of current playing track for display purposes. This value
2678 should not be used for resume purposes as it doesn't represent the actual
2679 index into the playlist */
2680 int playlist_get_display_index(void)
2682 struct playlist_info* playlist = &current_playlist;
2684 /* first_index should always be index 0 for display purposes */
2685 int index = rotate_index(playlist, playlist->index);
2687 return (index+1);
2690 /* returns number of tracks in current playlist */
2691 int playlist_amount(void)
2693 return playlist_amount_ex(NULL);
2697 * Create a new playlist If playlist is not NULL then we're loading a
2698 * playlist off disk for viewing/editing. The index_buffer is used to store
2699 * playlist indices (required for and only used if !current playlist). The
2700 * temp_buffer (if not NULL) is used as a scratchpad when loading indices.
2702 int playlist_create_ex(struct playlist_info* playlist,
2703 const char* dir, const char* file,
2704 void* index_buffer, int index_buffer_size,
2705 void* temp_buffer, int temp_buffer_size)
2707 if (!playlist)
2708 playlist = &current_playlist;
2709 else
2711 /* Initialize playlist structure */
2712 int r = rand() % 10;
2713 playlist->current = false;
2715 /* Use random name for control file */
2716 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
2717 "%s.%d", PLAYLIST_CONTROL_FILE, r);
2718 playlist->fd = -1;
2719 playlist->control_fd = -1;
2721 if (index_buffer)
2723 int num_indices = index_buffer_size / sizeof(int);
2725 #ifdef HAVE_DIRCACHE
2726 num_indices /= 2;
2727 #endif
2728 if (num_indices > global_settings.max_files_in_playlist)
2729 num_indices = global_settings.max_files_in_playlist;
2731 playlist->max_playlist_size = num_indices;
2732 playlist->indices = index_buffer;
2733 #ifdef HAVE_DIRCACHE
2734 playlist->filenames = (const struct dircache_entry **)
2735 &playlist->indices[num_indices];
2736 #endif
2738 else
2740 playlist->max_playlist_size = current_playlist.max_playlist_size;
2741 playlist->indices = current_playlist.indices;
2742 #ifdef HAVE_DIRCACHE
2743 playlist->filenames = current_playlist.filenames;
2744 #endif
2747 playlist->buffer_size = 0;
2748 playlist->buffer = NULL;
2749 mutex_init(&playlist->control_mutex);
2752 new_playlist(playlist, dir, file);
2754 if (file)
2755 /* load the playlist file */
2756 add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size);
2758 return 0;
2762 * Set the specified playlist as the current.
2763 * NOTE: You will get undefined behaviour if something is already playing so
2764 * remember to stop before calling this. Also, this call will
2765 * effectively close your playlist, making it unusable.
2767 int playlist_set_current(struct playlist_info* playlist)
2769 if (!playlist || (check_control(playlist) < 0))
2770 return -1;
2772 empty_playlist(&current_playlist, false);
2774 strncpy(current_playlist.filename, playlist->filename,
2775 sizeof(current_playlist.filename));
2777 current_playlist.utf8 = playlist->utf8;
2778 current_playlist.fd = playlist->fd;
2780 close(playlist->control_fd);
2781 close(current_playlist.control_fd);
2782 remove(current_playlist.control_filename);
2783 if (rename(playlist->control_filename,
2784 current_playlist.control_filename) < 0)
2785 return -1;
2786 current_playlist.control_fd = open(current_playlist.control_filename,
2787 O_RDWR);
2788 if (current_playlist.control_fd < 0)
2789 return -1;
2790 current_playlist.control_created = true;
2792 current_playlist.dirlen = playlist->dirlen;
2794 if (playlist->indices && playlist->indices != current_playlist.indices)
2796 memcpy(current_playlist.indices, playlist->indices,
2797 playlist->max_playlist_size*sizeof(int));
2798 #ifdef HAVE_DIRCACHE
2799 memcpy(current_playlist.filenames, playlist->filenames,
2800 playlist->max_playlist_size*sizeof(int));
2801 #endif
2804 current_playlist.first_index = playlist->first_index;
2805 current_playlist.amount = playlist->amount;
2806 current_playlist.last_insert_pos = playlist->last_insert_pos;
2807 current_playlist.seed = playlist->seed;
2808 current_playlist.shuffle_modified = playlist->shuffle_modified;
2809 current_playlist.deleted = playlist->deleted;
2810 current_playlist.num_inserted_tracks = playlist->num_inserted_tracks;
2812 memcpy(current_playlist.control_cache, playlist->control_cache,
2813 sizeof(current_playlist.control_cache));
2814 current_playlist.num_cached = playlist->num_cached;
2815 current_playlist.pending_control_sync = playlist->pending_control_sync;
2817 return 0;
2821 * Close files and delete control file for non-current playlist.
2823 void playlist_close(struct playlist_info* playlist)
2825 if (!playlist)
2826 return;
2828 if (playlist->fd >= 0)
2829 close(playlist->fd);
2831 if (playlist->control_fd >= 0)
2832 close(playlist->control_fd);
2834 if (playlist->control_created)
2835 remove(playlist->control_filename);
2838 void playlist_sync(struct playlist_info* playlist)
2840 if (!playlist)
2841 playlist = &current_playlist;
2843 sync_control(playlist, false);
2844 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
2845 audio_flush_and_reload_tracks();
2847 #ifdef HAVE_DIRCACHE
2848 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2849 #endif
2853 * Insert track into playlist at specified position (or one of the special
2854 * positions). Returns position where track was inserted or -1 if error.
2856 int playlist_insert_track(struct playlist_info* playlist, const char *filename,
2857 int position, bool queue, bool sync)
2859 int result;
2861 if (!playlist)
2862 playlist = &current_playlist;
2864 if (check_control(playlist) < 0)
2866 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2867 return -1;
2870 result = add_track_to_playlist(playlist, filename, position, queue, -1);
2872 /* Check if we want manually sync later. For example when adding
2873 * bunch of files from tagcache, syncing after every file wouldn't be
2874 * a good thing to do. */
2875 if (sync && result >= 0)
2876 playlist_sync(playlist);
2878 return result;
2882 * Insert all tracks from specified directory into playlist.
2884 int playlist_insert_directory(struct playlist_info* playlist,
2885 const char *dirname, int position, bool queue,
2886 bool recurse)
2888 int result;
2889 unsigned char *count_str;
2890 struct directory_search_context context;
2892 if (!playlist)
2893 playlist = &current_playlist;
2895 if (check_control(playlist) < 0)
2897 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2898 return -1;
2901 if (position == PLAYLIST_REPLACE)
2903 if (remove_all_tracks(playlist) == 0)
2904 position = PLAYLIST_INSERT_LAST;
2905 else
2906 return -1;
2909 if (queue)
2910 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
2911 else
2912 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
2914 display_playlist_count(0, count_str);
2916 context.playlist = playlist;
2917 context.position = position;
2918 context.queue = queue;
2919 context.count = 0;
2921 cpu_boost(true);
2923 result = playlist_directory_tracksearch(dirname, recurse,
2924 directory_search_callback, &context);
2926 sync_control(playlist, false);
2928 cpu_boost(false);
2930 display_playlist_count(context.count, count_str);
2932 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
2933 audio_flush_and_reload_tracks();
2935 #ifdef HAVE_DIRCACHE
2936 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2937 #endif
2939 return result;
2943 * Insert all tracks from specified playlist into dynamic playlist.
2945 int playlist_insert_playlist(struct playlist_info* playlist, char *filename,
2946 int position, bool queue)
2948 int fd;
2949 int max;
2950 char *temp_ptr;
2951 char *dir;
2952 unsigned char *count_str;
2953 char temp_buf[MAX_PATH+1];
2954 char trackname[MAX_PATH+1];
2955 int count = 0;
2956 int result = 0;
2958 if (!playlist)
2959 playlist = &current_playlist;
2961 if (check_control(playlist) < 0)
2963 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2964 return -1;
2967 fd = open(filename, O_RDONLY);
2968 if (fd < 0)
2970 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
2971 return -1;
2974 /* we need the directory name for formatting purposes */
2975 dir = filename;
2977 temp_ptr = strrchr(filename+1,'/');
2978 if (temp_ptr)
2979 *temp_ptr = 0;
2980 else
2981 dir = "/";
2983 if (queue)
2984 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
2985 else
2986 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
2988 display_playlist_count(count, count_str);
2990 if (position == PLAYLIST_REPLACE)
2992 if (remove_all_tracks(playlist) == 0)
2993 position = PLAYLIST_INSERT_LAST;
2994 else return -1;
2997 cpu_boost(true);
2999 while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0)
3001 /* user abort */
3002 if (action_userabort(TIMEOUT_NOBLOCK))
3003 break;
3005 if (temp_buf[0] != '#' && temp_buf[0] != '\0')
3007 int insert_pos;
3009 /* we need to format so that relative paths are correctly
3010 handled */
3011 if (format_track_path(trackname, temp_buf, sizeof(trackname), max,
3012 dir) < 0)
3014 result = -1;
3015 break;
3018 insert_pos = add_track_to_playlist(playlist, trackname, position,
3019 queue, -1);
3021 if (insert_pos < 0)
3023 result = -1;
3024 break;
3027 /* Make sure tracks are inserted in correct order if user
3028 requests INSERT_FIRST */
3029 if (position == PLAYLIST_INSERT_FIRST || position >= 0)
3030 position = insert_pos + 1;
3032 count++;
3034 if ((count%PLAYLIST_DISPLAY_COUNT) == 0)
3036 display_playlist_count(count, count_str);
3038 if (count == PLAYLIST_DISPLAY_COUNT &&
3039 (audio_status() & AUDIO_STATUS_PLAY) &&
3040 playlist->started)
3041 audio_flush_and_reload_tracks();
3045 /* let the other threads work */
3046 yield();
3049 close(fd);
3051 if (temp_ptr)
3052 *temp_ptr = '/';
3054 sync_control(playlist, false);
3056 cpu_boost(false);
3058 display_playlist_count(count, count_str);
3060 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
3061 audio_flush_and_reload_tracks();
3063 #ifdef HAVE_DIRCACHE
3064 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
3065 #endif
3067 return result;
3071 * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then
3072 * we want to delete the current playing track.
3074 int playlist_delete(struct playlist_info* playlist, int index)
3076 int result = 0;
3078 if (!playlist)
3079 playlist = &current_playlist;
3081 if (check_control(playlist) < 0)
3083 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
3084 return -1;
3087 if (index == PLAYLIST_DELETE_CURRENT)
3088 index = playlist->index;
3090 result = remove_track_from_playlist(playlist, index, true);
3092 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3093 playlist->started)
3094 audio_flush_and_reload_tracks();
3096 return result;
3100 * Move track at index to new_index. Tracks between the two are shifted
3101 * appropriately. Returns 0 on success and -1 on failure.
3103 int playlist_move(struct playlist_info* playlist, int index, int new_index)
3105 int result;
3106 int seek;
3107 bool control_file;
3108 bool queue;
3109 bool current = false;
3110 int r;
3111 char filename[MAX_PATH];
3113 if (!playlist)
3114 playlist = &current_playlist;
3116 if (check_control(playlist) < 0)
3118 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
3119 return -1;
3122 if (index == new_index)
3123 return -1;
3125 if (index == playlist->index)
3126 /* Moving the current track */
3127 current = true;
3129 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3130 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
3131 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3133 if (get_filename(playlist, index, seek, control_file, filename,
3134 sizeof(filename)) < 0)
3135 return -1;
3137 /* Delete track from original position */
3138 result = remove_track_from_playlist(playlist, index, true);
3140 if (result != -1)
3142 /* We want to insert the track at the position that was specified by
3143 new_index. This may be different then new_index because of the
3144 shifting that occurred after the delete */
3145 r = rotate_index(playlist, new_index);
3147 if (r == 0)
3148 /* First index */
3149 new_index = PLAYLIST_PREPEND;
3150 else if (r == playlist->amount)
3151 /* Append */
3152 new_index = PLAYLIST_INSERT_LAST;
3153 else
3154 /* Calculate index of desired position */
3155 new_index = (r+playlist->first_index)%playlist->amount;
3157 result = add_track_to_playlist(playlist, filename, new_index, queue,
3158 -1);
3160 if (result != -1)
3162 if (current)
3164 /* Moved the current track */
3165 switch (new_index)
3167 case PLAYLIST_PREPEND:
3168 playlist->index = playlist->first_index;
3169 break;
3170 case PLAYLIST_INSERT_LAST:
3171 playlist->index = playlist->first_index - 1;
3172 if (playlist->index < 0)
3173 playlist->index += playlist->amount;
3174 break;
3175 default:
3176 playlist->index = new_index;
3177 break;
3181 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
3182 audio_flush_and_reload_tracks();
3186 #ifdef HAVE_DIRCACHE
3187 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
3188 #endif
3190 return result;
3193 /* shuffle currently playing playlist */
3194 int playlist_randomise(struct playlist_info* playlist, unsigned int seed,
3195 bool start_current)
3197 int result;
3199 if (!playlist)
3200 playlist = &current_playlist;
3202 check_control(playlist);
3204 result = randomise_playlist(playlist, seed, start_current, true);
3206 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3207 playlist->started)
3208 audio_flush_and_reload_tracks();
3210 return result;
3213 /* sort currently playing playlist */
3214 int playlist_sort(struct playlist_info* playlist, bool start_current)
3216 int result;
3218 if (!playlist)
3219 playlist = &current_playlist;
3221 check_control(playlist);
3223 result = sort_playlist(playlist, start_current, true);
3225 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3226 playlist->started)
3227 audio_flush_and_reload_tracks();
3229 return result;
3232 /* returns true if playlist has been modified */
3233 bool playlist_modified(const struct playlist_info* playlist)
3235 if (!playlist)
3236 playlist = &current_playlist;
3238 if (playlist->shuffle_modified ||
3239 playlist->deleted ||
3240 playlist->num_inserted_tracks > 0)
3241 return true;
3243 return false;
3246 /* returns index of first track in playlist */
3247 int playlist_get_first_index(const struct playlist_info* playlist)
3249 if (!playlist)
3250 playlist = &current_playlist;
3252 return playlist->first_index;
3255 /* returns shuffle seed of playlist */
3256 int playlist_get_seed(const struct playlist_info* playlist)
3258 if (!playlist)
3259 playlist = &current_playlist;
3261 return playlist->seed;
3264 /* returns number of tracks in playlist (includes queued/inserted tracks) */
3265 int playlist_amount_ex(const struct playlist_info* playlist)
3267 if (!playlist)
3268 playlist = &current_playlist;
3270 return playlist->amount;
3273 /* returns full path of playlist (minus extension) */
3274 char *playlist_name(const struct playlist_info* playlist, char *buf,
3275 int buf_size)
3277 char *sep;
3279 if (!playlist)
3280 playlist = &current_playlist;
3282 snprintf(buf, buf_size, "%s", playlist->filename+playlist->dirlen);
3284 if (!buf[0])
3285 return NULL;
3287 /* Remove extension */
3288 sep = strrchr(buf, '.');
3289 if (sep)
3290 *sep = 0;
3292 return buf;
3295 /* returns the playlist filename */
3296 char *playlist_get_name(const struct playlist_info* playlist, char *buf,
3297 int buf_size)
3299 if (!playlist)
3300 playlist = &current_playlist;
3302 snprintf(buf, buf_size, "%s", playlist->filename);
3304 if (!buf[0])
3305 return NULL;
3307 return buf;
3310 /* Fills info structure with information about track at specified index.
3311 Returns 0 on success and -1 on failure */
3312 int playlist_get_track_info(struct playlist_info* playlist, int index,
3313 struct playlist_track_info* info)
3315 int seek;
3316 bool control_file;
3318 if (!playlist)
3319 playlist = &current_playlist;
3321 if (index < 0 || index >= playlist->amount)
3322 return -1;
3324 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3325 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3327 if (get_filename(playlist, index, seek, control_file, info->filename,
3328 sizeof(info->filename)) < 0)
3329 return -1;
3331 info->attr = 0;
3333 if (control_file)
3335 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
3336 info->attr |= PLAYLIST_ATTR_QUEUED;
3337 else
3338 info->attr |= PLAYLIST_ATTR_INSERTED;
3342 if (playlist->indices[index] & PLAYLIST_SKIPPED)
3343 info->attr |= PLAYLIST_ATTR_SKIPPED;
3345 info->index = index;
3346 info->display_index = rotate_index(playlist, index) + 1;
3348 return 0;
3351 /* save the current dynamic playlist to specified file */
3352 int playlist_save(struct playlist_info* playlist, char *filename)
3354 int fd;
3355 int i, index;
3356 int count = 0;
3357 char path[MAX_PATH+1];
3358 char tmp_buf[MAX_PATH+1];
3359 int result = 0;
3360 bool overwrite_current = false;
3361 int* index_buf = NULL;
3363 if (!playlist)
3364 playlist = &current_playlist;
3366 if (playlist->amount <= 0)
3367 return -1;
3369 /* use current working directory as base for pathname */
3370 if (format_track_path(path, filename, sizeof(tmp_buf),
3371 strlen(filename)+1, getcwd(NULL, -1)) < 0)
3372 return -1;
3374 if (!strncmp(playlist->filename, path, strlen(path)))
3376 /* Attempting to overwrite current playlist file.*/
3378 if (playlist->buffer_size < (int)(playlist->amount * sizeof(int)))
3380 /* not enough buffer space to store updated indices */
3381 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
3382 return -1;
3385 /* in_ram buffer is unused for m3u files so we'll use for storing
3386 updated indices */
3387 index_buf = (int*)playlist->buffer;
3389 /* use temporary pathname */
3390 snprintf(path, sizeof(path), "%s_temp", playlist->filename);
3391 overwrite_current = true;
3394 fd = open(path, O_CREAT|O_WRONLY|O_TRUNC);
3395 if (fd < 0)
3397 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
3398 return -1;
3401 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
3403 cpu_boost(true);
3405 index = playlist->first_index;
3406 for (i=0; i<playlist->amount; i++)
3408 bool control_file;
3409 bool queue;
3410 int seek;
3412 /* user abort */
3413 if (action_userabort(TIMEOUT_NOBLOCK))
3415 result = -1;
3416 break;
3419 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3420 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
3421 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3423 /* Don't save queued files */
3424 if (!queue)
3426 if (get_filename(playlist, index, seek, control_file, tmp_buf,
3427 MAX_PATH+1) < 0)
3429 result = -1;
3430 break;
3433 if (overwrite_current)
3434 index_buf[count] = lseek(fd, 0, SEEK_CUR);
3436 if (fdprintf(fd, "%s\n", tmp_buf) < 0)
3438 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
3439 result = -1;
3440 break;
3443 count++;
3445 if ((count % PLAYLIST_DISPLAY_COUNT) == 0)
3446 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
3448 yield();
3451 index = (index+1)%playlist->amount;
3454 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
3456 close(fd);
3458 if (overwrite_current && result >= 0)
3460 result = -1;
3462 mutex_lock(&playlist->control_mutex);
3464 /* Replace the current playlist with the new one and update indices */
3465 close(playlist->fd);
3466 if (remove(playlist->filename) >= 0)
3468 if (rename(path, playlist->filename) >= 0)
3470 playlist->fd = open(playlist->filename, O_RDONLY);
3471 if (playlist->fd >= 0)
3473 index = playlist->first_index;
3474 for (i=0, count=0; i<playlist->amount; i++)
3476 if (!(playlist->indices[index] & PLAYLIST_QUEUE_MASK))
3478 playlist->indices[index] = index_buf[count];
3479 count++;
3481 index = (index+1)%playlist->amount;
3484 /* we need to recreate control because inserted tracks are
3485 now part of the playlist and shuffle has been
3486 invalidated */
3487 result = recreate_control(playlist);
3492 mutex_unlock(&playlist->control_mutex);
3496 cpu_boost(false);
3498 return result;
3502 * Search specified directory for tracks and notify via callback. May be
3503 * called recursively.
3505 int playlist_directory_tracksearch(const char* dirname, bool recurse,
3506 int (*callback)(char*, void*),
3507 void* context)
3509 char buf[MAX_PATH+1];
3510 int result = 0;
3511 int num_files = 0;
3512 int i;
3513 struct entry *files;
3514 struct tree_context* tc = tree_get_context();
3515 int old_dirfilter = *(tc->dirfilter);
3517 if (!callback)
3518 return -1;
3520 /* use the tree browser dircache to load files */
3521 *(tc->dirfilter) = SHOW_ALL;
3523 if (ft_load(tc, dirname) < 0)
3525 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
3526 *(tc->dirfilter) = old_dirfilter;
3527 return -1;
3530 files = (struct entry*) tc->dircache;
3531 num_files = tc->filesindir;
3533 /* we've overwritten the dircache so tree browser will need to be
3534 reloaded */
3535 reload_directory();
3537 for (i=0; i<num_files; i++)
3539 /* user abort */
3540 if (action_userabort(TIMEOUT_NOBLOCK))
3542 result = -1;
3543 break;
3546 if (files[i].attr & ATTR_DIRECTORY)
3548 if (recurse)
3550 /* recursively add directories */
3551 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
3552 result = playlist_directory_tracksearch(buf, recurse,
3553 callback, context);
3554 if (result < 0)
3555 break;
3557 /* we now need to reload our current directory */
3558 if(ft_load(tc, dirname) < 0)
3560 result = -1;
3561 break;
3564 files = (struct entry*) tc->dircache;
3565 num_files = tc->filesindir;
3566 if (!num_files)
3568 result = -1;
3569 break;
3572 else
3573 continue;
3575 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
3577 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
3579 if (callback(buf, context) != 0)
3581 result = -1;
3582 break;
3585 /* let the other threads work */
3586 yield();
3590 /* restore dirfilter */
3591 *(tc->dirfilter) = old_dirfilter;
3593 return result;