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.tar.gz rockbox-9e4262081b4ab5bad2e2708ea064643cf828685c.zip |
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 726e8a9ecb..05149d164f 100644 --- a/apps/playlist.c +++ b/apps/playlist.c | |||
@@ -17,11 +17,58 @@ | |||
17 | * | 17 | * |
18 | ****************************************************************************/ | 18 | ****************************************************************************/ |
19 | 19 | ||
20 | /* | ||
21 | Dynamic playlist design (based on design originally proposed by ricII) | ||
22 | |||
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. | ||
30 | |||
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. | ||
36 | |||
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 | ||
58 | |||
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. | ||
64 | */ | ||
65 | |||
20 | #include <stdio.h> | 66 | #include <stdio.h> |
21 | #include <stdlib.h> | 67 | #include <stdlib.h> |
22 | #include <string.h> | 68 | #include <string.h> |
23 | #include "playlist.h" | 69 | #include "playlist.h" |
24 | #include "file.h" | 70 | #include "file.h" |
71 | #include "dir.h" | ||
25 | #include "sprintf.h" | 72 | #include "sprintf.h" |
26 | #include "debug.h" | 73 | #include "debug.h" |
27 | #include "mpeg.h" | 74 | #include "mpeg.h" |
@@ -32,6 +79,10 @@ | |||
32 | #include "applimits.h" | 79 | #include "applimits.h" |
33 | #include "screens.h" | 80 | #include "screens.h" |
34 | #include "buffer.h" | 81 | #include "buffer.h" |
82 | #include "atoi.h" | ||
83 | #include "misc.h" | ||
84 | #include "button.h" | ||
85 | #include "tree.h" | ||
35 | #ifdef HAVE_LCD_BITMAP | 86 | #ifdef HAVE_LCD_BITMAP |
36 | #include "icons.h" | 87 | #include "icons.h" |
37 | #include "widgets.h" | 88 | #include "widgets.h" |
@@ -41,526 +92,825 @@ | |||
41 | 92 | ||
42 | static struct playlist_info playlist; | 93 | static struct playlist_info playlist; |
43 | 94 | ||
44 | #define QUEUE_FILE ROCKBOX_DIR "/.queue_file" | 95 | #define PLAYLIST_CONTROL_FILE ROCKBOX_DIR "/.playlist_control" |
96 | #define PLAYLIST_CONTROL_FILE_VERSION 1 | ||
97 | |||
98 | /* | ||
99 | Each playlist index has a flag associated with it which identifies what | ||
100 | type of track it is. These flags are stored in the 3 high order bits of | ||
101 | the index. | ||
102 | |||
103 | NOTE: This limits the playlist file size to a max of 512K. | ||
104 | |||
105 | Bits 31-30: | ||
106 | 00 = Playlist track | ||
107 | 01 = Track was prepended into playlist | ||
108 | 10 = Track was inserted into playlist | ||
109 | 11 = Track was appended into playlist | ||
110 | Bit 29: | ||
111 | 0 = Added track | ||
112 | 1 = Queued track | ||
113 | */ | ||
114 | #define PLAYLIST_SEEK_MASK 0x1FFFFFFF | ||
115 | #define PLAYLIST_INSERT_TYPE_MASK 0xC0000000 | ||
116 | #define PLAYLIST_QUEUE_MASK 0x20000000 | ||
117 | |||
118 | #define PLAYLIST_INSERT_TYPE_PREPEND 0x40000000 | ||
119 | #define PLAYLIST_INSERT_TYPE_INSERT 0x80000000 | ||
120 | #define PLAYLIST_INSERT_TYPE_APPEND 0xC0000000 | ||
45 | 121 | ||
46 | static unsigned char *playlist_buffer; | 122 | #define PLAYLIST_QUEUED 0x20000000 |
47 | 123 | ||
48 | static int playlist_end_pos = 0; | 124 | #define PLAYLIST_DISPLAY_COUNT 10 |
49 | 125 | ||
50 | static char now_playing[MAX_PATH+1]; | 126 | static char now_playing[MAX_PATH+1]; |
51 | 127 | ||
52 | void playlist_init(void) | 128 | static void empty_playlist(bool resume); |
53 | { | 129 | static void update_playlist_filename(char *dir, char *file); |
54 | playlist.fd = -1; | 130 | static int add_indices_to_playlist(void); |
55 | playlist.max_playlist_size = global_settings.max_files_in_playlist; | 131 | static int add_track_to_playlist(char *filename, int position, bool queue, |
56 | playlist.indices = buffer_alloc(playlist.max_playlist_size * sizeof(int)); | 132 | int seek_pos); |
57 | playlist.buffer_size = | 133 | static int add_directory_to_playlist(char *dirname, int *position, bool queue, |
58 | AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir; | 134 | int *count); |
59 | playlist_buffer = buffer_alloc(playlist.buffer_size); | 135 | static int remove_track_from_playlist(int position, bool write); |
60 | } | 136 | static int randomise_playlist(unsigned int seed, bool start_current, |
137 | bool write); | ||
138 | static int sort_playlist(bool start_current, bool write); | ||
139 | static int get_next_index(int steps); | ||
140 | static void find_and_set_playlist_index(unsigned int seek); | ||
141 | static int compare(const void* p1, const void* p2); | ||
142 | static int get_filename(int seek, bool control_file, char *buf, | ||
143 | int buf_length); | ||
144 | static int format_track_path(char *dest, char *src, int buf_length, int max, | ||
145 | char *dir); | ||
146 | static void display_playlist_count(int count, char *fmt); | ||
147 | static void display_buffer_full(void); | ||
61 | 148 | ||
62 | /* | 149 | /* |
63 | * remove any files and indices associated with the playlist | 150 | * remove any files and indices associated with the playlist |
64 | */ | 151 | */ |
65 | static void empty_playlist(bool queue_resume) | 152 | static void empty_playlist(bool resume) |
66 | { | 153 | { |
67 | int fd; | ||
68 | |||
69 | playlist.filename[0] = '\0'; | 154 | playlist.filename[0] = '\0'; |
155 | |||
70 | if(-1 != playlist.fd) | 156 | if(-1 != playlist.fd) |
71 | /* If there is an already open playlist, close it. */ | 157 | /* If there is an already open playlist, close it. */ |
72 | close(playlist.fd); | 158 | close(playlist.fd); |
73 | playlist.fd = -1; | 159 | playlist.fd = -1; |
160 | |||
161 | if(-1 != playlist.control_fd) | ||
162 | close(playlist.control_fd); | ||
163 | playlist.control_fd = -1; | ||
164 | |||
165 | playlist.in_ram = false; | ||
166 | playlist.buffer[0] = 0; | ||
167 | playlist.buffer_end_pos = 0; | ||
168 | |||
74 | playlist.index = 0; | 169 | playlist.index = 0; |
75 | playlist.queue_index = 0; | 170 | playlist.first_index = 0; |
76 | playlist.last_queue_index = 0; | ||
77 | playlist.amount = 0; | 171 | playlist.amount = 0; |
78 | playlist.num_queued = 0; | 172 | playlist.last_insert_pos = -1; |
79 | playlist.start_queue = 0; | ||
80 | 173 | ||
81 | if (!queue_resume) | 174 | if (!resume) |
82 | { | 175 | { |
83 | /* start with fresh queue file when starting new playlist */ | 176 | int fd; |
84 | remove(QUEUE_FILE); | 177 | |
85 | fd = creat(QUEUE_FILE, 0); | 178 | /* start with fresh playlist control file when starting new |
86 | if (fd > 0) | 179 | playlist */ |
180 | fd = creat(PLAYLIST_CONTROL_FILE, 0000200); | ||
181 | if (fd >= 0) | ||
87 | close(fd); | 182 | close(fd); |
88 | } | 183 | } |
89 | } | 184 | } |
90 | 185 | ||
91 | /* update queue list after resume */ | 186 | /* |
92 | static void add_indices_to_queuelist(int seek) | 187 | * store directory and name of playlist file |
188 | */ | ||
189 | static void update_playlist_filename(char *dir, char *file) | ||
93 | { | 190 | { |
94 | int nread; | 191 | char *sep=""; |
95 | int fd = -1; | 192 | int dirlen = strlen(dir); |
96 | int i = seek; | 193 | |
97 | int count = 0; | 194 | /* If the dir does not end in trailing slash, we use a separator. |
195 | Otherwise we don't. */ | ||
196 | if('/' != dir[dirlen-1]) | ||
197 | { | ||
198 | sep="/"; | ||
199 | dirlen++; | ||
200 | } | ||
201 | |||
202 | playlist.dirlen = dirlen; | ||
203 | |||
204 | snprintf(playlist.filename, sizeof(playlist.filename), | ||
205 | "%s%s%s", | ||
206 | dir, sep, file); | ||
207 | } | ||
208 | |||
209 | /* | ||
210 | * calculate track offsets within a playlist file | ||
211 | */ | ||
212 | static int add_indices_to_playlist(void) | ||
213 | { | ||
214 | unsigned int nread; | ||
215 | unsigned int i = 0; | ||
216 | unsigned int count = 0; | ||
217 | int buflen; | ||
98 | bool store_index; | 218 | bool store_index; |
99 | char buf[MAX_PATH]; | 219 | char *buffer; |
220 | unsigned char *p; | ||
100 | 221 | ||
101 | unsigned char *p = buf; | 222 | if(-1 == playlist.fd) |
223 | playlist.fd = open(playlist.filename, O_RDONLY); | ||
224 | if(-1 == playlist.fd) | ||
225 | return -1; /* failure */ | ||
226 | |||
227 | #ifdef HAVE_LCD_BITMAP | ||
228 | if(global_settings.statusbar) | ||
229 | lcd_setmargins(0, STATUSBAR_HEIGHT); | ||
230 | else | ||
231 | lcd_setmargins(0, 0); | ||
232 | #endif | ||
102 | 233 | ||
103 | fd = open(QUEUE_FILE, O_RDONLY); | 234 | splash(0, 0, true, str(LANG_PLAYLIST_LOAD)); |
104 | if(fd < 0) | ||
105 | return; | ||
106 | 235 | ||
107 | nread = lseek(fd, seek, SEEK_SET); | 236 | /* use mp3 buffer for maximum load speed */ |
108 | if (nread < 0) | 237 | buflen = (mp3end - mp3buf); |
109 | return; | 238 | buffer = mp3buf; |
110 | 239 | ||
111 | store_index = true; | 240 | store_index = true; |
112 | 241 | ||
242 | mpeg_stop(); | ||
243 | |||
113 | while(1) | 244 | while(1) |
114 | { | 245 | { |
115 | nread = read(fd, buf, MAX_PATH); | 246 | nread = read(playlist.fd, buffer, buflen); |
247 | /* Terminate on EOF */ | ||
116 | if(nread <= 0) | 248 | if(nread <= 0) |
117 | break; | 249 | break; |
118 | |||
119 | p = buf; | ||
120 | 250 | ||
121 | for(count=0; count < nread; count++,p++) { | 251 | p = buffer; |
122 | if(*p == '\n') | 252 | |
253 | for(count=0; count < nread; count++,p++) { | ||
254 | |||
255 | /* Are we on a new line? */ | ||
256 | if((*p == '\n') || (*p == '\r')) | ||
257 | { | ||
123 | store_index = true; | 258 | store_index = true; |
259 | } | ||
124 | else if(store_index) | 260 | else if(store_index) |
125 | { | 261 | { |
126 | store_index = false; | 262 | store_index = false; |
127 | 263 | ||
128 | playlist.queue_indices[playlist.last_queue_index] = i+count; | 264 | if(*p != '#') |
129 | playlist.last_queue_index = | 265 | { |
130 | (playlist.last_queue_index + 1) % MAX_QUEUED_FILES; | 266 | /* Store a new entry */ |
131 | playlist.num_queued++; | 267 | playlist.indices[ playlist.amount ] = i+count; |
268 | playlist.amount++; | ||
269 | if ( playlist.amount >= playlist.max_playlist_size ) { | ||
270 | display_buffer_full(); | ||
271 | return -1; | ||
272 | } | ||
273 | } | ||
132 | } | 274 | } |
133 | } | 275 | } |
134 | 276 | ||
135 | i += count; | 277 | i+= count; |
136 | } | 278 | } |
279 | |||
280 | return 0; | ||
137 | } | 281 | } |
138 | 282 | ||
139 | static int get_next_index(int steps, bool *queue) | 283 | /* |
284 | * Add track to playlist at specified position. There are three special | ||
285 | * positions that can be specified: | ||
286 | * PLAYLIST_PREPEND - Add track at beginning of playlist | ||
287 | * PLAYLIST_INSERT - Add track after current song. NOTE: If there | ||
288 | * are already inserted tracks then track is added | ||
289 | * to the end of the insertion list. | ||
290 | * PLAYLIST_INSERT_FIRST - Add track immediately after current song, no | ||
291 | * matter what other tracks have been inserted. | ||
292 | * PLAYLIST_INSERT_LAST - Add track to end of playlist | ||
293 | */ | ||
294 | static int add_track_to_playlist(char *filename, int position, bool queue, | ||
295 | int seek_pos) | ||
140 | { | 296 | { |
141 | int current_index = playlist.index; | 297 | int insert_position = position; |
142 | int next_index = -1; | 298 | unsigned int flags = PLAYLIST_INSERT_TYPE_INSERT; |
299 | int i; | ||
143 | 300 | ||
144 | if (global_settings.repeat_mode == REPEAT_ONE) | 301 | if (playlist.amount >= playlist.max_playlist_size) |
145 | { | 302 | { |
146 | /* this is needed for repeat one to work with queue mode */ | 303 | display_buffer_full(); |
147 | steps = 0; | 304 | return -1; |
148 | } | 305 | } |
149 | else if (steps >= 0) | 306 | |
307 | switch (position) | ||
150 | { | 308 | { |
151 | /* Queue index starts from 0 which needs to be accounted for. Also, | 309 | case PLAYLIST_PREPEND: |
152 | after resume, this handles case where we want to begin with | 310 | insert_position = playlist.first_index; |
153 | playlist */ | 311 | flags = PLAYLIST_INSERT_TYPE_PREPEND; |
154 | steps -= playlist.start_queue; | 312 | break; |
313 | case PLAYLIST_INSERT: | ||
314 | /* if there are already inserted tracks then add track to end of | ||
315 | insertion list else add after current playing track */ | ||
316 | if (playlist.last_insert_pos >= 0 && | ||
317 | playlist.last_insert_pos < playlist.amount && | ||
318 | (playlist.indices[playlist.last_insert_pos]& | ||
319 | PLAYLIST_INSERT_TYPE_MASK) == PLAYLIST_INSERT_TYPE_INSERT) | ||
320 | position = insert_position = playlist.last_insert_pos+1; | ||
321 | else if (playlist.amount > 0) | ||
322 | position = insert_position = playlist.index + 1; | ||
323 | else | ||
324 | position = insert_position = 0; | ||
325 | |||
326 | playlist.last_insert_pos = position; | ||
327 | break; | ||
328 | case PLAYLIST_INSERT_FIRST: | ||
329 | if (playlist.amount > 0) | ||
330 | position = insert_position = playlist.index + 1; | ||
331 | else | ||
332 | position = insert_position = 0; | ||
333 | |||
334 | if (playlist.last_insert_pos < 0) | ||
335 | playlist.last_insert_pos = position; | ||
336 | break; | ||
337 | case PLAYLIST_INSERT_LAST: | ||
338 | if (playlist.first_index > 0) | ||
339 | insert_position = playlist.first_index; | ||
340 | else | ||
341 | insert_position = playlist.amount; | ||
342 | |||
343 | flags = PLAYLIST_INSERT_TYPE_APPEND; | ||
344 | break; | ||
155 | } | 345 | } |
346 | |||
347 | if (queue) | ||
348 | flags |= PLAYLIST_QUEUED; | ||
156 | 349 | ||
157 | if (steps >= 0 && playlist.num_queued > 0 && | 350 | /* shift indices so that track can be added */ |
158 | playlist.num_queued - steps > 0) | 351 | for (i=playlist.amount; i>insert_position; i--) |
159 | *queue = true; | 352 | playlist.indices[i] = playlist.indices[i-1]; |
160 | else | 353 | |
354 | /* update stored indices if needed */ | ||
355 | if (playlist.amount > 0 && insert_position <= playlist.index) | ||
356 | playlist.index++; | ||
357 | |||
358 | if (playlist.amount > 0 && insert_position <= playlist.first_index && | ||
359 | position != PLAYLIST_PREPEND) | ||
360 | playlist.first_index++; | ||
361 | |||
362 | if (insert_position < playlist.last_insert_pos || | ||
363 | (insert_position == playlist.last_insert_pos && position < 0)) | ||
364 | playlist.last_insert_pos++; | ||
365 | |||
366 | if (seek_pos < 0 && playlist.control_fd >= 0) | ||
161 | { | 367 | { |
162 | *queue = false; | 368 | int result = -1; |
163 | if (playlist.num_queued) | 369 | |
370 | mutex_lock(&playlist.control_mutex); | ||
371 | |||
372 | if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) | ||
164 | { | 373 | { |
165 | if (steps >= 0) | 374 | if (fprintf(playlist.control_fd, "%c:%d:%d:", (queue?'Q':'A'), |
166 | { | 375 | position, playlist.last_insert_pos) > 0) |
167 | /* skip the queued tracks */ | ||
168 | steps -= (playlist.num_queued - 1); | ||
169 | } | ||
170 | else if (!playlist.start_queue) | ||
171 | { | 376 | { |
172 | /* previous from queue list needs to go to current track in | 377 | /* save the position in file where track name is written */ |
173 | playlist */ | 378 | seek_pos = lseek(playlist.control_fd, 0, SEEK_CUR); |
174 | steps += 1; | 379 | |
380 | if (fprintf(playlist.control_fd, "%s\n", filename) > 0) | ||
381 | result = 0; | ||
175 | } | 382 | } |
176 | } | 383 | } |
384 | |||
385 | mutex_unlock(&playlist.control_mutex); | ||
386 | |||
387 | if (result < 0) | ||
388 | { | ||
389 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); | ||
390 | return result; | ||
391 | } | ||
177 | } | 392 | } |
178 | 393 | ||
179 | switch (global_settings.repeat_mode) | 394 | playlist.indices[insert_position] = flags | seek_pos; |
395 | |||
396 | playlist.amount++; | ||
397 | |||
398 | return insert_position; | ||
399 | } | ||
400 | |||
401 | /* | ||
402 | * Insert directory into playlist. May be called recursively. | ||
403 | */ | ||
404 | static int add_directory_to_playlist(char *dirname, int *position, bool queue, | ||
405 | int *count) | ||
406 | { | ||
407 | char buf[MAX_PATH+1]; | ||
408 | char *count_str; | ||
409 | int result = 0; | ||
410 | int num_files = 0; | ||
411 | bool buffer_full = false; | ||
412 | int i; | ||
413 | struct entry *files; | ||
414 | |||
415 | /* use the tree browser dircache to load files */ | ||
416 | files = load_and_sort_directory(dirname, SHOW_ALL, &num_files, | ||
417 | &buffer_full); | ||
418 | |||
419 | if(!files) | ||
180 | { | 420 | { |
181 | case REPEAT_OFF: | 421 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); |
182 | if (*queue) | 422 | return 0; |
183 | next_index = (playlist.queue_index+steps) % MAX_QUEUED_FILES; | 423 | } |
184 | else | 424 | |
185 | { | 425 | /* we've overwritten the dircache so tree browser will need to be |
186 | if (current_index < playlist.first_index) | 426 | reloaded */ |
187 | current_index += playlist.amount; | 427 | reload_directory(); |
188 | current_index -= playlist.first_index; | 428 | |
189 | 429 | if (queue) | |
190 | next_index = current_index+steps; | 430 | count_str = str(LANG_PLAYLIST_QUEUE_COUNT); |
191 | if ((next_index < 0) || (next_index >= playlist.amount)) | 431 | else |
192 | next_index = -1; | 432 | count_str = str(LANG_PLAYLIST_INSERT_COUNT); |
193 | else | 433 | |
194 | next_index = (next_index+playlist.first_index) % | 434 | for (i=0; i<num_files; i++) |
195 | playlist.amount; | 435 | { |
196 | } | 436 | /* user abort */ |
437 | #ifdef HAVE_PLAYER_KEYPAD | ||
438 | if (button_get(false) == BUTTON_STOP) | ||
439 | #else | ||
440 | if (button_get(false) == BUTTON_OFF) | ||
441 | #endif | ||
442 | { | ||
443 | result = -1; | ||
197 | break; | 444 | break; |
445 | } | ||
198 | 446 | ||
199 | case REPEAT_ONE: | 447 | if (files[i].attr & ATTR_DIRECTORY) |
200 | /* if we are still in playlist when repeat one is set, don't go to | 448 | { |
201 | queue list */ | 449 | if (global_settings.recursive_dir_insert) |
202 | if (*queue && !playlist.start_queue) | 450 | { |
203 | next_index = playlist.queue_index; | 451 | /* recursively add directories */ |
452 | snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name); | ||
453 | result = add_directory_to_playlist(buf, position, queue, | ||
454 | count); | ||
455 | if (result < 0) | ||
456 | break; | ||
457 | |||
458 | /* we now need to reload our current directory */ | ||
459 | files = load_and_sort_directory(dirname, SHOW_ALL, &num_files, | ||
460 | &buffer_full); | ||
461 | if (!files) | ||
462 | { | ||
463 | result = -1; | ||
464 | break; | ||
465 | } | ||
466 | } | ||
204 | else | 467 | else |
468 | continue; | ||
469 | } | ||
470 | else if (files[i].attr & TREE_ATTR_MPA) | ||
471 | { | ||
472 | int insert_pos; | ||
473 | |||
474 | snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name); | ||
475 | |||
476 | insert_pos = add_track_to_playlist(buf, *position, queue, -1); | ||
477 | if (insert_pos < 0) | ||
205 | { | 478 | { |
206 | next_index = current_index; | 479 | result = -1; |
207 | *queue = false; | 480 | break; |
208 | } | 481 | } |
209 | break; | ||
210 | 482 | ||
211 | case REPEAT_ALL: | 483 | (*count)++; |
212 | default: | 484 | |
213 | if (*queue) | 485 | /* Make sure tracks are inserted in correct order if user requests |
214 | next_index = (playlist.queue_index+steps) % MAX_QUEUED_FILES; | 486 | INSERT_FIRST */ |
215 | else | 487 | if (*position == PLAYLIST_INSERT_FIRST || *position >= 0) |
488 | *position = insert_pos + 1; | ||
489 | |||
490 | if ((*count%PLAYLIST_DISPLAY_COUNT) == 0) | ||
216 | { | 491 | { |
217 | next_index = (current_index+steps) % playlist.amount; | 492 | display_playlist_count(*count, count_str); |
218 | while (next_index < 0) | 493 | |
219 | next_index += playlist.amount; | 494 | if (*count == PLAYLIST_DISPLAY_COUNT) |
495 | mpeg_flush_and_reload_tracks(); | ||
220 | } | 496 | } |
221 | break; | 497 | |
498 | /* let the other threads work */ | ||
499 | yield(); | ||
500 | } | ||
222 | } | 501 | } |
223 | 502 | ||
224 | return next_index; | 503 | return result; |
225 | } | 504 | } |
226 | 505 | ||
227 | int playlist_amount(void) | 506 | /* |
507 | * remove track at specified position | ||
508 | */ | ||
509 | static int remove_track_from_playlist(int position, bool write) | ||
228 | { | 510 | { |
229 | return playlist.amount + playlist.num_queued; | 511 | int i; |
230 | } | ||
231 | 512 | ||
232 | int playlist_first_index(void) | 513 | if (playlist.amount <= 0) |
233 | { | 514 | return -1; |
234 | return playlist.first_index; | ||
235 | } | ||
236 | 515 | ||
237 | /* Get resume info for current playing song. If return value is -1 then | 516 | /* shift indices now that track has been removed */ |
238 | settings shouldn't be saved (eg. when playing queued song and save queue | 517 | for (i=position; i<playlist.amount; i++) |
239 | disabled) */ | 518 | playlist.indices[i] = playlist.indices[i+1]; |
240 | int playlist_get_resume_info(int *resume_index, int *queue_resume, | ||
241 | int *queue_resume_index) | ||
242 | { | ||
243 | int result = 0; | ||
244 | 519 | ||
245 | *resume_index = playlist.index; | 520 | playlist.amount--; |
521 | |||
522 | /* update stored indices if needed */ | ||
523 | if (position < playlist.index) | ||
524 | playlist.index--; | ||
525 | |||
526 | if (position < playlist.first_index) | ||
527 | playlist.first_index--; | ||
246 | 528 | ||
247 | if (playlist.num_queued > 0) | 529 | if (position <= playlist.last_insert_pos) |
530 | playlist.last_insert_pos--; | ||
531 | |||
532 | if (write && playlist.control_fd >= 0) | ||
248 | { | 533 | { |
249 | if (global_settings.save_queue_resume) | 534 | int result = -1; |
535 | |||
536 | mutex_lock(&playlist.control_mutex); | ||
537 | |||
538 | if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) | ||
250 | { | 539 | { |
251 | *queue_resume_index = | 540 | if (fprintf(playlist.control_fd, "D:%d\n", position) > 0) |
252 | playlist.queue_indices[playlist.queue_index]; | 541 | { |
253 | /* have we started playing the queue list yet? */ | 542 | fsync(playlist.control_fd); |
254 | if (playlist.start_queue) | 543 | result = 0; |
255 | *queue_resume = QUEUE_BEGIN_PLAYLIST; | 544 | } |
256 | else | ||
257 | *queue_resume = QUEUE_BEGIN_QUEUE; | ||
258 | } | 545 | } |
259 | else if (!playlist.start_queue) | 546 | |
547 | mutex_unlock(&playlist.control_mutex); | ||
548 | |||
549 | if (result < 0) | ||
260 | { | 550 | { |
261 | *queue_resume = QUEUE_OFF; | 551 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); |
262 | result = -1; | 552 | return result; |
263 | } | 553 | } |
264 | } | 554 | } |
265 | else | ||
266 | *queue_resume = QUEUE_OFF; | ||
267 | 555 | ||
268 | return result; | 556 | return 0; |
269 | } | 557 | } |
270 | 558 | ||
271 | char *playlist_name(char *buf, int buf_size) | 559 | /* |
560 | * randomly rearrange the array of indices for the playlist. If start_current | ||
561 | * is true then update the index to the new index of the current playing track | ||
562 | */ | ||
563 | static int randomise_playlist(unsigned int seed, bool start_current, bool write) | ||
272 | { | 564 | { |
273 | char *sep; | 565 | int count; |
566 | int candidate; | ||
567 | int store; | ||
568 | unsigned int current = playlist.indices[playlist.index]; | ||
569 | |||
570 | /* seed with the given seed */ | ||
571 | srand(seed); | ||
274 | 572 | ||
275 | snprintf(buf, buf_size, "%s", playlist.filename+playlist.dirlen); | 573 | /* randomise entire indices list */ |
574 | for(count = playlist.amount - 1; count >= 0; count--) | ||
575 | { | ||
576 | /* the rand is from 0 to RAND_MAX, so adjust to our value range */ | ||
577 | candidate = rand() % (count + 1); | ||
276 | 578 | ||
277 | if (0 == buf[0]) | 579 | /* now swap the values at the 'count' and 'candidate' positions */ |
278 | return NULL; | 580 | store = playlist.indices[candidate]; |
581 | playlist.indices[candidate] = playlist.indices[count]; | ||
582 | playlist.indices[count] = store; | ||
583 | } | ||
279 | 584 | ||
280 | /* Remove extension */ | 585 | if (start_current) |
281 | sep = strrchr(buf, '.'); | 586 | find_and_set_playlist_index(current); |
282 | if (NULL != sep) | ||
283 | *sep = 0; | ||
284 | |||
285 | return buf; | ||
286 | } | ||
287 | 587 | ||
288 | void playlist_clear(void) | 588 | /* indices have been moved so last insert position is no longer valid */ |
289 | { | 589 | playlist.last_insert_pos = -1; |
290 | playlist_end_pos = 0; | ||
291 | playlist_buffer[0] = 0; | ||
292 | } | ||
293 | 590 | ||
294 | int playlist_add(char *filename) | 591 | if (write && playlist.control_fd >= 0) |
295 | { | 592 | { |
296 | int len = strlen(filename); | 593 | int result = -1; |
297 | 594 | ||
298 | if(len+2 > playlist.buffer_size - playlist_end_pos) | 595 | mutex_lock(&playlist.control_mutex); |
299 | return -1; | 596 | |
597 | if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) | ||
598 | { | ||
599 | if (fprintf(playlist.control_fd, "S:%d:%d\n", seed, | ||
600 | playlist.first_index) > 0) | ||
601 | { | ||
602 | fsync(playlist.control_fd); | ||
603 | result = 0; | ||
604 | } | ||
605 | } | ||
606 | |||
607 | mutex_unlock(&playlist.control_mutex); | ||
608 | |||
609 | if (result < 0) | ||
610 | { | ||
611 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); | ||
612 | return result; | ||
613 | } | ||
614 | } | ||
300 | 615 | ||
301 | strcpy(&playlist_buffer[playlist_end_pos], filename); | ||
302 | playlist_end_pos += len; | ||
303 | playlist_buffer[playlist_end_pos++] = '\n'; | ||
304 | playlist_buffer[playlist_end_pos] = '\0'; | ||
305 | return 0; | 616 | return 0; |
306 | } | 617 | } |
307 | 618 | ||
308 | /* Add track to queue file */ | 619 | /* |
309 | int queue_add(char *filename) | 620 | * Sort the array of indices for the playlist. If start_current is true then |
621 | * set the index to the new index of the current song. | ||
622 | */ | ||
623 | static int sort_playlist(bool start_current, bool write) | ||
310 | { | 624 | { |
311 | int fd, seek, result; | 625 | unsigned int current = playlist.indices[playlist.index]; |
312 | int len = strlen(filename); | ||
313 | 626 | ||
314 | if(playlist.num_queued >= MAX_QUEUED_FILES) | 627 | if (playlist.amount > 0) |
315 | return -1; | 628 | qsort(playlist.indices, playlist.amount, sizeof(playlist.indices[0]), |
629 | compare); | ||
316 | 630 | ||
317 | fd = open(QUEUE_FILE, O_WRONLY); | 631 | if (start_current) |
318 | if (fd < 0) | 632 | find_and_set_playlist_index(current); |
319 | return -1; | ||
320 | 633 | ||
321 | seek = lseek(fd, 0, SEEK_END); | 634 | /* indices have been moved so last insert position is no longer valid */ |
322 | if (seek < 0) | 635 | playlist.last_insert_pos = -1; |
323 | { | ||
324 | close(fd); | ||
325 | return -1; | ||
326 | } | ||
327 | 636 | ||
328 | /* save the file name with a trailing \n. QUEUE_FILE can be used as a | 637 | if (write && playlist.control_fd >= 0) |
329 | playlist if desired */ | ||
330 | filename[len] = '\n'; | ||
331 | result = write(fd, filename, len+1); | ||
332 | if (result < 0) | ||
333 | { | 638 | { |
334 | close(fd); | 639 | int result = -1; |
335 | return -1; | ||
336 | } | ||
337 | filename[len] = '\0'; | ||
338 | 640 | ||
339 | close(fd); | 641 | mutex_lock(&playlist.control_mutex); |
340 | 642 | ||
341 | if (playlist.num_queued <= 0) | 643 | if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) |
342 | playlist.start_queue = 1; | 644 | { |
645 | if (fprintf(playlist.control_fd, "U:%d\n", playlist.first_index) > | ||
646 | 0) | ||
647 | { | ||
648 | fsync(playlist.control_fd); | ||
649 | result = 0; | ||
650 | } | ||
651 | } | ||
343 | 652 | ||
344 | playlist.queue_indices[playlist.last_queue_index] = seek; | 653 | mutex_unlock(&playlist.control_mutex); |
345 | playlist.last_queue_index = | ||
346 | (playlist.last_queue_index + 1) % MAX_QUEUED_FILES; | ||
347 | playlist.num_queued++; | ||
348 | 654 | ||
349 | /* the play order has changed */ | 655 | if (result < 0) |
350 | mpeg_flush_and_reload_tracks(); | 656 | { |
657 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); | ||
658 | return result; | ||
659 | } | ||
660 | } | ||
351 | 661 | ||
352 | return playlist.num_queued; | 662 | return 0; |
353 | } | 663 | } |
354 | 664 | ||
355 | int playlist_next(int steps) | 665 | /* |
666 | * returns the index of the track that is "steps" away from current playing | ||
667 | * track. | ||
668 | */ | ||
669 | static int get_next_index(int steps) | ||
356 | { | 670 | { |
357 | bool queue; | 671 | int current_index = playlist.index; |
358 | int index = get_next_index(steps, &queue); | 672 | int next_index = -1; |
359 | 673 | ||
360 | if (queue) | 674 | if (playlist.amount <= 0) |
361 | { | 675 | return -1; |
362 | /* queue_diff accounts for bad songs in queue list */ | ||
363 | int queue_diff = index - playlist.queue_index; | ||
364 | if (queue_diff < 0) | ||
365 | queue_diff += MAX_QUEUED_FILES; | ||
366 | 676 | ||
367 | playlist.num_queued -= queue_diff; | 677 | switch (global_settings.repeat_mode) |
368 | playlist.queue_index = index; | ||
369 | playlist.start_queue = 0; | ||
370 | } | ||
371 | else | ||
372 | { | 678 | { |
373 | playlist.index = index; | 679 | case REPEAT_OFF: |
374 | if (playlist.num_queued > 0 && !playlist.start_queue) | ||
375 | { | 680 | { |
376 | if (steps >= 0) | 681 | /* Rotate indices such that first_index is considered index 0 to |
377 | { | 682 | simplify next calculation */ |
378 | /* done with queue list */ | 683 | current_index -= playlist.first_index; |
379 | playlist.queue_index = playlist.last_queue_index; | 684 | if (current_index < 0) |
380 | playlist.num_queued = 0; | 685 | current_index += playlist.amount; |
381 | } | 686 | |
687 | next_index = current_index+steps; | ||
688 | if ((next_index < 0) || (next_index >= playlist.amount)) | ||
689 | next_index = -1; | ||
382 | else | 690 | else |
691 | next_index = (next_index+playlist.first_index) % | ||
692 | playlist.amount; | ||
693 | |||
694 | break; | ||
695 | } | ||
696 | |||
697 | case REPEAT_ONE: | ||
698 | next_index = current_index; | ||
699 | break; | ||
700 | |||
701 | case REPEAT_ALL: | ||
702 | default: | ||
703 | { | ||
704 | next_index = (current_index+steps) % playlist.amount; | ||
705 | while (next_index < 0) | ||
706 | next_index += playlist.amount; | ||
707 | |||
708 | if (steps >= playlist.amount) | ||
383 | { | 709 | { |
384 | /* user requested previous. however, don't forget about queue | 710 | int i, index; |
385 | list */ | 711 | |
386 | playlist.start_queue = 1; | 712 | index = next_index; |
713 | next_index = -1; | ||
714 | |||
715 | /* second time around so skip the queued files */ | ||
716 | for (i=0; i<playlist.amount; i++) | ||
717 | { | ||
718 | if (playlist.indices[index] & PLAYLIST_QUEUE_MASK) | ||
719 | index = (index+1) % playlist.amount; | ||
720 | else | ||
721 | { | ||
722 | next_index = index; | ||
723 | break; | ||
724 | } | ||
725 | } | ||
387 | } | 726 | } |
727 | break; | ||
388 | } | 728 | } |
389 | } | 729 | } |
390 | 730 | ||
391 | return index; | 731 | return next_index; |
392 | } | 732 | } |
393 | 733 | ||
394 | /* Returns false if 'steps' is out of bounds, else true */ | 734 | /* |
395 | bool playlist_check(int steps) | 735 | * Search for the seek track and set appropriate indices. Used after shuffle |
736 | * to make sure the current index is still pointing to correct track. | ||
737 | */ | ||
738 | static void find_and_set_playlist_index(unsigned int seek) | ||
396 | { | 739 | { |
397 | bool queue; | 740 | int i; |
398 | int index = get_next_index(steps, &queue); | 741 | |
399 | return (index >= 0); | 742 | /* Set the index to the current song */ |
743 | for (i=0; i<playlist.amount; i++) | ||
744 | { | ||
745 | if (playlist.indices[i] == seek) | ||
746 | { | ||
747 | playlist.index = playlist.first_index = i; | ||
748 | break; | ||
749 | } | ||
750 | } | ||
400 | } | 751 | } |
401 | 752 | ||
402 | char* playlist_peek(int steps) | 753 | /* |
754 | * used to sort track indices. Sort order is as follows: | ||
755 | * 1. Prepended tracks (in prepend order) | ||
756 | * 2. Playlist/directory tracks (in playlist order) | ||
757 | * 3. Inserted/Appended tracks (in insert order) | ||
758 | */ | ||
759 | static int compare(const void* p1, const void* p2) | ||
760 | { | ||
761 | unsigned int* e1 = (unsigned int*) p1; | ||
762 | unsigned int* e2 = (unsigned int*) p2; | ||
763 | unsigned int flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK; | ||
764 | unsigned int flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK; | ||
765 | |||
766 | if (flags1 == flags2) | ||
767 | return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); | ||
768 | else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND || | ||
769 | flags2 == PLAYLIST_INSERT_TYPE_APPEND) | ||
770 | return -1; | ||
771 | else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND || | ||
772 | flags2 == PLAYLIST_INSERT_TYPE_PREPEND) | ||
773 | return 1; | ||
774 | else if (flags1 && flags2) | ||
775 | return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); | ||
776 | else | ||
777 | return *e1 - *e2; | ||
778 | } | ||
779 | |||
780 | /* | ||
781 | * gets pathname for track at seek index | ||
782 | */ | ||
783 | static int get_filename(int seek, bool control_file, char *buf, | ||
784 | int buf_length) | ||
403 | { | 785 | { |
404 | int seek; | ||
405 | int max; | ||
406 | int i; | ||
407 | int fd; | 786 | int fd; |
408 | char *buf; | 787 | int max = -1; |
788 | char tmp_buf[MAX_PATH+1]; | ||
409 | char dir_buf[MAX_PATH+1]; | 789 | char dir_buf[MAX_PATH+1]; |
410 | char *dir_end; | ||
411 | int index; | ||
412 | bool queue; | ||
413 | 790 | ||
414 | index = get_next_index(steps, &queue); | 791 | if (buf_length > MAX_PATH+1) |
415 | if (index < 0) | 792 | buf_length = MAX_PATH+1; |
416 | return NULL; | ||
417 | 793 | ||
418 | if (queue) | 794 | if (playlist.in_ram && !control_file) |
419 | { | 795 | { |
420 | seek = playlist.queue_indices[index]; | 796 | strncpy(tmp_buf, &playlist.buffer[seek], sizeof(tmp_buf)); |
421 | fd = open(QUEUE_FILE, O_RDONLY); | 797 | tmp_buf[MAX_PATH] = '\0'; |
422 | if(-1 != fd) | 798 | max = strlen(tmp_buf) + 1; |
423 | { | ||
424 | buf = dir_buf; | ||
425 | lseek(fd, seek, SEEK_SET); | ||
426 | max = read(fd, buf, MAX_PATH); | ||
427 | close(fd); | ||
428 | } | ||
429 | else | ||
430 | return NULL; | ||
431 | } | 799 | } |
432 | else | 800 | else |
433 | { | 801 | { |
434 | seek = playlist.indices[index]; | 802 | if (control_file) |
435 | 803 | fd = playlist.control_fd; | |
436 | if(playlist.in_ram) | ||
437 | { | ||
438 | buf = playlist_buffer + seek; | ||
439 | max = playlist_end_pos - seek; | ||
440 | } | ||
441 | else | 804 | else |
442 | { | 805 | { |
443 | if(-1 == playlist.fd) | 806 | if(-1 == playlist.fd) |
444 | playlist.fd = open(playlist.filename, O_RDONLY); | 807 | playlist.fd = open(playlist.filename, O_RDONLY); |
808 | |||
809 | fd = playlist.fd; | ||
810 | } | ||
811 | |||
812 | if(-1 != fd) | ||
813 | { | ||
814 | if (control_file) | ||
815 | mutex_lock(&playlist.control_mutex); | ||
816 | |||
817 | lseek(fd, seek, SEEK_SET); | ||
818 | max = read(fd, tmp_buf, buf_length); | ||
819 | |||
820 | if (control_file) | ||
821 | mutex_unlock(&playlist.control_mutex); | ||
822 | } | ||
445 | 823 | ||
446 | if(-1 != playlist.fd) | 824 | if (max < 0) |
447 | { | 825 | { |
448 | buf = playlist_buffer; | 826 | if (control_file) |
449 | lseek(playlist.fd, seek, SEEK_SET); | 827 | splash(HZ*2, 0, true, |
450 | max = read(playlist.fd, buf, MAX_PATH); | 828 | str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); |
451 | } | ||
452 | else | 829 | else |
453 | return NULL; | 830 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_ACCESS_ERROR)); |
831 | |||
832 | return max; | ||
454 | } | 833 | } |
455 | } | 834 | } |
456 | 835 | ||
836 | strncpy(dir_buf, playlist.filename, playlist.dirlen-1); | ||
837 | dir_buf[playlist.dirlen-1] = 0; | ||
838 | |||
839 | return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf)); | ||
840 | } | ||
841 | |||
842 | /* | ||
843 | * Returns absolute path of track | ||
844 | */ | ||
845 | static int format_track_path(char *dest, char *src, int buf_length, int max, | ||
846 | char *dir) | ||
847 | { | ||
848 | int i = 0; | ||
849 | int j; | ||
850 | char *temp_ptr; | ||
851 | |||
457 | /* Zero-terminate the file name */ | 852 | /* Zero-terminate the file name */ |
458 | seek=0; | 853 | while((src[i] != '\n') && |
459 | while((buf[seek] != '\n') && | 854 | (src[i] != '\r') && |
460 | (buf[seek] != '\r') && | 855 | (i < max)) |
461 | (seek < max)) | 856 | i++; |
462 | seek++; | ||
463 | 857 | ||
464 | /* Now work back killing white space */ | 858 | /* Now work back killing white space */ |
465 | while((buf[seek-1] == ' ') || | 859 | while((src[i-1] == ' ') || |
466 | (buf[seek-1] == '\t')) | 860 | (src[i-1] == '\t')) |
467 | seek--; | 861 | i--; |
468 | 862 | ||
469 | buf[seek]=0; | 863 | src[i]=0; |
470 | 864 | ||
471 | /* replace backslashes with forward slashes */ | 865 | /* replace backslashes with forward slashes */ |
472 | for ( i=0; i<seek; i++ ) | 866 | for ( j=0; j<i; j++ ) |
473 | if ( buf[i] == '\\' ) | 867 | if ( src[j] == '\\' ) |
474 | buf[i] = '/'; | 868 | src[j] = '/'; |
475 | 869 | ||
476 | if('/' == buf[0]) { | 870 | if('/' == src[0]) |
477 | strcpy(now_playing, &buf[0]); | 871 | { |
872 | strncpy(dest, src, buf_length); | ||
478 | } | 873 | } |
479 | else { | 874 | else |
480 | strncpy(dir_buf, playlist.filename, playlist.dirlen-1); | 875 | { |
481 | dir_buf[playlist.dirlen-1] = 0; | ||
482 | |||
483 | /* handle dos style drive letter */ | 876 | /* handle dos style drive letter */ |
484 | if ( ':' == buf[1] ) { | 877 | if (':' == src[1]) |
485 | strcpy(now_playing, &buf[2]); | 878 | strcpy(src, &dest[2]); |
486 | } | 879 | else if ('.' == src[0] && '.' == src[1] && '/' == src[2]) |
487 | else if ( '.' == buf[0] && '.' == buf[1] && '/' == buf[2] ) { | 880 | { |
488 | /* handle relative paths */ | 881 | /* handle relative paths */ |
489 | seek=3; | 882 | i=3; |
490 | while(buf[seek] == '.' && | 883 | while(src[i] == '.' && |
491 | buf[seek+1] == '.' && | 884 | src[i] == '.' && |
492 | buf[seek+2] == '/') | 885 | src[i] == '/') |
493 | seek += 3; | 886 | i += 3; |
494 | for (i=0; i<seek/3; i++) { | 887 | for (j=0; j<i/3; j++) { |
495 | dir_end = strrchr(dir_buf, '/'); | 888 | temp_ptr = strrchr(dir, '/'); |
496 | if (dir_end) | 889 | if (temp_ptr) |
497 | *dir_end = '\0'; | 890 | *temp_ptr = '\0'; |
498 | else | 891 | else |
499 | break; | 892 | break; |
500 | } | 893 | } |
501 | snprintf(now_playing, MAX_PATH+1, "%s/%s", dir_buf, &buf[seek]); | 894 | snprintf(dest, buf_length, "%s/%s", dir, &src[i]); |
502 | } | 895 | } |
503 | else if ( '.' == buf[0] && '/' == buf[1] ) { | 896 | else if ( '.' == src[0] && '/' == src[1] ) { |
504 | snprintf(now_playing, MAX_PATH+1, "%s/%s", dir_buf, &buf[2]); | 897 | snprintf(dest, buf_length, "%s/%s", dir, &src[2]); |
505 | } | 898 | } |
506 | else { | 899 | else { |
507 | snprintf(now_playing, MAX_PATH+1, "%s/%s", dir_buf, buf); | 900 | snprintf(dest, buf_length, "%s/%s", dir, src); |
508 | } | ||
509 | } | ||
510 | |||
511 | buf = now_playing; | ||
512 | |||
513 | /* remove bogus dirs from beginning of path | ||
514 | (workaround for buggy playlist creation tools) */ | ||
515 | if(!playlist.in_ram) | ||
516 | { | ||
517 | while (buf) | ||
518 | { | ||
519 | fd = open(buf, O_RDONLY); | ||
520 | if (fd >= 0) | ||
521 | { | ||
522 | close(fd); | ||
523 | break; | ||
524 | } | ||
525 | |||
526 | buf = strchr(buf+1, '/'); | ||
527 | } | 901 | } |
528 | } | 902 | } |
529 | |||
530 | if (!buf) | ||
531 | { | ||
532 | /* Even though this is an invalid file, we still need to pass a file | ||
533 | name to the caller because NULL is used to indicate end of | ||
534 | playlist */ | ||
535 | return now_playing; | ||
536 | } | ||
537 | 903 | ||
538 | return buf; | 904 | return 0; |
539 | } | 905 | } |
540 | 906 | ||
541 | /* | 907 | /* |
542 | * This function is called to start playback of a given playlist. This | 908 | * Display splash message showing progress of playlist/directory insertion or |
543 | * playlist may be stored in RAM (when using full-dir playback). | 909 | * save. |
544 | * | ||
545 | * Return: the new index (possibly different due to shuffle) | ||
546 | */ | 910 | */ |
547 | int play_list(char *dir, /* "current directory" */ | 911 | static void display_playlist_count(int count, char *fmt) |
548 | char *file, /* playlist */ | ||
549 | int start_index, /* index in the playlist */ | ||
550 | bool shuffled_index, /* if TRUE the specified index is for the | ||
551 | playlist AFTER the shuffle */ | ||
552 | int start_offset, /* offset in the file */ | ||
553 | int random_seed, /* used for shuffling */ | ||
554 | int first_index, /* first index of playlist */ | ||
555 | int queue_resume, /* resume queue list? */ | ||
556 | int queue_resume_index ) /* queue list seek pos */ | ||
557 | { | 912 | { |
558 | char *sep=""; | 913 | lcd_clear_display(); |
559 | int dirlen; | ||
560 | empty_playlist(queue_resume); | ||
561 | |||
562 | playlist.index = start_index; | ||
563 | playlist.first_index = first_index; | ||
564 | 914 | ||
565 | #ifdef HAVE_LCD_BITMAP | 915 | #ifdef HAVE_LCD_BITMAP |
566 | if(global_settings.statusbar) | 916 | if(global_settings.statusbar) |
@@ -569,228 +919,915 @@ int play_list(char *dir, /* "current directory" */ | |||
569 | lcd_setmargins(0, 0); | 919 | lcd_setmargins(0, 0); |
570 | #endif | 920 | #endif |
571 | 921 | ||
572 | /* If file is NULL, the list is in RAM */ | 922 | #ifdef HAVE_PLAYER_KEYPAD |
573 | if(file) { | 923 | splash(0, 0, true, fmt, count, str(LANG_STOP_ABORT)); |
574 | splash(0, 0, true, str(LANG_PLAYLIST_LOAD)); | 924 | #else |
575 | playlist.in_ram = false; | 925 | splash(0, 0, true, fmt, count, str(LANG_OFF_ABORT)); |
576 | } else { | 926 | #endif |
577 | /* Assign a dummy filename */ | 927 | } |
578 | file = ""; | 928 | |
579 | playlist.in_ram = true; | 929 | /* |
930 | * Display buffer full message | ||
931 | */ | ||
932 | static void display_buffer_full(void) | ||
933 | { | ||
934 | lcd_clear_display(); | ||
935 | lcd_puts(0,0,str(LANG_PLAYINDICES_PLAYLIST)); | ||
936 | lcd_puts(0,1,str(LANG_PLAYINDICES_BUFFER)); | ||
937 | lcd_update(); | ||
938 | sleep(HZ*2); | ||
939 | lcd_clear_display(); | ||
940 | } | ||
941 | |||
942 | /* | ||
943 | * Initialize playlist entries at startup | ||
944 | */ | ||
945 | void playlist_init(void) | ||
946 | { | ||
947 | playlist.fd = -1; | ||
948 | playlist.control_fd = -1; | ||
949 | playlist.max_playlist_size = global_settings.max_files_in_playlist; | ||
950 | playlist.indices = buffer_alloc(playlist.max_playlist_size * sizeof(int)); | ||
951 | playlist.buffer_size = | ||
952 | AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir; | ||
953 | playlist.buffer = buffer_alloc(playlist.buffer_size); | ||
954 | mutex_init(&playlist.control_mutex); | ||
955 | empty_playlist(true); | ||
956 | } | ||
957 | |||
958 | /* | ||
959 | * Create new playlist | ||
960 | */ | ||
961 | int playlist_create(char *dir, char *file) | ||
962 | { | ||
963 | empty_playlist(false); | ||
964 | |||
965 | playlist.control_fd = open(PLAYLIST_CONTROL_FILE, O_RDWR); | ||
966 | if (-1 == playlist.control_fd) | ||
967 | { | ||
968 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
969 | return -1; | ||
580 | } | 970 | } |
581 | 971 | ||
582 | dirlen = strlen(dir); | 972 | if (!file) |
973 | { | ||
974 | file = ""; | ||
583 | 975 | ||
584 | /* If the dir does not end in trailing slash, we use a separator. | 976 | if (dir) |
585 | Otherwise we don't. */ | 977 | playlist.in_ram = true; |
586 | if('/' != dir[dirlen-1]) { | 978 | else |
587 | sep="/"; | 979 | dir = ""; /* empty playlist */ |
588 | dirlen++; | ||
589 | } | 980 | } |
590 | 981 | ||
591 | playlist.dirlen = dirlen; | 982 | update_playlist_filename(dir, file); |
592 | 983 | ||
593 | snprintf(playlist.filename, sizeof(playlist.filename), | 984 | if (fprintf(playlist.control_fd, "P:%d:%s:%s\n", |
594 | "%s%s%s", | 985 | PLAYLIST_CONTROL_FILE_VERSION, dir, file) > 0) |
595 | dir, sep, file); | 986 | fsync(playlist.control_fd); |
987 | else | ||
988 | { | ||
989 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); | ||
990 | return -1; | ||
991 | } | ||
596 | 992 | ||
597 | /* add track indices to playlist data structure */ | 993 | /* load the playlist file */ |
598 | add_indices_to_playlist(); | 994 | if (file[0] != '\0') |
599 | 995 | add_indices_to_playlist(); | |
600 | if(global_settings.playlist_shuffle) { | 996 | |
601 | if(!playlist.in_ram) { | 997 | return 0; |
602 | splash(0, 0, true, str(LANG_PLAYLIST_SHUFFLE)); | 998 | } |
603 | randomise_playlist( random_seed ); | 999 | |
604 | } | 1000 | #define PLAYLIST_COMMAND_SIZE (MAX_PATH+12) |
605 | else { | 1001 | |
606 | int i; | 1002 | /* |
1003 | * Restore the playlist state based on control file commands. Called to | ||
1004 | * resume playback after shutdown. | ||
1005 | */ | ||
1006 | int playlist_resume(void) | ||
1007 | { | ||
1008 | char *buffer; | ||
1009 | int buflen; | ||
1010 | int nread; | ||
1011 | int total_read = 0; | ||
1012 | bool first = true; | ||
1013 | |||
1014 | enum { | ||
1015 | resume_playlist, | ||
1016 | resume_add, | ||
1017 | resume_queue, | ||
1018 | resume_delete, | ||
1019 | resume_shuffle, | ||
1020 | resume_unshuffle, | ||
1021 | resume_reset, | ||
1022 | resume_comment | ||
1023 | }; | ||
1024 | |||
1025 | /* use mp3 buffer for maximum load speed */ | ||
1026 | buflen = (mp3end - mp3buf); | ||
1027 | buffer = mp3buf; | ||
1028 | |||
1029 | empty_playlist(true); | ||
1030 | |||
1031 | playlist.control_fd = open(PLAYLIST_CONTROL_FILE, O_RDWR); | ||
1032 | if (-1 == playlist.control_fd) | ||
1033 | { | ||
1034 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
1035 | return -1; | ||
1036 | } | ||
607 | 1037 | ||
608 | /* store the seek position before the shuffle */ | 1038 | /* read a small amount first to get the header */ |
609 | int seek_pos = playlist.indices[start_index]; | 1039 | nread = read(playlist.control_fd, buffer, |
1040 | PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); | ||
1041 | if(nread <= 0) | ||
1042 | { | ||
1043 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
1044 | return -1; | ||
1045 | } | ||
610 | 1046 | ||
611 | /* now shuffle around the indices */ | 1047 | while (1) |
612 | randomise_playlist( random_seed ); | 1048 | { |
1049 | int result = 0; | ||
1050 | int count; | ||
1051 | int current_command = resume_comment; | ||
1052 | int last_newline = 0; | ||
1053 | int str_count = -1; | ||
1054 | bool newline = true; | ||
1055 | bool exit_loop = false; | ||
1056 | char *p = buffer; | ||
1057 | char *str1 = NULL; | ||
1058 | char *str2 = NULL; | ||
1059 | char *str3 = NULL; | ||
1060 | |||
1061 | for(count=0; count<nread && !exit_loop; count++,p++) | ||
1062 | { | ||
1063 | /* Are we on a new line? */ | ||
1064 | if((*p == '\n') || (*p == '\r')) | ||
1065 | { | ||
1066 | *p = '\0'; | ||
613 | 1067 | ||
614 | if(!shuffled_index && global_settings.play_selected) { | 1068 | /* save last_newline in case we need to load more data */ |
615 | /* The given index was for the unshuffled list, so we need | 1069 | last_newline = count; |
616 | to figure out the index AFTER the shuffle has been made. | ||
617 | We scan for the seek position we remmber from before. */ | ||
618 | 1070 | ||
619 | for(i=0; i< playlist.amount; i++) { | 1071 | switch (current_command) |
620 | if(seek_pos == playlist.indices[i]) { | 1072 | { |
621 | /* here's the start song! yiepee! */ | 1073 | case resume_playlist: |
622 | playlist.index = i; | 1074 | { |
623 | playlist.first_index = i; | 1075 | /* str1=version str2=dir str3=file */ |
624 | break; /* now stop searching */ | 1076 | int version; |
1077 | |||
1078 | if (!str1) | ||
1079 | { | ||
1080 | result = -1; | ||
1081 | exit_loop = true; | ||
1082 | break; | ||
1083 | } | ||
1084 | |||
1085 | if (!str2) | ||
1086 | str2 = ""; | ||
1087 | |||
1088 | if (!str3) | ||
1089 | str3 = ""; | ||
1090 | |||
1091 | version = atoi(str1); | ||
1092 | |||
1093 | if (version != PLAYLIST_CONTROL_FILE_VERSION) | ||
1094 | { | ||
1095 | result = -1; | ||
1096 | exit_loop = true; | ||
1097 | break; | ||
1098 | } | ||
1099 | |||
1100 | update_playlist_filename(str2, str3); | ||
1101 | |||
1102 | if (str3[0] != '\0') | ||
1103 | { | ||
1104 | /* NOTE: add_indices_to_playlist() overwrites the | ||
1105 | mp3buf so we need to reload control file | ||
1106 | data */ | ||
1107 | add_indices_to_playlist(); | ||
1108 | } | ||
1109 | else if (str2[0] != '\0') | ||
1110 | { | ||
1111 | playlist.in_ram = true; | ||
1112 | resume_directory(str2); | ||
1113 | } | ||
1114 | |||
1115 | /* load the rest of the data */ | ||
1116 | first = false; | ||
1117 | exit_loop = true; | ||
1118 | |||
1119 | break; | ||
1120 | } | ||
1121 | case resume_add: | ||
1122 | case resume_queue: | ||
1123 | { | ||
1124 | /* str1=position str2=last_position str3=file */ | ||
1125 | int position, last_position; | ||
1126 | bool queue; | ||
1127 | |||
1128 | if (!str1 || !str2 || !str3) | ||
1129 | { | ||
1130 | result = -1; | ||
1131 | exit_loop = true; | ||
1132 | break; | ||
1133 | } | ||
1134 | |||
1135 | position = atoi(str1); | ||
1136 | last_position = atoi(str2); | ||
1137 | |||
1138 | queue = (current_command == resume_add)?false:true; | ||
1139 | |||
1140 | /* seek position is based on str3's position in | ||
1141 | buffer */ | ||
1142 | if (add_track_to_playlist(str3, position, queue, | ||
1143 | total_read+(str3-buffer)) < 0) | ||
1144 | return -1; | ||
1145 | |||
1146 | playlist.last_insert_pos = last_position; | ||
1147 | |||
1148 | break; | ||
1149 | } | ||
1150 | case resume_delete: | ||
1151 | { | ||
1152 | /* str1=position */ | ||
1153 | int position; | ||
1154 | |||
1155 | if (!str1) | ||
1156 | { | ||
1157 | result = -1; | ||
1158 | exit_loop = true; | ||
1159 | break; | ||
1160 | } | ||
1161 | |||
1162 | position = atoi(str1); | ||
1163 | |||
1164 | if (remove_track_from_playlist(position, | ||
1165 | false) < 0) | ||
1166 | return -1; | ||
1167 | |||
1168 | break; | ||
1169 | } | ||
1170 | case resume_shuffle: | ||
1171 | { | ||
1172 | /* str1=seed str2=first_index */ | ||
1173 | int seed; | ||
1174 | |||
1175 | if (!str1 || !str2) | ||
1176 | { | ||
1177 | result = -1; | ||
1178 | exit_loop = true; | ||
1179 | break; | ||
1180 | } | ||
1181 | |||
1182 | seed = atoi(str1); | ||
1183 | playlist.first_index = atoi(str2); | ||
1184 | |||
1185 | if (randomise_playlist(seed, false, false) < 0) | ||
1186 | return -1; | ||
1187 | |||
1188 | break; | ||
1189 | } | ||
1190 | case resume_unshuffle: | ||
1191 | { | ||
1192 | /* str1=first_index */ | ||
1193 | if (!str1) | ||
1194 | { | ||
1195 | result = -1; | ||
1196 | exit_loop = true; | ||
1197 | break; | ||
1198 | } | ||
1199 | |||
1200 | playlist.first_index = atoi(str1); | ||
1201 | |||
1202 | if (sort_playlist(false, false) < 0) | ||
1203 | return -1; | ||
1204 | |||
1205 | break; | ||
1206 | } | ||
1207 | case resume_reset: | ||
1208 | { | ||
1209 | playlist.last_insert_pos = -1; | ||
1210 | break; | ||
1211 | } | ||
1212 | case resume_comment: | ||
1213 | default: | ||
1214 | break; | ||
1215 | } | ||
1216 | |||
1217 | newline = true; | ||
1218 | |||
1219 | /* to ignore any extra newlines */ | ||
1220 | current_command = resume_comment; | ||
1221 | } | ||
1222 | else if(newline) | ||
1223 | { | ||
1224 | newline = false; | ||
1225 | |||
1226 | /* first non-comment line must always specify playlist */ | ||
1227 | if (first && *p != 'P' && *p != '#') | ||
1228 | { | ||
1229 | result = -1; | ||
1230 | exit_loop = true; | ||
1231 | break; | ||
1232 | } | ||
1233 | |||
1234 | switch (*p) | ||
1235 | { | ||
1236 | case 'P': | ||
1237 | /* playlist can only be specified once */ | ||
1238 | if (!first) | ||
1239 | { | ||
1240 | result = -1; | ||
1241 | exit_loop = true; | ||
1242 | break; | ||
1243 | } | ||
1244 | |||
1245 | current_command = resume_playlist; | ||
1246 | break; | ||
1247 | case 'A': | ||
1248 | current_command = resume_add; | ||
1249 | break; | ||
1250 | case 'Q': | ||
1251 | current_command = resume_queue; | ||
1252 | break; | ||
1253 | case 'D': | ||
1254 | current_command = resume_delete; | ||
1255 | break; | ||
1256 | case 'S': | ||
1257 | current_command = resume_shuffle; | ||
1258 | break; | ||
1259 | case 'U': | ||
1260 | current_command = resume_unshuffle; | ||
1261 | break; | ||
1262 | case 'R': | ||
1263 | current_command = resume_reset; | ||
1264 | break; | ||
1265 | case '#': | ||
1266 | current_command = resume_comment; | ||
1267 | break; | ||
1268 | default: | ||
1269 | result = -1; | ||
1270 | exit_loop = true; | ||
1271 | break; | ||
1272 | } | ||
1273 | |||
1274 | str_count = -1; | ||
1275 | str1 = NULL; | ||
1276 | str2 = NULL; | ||
1277 | str3 = NULL; | ||
1278 | } | ||
1279 | else if(current_command != resume_comment) | ||
1280 | { | ||
1281 | /* all control file strings are separated with a colon. | ||
1282 | Replace the colon with 0 to get proper strings that can be | ||
1283 | used by commands above */ | ||
1284 | if (*p == ':') | ||
1285 | { | ||
1286 | *p = '\0'; | ||
1287 | str_count++; | ||
1288 | |||
1289 | if ((count+1) < nread) | ||
1290 | { | ||
1291 | switch (str_count) | ||
1292 | { | ||
1293 | case 0: | ||
1294 | str1 = p+1; | ||
1295 | break; | ||
1296 | case 1: | ||
1297 | str2 = p+1; | ||
1298 | break; | ||
1299 | case 2: | ||
1300 | str3 = p+1; | ||
1301 | break; | ||
1302 | default: | ||
1303 | /* allow last string to contain colons */ | ||
1304 | *p = ':'; | ||
1305 | break; | ||
1306 | } | ||
625 | } | 1307 | } |
626 | } | 1308 | } |
627 | /* if we for any reason wouldn't find the index again, it | ||
628 | won't set the index again and we should start at index 0 | ||
629 | instead */ | ||
630 | } | 1309 | } |
631 | } | 1310 | } |
632 | } | ||
633 | 1311 | ||
634 | if (queue_resume) | 1312 | if (result < 0) |
635 | { | 1313 | { |
636 | /* update the queue indices */ | 1314 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_CONTROL_INVALID)); |
637 | add_indices_to_queuelist(queue_resume_index); | 1315 | return result; |
1316 | } | ||
638 | 1317 | ||
639 | if (queue_resume == QUEUE_BEGIN_PLAYLIST) | 1318 | if (!newline || (exit_loop && count<nread)) |
640 | { | 1319 | { |
641 | /* begin with current playlist index */ | 1320 | /* We didn't end on a newline or we exited loop prematurely. |
642 | playlist.start_queue = 1; | 1321 | Either way, re-read the remainder. |
643 | playlist.index++; /* so we begin at the correct track */ | 1322 | NOTE: because of this, control file must always end with a |
1323 | newline */ | ||
1324 | count = last_newline; | ||
1325 | lseek(playlist.control_fd, total_read+count, SEEK_SET); | ||
644 | } | 1326 | } |
1327 | |||
1328 | total_read += count; | ||
1329 | |||
1330 | if (first) | ||
1331 | /* still looking for header */ | ||
1332 | nread = read(playlist.control_fd, buffer, | ||
1333 | PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); | ||
1334 | else | ||
1335 | nread = read(playlist.control_fd, buffer, buflen); | ||
1336 | |||
1337 | /* Terminate on EOF */ | ||
1338 | if(nread <= 0) | ||
1339 | break; | ||
1340 | } | ||
1341 | |||
1342 | return 0; | ||
1343 | } | ||
1344 | |||
1345 | /* | ||
1346 | * Add track to in_ram playlist. Used when playing directories. | ||
1347 | */ | ||
1348 | int playlist_add(char *filename) | ||
1349 | { | ||
1350 | int len = strlen(filename); | ||
1351 | |||
1352 | if((len+1 > playlist.buffer_size - playlist.buffer_end_pos) || | ||
1353 | (playlist.amount >= playlist.max_playlist_size)) | ||
1354 | { | ||
1355 | display_buffer_full(); | ||
1356 | return -1; | ||
645 | } | 1357 | } |
646 | 1358 | ||
647 | /* also make the first song get playing */ | 1359 | playlist.indices[playlist.amount++] = playlist.buffer_end_pos; |
648 | mpeg_play(start_offset); | ||
649 | 1360 | ||
650 | return playlist.index; | 1361 | strcpy(&playlist.buffer[playlist.buffer_end_pos], filename); |
1362 | playlist.buffer_end_pos += len; | ||
1363 | playlist.buffer[playlist.buffer_end_pos++] = '\0'; | ||
1364 | |||
1365 | return 0; | ||
651 | } | 1366 | } |
652 | 1367 | ||
653 | /* | 1368 | /* |
654 | * calculate track offsets within a playlist file | 1369 | * Insert track into playlist at specified position (or one of the special |
1370 | * positions). Returns position where track was inserted or -1 if error. | ||
655 | */ | 1371 | */ |
656 | void add_indices_to_playlist(void) | 1372 | int playlist_insert_track(char *filename, int position, bool queue) |
1373 | { | ||
1374 | int result = add_track_to_playlist(filename, position, queue, -1); | ||
1375 | |||
1376 | if (result != -1) | ||
1377 | { | ||
1378 | fsync(playlist.control_fd); | ||
1379 | mpeg_flush_and_reload_tracks(); | ||
1380 | } | ||
1381 | |||
1382 | return result; | ||
1383 | } | ||
1384 | |||
1385 | /* | ||
1386 | * Insert all tracks from specified directory into playlist. | ||
1387 | */ | ||
1388 | int playlist_insert_directory(char *dirname, int position, bool queue) | ||
657 | { | 1389 | { |
658 | int nread; | ||
659 | int i = 0; | ||
660 | int count = 0; | 1390 | int count = 0; |
661 | unsigned char* buffer = playlist_buffer; | 1391 | int result; |
662 | int buflen = playlist.buffer_size; | 1392 | char *count_str; |
663 | bool store_index; | ||
664 | unsigned char *p; | ||
665 | 1393 | ||
666 | if(!playlist.in_ram) { | 1394 | if (queue) |
667 | if(-1 == playlist.fd) | 1395 | count_str = str(LANG_PLAYLIST_QUEUE_COUNT); |
668 | playlist.fd = open(playlist.filename, O_RDONLY); | 1396 | else |
669 | if(-1 == playlist.fd) | 1397 | count_str = str(LANG_PLAYLIST_INSERT_COUNT); |
670 | return; /* failure */ | 1398 | |
671 | 1399 | display_playlist_count(count, count_str); | |
672 | #ifndef SIMULATOR | 1400 | |
673 | /* use mp3 buffer for maximum load speed */ | 1401 | result = add_directory_to_playlist(dirname, &position, queue, &count); |
674 | buflen = (mp3end - mp3buf); | 1402 | fsync(playlist.control_fd); |
675 | buffer = mp3buf; | 1403 | |
1404 | display_playlist_count(count, count_str); | ||
1405 | mpeg_flush_and_reload_tracks(); | ||
1406 | |||
1407 | return result; | ||
1408 | } | ||
1409 | |||
1410 | /* | ||
1411 | * Insert all tracks from specified playlist into dynamic playlist | ||
1412 | */ | ||
1413 | int playlist_insert_playlist(char *filename, int position, bool queue) | ||
1414 | { | ||
1415 | int fd; | ||
1416 | int max; | ||
1417 | char *temp_ptr; | ||
1418 | char *dir; | ||
1419 | char *count_str; | ||
1420 | char temp_buf[MAX_PATH+1]; | ||
1421 | char trackname[MAX_PATH+1]; | ||
1422 | int count = 0; | ||
1423 | int result = 0; | ||
1424 | |||
1425 | fd = open(filename, O_RDONLY); | ||
1426 | if (fd < 0) | ||
1427 | { | ||
1428 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_ACCESS_ERROR)); | ||
1429 | return -1; | ||
1430 | } | ||
1431 | |||
1432 | /* we need the directory name for formatting purposes */ | ||
1433 | dir = filename; | ||
1434 | |||
1435 | temp_ptr = strrchr(filename+1,'/'); | ||
1436 | if (temp_ptr) | ||
1437 | *temp_ptr = 0; | ||
1438 | else | ||
1439 | dir = "/"; | ||
1440 | |||
1441 | if (queue) | ||
1442 | count_str = str(LANG_PLAYLIST_QUEUE_COUNT); | ||
1443 | else | ||
1444 | count_str = str(LANG_PLAYLIST_INSERT_COUNT); | ||
1445 | |||
1446 | display_playlist_count(count, count_str); | ||
1447 | |||
1448 | while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0) | ||
1449 | { | ||
1450 | /* user abort */ | ||
1451 | #ifdef HAVE_PLAYER_KEYPAD | ||
1452 | if (button_get(false) == BUTTON_STOP) | ||
1453 | #else | ||
1454 | if (button_get(false) == BUTTON_OFF) | ||
676 | #endif | 1455 | #endif |
1456 | break; | ||
1457 | |||
1458 | if (temp_buf[0] != '#' || temp_buf[0] != '\0') | ||
1459 | { | ||
1460 | int insert_pos; | ||
1461 | |||
1462 | /* we need to format so that relative paths are correctly | ||
1463 | handled */ | ||
1464 | if (format_track_path(trackname, temp_buf, sizeof(trackname), max, | ||
1465 | dir) < 0) | ||
1466 | { | ||
1467 | result = -1; | ||
1468 | break; | ||
1469 | } | ||
1470 | |||
1471 | insert_pos = add_track_to_playlist(trackname, position, queue, | ||
1472 | -1); | ||
1473 | |||
1474 | if (insert_pos < 0) | ||
1475 | { | ||
1476 | result = -1; | ||
1477 | break; | ||
1478 | } | ||
1479 | |||
1480 | /* Make sure tracks are inserted in correct order if user | ||
1481 | requests INSERT_FIRST */ | ||
1482 | if (position == PLAYLIST_INSERT_FIRST || position >= 0) | ||
1483 | position = insert_pos + 1; | ||
1484 | |||
1485 | count++; | ||
1486 | |||
1487 | if ((count%PLAYLIST_DISPLAY_COUNT) == 0) | ||
1488 | { | ||
1489 | display_playlist_count(count, count_str); | ||
1490 | |||
1491 | if (count == PLAYLIST_DISPLAY_COUNT) | ||
1492 | mpeg_flush_and_reload_tracks(); | ||
1493 | } | ||
1494 | } | ||
1495 | |||
1496 | /* let the other threads work */ | ||
1497 | yield(); | ||
677 | } | 1498 | } |
678 | 1499 | ||
679 | store_index = true; | 1500 | close(fd); |
1501 | fsync(playlist.control_fd); | ||
680 | 1502 | ||
681 | mpeg_stop(); | 1503 | *temp_ptr = '/'; |
1504 | |||
1505 | display_playlist_count(count, count_str); | ||
1506 | mpeg_flush_and_reload_tracks(); | ||
1507 | |||
1508 | return result; | ||
1509 | } | ||
1510 | |||
1511 | /* delete track at specified index */ | ||
1512 | int playlist_delete(int index) | ||
1513 | { | ||
1514 | int result = remove_track_from_playlist(index, true); | ||
1515 | |||
1516 | if (result != -1) | ||
1517 | mpeg_flush_and_reload_tracks(); | ||
1518 | |||
1519 | return result; | ||
1520 | } | ||
1521 | |||
1522 | /* shuffle newly created playlist using random seed. */ | ||
1523 | int playlist_shuffle(int random_seed, int start_index) | ||
1524 | { | ||
1525 | unsigned int seek_pos = 0; | ||
1526 | bool start_current = false; | ||
1527 | |||
1528 | if (start_index >= 0 && global_settings.play_selected) | ||
1529 | { | ||
1530 | /* store the seek position before the shuffle */ | ||
1531 | seek_pos = playlist.indices[start_index]; | ||
1532 | playlist.index = playlist.first_index = start_index; | ||
1533 | start_current = true; | ||
1534 | } | ||
1535 | |||
1536 | splash(0, 0, true, str(LANG_PLAYLIST_SHUFFLE)); | ||
682 | 1537 | ||
683 | while(1) | 1538 | randomise_playlist(random_seed, start_current, true); |
1539 | |||
1540 | return playlist.index; | ||
1541 | } | ||
1542 | |||
1543 | /* shuffle currently playing playlist */ | ||
1544 | int playlist_randomise(unsigned int seed, bool start_current) | ||
1545 | { | ||
1546 | int result = randomise_playlist(seed, start_current, true); | ||
1547 | |||
1548 | if (result != -1) | ||
1549 | mpeg_flush_and_reload_tracks(); | ||
1550 | |||
1551 | return result; | ||
1552 | } | ||
1553 | |||
1554 | /* sort currently playing playlist */ | ||
1555 | int playlist_sort(bool start_current) | ||
1556 | { | ||
1557 | int result = sort_playlist(start_current, true); | ||
1558 | |||
1559 | if (result != -1) | ||
1560 | mpeg_flush_and_reload_tracks(); | ||
1561 | |||
1562 | return result; | ||
1563 | } | ||
1564 | |||
1565 | /* start playing current playlist at specified index/offset */ | ||
1566 | int playlist_start(int start_index, int offset) | ||
1567 | { | ||
1568 | playlist.index = start_index; | ||
1569 | mpeg_play(offset); | ||
1570 | |||
1571 | return 0; | ||
1572 | } | ||
1573 | |||
1574 | /* Returns false if 'steps' is out of bounds, else true */ | ||
1575 | bool playlist_check(int steps) | ||
1576 | { | ||
1577 | int index = get_next_index(steps); | ||
1578 | return (index >= 0); | ||
1579 | } | ||
1580 | |||
1581 | /* get trackname of track that is "steps" away from current playing track. | ||
1582 | NULL is used to identify end of playlist */ | ||
1583 | char* playlist_peek(int steps) | ||
1584 | { | ||
1585 | int seek; | ||
1586 | int fd; | ||
1587 | char *temp_ptr; | ||
1588 | int index; | ||
1589 | bool control_file; | ||
1590 | |||
1591 | index = get_next_index(steps); | ||
1592 | if (index < 0) | ||
1593 | return NULL; | ||
1594 | |||
1595 | control_file = playlist.indices[index] & PLAYLIST_INSERT_TYPE_MASK; | ||
1596 | seek = playlist.indices[index] & PLAYLIST_SEEK_MASK; | ||
1597 | |||
1598 | if (get_filename(seek, control_file, now_playing, MAX_PATH+1) < 0) | ||
1599 | return NULL; | ||
1600 | |||
1601 | temp_ptr = now_playing; | ||
1602 | |||
1603 | if (!playlist.in_ram || control_file) | ||
684 | { | 1604 | { |
685 | if(playlist.in_ram) { | 1605 | /* remove bogus dirs from beginning of path |
686 | nread = playlist_end_pos; | 1606 | (workaround for buggy playlist creation tools) */ |
687 | } else { | 1607 | while (temp_ptr) |
688 | nread = read(playlist.fd, buffer, buflen); | 1608 | { |
689 | /* Terminate on EOF */ | 1609 | fd = open(temp_ptr, O_RDONLY); |
690 | if(nread <= 0) | 1610 | if (fd >= 0) |
1611 | { | ||
1612 | close(fd); | ||
691 | break; | 1613 | break; |
1614 | } | ||
1615 | |||
1616 | temp_ptr = strchr(temp_ptr+1, '/'); | ||
692 | } | 1617 | } |
693 | 1618 | ||
694 | p = buffer; | 1619 | if (!temp_ptr) |
1620 | { | ||
1621 | /* Even though this is an invalid file, we still need to pass a | ||
1622 | file name to the caller because NULL is used to indicate end | ||
1623 | of playlist */ | ||
1624 | return now_playing; | ||
1625 | } | ||
1626 | } | ||
695 | 1627 | ||
696 | for(count=0; count < nread; count++,p++) { | 1628 | return temp_ptr; |
1629 | } | ||
697 | 1630 | ||
698 | /* Are we on a new line? */ | 1631 | /* |
699 | if((*p == '\n') || (*p == '\r')) | 1632 | * Update indices as track has changed |
1633 | */ | ||
1634 | int playlist_next(int steps) | ||
1635 | { | ||
1636 | int index; | ||
1637 | |||
1638 | if (steps > 0) | ||
1639 | { | ||
1640 | int i, j; | ||
1641 | |||
1642 | /* We need to delete all the queued songs */ | ||
1643 | for (i=0, j=steps; i<j; i++) | ||
1644 | { | ||
1645 | index = get_next_index(i); | ||
1646 | |||
1647 | if (playlist.indices[index] & PLAYLIST_QUEUE_MASK) | ||
700 | { | 1648 | { |
701 | store_index = true; | 1649 | remove_track_from_playlist(index, true); |
702 | } | 1650 | steps--; /* one less track */ |
703 | else if(store_index) | 1651 | } |
1652 | } | ||
1653 | } | ||
1654 | |||
1655 | index = get_next_index(steps); | ||
1656 | playlist.index = index; | ||
1657 | |||
1658 | if (playlist.last_insert_pos >= 0) | ||
1659 | { | ||
1660 | /* check to see if we've gone beyond the last inserted track */ | ||
1661 | int rot_index = index; | ||
1662 | int rot_last_pos = playlist.last_insert_pos; | ||
1663 | |||
1664 | rot_index -= playlist.first_index; | ||
1665 | if (rot_index < 0) | ||
1666 | rot_index += playlist.amount; | ||
1667 | |||
1668 | rot_last_pos -= playlist.first_index; | ||
1669 | if (rot_last_pos < 0) | ||
1670 | rot_last_pos += playlist.amount; | ||
1671 | |||
1672 | if (rot_index > rot_last_pos) | ||
1673 | { | ||
1674 | /* reset last inserted track */ | ||
1675 | playlist.last_insert_pos = -1; | ||
1676 | |||
1677 | if (playlist.control_fd >= 0) | ||
704 | { | 1678 | { |
705 | store_index = false; | 1679 | int result = -1; |
706 | 1680 | ||
707 | if(playlist.in_ram || (*p != '#')) | 1681 | mutex_lock(&playlist.control_mutex); |
1682 | |||
1683 | if (lseek(playlist.control_fd, 0, SEEK_END) >= 0) | ||
708 | { | 1684 | { |
709 | /* Store a new entry */ | 1685 | if (fprintf(playlist.control_fd, "R\n") > 0) |
710 | playlist.indices[ playlist.amount ] = i+count; | 1686 | { |
711 | playlist.amount++; | 1687 | fsync(playlist.control_fd); |
712 | if ( playlist.amount >= playlist.max_playlist_size ) { | 1688 | result = 0; |
713 | lcd_clear_display(); | ||
714 | lcd_puts(0,0,str(LANG_PLAYINDICES_PLAYLIST)); | ||
715 | lcd_puts(0,1,str(LANG_PLAYINDICES_BUFFER)); | ||
716 | lcd_update(); | ||
717 | sleep(HZ*2); | ||
718 | lcd_clear_display(); | ||
719 | |||
720 | return; | ||
721 | } | 1689 | } |
722 | } | 1690 | } |
1691 | |||
1692 | mutex_unlock(&playlist.control_mutex); | ||
1693 | |||
1694 | if (result < 0) | ||
1695 | { | ||
1696 | splash(HZ*2, 0, true, | ||
1697 | str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); | ||
1698 | return result; | ||
1699 | } | ||
723 | } | 1700 | } |
724 | } | 1701 | } |
1702 | } | ||
725 | 1703 | ||
726 | i+= count; | 1704 | return index; |
1705 | } | ||
727 | 1706 | ||
728 | if(playlist.in_ram) | 1707 | /* Get resume info for current playing song. If return value is -1 then |
729 | break; | 1708 | settings shouldn't be saved. */ |
730 | } | 1709 | int playlist_get_resume_info(int *resume_index) |
1710 | { | ||
1711 | *resume_index = playlist.index; | ||
1712 | |||
1713 | return 0; | ||
731 | } | 1714 | } |
732 | 1715 | ||
733 | /* | 1716 | /* Returns index of current playing track for display purposes. This value |
734 | * randomly rearrange the array of indices for the playlist | 1717 | should not be used for resume purposes as it doesn't represent the actual |
735 | */ | 1718 | index into the playlist */ |
736 | void randomise_playlist( unsigned int seed ) | 1719 | int playlist_get_display_index(void) |
737 | { | 1720 | { |
738 | int count; | 1721 | int index = playlist.index; |
739 | int candidate; | ||
740 | int store; | ||
741 | |||
742 | /* seed with the given seed */ | ||
743 | srand( seed ); | ||
744 | 1722 | ||
745 | /* randomise entire indices list */ | 1723 | /* first_index should always be index 0 for display purposes */ |
746 | for(count = playlist.amount - 1; count >= 0; count--) | 1724 | index -= playlist.first_index; |
747 | { | 1725 | if (index < 0) |
748 | /* the rand is from 0 to RAND_MAX, so adjust to our value range */ | 1726 | index += playlist.amount; |
749 | candidate = rand() % (count + 1); | ||
750 | 1727 | ||
751 | /* now swap the values at the 'count' and 'candidate' positions */ | 1728 | return (index+1); |
752 | store = playlist.indices[candidate]; | 1729 | } |
753 | playlist.indices[candidate] = playlist.indices[count]; | ||
754 | playlist.indices[count] = store; | ||
755 | } | ||
756 | 1730 | ||
757 | mpeg_flush_and_reload_tracks(); | 1731 | /* returns number of tracks in playlist (includes queued/inserted tracks) */ |
1732 | int playlist_amount(void) | ||
1733 | { | ||
1734 | return playlist.amount; | ||
758 | } | 1735 | } |
759 | 1736 | ||
760 | static int compare(const void* p1, const void* p2) | 1737 | /* returns playlist name */ |
1738 | char *playlist_name(char *buf, int buf_size) | ||
761 | { | 1739 | { |
762 | int* e1 = (int*) p1; | 1740 | char *sep; |
763 | int* e2 = (int*) p2; | 1741 | |
1742 | snprintf(buf, buf_size, "%s", playlist.filename+playlist.dirlen); | ||
1743 | |||
1744 | if (0 == buf[0]) | ||
1745 | return NULL; | ||
764 | 1746 | ||
765 | return *e1 - *e2; | 1747 | /* Remove extension */ |
1748 | sep = strrchr(buf, '.'); | ||
1749 | if (NULL != sep) | ||
1750 | *sep = 0; | ||
1751 | |||
1752 | return buf; | ||
766 | } | 1753 | } |
767 | 1754 | ||
768 | /* | 1755 | /* save the current dynamic playlist to specified file */ |
769 | * Sort the array of indices for the playlist. If start_current is true then | 1756 | int playlist_save(char *filename) |
770 | * set the index to the new index of the current song. | ||
771 | */ | ||
772 | void sort_playlist(bool start_current) | ||
773 | { | 1757 | { |
774 | int i; | 1758 | int fd; |
775 | int current = playlist.indices[playlist.index]; | 1759 | int i, index; |
1760 | int count = 0; | ||
1761 | char tmp_buf[MAX_PATH+1]; | ||
1762 | int result = 0; | ||
776 | 1763 | ||
777 | if (playlist.amount > 0) | 1764 | if (playlist.amount <= 0) |
1765 | return -1; | ||
1766 | |||
1767 | /* use current working directory as base for pathname */ | ||
1768 | if (format_track_path(tmp_buf, filename, sizeof(tmp_buf), | ||
1769 | strlen(filename)+1, getcwd(NULL, -1)) < 0) | ||
1770 | return -1; | ||
1771 | |||
1772 | fd = open(tmp_buf, O_CREAT|O_WRONLY|O_TRUNC); | ||
1773 | if (fd < 0) | ||
778 | { | 1774 | { |
779 | qsort(playlist.indices, playlist.amount, sizeof(playlist.indices[0]), compare); | 1775 | splash(HZ*2, 0, true, str(LANG_PLAYLIST_ACCESS_ERROR)); |
1776 | return -1; | ||
780 | } | 1777 | } |
781 | 1778 | ||
782 | if (start_current) | 1779 | display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT)); |
1780 | |||
1781 | index = playlist.first_index; | ||
1782 | for (i=0; i<playlist.amount; i++) | ||
783 | { | 1783 | { |
784 | /* Set the index to the current song */ | 1784 | bool control_file; |
785 | for (i=0; i<playlist.amount; i++) | 1785 | bool queue; |
1786 | int seek; | ||
1787 | |||
1788 | /* user abort */ | ||
1789 | #ifdef HAVE_PLAYER_KEYPAD | ||
1790 | if (button_get(false) == BUTTON_STOP) | ||
1791 | #else | ||
1792 | if (button_get(false) == BUTTON_OFF) | ||
1793 | #endif | ||
1794 | break; | ||
1795 | |||
1796 | control_file = playlist.indices[index] & PLAYLIST_INSERT_TYPE_MASK; | ||
1797 | queue = playlist.indices[index] & PLAYLIST_QUEUE_MASK; | ||
1798 | seek = playlist.indices[index] & PLAYLIST_SEEK_MASK; | ||
1799 | |||
1800 | /* Don't save queued files */ | ||
1801 | if (!queue) | ||
786 | { | 1802 | { |
787 | if (playlist.indices[i] == current) | 1803 | if (get_filename(seek, control_file, tmp_buf, MAX_PATH+1) < 0) |
788 | { | 1804 | { |
789 | playlist.index = i; | 1805 | result = -1; |
790 | break; | 1806 | break; |
791 | } | 1807 | } |
1808 | |||
1809 | if (fprintf(fd, "%s\n", tmp_buf) < 0) | ||
1810 | { | ||
1811 | splash(HZ*2, 0, true, | ||
1812 | str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); | ||
1813 | result = -1; | ||
1814 | break; | ||
1815 | } | ||
1816 | |||
1817 | count++; | ||
1818 | |||
1819 | if ((count%PLAYLIST_DISPLAY_COUNT) == 0) | ||
1820 | display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT)); | ||
1821 | |||
1822 | yield(); | ||
792 | } | 1823 | } |
1824 | |||
1825 | index = (index+1)%playlist.amount; | ||
793 | } | 1826 | } |
794 | 1827 | ||
795 | mpeg_flush_and_reload_tracks(); | 1828 | display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT)); |
1829 | |||
1830 | close(fd); | ||
1831 | |||
1832 | return result; | ||
796 | } | 1833 | } |