summaryrefslogtreecommitdiff
path: root/apps/playlist.c
diff options
context:
space:
mode:
authorHardeep Sidhu <dyp@pobox.com>2003-07-01 21:05:43 +0000
committerHardeep Sidhu <dyp@pobox.com>2003-07-01 21:05:43 +0000
commit9e4262081b4ab5bad2e2708ea064643cf828685c (patch)
treebd809cc4616a2ed61bdbff217d26a13fd78b6609 /apps/playlist.c
parent928a09e3f464dc62e2863f8d77e766578788ba13 (diff)
downloadrockbox-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.c2039
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
42static struct playlist_info playlist; 93static 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
46static unsigned char *playlist_buffer; 122#define PLAYLIST_QUEUED 0x20000000
47 123
48static int playlist_end_pos = 0; 124#define PLAYLIST_DISPLAY_COUNT 10
49 125
50static char now_playing[MAX_PATH+1]; 126static char now_playing[MAX_PATH+1];
51 127
52void playlist_init(void) 128static void empty_playlist(bool resume);
53{ 129static void update_playlist_filename(char *dir, char *file);
54 playlist.fd = -1; 130static int add_indices_to_playlist(void);
55 playlist.max_playlist_size = global_settings.max_files_in_playlist; 131static 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 = 133static 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); 135static int remove_track_from_playlist(int position, bool write);
60} 136static int randomise_playlist(unsigned int seed, bool start_current,
137 bool write);
138static int sort_playlist(bool start_current, bool write);
139static int get_next_index(int steps);
140static void find_and_set_playlist_index(unsigned int seek);
141static int compare(const void* p1, const void* p2);
142static int get_filename(int seek, bool control_file, char *buf,
143 int buf_length);
144static int format_track_path(char *dest, char *src, int buf_length, int max,
145 char *dir);
146static void display_playlist_count(int count, char *fmt);
147static 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 */
65static void empty_playlist(bool queue_resume) 152static 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/*
92static void add_indices_to_queuelist(int seek) 187 * store directory and name of playlist file
188 */
189static 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 */
212static 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
139static 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 */
294static 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 */
404static 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
227int playlist_amount(void) 506/*
507 * remove track at specified position
508 */
509static int remove_track_from_playlist(int position, bool write)
228{ 510{
229 return playlist.amount + playlist.num_queued; 511 int i;
230}
231 512
232int 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];
240int 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
271char *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 */
563static 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
288void 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
294int 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/*
309int 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 */
623static 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
355int playlist_next(int steps) 665/*
666 * returns the index of the track that is "steps" away from current playing
667 * track.
668 */
669static 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/*
395bool 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 */
738static 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
402char* 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 */
759static 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 */
783static 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 */
845static 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 */
547int play_list(char *dir, /* "current directory" */ 911static 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 */
932static 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 */
945void 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 */
961int 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 */
1006int 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 */
1348int 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 */
656void add_indices_to_playlist(void) 1372int 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 */
1388int 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 */
1413int 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 */
1512int 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. */
1523int 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 */
1544int 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 */
1555int 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 */
1566int 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 */
1575bool 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 */
1583char* 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 */
1634int 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 } 1709int 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 */
736void randomise_playlist( unsigned int seed ) 1719int 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) */
1732int playlist_amount(void)
1733{
1734 return playlist.amount;
758} 1735}
759 1736
760static int compare(const void* p1, const void* p2) 1737/* returns playlist name */
1738char *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 1756int playlist_save(char *filename)
770 * set the index to the new index of the current song.
771 */
772void 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}