summaryrefslogtreecommitdiff
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
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
-rw-r--r--apps/applimits.h1
-rw-r--r--apps/lang/english.lang90
-rw-r--r--apps/main_menu.c3
-rw-r--r--apps/onplay.c116
-rw-r--r--apps/onplay.h6
-rw-r--r--apps/playlist.c2039
-rw-r--r--apps/playlist.h69
-rw-r--r--apps/playlist_menu.c71
-rw-r--r--apps/playlist_menu.h24
-rw-r--r--apps/screens.c4
-rw-r--r--apps/settings.c51
-rw-r--r--apps/settings.h10
-rw-r--r--apps/settings_menu.c4
-rw-r--r--apps/tree.c477
-rw-r--r--apps/tree.h11
-rw-r--r--apps/wps-display.c7
-rw-r--r--apps/wps.c13
-rw-r--r--firmware/common/file.c2
-rw-r--r--uisimulator/win32/Makefile5
-rw-r--r--uisimulator/win32/file.h2
-rw-r--r--uisimulator/win32/kernel.c12
-rw-r--r--uisimulator/win32/rockbox.dsp8
-rw-r--r--uisimulator/x11/Makefile5
-rw-r--r--uisimulator/x11/kernel.h3
24 files changed, 2169 insertions, 864 deletions
diff --git a/apps/applimits.h b/apps/applimits.h
index 6372dac924..a18544e87d 100644
--- a/apps/applimits.h
+++ b/apps/applimits.h
@@ -23,6 +23,5 @@
23#define AVERAGE_FILENAME_LENGTH 40 23#define AVERAGE_FILENAME_LENGTH 40
24#define MAX_DIR_LEVELS 10 24#define MAX_DIR_LEVELS 10
25#define MAX_PLAYLIST_SIZE 10000 25#define MAX_PLAYLIST_SIZE 10000
26#define MAX_QUEUED_FILES 100
27 26
28#endif 27#endif
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 57bec0c656..dab8fa6bd4 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -1632,3 +1632,93 @@ id: LANG_STAR
1632desc: in the games menu 1632desc: in the games menu
1633eng: "Star" 1633eng: "Star"
1634new: 1634new:
1635
1636id: LANG_QUEUE_LAST
1637desc: in onplay menu. queue a track/playlist at end of playlist.
1638eng: "Queue last"
1639new:
1640
1641id: LANG_INSERT
1642desc: in onplay menu. insert a track/playlist into dynamic playlist.
1643eng: "Insert"
1644new:
1645
1646id: LANG_INSERT_LAST
1647desc: in onplay menu. append a track/playlist into dynamic playlist.
1648eng: "Insert last"
1649new:
1650
1651id: LANG_QUEUE_FIRST
1652desc: in onplay menu. queue a track/playlist into dynamic playlist.
1653eng: "Queue first"
1654new:
1655
1656id: LANG_INSERT_FIRST
1657desc: in onplay menu. insert a track/playlist into dynamic playlist.
1658eng: "Insert first"
1659new:
1660
1661id: LANG_SAVE_DYNAMIC_PLAYLIST
1662desc: in playlist menu.
1663eng: "Save Dynamic Playlist"
1664new:
1665
1666id: LANG_PLAYLIST_MENU
1667desc: in main menu.
1668eng: "Playlist Options"
1669new:
1670
1671id: LANG_PLAYLIST_INSERT_COUNT
1672desc: splash number of tracks inserted
1673eng: "Inserted %d tracks (%s)"
1674new:
1675
1676id: LANG_PLAYLIST_QUEUE_COUNT
1677desc: splash number of tracks queued
1678eng: "Queued %d tracks (%s)"
1679new:
1680
1681id: LANG_PLAYLIST_SAVE_COUNT
1682desc: splash number of tracks saved
1683eng: "Saved %d tracks (%s)"
1684new:
1685
1686id: LANG_OFF_ABORT
1687desc: Used on recorder models
1688eng: "OFF to abort"
1689new:
1690
1691id: LANG_STOP_ABORT
1692desc: Used on player models
1693eng: "STOP to abort"
1694new:
1695
1696id: LANG_PLAYLIST_CONTROL_UPDATE_ERROR
1697desc: Playlist error
1698eng: "Error updating playlist control file"
1699new:
1700
1701id: LANG_PLAYLIST_ACCESS_ERROR
1702desc: Playlist error
1703eng: "Error accessing playlist file"
1704new:
1705
1706id: LANG_PLAYLIST_CONTROL_ACCESS_ERROR
1707desc: Playlist error
1708eng: "Error accessing playlist control file"
1709new:
1710
1711id: LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR
1712desc: Playlist error
1713eng: "Error accessing directory"
1714new:
1715
1716id: LANG_PLAYLIST_CONTROL_INVALID
1717desc: Playlist resume error
1718eng: "Playlist control file is invalid"
1719new:
1720
1721id: LANG_RECURSE_DIRECTORY
1722desc: In playlist menu
1723eng: "Recursively Insert Directories"
1724new:
diff --git a/apps/main_menu.c b/apps/main_menu.c
index b4d952ea5a..e356603653 100644
--- a/apps/main_menu.c
+++ b/apps/main_menu.c
@@ -43,6 +43,7 @@
43#include "wps.h" 43#include "wps.h"
44#include "buffer.h" 44#include "buffer.h"
45#include "screens.h" 45#include "screens.h"
46#include "playlist_menu.h"
46#ifdef HAVE_FMRADIO 47#ifdef HAVE_FMRADIO
47#include "radio.h" 48#include "radio.h"
48#endif 49#endif
@@ -266,7 +267,7 @@ bool main_menu(void)
266 { str(LANG_RECORDING), recording_screen }, 267 { str(LANG_RECORDING), recording_screen },
267 { str(LANG_RECORDING_SETTINGS), recording_menu }, 268 { str(LANG_RECORDING_SETTINGS), recording_menu },
268#endif 269#endif
269 { str(LANG_CREATE_PLAYLIST), create_playlist }, 270 { str(LANG_PLAYLIST_MENU), playlist_menu },
270 { str(LANG_MENU_SHOW_ID3_INFO), browse_id3 }, 271 { str(LANG_MENU_SHOW_ID3_INFO), browse_id3 },
271 { str(LANG_SLEEP_TIMER), sleeptimer_screen }, 272 { str(LANG_SLEEP_TIMER), sleeptimer_screen },
272#ifdef HAVE_ALARM_MOD 273#ifdef HAVE_ALARM_MOD
diff --git a/apps/onplay.c b/apps/onplay.c
index 3e0f5a365e..6c538f8d8a 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -38,13 +38,103 @@
38#include "screens.h" 38#include "screens.h"
39#include "tree.h" 39#include "tree.h"
40#include "buffer.h" 40#include "buffer.h"
41#include "settings.h"
42#include "status.h"
43#include "onplay.h"
41 44
42static char* selected_file = NULL; 45static char* selected_file = NULL;
43static bool reload_dir = false; 46static int selected_file_attr = 0;
47static int onplay_result = ONPLAY_OK;
44 48
45static bool queue_file(void) 49/* For playlist options */
50struct playlist_args {
51 int position;
52 bool queue;
53};
54
55static bool add_to_playlist(int position, bool queue)
46{ 56{
47 queue_add(selected_file); 57 bool new_playlist = !(mpeg_status() & MPEG_STATUS_PLAY);
58
59 if (new_playlist)
60 playlist_create(NULL, NULL);
61
62 if (selected_file_attr & TREE_ATTR_MPA)
63 playlist_insert_track(selected_file, position, queue);
64 else if (selected_file_attr & ATTR_DIRECTORY)
65 playlist_insert_directory(selected_file, position, queue);
66 else if (selected_file_attr & TREE_ATTR_M3U)
67 playlist_insert_playlist(selected_file, position, queue);
68
69 if (new_playlist && (playlist_amount() > 0))
70 {
71 /* nothing is currently playing so begin playing what we just
72 inserted */
73 if (global_settings.playlist_shuffle)
74 playlist_shuffle(current_tick, -1);
75 playlist_start(0,0);
76 status_set_playmode(STATUS_PLAY);
77 status_draw(false);
78 onplay_result = ONPLAY_START_PLAY;
79 }
80
81 return false;
82}
83
84/* Sub-menu for playlist options */
85static bool playlist_options(void)
86{
87 struct menu_items menu[6];
88 struct playlist_args args[6]; /* increase these 2 if you add entries! */
89 int m, i=0, result;
90
91 if (mpeg_status() & MPEG_STATUS_PLAY)
92 {
93 menu[i].desc = str(LANG_INSERT);
94 args[i].position = PLAYLIST_INSERT;
95 args[i].queue = false;
96 i++;
97
98 menu[i].desc = str(LANG_INSERT_FIRST);
99 args[i].position = PLAYLIST_INSERT_FIRST;
100 args[i].queue = false;
101 i++;
102
103 menu[i].desc = str(LANG_INSERT_LAST);
104 args[i].position = PLAYLIST_INSERT_LAST;
105 args[i].queue = false;
106 i++;
107
108 menu[i].desc = str(LANG_QUEUE);
109 args[i].position = PLAYLIST_INSERT;
110 args[i].queue = true;
111 i++;
112
113 menu[i].desc = str(LANG_QUEUE_FIRST);
114 args[i].position = PLAYLIST_INSERT_FIRST;
115 args[i].queue = true;
116 i++;
117
118 menu[i].desc = str(LANG_QUEUE_LAST);
119 args[i].position = PLAYLIST_INSERT_LAST;
120 args[i].queue = true;
121 i++;
122 }
123 else if ((selected_file_attr & TREE_ATTR_MPA) ||
124 (selected_file_attr & ATTR_DIRECTORY))
125 {
126 menu[i].desc = str(LANG_INSERT);
127 args[i].position = PLAYLIST_INSERT;
128 args[i].queue = false;
129 i++;
130 }
131
132 m = menu_init( menu, i );
133 result = menu_show(m);
134 if (result >= 0)
135 add_to_playlist(args[result].position, args[result].queue);
136 menu_exit(m);
137
48 return false; 138 return false;
49} 139}
50 140
@@ -68,7 +158,7 @@ static bool delete_file(void)
68 switch (btn) { 158 switch (btn) {
69 case BUTTON_PLAY: 159 case BUTTON_PLAY:
70 if (!remove(selected_file)) { 160 if (!remove(selected_file)) {
71 reload_dir = true; 161 onplay_result = ONPLAY_RELOAD_DIR;
72 lcd_clear_display(); 162 lcd_clear_display();
73 lcd_puts(0,0,str(LANG_DELETED)); 163 lcd_puts(0,0,str(LANG_DELETED));
74 lcd_puts_scroll(0,1,selected_file); 164 lcd_puts_scroll(0,1,selected_file);
@@ -104,7 +194,7 @@ static bool rename_file(void)
104 sleep(HZ*2); 194 sleep(HZ*2);
105 } 195 }
106 else 196 else
107 reload_dir = true; 197 onplay_result = ONPLAY_RELOAD_DIR;
108 } 198 }
109 199
110 return false; 200 return false;
@@ -225,7 +315,7 @@ static bool vbr_fix(void)
225 315
226 if(mpeg_status()) { 316 if(mpeg_status()) {
227 splash(HZ*2, 0, true, str(LANG_VBRFIX_STOP_PLAY)); 317 splash(HZ*2, 0, true, str(LANG_VBRFIX_STOP_PLAY));
228 return reload_dir; 318 return onplay_result;
229 } 319 }
230 320
231 lcd_clear_display(); 321 lcd_clear_display();
@@ -356,12 +446,16 @@ int onplay(char* file, int attr)
356 struct menu_items menu[5]; /* increase this if you add entries! */ 446 struct menu_items menu[5]; /* increase this if you add entries! */
357 int m, i=0, result; 447 int m, i=0, result;
358 448
449 onplay_result = ONPLAY_OK;
450
359 selected_file = file; 451 selected_file = file;
360 452 selected_file_attr = attr;
361 if ((mpeg_status() & MPEG_STATUS_PLAY) && (attr & TREE_ATTR_MPA)) 453
454 if ((attr & TREE_ATTR_MPA) || (attr & ATTR_DIRECTORY) ||
455 (attr & TREE_ATTR_M3U))
362 { 456 {
363 menu[i].desc = str(LANG_QUEUE); 457 menu[i].desc = str(LANG_PLAYINDICES_PLAYLIST);
364 menu[i].function = queue_file; 458 menu[i].function = playlist_options;
365 i++; 459 i++;
366 } 460 }
367 461
@@ -390,5 +484,5 @@ int onplay(char* file, int attr)
390 menu[result].function(); 484 menu[result].function();
391 menu_exit(m); 485 menu_exit(m);
392 486
393 return reload_dir; 487 return onplay_result;
394} 488}
diff --git a/apps/onplay.h b/apps/onplay.h
index 8540aaf2c7..7b47479e4b 100644
--- a/apps/onplay.h
+++ b/apps/onplay.h
@@ -21,4 +21,10 @@
21 21
22int onplay(char* file, int attr); 22int onplay(char* file, int attr);
23 23
24enum {
25 ONPLAY_OK,
26 ONPLAY_RELOAD_DIR,
27 ONPLAY_START_PLAY
28};
29
24#endif 30#endif
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}
diff --git a/apps/playlist.h b/apps/playlist.h
index 9ff5122f44..a5318cd202 100644
--- a/apps/playlist.h
+++ b/apps/playlist.h
@@ -22,57 +22,56 @@
22 22
23#include <stdbool.h> 23#include <stdbool.h>
24#include "file.h" 24#include "file.h"
25#include "applimits.h" 25#include "kernel.h"
26 26
27/* playlist data */ 27/* playlist data */
28 28
29struct playlist_info 29struct playlist_info
30{ 30{
31 char filename[MAX_PATH]; /* path name of m3u playlist on disk */ 31 char filename[MAX_PATH]; /* path name of m3u playlist on disk */
32 int fd; /* file descriptor of the open playlist */ 32 int fd; /* descriptor of the open playlist file */
33 int control_fd; /* descriptor of the open control file */
33 int dirlen; /* Length of the path to the playlist file */ 34 int dirlen; /* Length of the path to the playlist file */
34 int *indices; /* array of indices */ 35 unsigned int *indices; /* array of indices */
35 int max_playlist_size; /* Max number of files in playlist. Mirror of 36 int max_playlist_size; /* Max number of files in playlist. Mirror of
36 global_settings.max_files_in_playlist */ 37 global_settings.max_files_in_playlist */
37 int buffer_size; /* Playlist buffer size */ 38 bool in_ram; /* playlist stored in ram (dirplay) */
39 char *buffer; /* buffer for in-ram playlists */
40 int buffer_size; /* size of buffer */
41 int buffer_end_pos; /* last position where buffer was written */
38 int index; /* index of current playing track */ 42 int index; /* index of current playing track */
39 int first_index; /* index of first song in playlist */ 43 int first_index; /* index of first song in playlist */
40 int seed; /* random seed */
41 int amount; /* number of tracks in the index */ 44 int amount; /* number of tracks in the index */
42 bool in_ram; /* True if the playlist is RAM-based */ 45 int last_insert_pos; /* last position we inserted a track */
43 46 struct mutex control_mutex; /* mutex for control file access */
44 /* Queue function */
45 int queue_indices[MAX_QUEUED_FILES]; /* array of queue indices */
46 int last_queue_index; /* index of last queued track */
47 int queue_index; /* index of current playing queued track */
48 int num_queued; /* number of songs queued */
49 int start_queue; /* the first song was queued */
50}; 47};
51 48
52extern struct playlist_info playlist;
53extern bool playlist_shuffle;
54
55void playlist_init(void); 49void playlist_init(void);
56int play_list(char *dir, char *file, int start_index, 50int playlist_create(char *dir, char *file);
57 bool shuffled_index, int start_offset, 51int playlist_resume(void);
58 int random_seed, int first_index, int queue_resume,
59 int queue_resume_index);
60char* playlist_peek(int steps);
61char* playlist_name(char *name, int name_size);
62int playlist_next(int steps);
63bool playlist_check(int steps);
64void randomise_playlist( unsigned int seed );
65void sort_playlist(bool start_current);
66void add_indices_to_playlist(void);
67void playlist_clear(void);
68int playlist_add(char *filename); 52int playlist_add(char *filename);
69int queue_add(char *filename); 53int playlist_insert_track(char *filename, int position, bool queue);
54int playlist_insert_directory(char *dirname, int position, bool queue);
55int playlist_insert_playlist(char *filename, int position, bool queue);
56int playlist_delete(int index);
57int playlist_shuffle(int random_seed, int start_index);
58int playlist_randomise(unsigned int seed, bool start_current);
59int playlist_sort(bool start_current);
60int playlist_start(int start_index, int offset);
61bool playlist_check(int steps);
62char *playlist_peek(int steps);
63int playlist_next(int steps);
64int playlist_get_resume_info(int *resume_index);
65int playlist_get_display_index(void);
70int playlist_amount(void); 66int playlist_amount(void);
71int playlist_first_index(void); 67char *playlist_name(char *buf, int buf_size);
72int playlist_get_resume_info(int *resume_index, int *queue_resume, 68int playlist_save(char *filename);
73 int *queue_resume_index);
74 69
75enum { QUEUE_OFF, QUEUE_BEGIN_QUEUE, QUEUE_BEGIN_PLAYLIST, NUM_QUEUE_MODES }; 70enum {
71 PLAYLIST_PREPEND = -1,
72 PLAYLIST_INSERT = -2,
73 PLAYLIST_INSERT_LAST = -3,
74 PLAYLIST_INSERT_FIRST = -4
75};
76 76
77#endif /* __PLAYLIST_H__ */ 77#endif /* __PLAYLIST_H__ */
78
diff --git a/apps/playlist_menu.c b/apps/playlist_menu.c
new file mode 100644
index 0000000000..3508240efe
--- /dev/null
+++ b/apps/playlist_menu.c
@@ -0,0 +1,71 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Björn Stenberg
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20#include <string.h>
21
22#include "menu.h"
23#include "file.h"
24#include "keyboard.h"
25#include "playlist.h"
26#include "tree.h"
27#include "settings.h"
28
29#include "lang.h"
30
31#define DEFAULT_PLAYLIST_NAME "/dynamic.m3u"
32
33static bool save_playlist(void)
34{
35 char filename[MAX_PATH+1];
36
37 strncpy(filename, DEFAULT_PLAYLIST_NAME, sizeof(filename));
38
39 if (!kbd_input(filename, sizeof(filename)))
40 {
41 playlist_save(filename);
42
43 /* reload in case playlist was saved to cwd */
44 reload_directory();
45 }
46
47 return false;
48}
49
50static bool recurse_directory(void)
51{
52 return (set_bool( str(LANG_RECURSE_DIRECTORY),
53 &global_settings.recursive_dir_insert));
54}
55
56bool playlist_menu(void)
57{
58 int m;
59 bool result;
60
61 struct menu_items items[] = {
62 { str(LANG_CREATE_PLAYLIST), create_playlist },
63 { str(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist },
64 { str(LANG_RECURSE_DIRECTORY), recurse_directory },
65 };
66
67 m = menu_init( items, sizeof items / sizeof(struct menu_items) );
68 result = menu_run(m);
69 menu_exit(m);
70 return result;
71}
diff --git a/apps/playlist_menu.h b/apps/playlist_menu.h
new file mode 100644
index 0000000000..e10fc81299
--- /dev/null
+++ b/apps/playlist_menu.h
@@ -0,0 +1,24 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Björn Stenberg
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19#ifndef _PLAYLIST_MENU_H
20#define _PLAYLIST_MENU_H
21
22bool playlist_menu(void);
23
24#endif
diff --git a/apps/screens.c b/apps/screens.c
index 6055be3a2d..eb31eff5cf 100644
--- a/apps/screens.c
+++ b/apps/screens.c
@@ -341,9 +341,9 @@ bool f2_screen(void)
341 if(mpeg_status() & MPEG_STATUS_PLAY) 341 if(mpeg_status() & MPEG_STATUS_PLAY)
342 { 342 {
343 if (global_settings.playlist_shuffle) 343 if (global_settings.playlist_shuffle)
344 randomise_playlist(current_tick); 344 playlist_randomise(current_tick, true);
345 else 345 else
346 sort_playlist(true); 346 playlist_sort(true);
347 } 347 }
348 used = true; 348 used = true;
349 break; 349 break;
diff --git a/apps/settings.c b/apps/settings.c
index d6e555f03e..e5fbfb5e06 100644
--- a/apps/settings.c
+++ b/apps/settings.c
@@ -62,7 +62,7 @@
62struct user_settings global_settings; 62struct user_settings global_settings;
63char rockboxdir[] = ROCKBOX_DIR; /* config/font/data file directory */ 63char rockboxdir[] = ROCKBOX_DIR; /* config/font/data file directory */
64 64
65#define CONFIG_BLOCK_VERSION 6 65#define CONFIG_BLOCK_VERSION 7
66#define CONFIG_BLOCK_SIZE 512 66#define CONFIG_BLOCK_SIZE 512
67#define RTC_BLOCK_SIZE 44 67#define RTC_BLOCK_SIZE 44
68 68
@@ -98,10 +98,10 @@ offset abs
980x12 0x26 <(int) Resume playlist index, or -1 if no playlist resume> 980x12 0x26 <(int) Resume playlist index, or -1 if no playlist resume>
990x16 0x2a <(int) Byte offset into resume file> 990x16 0x2a <(int) Byte offset into resume file>
1000x1a 0x2e <time until disk spindown> 1000x1a 0x2e <time until disk spindown>
1010x1b 0x2f <browse current, play selected, queue_resume> 1010x1b 0x2f <browse current, play selected, recursive dir insert>
1020x1c 0x30 <peak meter hold timeout (bit 0-4), 1020x1c 0x30 <peak meter hold timeout (bit 0-4),
103 rec_editable (bit 7)> 103 rec_editable (bit 7)>
1040x1d 0x31 <(int) queue resume index> 1040x1d 0x31 <unused>
1050x21 0x35 <repeat mode (bit 0-1), rec. channels (bit 2), 1050x21 0x35 <repeat mode (bit 0-1), rec. channels (bit 2),
106 mic gain (bit 4-7)> 106 mic gain (bit 4-7)>
1070x22 0x36 <rec. quality (bit 0-2), source (bit 3-4), frequency (bit 5-7)> 1070x22 0x36 <rec. quality (bit 0-2), source (bit 3-4), frequency (bit 5-7)>
@@ -144,9 +144,9 @@ Rest of config block, only saved to disk:
1440xB8 (char[20]) WPS file 1440xB8 (char[20]) WPS file
1450xCC (char[20]) Lang file 1450xCC (char[20]) Lang file
1460xE0 (char[20]) Font file 1460xE0 (char[20]) Font file
1470xF4 (int) Playlist first index 1470xF4 <unused>
1480xF8 (int) Playlist shuffle seed 1480xF8 <unused>
1490xFC-0x1FF (char[260]) Resume playlist (path/to/dir or path/to/playlist.m3u) 1490xFC <unused>
150 150
151*************************************/ 151*************************************/
152 152
@@ -343,18 +343,19 @@ int settings_save( void )
343 343
344 memcpy(&config_block[0x12], &global_settings.resume_index, 4); 344 memcpy(&config_block[0x12], &global_settings.resume_index, 4);
345 memcpy(&config_block[0x16], &global_settings.resume_offset, 4); 345 memcpy(&config_block[0x16], &global_settings.resume_offset, 4);
346 DEBUGF( "+Resume index %X offset %X\n",
347 global_settings.resume_index,
348 global_settings.resume_offset );
346 349
347 config_block[0x1a] = (unsigned char)global_settings.disk_spindown; 350 config_block[0x1a] = (unsigned char)global_settings.disk_spindown;
348 config_block[0x1b] = (unsigned char) 351 config_block[0x1b] = (unsigned char)
349 (((global_settings.browse_current & 1)) | 352 (((global_settings.browse_current & 1)) |
350 ((global_settings.play_selected & 1) << 1) | 353 ((global_settings.play_selected & 1) << 1) |
351 ((global_settings.queue_resume & 3) << 2)); 354 ((global_settings.recursive_dir_insert & 1) << 2));
352 355
353 config_block[0x1c] = (unsigned char)global_settings.peak_meter_hold | 356 config_block[0x1c] = (unsigned char)global_settings.peak_meter_hold |
354 (global_settings.rec_editable?0x80:0); 357 (global_settings.rec_editable?0x80:0);
355 358
356 memcpy(&config_block[0x1d], &global_settings.queue_resume_index, 4);
357
358 config_block[0x21] = (unsigned char) 359 config_block[0x21] = (unsigned char)
359 ((global_settings.repeat_mode & 3) | 360 ((global_settings.repeat_mode & 3) |
360 ((global_settings.rec_channels & 1) << 2) | 361 ((global_settings.rec_channels & 1) << 2) |
@@ -415,17 +416,6 @@ int settings_save( void )
415 strncpy(&config_block[0xb8], global_settings.wps_file, MAX_FILENAME); 416 strncpy(&config_block[0xb8], global_settings.wps_file, MAX_FILENAME);
416 strncpy(&config_block[0xcc], global_settings.lang_file, MAX_FILENAME); 417 strncpy(&config_block[0xcc], global_settings.lang_file, MAX_FILENAME);
417 strncpy(&config_block[0xe0], global_settings.font_file, MAX_FILENAME); 418 strncpy(&config_block[0xe0], global_settings.font_file, MAX_FILENAME);
418 memcpy(&config_block[0xF4], &global_settings.resume_first_index, 4);
419 memcpy(&config_block[0xF8], &global_settings.resume_seed, 4);
420
421 strncpy(&config_block[0xFC], global_settings.resume_file, MAX_PATH);
422 DEBUGF( "+Resume file %s\n",global_settings.resume_file );
423 DEBUGF( "+Resume index %X offset %X\n",
424 global_settings.resume_index,
425 global_settings.resume_offset );
426 DEBUGF( "+Resume shuffle %s seed %X\n",
427 global_settings.playlist_shuffle?"on":"off",
428 global_settings.resume_seed );
429 419
430 if(save_config_buffer()) 420 if(save_config_buffer())
431 { 421 {
@@ -655,7 +645,8 @@ void settings_load(void)
655 if (config_block[0x1b] != 0xFF) { 645 if (config_block[0x1b] != 0xFF) {
656 global_settings.browse_current = (config_block[0x1b]) & 1; 646 global_settings.browse_current = (config_block[0x1b]) & 1;
657 global_settings.play_selected = (config_block[0x1b] >> 1) & 1; 647 global_settings.play_selected = (config_block[0x1b] >> 1) & 1;
658 global_settings.queue_resume = (config_block[0x1b] >> 2) & 3; 648 global_settings.recursive_dir_insert =
649 (config_block[0x1b] >> 2) & 1;
659 } 650 }
660 651
661 if (config_block[0x1c] != 0xFF) { 652 if (config_block[0x1c] != 0xFF) {
@@ -664,10 +655,6 @@ void settings_load(void)
664 (config_block[0x1c] & 0x80)?true:false; 655 (config_block[0x1c] & 0x80)?true:false;
665 } 656 }
666 657
667 if (config_block[0x1d] != 0xFF)
668 memcpy(&global_settings.queue_resume_index, &config_block[0x1d],
669 4);
670
671 if (config_block[0x21] != 0xFF) 658 if (config_block[0x21] != 0xFF)
672 { 659 {
673 global_settings.repeat_mode = config_block[0x21] & 3; 660 global_settings.repeat_mode = config_block[0x21] & 3;
@@ -745,14 +732,9 @@ void settings_load(void)
745 global_settings.max_files_in_playlist = 732 global_settings.max_files_in_playlist =
746 config_block[0xaa] | (config_block[0xab] << 8); 733 config_block[0xaa] | (config_block[0xab] << 8);
747 734
748 memcpy(&global_settings.resume_first_index, &config_block[0xF4], 4);
749 memcpy(&global_settings.resume_seed, &config_block[0xF8], 4);
750
751 strncpy(global_settings.wps_file, &config_block[0xb8], MAX_FILENAME); 735 strncpy(global_settings.wps_file, &config_block[0xb8], MAX_FILENAME);
752 strncpy(global_settings.lang_file, &config_block[0xcc], MAX_FILENAME); 736 strncpy(global_settings.lang_file, &config_block[0xcc], MAX_FILENAME);
753 strncpy(global_settings.font_file, &config_block[0xe0], MAX_FILENAME); 737 strncpy(global_settings.font_file, &config_block[0xe0], MAX_FILENAME);
754 strncpy(global_settings.resume_file, &config_block[0xFC], MAX_PATH);
755 global_settings.resume_file[MAX_PATH]=0;
756#ifdef HAVE_LCD_CHARCELLS 738#ifdef HAVE_LCD_CHARCELLS
757 if (config_block[0xa8] != 0xff) 739 if (config_block[0xa8] != 0xff)
758 global_settings.jump_scroll = config_block[0xa8]; 740 global_settings.jump_scroll = config_block[0xa8];
@@ -1097,6 +1079,8 @@ bool settings_load_config(char* file)
1097 else if (!strcasecmp(name, "max files in playlist")) 1079 else if (!strcasecmp(name, "max files in playlist"))
1098 set_cfg_int(&global_settings.max_files_in_playlist, value, 1080 set_cfg_int(&global_settings.max_files_in_playlist, value,
1099 1000, 20000); 1081 1000, 20000);
1082 else if (!strcasecmp(name, "recursive directory insert"))
1083 set_cfg_bool(&global_settings.recursive_dir_insert, value);
1100 } 1084 }
1101 1085
1102 close(fd); 1086 close(fd);
@@ -1385,6 +1369,9 @@ bool settings_save_config(void)
1385 fprintf(fd, "max files in playlist: %d\r\n", 1369 fprintf(fd, "max files in playlist: %d\r\n",
1386 global_settings.max_files_in_playlist); 1370 global_settings.max_files_in_playlist);
1387 1371
1372 fprintf(fd, "recursive directory insert: %s\r\n",
1373 boolopt[global_settings.recursive_dir_insert]);
1374
1388 close(fd); 1375 close(fd);
1389 1376
1390 lcd_clear_display(); 1377 lcd_clear_display();
@@ -1450,9 +1437,6 @@ void settings_reset(void) {
1450 global_settings.ff_rewind_accel = DEFAULT_FF_REWIND_ACCEL_SETTING; 1437 global_settings.ff_rewind_accel = DEFAULT_FF_REWIND_ACCEL_SETTING;
1451 global_settings.resume_index = -1; 1438 global_settings.resume_index = -1;
1452 global_settings.resume_offset = -1; 1439 global_settings.resume_offset = -1;
1453 global_settings.save_queue_resume = true;
1454 global_settings.queue_resume = 0;
1455 global_settings.queue_resume_index = -1;
1456 global_settings.disk_spindown = 5; 1440 global_settings.disk_spindown = 5;
1457 global_settings.disk_poweroff = false; 1441 global_settings.disk_poweroff = false;
1458 global_settings.buffer_margin = 0; 1442 global_settings.buffer_margin = 0;
@@ -1475,6 +1459,7 @@ void settings_reset(void) {
1475 global_settings.max_files_in_dir = 400; 1459 global_settings.max_files_in_dir = 400;
1476 global_settings.max_files_in_playlist = 10000; 1460 global_settings.max_files_in_playlist = 10000;
1477 global_settings.show_icons = true; 1461 global_settings.show_icons = true;
1462 global_settings.recursive_dir_insert = false;
1478} 1463}
1479 1464
1480bool set_bool(char* string, bool* variable ) 1465bool set_bool(char* string, bool* variable )
diff --git a/apps/settings.h b/apps/settings.h
index 624b06e06b..25efd65e9a 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -103,16 +103,7 @@ struct user_settings
103 int resume; /* resume option: 0=off, 1=ask, 2=on */ 103 int resume; /* resume option: 0=off, 1=ask, 2=on */
104 int resume_index; /* index in playlist (-1 for no active resume) */ 104 int resume_index; /* index in playlist (-1 for no active resume) */
105 int resume_offset; /* byte offset in mp3 file */ 105 int resume_offset; /* byte offset in mp3 file */
106 int resume_seed; /* random seed for playlist shuffle */
107 int resume_first_index; /* first index of playlist */
108 106
109 bool save_queue_resume; /* save queued songs for resume */
110 int queue_resume; /* resume queue file?: 0 = no
111 1 = resume at queue index
112 2 = resume at playlist index */
113 int queue_resume_index; /* queue index (seek point in queue file) */
114
115 unsigned char resume_file[MAX_PATH+1]; /* playlist name (or dir) */
116 unsigned char font_file[MAX_FILENAME+1]; /* last font */ 107 unsigned char font_file[MAX_FILENAME+1]; /* last font */
117 unsigned char wps_file[MAX_FILENAME+1]; /* last wps */ 108 unsigned char wps_file[MAX_FILENAME+1]; /* last wps */
118 unsigned char lang_file[MAX_FILENAME+1]; /* last language */ 109 unsigned char lang_file[MAX_FILENAME+1]; /* last language */
@@ -175,6 +166,7 @@ struct user_settings
175 int max_files_in_dir; /* Max entries in directory (file browser) */ 166 int max_files_in_dir; /* Max entries in directory (file browser) */
176 int max_files_in_playlist; /* Max entries in playlist */ 167 int max_files_in_playlist; /* Max entries in playlist */
177 bool show_icons; /* 0=hide 1=show */ 168 bool show_icons; /* 0=hide 1=show */
169 bool recursive_dir_insert;/* should directories be inserted recursively */
178}; 170};
179 171
180enum optiontype { INT, BOOL }; 172enum optiontype { INT, BOOL };
diff --git a/apps/settings_menu.c b/apps/settings_menu.c
index 57ba18132a..f64ee17ccd 100644
--- a/apps/settings_menu.c
+++ b/apps/settings_menu.c
@@ -724,11 +724,11 @@ static bool playback_settings_menu(void)
724 { 724 {
725 if (global_settings.playlist_shuffle) 725 if (global_settings.playlist_shuffle)
726 { 726 {
727 randomise_playlist(current_tick); 727 playlist_randomise(current_tick, true);
728 } 728 }
729 else 729 else
730 { 730 {
731 sort_playlist(true); 731 playlist_sort(true);
732 } 732 }
733 } 733 }
734 return result; 734 return result;
diff --git a/apps/tree.c b/apps/tree.c
index f7a83de85f..29ca4dfe15 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -66,10 +66,6 @@ static int max_files_in_dir;
66static char *name_buffer; 66static char *name_buffer;
67static int name_buffer_size; /* Size of allocated buffer */ 67static int name_buffer_size; /* Size of allocated buffer */
68static int name_buffer_length; /* Currently used amount */ 68static int name_buffer_length; /* Currently used amount */
69struct entry {
70 short attr; /* FAT attributes + file type flags */
71 char *name;
72};
73 69
74static struct entry *dircache; 70static struct entry *dircache;
75 71
@@ -87,6 +83,8 @@ static int boot_size = 0;
87static int boot_cluster; 83static int boot_cluster;
88static bool boot_changed = false; 84static bool boot_changed = false;
89 85
86static bool dirbrowse(char *root);
87
90void browse_root(void) 88void browse_root(void)
91{ 89{
92#ifndef SIMULATOR 90#ifndef SIMULATOR
@@ -158,14 +156,13 @@ static int build_playlist(int start_index)
158 int i; 156 int i;
159 int start=start_index; 157 int start=start_index;
160 158
161 playlist_clear();
162
163 for(i = 0;i < filesindir;i++) 159 for(i = 0;i < filesindir;i++)
164 { 160 {
165 if(dircache[i].attr & TREE_ATTR_MPA) 161 if(dircache[i].attr & TREE_ATTR_MPA)
166 { 162 {
167 DEBUGF("Adding %s\n", dircache[i].name); 163 DEBUGF("Adding %s\n", dircache[i].name);
168 playlist_add(dircache[i].name); 164 if (playlist_add(dircache[i].name) < 0)
165 break;
169 } 166 }
170 else 167 else
171 { 168 {
@@ -237,6 +234,133 @@ static void showfileline(int line, int direntry, bool scroll)
237 } 234 }
238} 235}
239 236
237/* load sorted directory into dircache. returns NULL on failure. */
238struct entry* load_and_sort_directory(char *dirname, int dirfilter,
239 int *num_files, bool *buffer_full)
240{
241 int i;
242
243 DIR *dir = opendir(dirname);
244 if(!dir)
245 return NULL; /* not a directory */
246
247 name_buffer_length = 0;
248 *buffer_full = false;
249
250 for ( i=0; i < max_files_in_dir; i++ ) {
251 int len;
252 struct dirent *entry = readdir(dir);
253 struct entry* dptr = &dircache[i];
254 if (!entry)
255 break;
256
257 len = strlen(entry->d_name);
258
259 /* skip directories . and .. */
260 if ((entry->attribute & ATTR_DIRECTORY) &&
261 (((len == 1) &&
262 (!strncmp(entry->d_name, ".", 1))) ||
263 ((len == 2) &&
264 (!strncmp(entry->d_name, "..", 2))))) {
265 i--;
266 continue;
267 }
268
269 /* Skip FAT volume ID */
270 if (entry->attribute & ATTR_VOLUME_ID) {
271 i--;
272 continue;
273 }
274
275 /* filter out dotfiles and hidden files */
276 if (dirfilter != SHOW_ALL &&
277 ((entry->d_name[0]=='.') ||
278 (entry->attribute & ATTR_HIDDEN))) {
279 i--;
280 continue;
281 }
282
283 dptr->attr = entry->attribute;
284
285 /* mark mp? and m3u files as such */
286 if ( !(dptr->attr & ATTR_DIRECTORY) && (len > 4) ) {
287 if (!strcasecmp(&entry->d_name[len-4], ".mp3") ||
288 (!strcasecmp(&entry->d_name[len-4], ".mp2")) ||
289 (!strcasecmp(&entry->d_name[len-4], ".mpa")))
290 dptr->attr |= TREE_ATTR_MPA;
291 else if (!strcasecmp(&entry->d_name[len-4], ".m3u"))
292 dptr->attr |= TREE_ATTR_M3U;
293 else if (!strcasecmp(&entry->d_name[len-4], ".cfg"))
294 dptr->attr |= TREE_ATTR_CFG;
295 else if (!strcasecmp(&entry->d_name[len-4], ".wps"))
296 dptr->attr |= TREE_ATTR_WPS;
297 else if (!strcasecmp(&entry->d_name[len-4], ".txt"))
298 dptr->attr |= TREE_ATTR_TXT;
299 else if (!strcasecmp(&entry->d_name[len-4], ".lng"))
300 dptr->attr |= TREE_ATTR_LNG;
301#ifdef HAVE_RECORDER_KEYPAD
302 else if (!strcasecmp(&entry->d_name[len-4], ".fnt"))
303 dptr->attr |= TREE_ATTR_FONT;
304 else if (!strcasecmp(&entry->d_name[len-4], ".ajz"))
305#else
306 else if (!strcasecmp(&entry->d_name[len-4], ".mod"))
307#endif
308 dptr->attr |= TREE_ATTR_MOD;
309 else if (!strcasecmp(&entry->d_name[len-5], ".rock"))
310 dptr->attr |= TREE_ATTR_ROCK;
311 }
312
313 /* memorize/compare details about the boot file */
314 if ((currdir[1] == 0) && !strcmp(entry->d_name, BOOTFILE)) {
315 if (boot_size) {
316 if ((entry->size != boot_size) ||
317 (entry->startcluster != boot_cluster))
318 boot_changed = true;
319 }
320 boot_size = entry->size;
321 boot_cluster = entry->startcluster;
322 }
323
324 /* filter out all non-playlist files */
325 if ( dirfilter == SHOW_PLAYLIST &&
326 (!(dptr->attr &
327 (ATTR_DIRECTORY|TREE_ATTR_M3U))) ) {
328 i--;
329 continue;
330 }
331
332 /* filter out non-music files */
333 if ( dirfilter == SHOW_MUSIC &&
334 (!(dptr->attr &
335 (ATTR_DIRECTORY|TREE_ATTR_MPA|TREE_ATTR_M3U))) ) {
336 i--;
337 continue;
338 }
339
340 /* filter out non-supported files */
341 if ( dirfilter == SHOW_SUPPORTED &&
342 (!(dptr->attr & TREE_ATTR_MASK)) ) {
343 i--;
344 continue;
345 }
346
347 if (len > name_buffer_size - name_buffer_length - 1) {
348 /* Tell the world that we ran out of buffer space */
349 *buffer_full = true;
350 break;
351 }
352 dptr->name = &name_buffer[name_buffer_length];
353 strcpy(dptr->name,entry->d_name);
354 name_buffer_length += len + 1;
355 }
356 *num_files = i;
357 closedir(dir);
358 strncpy(lastdir,dirname,sizeof(lastdir));
359 lastdir[sizeof(lastdir)-1] = 0;
360 qsort(dircache,i,sizeof(struct entry),compare);
361
362 return dircache;
363}
240 364
241static int showdir(char *path, int start) 365static int showdir(char *path, int start)
242{ 366{
@@ -258,124 +382,9 @@ static int showdir(char *path, int start)
258 382
259 /* new dir? cache it */ 383 /* new dir? cache it */
260 if (strncmp(path,lastdir,sizeof(lastdir)) || reload_dir) { 384 if (strncmp(path,lastdir,sizeof(lastdir)) || reload_dir) {
261 DIR *dir = opendir(path); 385 if (!load_and_sort_directory(path, global_settings.dirfilter,
262 if(!dir) 386 &filesindir, &dir_buffer_full))
263 return -1; /* not a directory */ 387 return -1;
264
265 name_buffer_length = 0;
266 dir_buffer_full = false;
267
268 for ( i=0; i < max_files_in_dir; i++ ) {
269 int len;
270 struct dirent *entry = readdir(dir);
271 struct entry* dptr = &dircache[i];
272 if (!entry)
273 break;
274
275 len = strlen(entry->d_name);
276
277 /* skip directories . and .. */
278 if ((entry->attribute & ATTR_DIRECTORY) &&
279 (((len == 1) &&
280 (!strncmp(entry->d_name, ".", 1))) ||
281 ((len == 2) &&
282 (!strncmp(entry->d_name, "..", 2))))) {
283 i--;
284 continue;
285 }
286
287 /* Skip FAT volume ID */
288 if (entry->attribute & ATTR_VOLUME_ID) {
289 i--;
290 continue;
291 }
292
293 /* filter out dotfiles and hidden files */
294 if (global_settings.dirfilter != SHOW_ALL &&
295 ((entry->d_name[0]=='.') ||
296 (entry->attribute & ATTR_HIDDEN))) {
297 i--;
298 continue;
299 }
300
301 dptr->attr = entry->attribute;
302
303 /* mark mp? and m3u files as such */
304 if ( !(dptr->attr & ATTR_DIRECTORY) && (len > 4) ) {
305 if (!strcasecmp(&entry->d_name[len-4], ".mp3") ||
306 (!strcasecmp(&entry->d_name[len-4], ".mp2")) ||
307 (!strcasecmp(&entry->d_name[len-4], ".mpa")))
308 dptr->attr |= TREE_ATTR_MPA;
309 else if (!strcasecmp(&entry->d_name[len-4], ".m3u"))
310 dptr->attr |= TREE_ATTR_M3U;
311 else if (!strcasecmp(&entry->d_name[len-4], ".cfg"))
312 dptr->attr |= TREE_ATTR_CFG;
313 else if (!strcasecmp(&entry->d_name[len-4], ".wps"))
314 dptr->attr |= TREE_ATTR_WPS;
315 else if (!strcasecmp(&entry->d_name[len-4], ".txt"))
316 dptr->attr |= TREE_ATTR_TXT;
317 else if (!strcasecmp(&entry->d_name[len-4], ".lng"))
318 dptr->attr |= TREE_ATTR_LNG;
319#ifdef HAVE_RECORDER_KEYPAD
320 else if (!strcasecmp(&entry->d_name[len-4], ".fnt"))
321 dptr->attr |= TREE_ATTR_FONT;
322 else if (!strcasecmp(&entry->d_name[len-4], ".ajz"))
323#else
324 else if (!strcasecmp(&entry->d_name[len-4], ".mod"))
325#endif
326 dptr->attr |= TREE_ATTR_MOD;
327 else if (!strcasecmp(&entry->d_name[len-5], ".rock"))
328 dptr->attr |= TREE_ATTR_ROCK;
329 }
330
331 /* memorize/compare details about the boot file */
332 if ((currdir[1] == 0) && !strcmp(entry->d_name, BOOTFILE)) {
333 if (boot_size) {
334 if ((entry->size != boot_size) ||
335 (entry->startcluster != boot_cluster))
336 boot_changed = true;
337 }
338 boot_size = entry->size;
339 boot_cluster = entry->startcluster;
340 }
341
342 /* filter out all non-playlist files */
343 if ( global_settings.dirfilter == SHOW_PLAYLIST &&
344 (!(dptr->attr &
345 (ATTR_DIRECTORY|TREE_ATTR_M3U))) ) {
346 i--;
347 continue;
348 }
349
350 /* filter out non-music files */
351 if ( global_settings.dirfilter == SHOW_MUSIC &&
352 (!(dptr->attr &
353 (ATTR_DIRECTORY|TREE_ATTR_MPA|TREE_ATTR_M3U))) ) {
354 i--;
355 continue;
356 }
357
358 /* filter out non-supported files */
359 if ( global_settings.dirfilter == SHOW_SUPPORTED &&
360 (!(dptr->attr & TREE_ATTR_MASK)) ) {
361 i--;
362 continue;
363 }
364
365 if (len > name_buffer_size - name_buffer_length - 1) {
366 /* Tell the world that we ran out of buffer space */
367 dir_buffer_full = true;
368 break;
369 }
370 dptr->name = &name_buffer[name_buffer_length];
371 strcpy(dptr->name,entry->d_name);
372 name_buffer_length += len + 1;
373 }
374 filesindir = i;
375 closedir(dir);
376 strncpy(lastdir,path,sizeof(lastdir));
377 lastdir[sizeof(lastdir)-1] = 0;
378 qsort(dircache,filesindir,sizeof(struct entry),compare);
379 388
380 if ( dir_buffer_full || filesindir == max_files_in_dir ) { 389 if ( dir_buffer_full || filesindir == max_files_in_dir ) {
381#ifdef HAVE_LCD_CHARCELLS 390#ifdef HAVE_LCD_CHARCELLS
@@ -531,7 +540,7 @@ static int showdir(char *path, int start)
531 return filesindir; 540 return filesindir;
532} 541}
533 542
534bool ask_resume(void) 543static bool ask_resume(void)
535{ 544{
536#ifdef HAVE_LCD_CHARCELLS 545#ifdef HAVE_LCD_CHARCELLS
537 lcd_double_height(false); 546 lcd_double_height(false);
@@ -570,92 +579,62 @@ bool ask_resume(void)
570 return false; 579 return false;
571} 580}
572 581
573void start_resume(void) 582/* load tracks from specified directory to resume play */
583void resume_directory(char *dir)
584{
585 bool buffer_full;
586
587 if (!load_and_sort_directory(dir, global_settings.dirfilter, &filesindir,
588 &buffer_full))
589 return;
590 lastdir[0] = 0;
591
592 build_playlist(0);
593}
594
595/* Returns the current working directory and also writes cwd to buf if
596 non-NULL. In case of error, returns NULL. */
597char *getcwd(char *buf, int size)
598{
599 if (!buf)
600 return currdir;
601 else if (size > 0)
602 {
603 strncpy(buf, currdir, size);
604 return buf;
605 }
606 else
607 return NULL;
608}
609
610/* Force a reload of the directory next time directory browser is called */
611void reload_directory(void)
612{
613 reload_dir = true;
614}
615
616static void start_resume(void)
574{ 617{
575 if ( global_settings.resume && 618 if ( global_settings.resume &&
576 global_settings.resume_index != -1 ) { 619 global_settings.resume_index != -1 ) {
577 int len = strlen(global_settings.resume_file);
578
579 DEBUGF("Resume file %s\n",global_settings.resume_file);
580 DEBUGF("Resume index %X offset %X\n", 620 DEBUGF("Resume index %X offset %X\n",
581 global_settings.resume_index, 621 global_settings.resume_index,
582 global_settings.resume_offset); 622 global_settings.resume_offset);
583 DEBUGF("Resume shuffle %s seed %X\n",
584 global_settings.playlist_shuffle?"on":"off",
585 global_settings.resume_seed);
586
587 /* playlist? */
588 if (!strcasecmp(&global_settings.resume_file[len-4], ".m3u")) {
589 char* slash;
590
591 /* check that the file exists */
592 int fd = open(global_settings.resume_file, O_RDONLY);
593 if(fd<0)
594 return;
595 close(fd);
596
597 if (!ask_resume())
598 return;
599
600 slash = strrchr(global_settings.resume_file,'/');
601 if (slash) {
602 *slash=0;
603 play_list(global_settings.resume_file,
604 slash+1,
605 global_settings.resume_index,
606 true, /* the index is AFTER shuffle */
607 global_settings.resume_offset,
608 global_settings.resume_seed,
609 global_settings.resume_first_index,
610 global_settings.queue_resume,
611 global_settings.queue_resume_index);
612 *slash='/';
613 }
614 else {
615 /* check that the dir exists */
616 DIR* dir = opendir(global_settings.resume_file);
617 if(!dir)
618 return;
619 closedir(dir);
620
621 if (!ask_resume())
622 return;
623
624 play_list("/",
625 global_settings.resume_file,
626 global_settings.resume_index,
627 true,
628 global_settings.resume_offset,
629 global_settings.resume_seed,
630 global_settings.resume_first_index,
631 global_settings.queue_resume,
632 global_settings.queue_resume_index);
633 }
634 }
635 else {
636 if (!ask_resume())
637 return;
638
639 if (showdir(global_settings.resume_file, 0) < 0 )
640 return;
641
642 lastdir[0] = '\0';
643
644 build_playlist(global_settings.resume_index);
645 play_list(global_settings.resume_file,
646 NULL,
647 global_settings.resume_index,
648 true,
649 global_settings.resume_offset,
650 global_settings.resume_seed,
651 global_settings.resume_first_index,
652 global_settings.queue_resume,
653 global_settings.queue_resume_index);
654 }
655 623
656 status_set_playmode(STATUS_PLAY); 624 if (!ask_resume())
657 status_draw(true); 625 return;
658 wps_show(); 626
627 if (playlist_resume() != -1)
628 {
629 playlist_start(global_settings.resume_index,
630 global_settings.resume_offset);
631
632 status_set_playmode(STATUS_PLAY);
633 status_draw(true);
634 wps_show();
635 }
636 else
637 return;
659 } 638 }
660} 639}
661 640
@@ -751,19 +730,33 @@ static bool handle_on(int* ds, int* dc, int numentries, int tree_max_on_screen)
751 730
752 case BUTTON_PLAY: 731 case BUTTON_PLAY:
753 case BUTTON_RC_PLAY: 732 case BUTTON_RC_PLAY:
754 case BUTTON_ON | BUTTON_PLAY: 733 case BUTTON_ON | BUTTON_PLAY: {
734 int onplay_result;
735
755 if (currdir[1]) 736 if (currdir[1])
756 snprintf(buf, sizeof buf, "%s/%s", 737 snprintf(buf, sizeof buf, "%s/%s",
757 currdir, dircache[dircursor+dirstart].name); 738 currdir, dircache[dircursor+dirstart].name);
758 else 739 else
759 snprintf(buf, sizeof buf, "/%s", 740 snprintf(buf, sizeof buf, "/%s",
760 dircache[dircursor+dirstart].name); 741 dircache[dircursor+dirstart].name);
761 if (onplay(buf, dircache[dircursor+dirstart].attr)) 742 onplay_result = onplay(buf,
762 reload_dir = 1; 743 dircache[dircursor+dirstart].attr);
744 switch (onplay_result)
745 {
746 case ONPLAY_OK:
747 used = true;
748 break;
749 case ONPLAY_RELOAD_DIR:
750 reload_dir = 1;
751 used = true;
752 break;
753 case ONPLAY_START_PLAY:
754 used = false; /* this will enable the wps */
755 break;
756 }
763 exit = true; 757 exit = true;
764 used = true;
765 break; 758 break;
766 759 }
767 case BUTTON_ON | BUTTON_REL: 760 case BUTTON_ON | BUTTON_REL:
768 case BUTTON_ON | TREE_PREV | BUTTON_REL: 761 case BUTTON_ON | TREE_PREV | BUTTON_REL:
769 case BUTTON_ON | TREE_NEXT | BUTTON_REL: 762 case BUTTON_ON | TREE_NEXT | BUTTON_REL:
@@ -793,7 +786,7 @@ static bool handle_on(int* ds, int* dc, int numentries, int tree_max_on_screen)
793 return used; 786 return used;
794} 787}
795 788
796bool dirbrowse(char *root) 789static bool dirbrowse(char *root)
797{ 790{
798 int numentries=0; 791 int numentries=0;
799 char buf[MAX_PATH]; 792 char buf[MAX_PATH];
@@ -934,41 +927,36 @@ bool dirbrowse(char *root)
934 lcd_stop_scroll(); 927 lcd_stop_scroll();
935 switch ( file->attr & TREE_ATTR_MASK ) { 928 switch ( file->attr & TREE_ATTR_MASK ) {
936 case TREE_ATTR_M3U: 929 case TREE_ATTR_M3U:
937 if ( global_settings.resume ) { 930 if (playlist_create(currdir, file->name) != -1)
938 if (currdir[1]) 931 {
939 snprintf(global_settings.resume_file, 932 if (global_settings.playlist_shuffle)
940 MAX_PATH, "%s/%s", 933 playlist_shuffle(seed, -1);
941 currdir, file->name); 934 start_index = 0;
942 else 935 playlist_start(start_index,0);
943 snprintf(global_settings.resume_file, 936 play = true;
944 MAX_PATH, "/%s", file->name);
945 } 937 }
946 play_list(currdir, file->name, 0, false, 0,
947 seed, 0, 0, -1);
948 start_index = 0;
949 play = true;
950 break; 938 break;
951 939
952 case TREE_ATTR_MPA: 940 case TREE_ATTR_MPA:
953 if ( global_settings.resume ) 941 if (playlist_create(currdir, NULL) != -1)
954 strncpy(global_settings.resume_file, 942 {
955 currdir, MAX_PATH); 943 start_index =
956 944 build_playlist(dircursor+dirstart);
957 start_index = 945 if (global_settings.playlist_shuffle)
958 build_playlist(dircursor+dirstart); 946 {
959 947 start_index =
960 /* when shuffling dir.: play all files even if the 948 playlist_shuffle(seed,start_index);
961 file selected by user is not the first one */ 949
962 if (global_settings.playlist_shuffle 950 /* when shuffling dir.: play all files
963 && !global_settings.play_selected) 951 even if the file selected by user is
964 start_index = 0; 952 not the first one */
965 953 if (!global_settings.play_selected)
966 /* it is important that we get back the index 954 start_index = 0;
967 in the (shuffled) list and store that */ 955 }
968 start_index = play_list(currdir, NULL, 956
969 start_index, false, 957 playlist_start(start_index, 0);
970 0, seed, 0, 0, -1); 958 play = true;
971 play = true; 959 }
972 break; 960 break;
973 961
974 /* wps config file */ 962 /* wps config file */
@@ -1055,9 +1043,6 @@ bool dirbrowse(char *root)
1055 shuffled list in case shuffle is enabled */ 1043 shuffled list in case shuffle is enabled */
1056 global_settings.resume_index = start_index; 1044 global_settings.resume_index = start_index;
1057 global_settings.resume_offset = 0; 1045 global_settings.resume_offset = 0;
1058 global_settings.resume_first_index =
1059 playlist_first_index();
1060 global_settings.resume_seed = seed;
1061 settings_save(); 1046 settings_save();
1062 } 1047 }
1063 1048
diff --git a/apps/tree.h b/apps/tree.h
index aa8f2127f3..66e83bc8f0 100644
--- a/apps/tree.h
+++ b/apps/tree.h
@@ -21,6 +21,11 @@
21 21
22#include <stdbool.h> 22#include <stdbool.h>
23 23
24struct entry {
25 short attr; /* FAT attributes + file type flags */
26 char *name;
27};
28
24/* using attribute not used by FAT */ 29/* using attribute not used by FAT */
25#define TREE_ATTR_MPA 0x40 /* mpeg audio file */ 30#define TREE_ATTR_MPA 0x40 /* mpeg audio file */
26#define TREE_ATTR_M3U 0x80 /* playlist */ 31#define TREE_ATTR_M3U 0x80 /* playlist */
@@ -36,7 +41,11 @@
36void tree_init(void); 41void tree_init(void);
37void browse_root(void); 42void browse_root(void);
38void set_current_file(char *path); 43void set_current_file(char *path);
39bool dirbrowse(char *root);
40bool create_playlist(void); 44bool create_playlist(void);
45void resume_directory(char *dir);
46char *getcwd(char *buf, int size);
47void reload_directory(void);
48struct entry* load_and_sort_directory(char *dirname, int dirfilter,
49 int *num_files, bool *buffer_full);
41 50
42#endif 51#endif
diff --git a/apps/wps-display.c b/apps/wps-display.c
index 71ba4c5433..d3c2613a1e 100644
--- a/apps/wps-display.c
+++ b/apps/wps-display.c
@@ -418,12 +418,7 @@ static char* get_tag(struct mp3entry* id3,
418#endif 418#endif
419 case 'p': /* Playlist Position */ 419 case 'p': /* Playlist Position */
420 *flags |= WPS_REFRESH_STATIC; 420 *flags |= WPS_REFRESH_STATIC;
421 { 421 snprintf(buf, buf_size, "%d", playlist_get_display_index());
422 int index = id3->index - playlist_first_index();
423 if (index < 0)
424 index += playlist_amount();
425 snprintf(buf, buf_size, "%d", index + 1);
426 }
427 return buf; 422 return buf;
428 423
429 case 'n': /* Playlist Name (without path) */ 424 case 'n': /* Playlist Name (without path) */
diff --git a/apps/wps.c b/apps/wps.c
index c5a27d9e9d..ca1d80fe28 100644
--- a/apps/wps.c
+++ b/apps/wps.c
@@ -223,13 +223,8 @@ bool browse_id3(void)
223 223
224 case 7: 224 case 7:
225 lcd_puts(0, 0, str(LANG_ID3_PLAYLIST)); 225 lcd_puts(0, 0, str(LANG_ID3_PLAYLIST));
226 { 226 snprintf(scroll_text,sizeof(scroll_text), "%d/%d",
227 int index = id3->index - playlist_first_index(); 227 playlist_get_display_index(), playlist_amount());
228 if (index < 0)
229 index += playlist_amount();
230 snprintf(scroll_text,sizeof(scroll_text), "%d/%d",
231 index + 1, playlist_amount());
232 }
233 lcd_puts_scroll(0, 1, scroll_text); 228 lcd_puts_scroll(0, 1, scroll_text);
234 break; 229 break;
235 230
@@ -455,9 +450,7 @@ static bool update(void)
455 DEBUGF("R%X,%X (%X)\n", global_settings.resume_offset, 450 DEBUGF("R%X,%X (%X)\n", global_settings.resume_offset,
456 id3->offset,id3); 451 id3->offset,id3);
457 452
458 if (!playlist_get_resume_info(&global_settings.resume_index, 453 if (!playlist_get_resume_info(&global_settings.resume_index))
459 &global_settings.queue_resume,
460 &global_settings.queue_resume_index))
461 { 454 {
462 global_settings.resume_offset = id3->offset; 455 global_settings.resume_offset = id3->offset;
463 settings_save(); 456 settings_save();
diff --git a/firmware/common/file.c b/firmware/common/file.c
index 6f86b2f8da..b4b879f8fb 100644
--- a/firmware/common/file.c
+++ b/firmware/common/file.c
@@ -33,7 +33,7 @@
33 The penalty is the RAM used for the cache and slightly more complex code. 33 The penalty is the RAM used for the cache and slightly more complex code.
34*/ 34*/
35 35
36#define MAX_OPEN_FILES 4 36#define MAX_OPEN_FILES 8
37 37
38struct filedesc { 38struct filedesc {
39 unsigned char cache[SECTOR_SIZE]; 39 unsigned char cache[SECTOR_SIZE];
diff --git a/uisimulator/win32/Makefile b/uisimulator/win32/Makefile
index d66fa6b2c4..42388b743d 100644
--- a/uisimulator/win32/Makefile
+++ b/uisimulator/win32/Makefile
@@ -100,7 +100,7 @@ APPS = main.c tree.c menu.c credits.c main_menu.c icons.c language.c \
100 screens.c peakmeter.c sleeptimer.c keyboard.c onplay.c\ 100 screens.c peakmeter.c sleeptimer.c keyboard.c onplay.c\
101 misc.c plugin.c 101 misc.c plugin.c
102 102
103MENUS = games_menu.c demo_menu.c settings_menu.c sound_menu.c 103MENUS = games_menu.c demo_menu.c settings_menu.c sound_menu.c playlist_menu.c
104 104
105ifeq ($(DISPLAY),-DHAVE_LCD_BITMAP) 105ifeq ($(DISPLAY),-DHAVE_LCD_BITMAP)
106 APPS += bmp.c widgets.c 106 APPS += bmp.c widgets.c
@@ -160,6 +160,9 @@ $(OBJDIR)/demo_menu.o: $(APPDIR)/demo_menu.c
160$(OBJDIR)/settings_menu.o: $(APPDIR)/settings_menu.c 160$(OBJDIR)/settings_menu.o: $(APPDIR)/settings_menu.c
161 $(CC) $(APPCFLAGS) -c $< -o $@ 161 $(CC) $(APPCFLAGS) -c $< -o $@
162 162
163$(OBJDIR)/playlist_menu.o: $(APPDIR)/playlist_menu.c
164 $(CC) $(APPCFLAGS) -c $< -o $@
165
163$(OBJDIR)/icons.o: $(MACHINEDIR)/icons.c 166$(OBJDIR)/icons.o: $(MACHINEDIR)/icons.c
164 $(CC) $(APPCFLAGS) -c $< -o $@ 167 $(CC) $(APPCFLAGS) -c $< -o $@
165 168
diff --git a/uisimulator/win32/file.h b/uisimulator/win32/file.h
index 4e13c8188e..1ba4e222cb 100644
--- a/uisimulator/win32/file.h
+++ b/uisimulator/win32/file.h
@@ -29,7 +29,7 @@ int win32_filesize(int fd);
29 29
30#define rename(x,y) win32_rename(x,y) 30#define rename(x,y) win32_rename(x,y)
31#define filesize(x) win32_filesize(x) 31#define filesize(x) win32_filesize(x)
32#define flush(x) _commit(x) 32#define fsync(x) _commit(x)
33 33
34#include "../../firmware/include/file.h" 34#include "../../firmware/include/file.h"
35 35
diff --git a/uisimulator/win32/kernel.c b/uisimulator/win32/kernel.c
index 567ed9ee39..f2a9dbbb59 100644
--- a/uisimulator/win32/kernel.c
+++ b/uisimulator/win32/kernel.c
@@ -103,3 +103,15 @@ int set_irq_level (int level)
103 static int _lv = 0; 103 static int _lv = 0;
104 return (_lv = level); 104 return (_lv = level);
105} 105}
106
107void mutex_init(struct mutex *m)
108{
109}
110
111void mutex_lock(struct mutex *m)
112{
113}
114
115void mutex_unlock(struct mutex *m)
116{
117}
diff --git a/uisimulator/win32/rockbox.dsp b/uisimulator/win32/rockbox.dsp
index 558c256bfa..28340463fc 100644
--- a/uisimulator/win32/rockbox.dsp
+++ b/uisimulator/win32/rockbox.dsp
@@ -43,7 +43,7 @@ RSC=rc.exe
43# PROP Ignore_Export_Lib 0 43# PROP Ignore_Export_Lib 0
44# PROP Target_Dir "" 44# PROP Target_Dir ""
45# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c 45# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c
46# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../../firmware/export" /I "../../firmware/drivers" /I "../../firmware/common" /I "../common" /I "../win32" /I "../../apps" /I "../../apps/recorder" /D "HAVE_LCD_BITMAP" /D "HAVE_RECORDER_KEYPAD" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "HAVE_CONFIG_H" /D "GETTIMEOFDAY_TWO_ARGS" /D "SIMULATOR" /D APPSVERSION=\"WIN32SIM\" /FR /YX /FD /GZ /c 46# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../../firmware/export" /I "../../firmware/drivers" /I "../../firmware/common" /I "../common" /I "../win32" /I "../../apps" /I "../../apps/recorder" /D "HAVE_LCD_BITMAP" /D "HAVE_RECORDER_KEYPAD" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "HAVE_CONFIG_H" /D "GETTIMEOFDAY_TWO_ARGS" /D "SIMULATOR" /D APPSVERSION=\"WIN32SIM\" /FR /YX /FD /GZ /c
47# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 47# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
48# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 48# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
49# ADD BASE RSC /l 0x407 /d "_DEBUG" 49# ADD BASE RSC /l 0x407 /d "_DEBUG"
@@ -69,7 +69,7 @@ LINK32=link.exe
69# PROP Ignore_Export_Lib 0 69# PROP Ignore_Export_Lib 0
70# PROP Target_Dir "" 70# PROP Target_Dir ""
71# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c 71# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c
72# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../../firmware/export" /I "../../firmware/drivers" /I "../../firmware/common" /I "../common" /I "../win32" /I "../../apps" /I "../../apps/player" /D "HAVE_LCD_CHARCELLS" /D "HAVE_PLAYER_KEYPAD" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "HAVE_CONFIG_H" /D "GETTIMEOFDAY_TWO_ARGS" /D "SIMULATOR" /D APPSVERSION=\"WIN32SIM\" /FR /YX /FD /GZ /c 72# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../../firmware/export" /I "../../firmware/drivers" /I "../../firmware/common" /I "../common" /I "../win32" /I "../../apps" /I "../../apps/player" /D "HAVE_LCD_CHARCELLS" /D "HAVE_PLAYER_KEYPAD" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "HAVE_CONFIG_H" /D "GETTIMEOFDAY_TWO_ARGS" /D "SIMULATOR" /D APPSVERSION=\"WIN32SIM\" /FR /YX /FD /GZ /c
73# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 73# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
74# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 74# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
75# ADD BASE RSC /l 0x407 /d "_DEBUG" 75# ADD BASE RSC /l 0x407 /d "_DEBUG"
@@ -369,6 +369,10 @@ SOURCE=..\..\apps\playlist.c
369# End Source File 369# End Source File
370# Begin Source File 370# Begin Source File
371 371
372SOURCE=..\..\apps\playlist_menu.c
373# End Source File
374# Begin Source File
375
372SOURCE=..\..\apps\plugin.c 376SOURCE=..\..\apps\plugin.c
373# End Source File 377# End Source File
374# Begin Source File 378# Begin Source File
diff --git a/uisimulator/x11/Makefile b/uisimulator/x11/Makefile
index b501ea2462..05116edbd9 100644
--- a/uisimulator/x11/Makefile
+++ b/uisimulator/x11/Makefile
@@ -95,7 +95,7 @@ APPS = main.c tree.c menu.c credits.c main_menu.c language.c\
95 screens.c peakmeter.c sleeptimer.c keyboard.c onplay.c\ 95 screens.c peakmeter.c sleeptimer.c keyboard.c onplay.c\
96 misc.c plugin.c 96 misc.c plugin.c
97 97
98MENUS = games_menu.c demo_menu.c settings_menu.c sound_menu.c 98MENUS = games_menu.c demo_menu.c settings_menu.c sound_menu.c playlist_menu.c
99 99
100ifeq ($(DISPLAY),-DHAVE_LCD_BITMAP) 100ifeq ($(DISPLAY),-DHAVE_LCD_BITMAP)
101 APPS += bmp.c widgets.c 101 APPS += bmp.c widgets.c
@@ -156,6 +156,9 @@ $(OBJDIR)/demo_menu.o: $(APPDIR)/demo_menu.c
156$(OBJDIR)/settings_menu.o: $(APPDIR)/settings_menu.c 156$(OBJDIR)/settings_menu.o: $(APPDIR)/settings_menu.c
157 $(CC) $(APPCFLAGS) -c $< -o $@ 157 $(CC) $(APPCFLAGS) -c $< -o $@
158 158
159$(OBJDIR)/playlist_menu.o: $(APPDIR)/playlist_menu.c
160 $(CC) $(APPCFLAGS) -c $< -o $@
161
159$(OBJDIR)/icons.o: $(MACHINEDIR)/icons.c 162$(OBJDIR)/icons.o: $(MACHINEDIR)/icons.c
160 $(CC) $(APPCFLAGS) -c $< -o $@ 163 $(CC) $(APPCFLAGS) -c $< -o $@
161 164
diff --git a/uisimulator/x11/kernel.h b/uisimulator/x11/kernel.h
index 7ec2979748..a045a3f117 100644
--- a/uisimulator/x11/kernel.h
+++ b/uisimulator/x11/kernel.h
@@ -22,6 +22,9 @@
22#ifndef NO_REDEFINES_PLEASE 22#ifndef NO_REDEFINES_PLEASE
23 23
24#define sleep(x) x11_sleep(x) 24#define sleep(x) x11_sleep(x)
25#define mutex_init(x) (void)x
26#define mutex_lock(x) (void)x
27#define mutex_unlock(x) (void)x
25 28
26#endif 29#endif
27 30