summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--config.h3
-rw-r--r--library.c84
-rw-r--r--library.h11
-rw-r--r--main.c447
-rw-r--r--plat.c27
-rw-r--r--plat.h3
-rw-r--r--playlist.c123
-rw-r--r--playlist.h22
9 files changed, 702 insertions, 21 deletions
diff --git a/Makefile b/Makefile
index 21c1210..25fb8bc 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,8 @@ sources = \
library.c \
main.c \
memory.c \
- plat.c
+ plat.c \
+ playlist.c
objects = $(sources:%.c=%.o)
diff --git a/config.h b/config.h
index 47fb153..b8c2bd0 100644
--- a/config.h
+++ b/config.h
@@ -9,13 +9,14 @@
#define album_cover_h 600
/* For each channel. */
-#define audio_buffer_size (1024 * 4)
+#define audio_buffer_size (1024 * 64)
#define memory_heap_size (1024 * 1024 * 512)
#define memory_size (memory_heap_size + (1024 * 1024 * 8))
#define library_memory_size (1024 * 1024 * 256)
#define library_path "/home/quou/music/"
+#define playlist_max 2048
#if defined(is64bit) && is64bit
#define allocation_default_alignment 8
diff --git a/library.c b/library.c
index 350789a..3d5aaa4 100644
--- a/library.c
+++ b/library.c
@@ -29,6 +29,14 @@ Song* find_song(Library* l, const char* path) {
return 0;
}
+void init_song(Song* s) {
+ s->track = 0;
+ s->path[0] = 0;
+ s->name[0] = 0;
+ s->artist[0] = 0;
+ s->album[0] = 0;
+}
+
static void cap_iter(
void* uptr,
const char* path
@@ -45,6 +53,7 @@ static void adder_iter(
Song* s;
Library* lib = uptr;
s = find_song(lib, path);
+ init_song(s);
if (get_song_meta(path, s)) {
strcpy(s->path, path);
lib->indices[lib->cnt++] = s - lib->songs;
@@ -57,7 +66,7 @@ void build_library(
const char* path
) {
int i;
- lib->cnt = lib->cap = 0;
+ lib->cnt = lib->cap = lib->fcnt = 0;
iter_dir(path, cap_iter, lib);
lib->cap += 16;
lib->songs = arena_alloc(
@@ -68,20 +77,76 @@ void build_library(
a,
lib->cap * sizeof *lib->indices
);
+ lib->filtered = arena_alloc(
+ a,
+ lib->cap * sizeof *lib->filtered
+ );
for (i = 0; i < lib->cap; i++)
lib->songs[i].path[0] = 0;
iter_dir(path, adder_iter, lib);
}
+char upper(char c) {
+ return c >= 'a' && c <= 'z'? c - ('a' - 'A'): c;
+}
+
+int string_eq(
+ const char* a,
+ const char* b
+) {
+ for (; *a && *b; a++, b++)
+ if (upper(*a) != upper(*b)) return 0;
+ return *b == 0;
+}
+
+int string_match(
+ const char* h,
+ const char* n
+) {
+ const char* c;
+ for (c = h; *c; c++) {
+ if (string_eq(c, n)) return 1;
+ }
+ return 0;
+}
+
+int song_match(
+ const Song* s,
+ const char* str
+) {
+#define check(n) \
+ (s->n[0] && string_match(s->n, str))
+ return s->path[0] && (
+ check(path) ||
+ check(name) ||
+ check(artist) ||
+ check(album)
+ );
+#undef check
+}
+
+void filter_library(
+ Library* l,
+ const char* str
+) {
+ int i;
+ Song* s;
+ l->fcnt = 0;
+ for (i = 0; i < l->cap; i++) {
+ s = &l->songs[i];
+ if (song_match(s, str))
+ l->filtered[l->fcnt++] = i;
+ }
+}
+
void parse_vorbis_comment(
Song* s,
const char* com,
int len
) {
/* this is not safe xDDDD idrc */
- if (len > 64) {
+ if (len > 64)
len = 64;
- }
if (!memcmp(com, "ARTIST=", 7)) {
len -= 7;
memcpy(s->artist, com + 7, len);
@@ -94,6 +159,9 @@ void parse_vorbis_comment(
len -= 6;
memcpy(s->album, com + 6, len);
s->album[len] = 0;
+ } else if (!memcmp(com, "TRACKNUMBER=", 12)) {
+ len -= 6;
+ s->track = (int)strtol(com + 12, 0, 10);
}
}
@@ -200,6 +268,8 @@ int get_song_meta(const char* path, Song* s) {
return 0;
}
+void ui_update_seek(void);
+
int sound_mix(
void* uptr,
unsigned char* buf,
@@ -219,7 +289,8 @@ int sound_mix(
);
if (!r) p->play = 0;
p->cs += r;
- ui_update_seek(p);
+ /* todo fix race condition here */
+ ui_update_seek();
return r * p->channels * 4;
}
@@ -278,7 +349,7 @@ void try_load_art(Player* p) {
}
}
-void play_song(Player* p, Song* song) {
+int play_song(Player* p, Song* song) {
drflac* f;
int sr, channels;
p->has_art = 0;
@@ -289,7 +360,7 @@ void play_song(Player* p, Song* song) {
0
);
/* todo errors and stuff */
- if (!f) return;
+ if (!f) return 0;
if (p->f)
drflac_close(p->f);
stop_audio();
@@ -308,6 +379,7 @@ void play_song(Player* p, Song* song) {
sr,
channels
);
+ return 1;
}
void play_seek(Player* p, float v) {
diff --git a/library.h b/library.h
index 81ce96b..cbb8df6 100644
--- a/library.h
+++ b/library.h
@@ -4,6 +4,7 @@
#include "memory.h"
typedef struct {
+ int track;
char path[256];
char name[64];
char artist[64];
@@ -13,7 +14,8 @@ typedef struct {
typedef struct {
Song* songs;
int* indices;
- int cap, cnt;
+ int* filtered;
+ int cap, cnt, fcnt;
} Library;
typedef struct {
@@ -30,14 +32,19 @@ typedef struct {
unsigned cover[album_cover_w * album_cover_h];
} Player;
+Song* find_song(Library* l, const char* path);
void build_library(
Arena* a,
Library* lib,
const char* path
);
+void filter_library(
+ Library* l,
+ const char* str
+);
int get_song_meta(const char* path, Song* s);
-void play_song(Player* p, Song* song);
+int play_song(Player* p, Song* song);
void play_seek(Player* p, float v);
void init_player(Player* p);
void deinit_player(Player* p);
diff --git a/main.c b/main.c
index 0e6d281..9ba07d3 100644
--- a/main.c
+++ b/main.c
@@ -3,15 +3,66 @@
#include "library.h"
#include "luigi.h"
#include "memory.h"
+#include "plat.h"
+#include "playlist.h"
+
+typedef struct {
+ UITable* tab;
+ Playlist* p;
+} Playlist_UI;
+
+typedef struct {
+ int idx;
+ UIMenu* menu;
+} PS_Ctx;
Library lib;
Player pl;
+Playlist queue, playlist;
+Playlist_UI queue_ui, playlist_ui;
UIButton* play_button;
UISlider* seek_slider;
UIImageDisplay* cover_image;
UILabel* title_text;
UILabel* album_text;
UILabel* artist_text;
+UIButton* playlist_select;
+UITable* libtab;
+char filter_text[64];
+char playlist_names[64][64];
+
+void ui_update_playlist(void) {
+ playlist_ui.tab->itemCount = playlist.cnt;
+ UITableResizeColumns(playlist_ui.tab);
+ UIElementRefresh(&playlist_ui.tab->e);
+ playlist_select->label = UIStringCopy(
+ playlist.name,
+ -1
+ );
+ playlist_select->labelBytes = strlen(playlist.name);
+ UIElementRefresh(playlist_select->e.parent);
+}
+
+void ui_update_queue(void) {
+ queue_ui.tab->itemCount = queue.cnt;
+ UITableResizeColumns(queue_ui.tab);
+ UIElementRefresh(&queue_ui.tab->e);
+}
+
+void ui_add_to_playlist(void* cp) {
+ playlist_add(&playlist, cp);
+ ui_update_playlist();
+}
+
+void ui_add_to_queue(void* cp) {
+ playlist_add(&queue, cp);
+ ui_update_queue();
+}
+
+void ui_queue_next(void* cp) {
+ playlist_add_first(&queue, cp);
+ ui_update_queue();
+}
void ui_update_play(void) {
UI_FREE(play_button->label);
@@ -52,6 +103,162 @@ void ui_update_seek(void) {
UIElementRefresh((UIElement*)seek_slider);
}
+int filter_msg(
+ UIElement* el,
+ UIMessage msg,
+ int di,
+ void* dp
+) {
+ UITextbox* tb = (UITextbox*)el;
+ (void)di;
+ (void)dp;
+ int s = sizeof filter_text - 1;
+ if (msg == UI_MSG_VALUE_CHANGED) {
+ if (!tb->bytes) {
+ filter_text[0] = 0;
+ libtab->itemCount = lib.cnt;
+ UIElementRefresh((UIElement*)libtab);
+ return 0;
+ }
+ if (s > tb->bytes)
+ s = tb->bytes - 1;
+ memcpy(filter_text, tb->string, s);
+ filter_text[tb->bytes] = 0;
+ filter_library(&lib, filter_text);
+ libtab->itemCount = lib.fcnt;
+ UIElementRefresh((UIElement*)libtab);
+ }
+ return 0;
+}
+
+int new_msg(
+ UIElement* el,
+ UIMessage msg,
+ int di,
+ void* dp
+) {
+ char* buf;
+ const char* r;
+ int e;
+ (void)di;
+ (void)dp;
+ if (msg == UI_MSG_CLICKED) {
+ r = UIDialogShow(
+ el->window,
+ 0,
+ "%s%t\n%b%b",
+ "Name",
+ &buf,
+ "Okay",
+ "Cancel"
+ );
+ if (!strcmp(r, "Okay")) {
+ init_playlist(&playlist);
+ memcpy(playlist.name, buf, sizeof playlist.name);
+ e = sizeof playlist.name - 1;
+ if (playlist.name[e]) playlist.name[e] = 0;
+ ui_update_playlist();
+ }
+ }
+ return 0;
+}
+
+int save_msg(
+ UIElement* el,
+ UIMessage msg,
+ int di,
+ void* dp
+) {
+ char* buf;
+ const char* r;
+ int e;
+ (void)di;
+ (void)dp;
+ if (msg == UI_MSG_CLICKED) {
+ if (!playlist.name[0]) {
+ r = UIDialogShow(
+ el->window,
+ 0,
+ "%s%t\n%b%b",
+ "Name",
+ &buf,
+ "Okay",
+ "Cancel"
+ );
+ if (!strcmp(r, "Okay")) {
+ memcpy(playlist.name, buf, sizeof playlist.name);
+ e = sizeof playlist.name - 1;
+ if (playlist.name[e]) playlist.name[e] = 0;
+ ui_update_playlist();
+ } else {
+ return 0;
+ }
+ }
+ playlist_save(&playlist);
+ }
+ return 0;
+}
+
+void ps_select(void* cp) {
+ int idx;
+ const char* n;
+ idx = (int)(uintptr_t)cp;
+ n = playlist_names[idx];
+ if (playlist_load(&lib, &playlist, n))
+ ui_update_playlist();
+}
+
+void ps_iter(void* u, const char* path) {
+ PS_Ctx* ctx = u;
+ UIMenu* menu;
+ const char* name;
+ int nl;
+ menu = ctx->menu;
+ name = path + strlen(path);
+ for (; name != path && *name != '/'; name--);
+ for (nl = 0; name[nl] && name[nl] != '.'; nl++);
+ name++; /* this fails if library path is bad xDDD */
+ nl--; /* ... or if the path doesn't have an extension */
+ memcpy(playlist_names[ctx->idx], name, nl);
+ playlist_names[ctx->idx][nl] = 0;
+ UIMenuAddItem(
+ menu,
+ 0,
+ name,
+ nl,
+ ps_select,
+ (void*)(uintptr_t)ctx->idx
+ );
+ ctx->idx++;
+}
+
+int ps_msg(
+ UIElement* el,
+ UIMessage msg,
+ int di,
+ void* dp
+) {
+ (void)di;
+ (void)dp;
+ if (msg == UI_MSG_CLICKED) {
+ UIMenu* menu;
+ char buf[256];
+ PS_Ctx ctx;
+ get_playlist_dir(buf);
+ if (dir_exist(buf)) {
+ menu = UIMenuCreate(
+ &el->window->e,
+ 0
+ );
+ ctx.menu = menu;
+ ctx.idx = 0;
+ iter_dir(buf, ps_iter, &ctx);
+ UIMenuShow(menu);
+ }
+ }
+ return 0;
+}
+
int libtab_msg(
UIElement* el,
UIMessage msg,
@@ -59,35 +266,44 @@ int libtab_msg(
void* dp
) {
Song* song;
+ int* ind;
(void)di;
(void)dp;
+ ind = filter_text[0]? lib.filtered: lib.indices;
if (msg == UI_MSG_TABLE_GET_ITEM) {
UITableGetItem *m = (UITableGetItem*)dp;
- song = &lib.songs[lib.indices[m->index]];
+ song = &lib.songs[ind[m->index]];
m->isSelected = 0;
switch (m->column) {
case 0:
return snprintf(
m->buffer,
m->bufferBytes,
+ "%d",
+ song->track
+ );
+ case 1:
+ return snprintf(
+ m->buffer,
+ m->bufferBytes,
"%s",
song->name
);
- case 1:
+ case 2:
return snprintf(
m->buffer,
m->bufferBytes,
"%s",
song->artist
);
- case 2:
+ case 3:
return snprintf(
m->buffer,
m->bufferBytes,
"%s",
song->album
);
- case 3:
+ case 4:
return snprintf(
m->buffer,
m->bufferBytes,
@@ -102,13 +318,139 @@ int libtab_msg(
el->window->cursorX,
el->window->cursorY
);
- song = &lib.songs[lib.indices[hit]];
+ song = &lib.songs[ind[hit]];
play_song(&pl, song);
ui_update_play();
+ } else if (msg == UI_MSG_RIGHT_DOWN) {
+ UIMenu* menu;
+ int hit = UITableHitTest(
+ (UITable*)el,
+ el->window->cursorX,
+ el->window->cursorY
+ );
+ song = &lib.songs[ind[hit]];
+ menu = UIMenuCreate(
+ &el->window->e,
+ UI_MENU_NO_SCROLL
+ );
+ UIMenuAddItem(
+ menu,
+ 0,
+ "Add to Playlist",
+ -1,
+ ui_add_to_playlist,
+ song
+ );
+ UIMenuAddItem(
+ menu,
+ 0,
+ "Add to Queue",
+ -1,
+ ui_add_to_queue,
+ song
+ );
+ UIMenuAddItem(
+ menu,
+ 0,
+ "Queue Next",
+ -1,
+ ui_queue_next,
+ song
+ );
+ UIMenuShow(menu);
}
return 0;
}
+int playlist_tab_msg(
+ UIElement* el,
+ UIMessage msg,
+ int di,
+ void* dp
+) {
+ Playlist* p = el->cp;
+ Song* song;
+ int* ind;
+ (void)di;
+ ind = filter_text[0]? lib.filtered: lib.indices;
+ if (msg == UI_MSG_TABLE_GET_ITEM) {
+ UITableGetItem *m = (UITableGetItem*)dp;
+ song = p->songs[m->index];
+ switch (m->column) {
+ case 0:
+ return snprintf(
+ m->buffer,
+ m->bufferBytes,
+ "%d",
+ song->track
+ );
+ case 1:
+ return snprintf(
+ m->buffer,
+ m->bufferBytes,
+ "%s",
+ song->name
+ );
+ case 2:
+ return snprintf(
+ m->buffer,
+ m->bufferBytes,
+ "%s",
+ song->artist
+ );
+ case 3:
+ return snprintf(
+ m->buffer,
+ m->bufferBytes,
+ "%s",
+ song->album
+ );
+ default: return 0;
+ }
+ } else if (msg == UI_MSG_RIGHT_DOWN) {
+ if (p == &playlist) {
+ UIMenu* menu;
+ int hit = UITableHitTest(
+ (UITable*)el,
+ el->window->cursorX,
+ el->window->cursorY
+ );
+ song = playlist.songs[hit];
+ menu = UIMenuCreate(
+ &el->window->e,
+ UI_MENU_NO_SCROLL
+ );
+ UIMenuAddItem(
+ menu,
+ 0,
+ "Add to Queue",
+ -1,
+ ui_add_to_queue,
+ song
+ );
+ UIMenuAddItem(
+ menu,
+ 0,
+ "Queue Next",
+ -1,
+ ui_queue_next,
+ song
+ );
+ UIMenuShow(menu);
+ }
+ } else if (msg == UI_MSG_LEFT_UP) {
+ int hit = UITableHitTest(
+ (UITable*)el,
+ el->window->cursorX,
+ el->window->cursorY
+ );
+ song = &lib.songs[ind[hit]];
+ if (play_song(&pl, song))
+ ui_update_play();
+ }
+ return 0;
+}
+
int playpause_msg(
UIElement* el,
UIMessage msg,
@@ -178,13 +520,32 @@ int seek_msg(
return 0;
}
+void ui_create_playlist(
+ UIElement* pa,
+ Playlist_UI* pu,
+ Playlist* p
+) {
+ UITable* tab;
+ tab = UITableCreate(
+ pa,
+ UI_ELEMENT_H_FILL | UI_ELEMENT_V_FILL,
+ "Track\tTitle\tArtist\tAlbum"
+ );
+ tab->itemCount = p->cnt;
+ tab->e.messageUser = playlist_tab_msg;
+ tab->e.cp = p;
+ UITableResizeColumns(tab);
+ pu->tab = tab;
+ pu->p = p;
+}
+
int prog_main(void* mem) {
Arena liba;
UIWindow* wi;
UISplitPane* split1, * split2, * split3;
- UIPanel* plib, * pctrl, * plist, * pqueue;
- UITable* libtab;
+ UIPanel* plib, * pctrl, * plist, * pqueue, * row;
int r;
+ srand(time(0));
memory_init(mem);
init_arena(
&liba,
@@ -193,6 +554,8 @@ int prog_main(void* mem) {
);
build_library(&liba, &lib, library_path);
init_player(&pl);
+ init_playlist(&queue);
+ init_playlist(&playlist);
UIInitialise();
wi = UIWindowCreate(
0,
@@ -280,6 +643,7 @@ int prog_main(void* mem) {
"Queue",
5
);
+ ui_create_playlist(&pqueue->e, &queue_ui, &queue);
plist = UIPanelCreate(&split2->e, UI_PANEL_GRAY);
plist->gap = 5;
@@ -290,6 +654,57 @@ int prog_main(void* mem) {
"Playlist",
8
);
+ playlist_select = UIButtonCreate(
+ &plist->e,
+ 0,
+ "",
+ 0
+ );
+ playlist_select->e.messageUser = ps_msg;
+ row = UIPanelCreate(
+ &plist->e,
+ UI_PANEL_HORIZONTAL |
+ UI_ELEMENT_H_FILL
+ );
+ UIButtonCreate(
+ &row->e,
+ 0,
+ "New",
+ -1
+ )->e.messageUser = new_msg;
+ UIButtonCreate(
+ &row->e,
+ 0,
+ "Save",
+ -1
+ )->e.messageUser = save_msg;
+ UISpacerCreate(&row->e, 0, 10, 0);
+ UIButtonCreate(
+ &row->e,
+ 0,
+ "Play",
+ -1
+ );
+ UIButtonCreate(
+ &row->e,
+ 0,
+ "Play Shuffled",
+ -1
+ );
+ UISpacerCreate(&row->e, 0, 10, 0);
+ UIButtonCreate(
+ &row->e,
+ 0,
+ "Enqueue",
+ -1
+ );
+ UIButtonCreate(
+ &row->e,
+ 0,
+ "Enqueue Shuffled",
+ -1
+ );
+ ui_create_playlist(&plist->e, &playlist_ui, &playlist);
plib = UIPanelCreate(&split2->e, UI_PANEL_GRAY);
plib->gap = 5;
plib->border = UI_RECT_1(5);
@@ -299,10 +714,26 @@ int prog_main(void* mem) {
"Library",
7
);
+ row = UIPanelCreate(
+ &plib->e,
+ UI_PANEL_HORIZONTAL |
+ UI_ELEMENT_H_FILL
+ );
+ UILabelCreate(
+ &row->e,
+ 0,
+ "Filter",
+ -1
+ );
+ UISpacerCreate(&row->e, 0, 10, 0);
+ UITextboxCreate(
+ &row->e,
+ UI_ELEMENT_H_FILL
+ )->e.messageUser = filter_msg;
libtab = UITableCreate(
&plib->e,
UI_ELEMENT_H_FILL | UI_ELEMENT_V_FILL,
- "Track\tArtist\tAlbum\tFilename"
+ "Track\tTitle\tArtist\tAlbum\tFilename"
);
libtab->itemCount = lib.cnt;
libtab->e.messageUser = libtab_msg;
diff --git a/plat.c b/plat.c
index a70fe52..bb8b4db 100644
--- a/plat.c
+++ b/plat.c
@@ -5,13 +5,14 @@
#ifdef plat_posix
#define _POSIX_SOURCE
#define _GNU_SOURCE
+#include <dirent.h>
+#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
-#include <unistd.h>
#include <stdlib.h>
-#include <dirent.h>
#include <string.h>
-#include <pthread.h>
+#include <sys/stat.h>
+#include <unistd.h>
extern int fileno(FILE*);
@@ -93,6 +94,26 @@ void iter_dir(const char* path, Dir_Iter fn, void* u) {
closedir(di);
}
+int dir_exist(const char* p) {
+ DIR* di;
+ di = opendir(p);
+ if (di) {
+ closedir(di);
+ return 1;
+ }
+ return 0;
+}
+
+int make_dir(const char* p) {
+ return mkdir(
+ p,
+ S_IRWXU |
+ S_IRWXG |
+ S_IROTH |
+ S_IXOTH
+ ) == 0;
+}
+
pthread_t audio_thread;
pthread_mutex_t audio_mutex;
diff --git a/plat.h b/plat.h
index ae9cff2..a49418d 100644
--- a/plat.h
+++ b/plat.h
@@ -18,4 +18,7 @@ void unlock_audio(void);
void stop_audio(void);
int audio_done(void);
+int dir_exist(const char* p);
+int make_dir(const char* p);
+
#endif
diff --git a/playlist.c b/playlist.c
new file mode 100644
index 0000000..8dda913
--- /dev/null
+++ b/playlist.c
@@ -0,0 +1,123 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "plat.h"
+#include "playlist.h"
+
+void init_playlist(Playlist* p) {
+ p->name[0] = 0;
+ p->cnt = 0;
+}
+
+void playlist_rm(Playlist* p, int i) {
+ int j;
+ for (j = i + 1; j < p->cnt; j++) {
+ p->songs[j - 1] = p->songs[j];
+ }
+ p->cnt--;
+}
+
+void playlist_add(Playlist* p, Song* s) {
+ if (p->cnt >= playlist_max)
+ return;
+ p->songs[p->cnt++] = s;
+}
+
+void playlist_add_first(Playlist* p, Song* s) {
+ int i;
+ if (p->cnt >= playlist_max)
+ return;
+ for (i = p->cnt - 1; i >= 0; i--) {
+ p->songs[i + 1] = p->songs[i];
+ }
+ p->songs[0] = s;
+ p->cnt++;
+}
+
+void playlist_shuffle(Playlist* p) {
+ int ci, ri;
+ Song* t;
+ for (ci = p->cnt; ci;) {
+ ri = rand() % ci;
+ ci--;
+ t = p->songs[ci];
+ p->songs[ci] = p->songs[ri];
+ p->songs[ri] = t;
+ }
+}
+
+void get_playlist_dir(char* buf) {
+ int llen;
+ llen = strlen(library_path);
+ strcpy(buf, library_path);
+ if (buf[llen - 1] != '/')
+ strcat(buf, "/");
+ strcat(buf, "playlists/");
+}
+
+void playlist_save(const Playlist* p) {
+ int i, plen, llen;
+ const char* path, * ws;
+ FILE* f;
+ char wp[256];
+ llen = strlen(library_path);
+ get_playlist_dir(wp);
+ if (!dir_exist(wp))
+ if (!make_dir(wp)) return;
+ strcat(wp, p->name);
+ strcat(wp, ".m3u");
+ f = fopen(wp, "w");
+ if (!f) return;
+ for (i = 0; i < p->cnt; i++) {
+ path = p->songs[i]->path;
+ plen = strlen(path);
+ if (
+ plen >= llen &&
+ !memcmp(path, library_path, llen)
+ )
+ ws = path + llen;
+ else
+ ws = path;
+ fputs(ws, f);
+ fputs("\n", f);
+ }
+ fclose(f);
+}
+
+int playlist_load(Library* l, Playlist* p, const char* name) {
+ char path[256];
+ char line[256];
+ const char* sp;
+ FILE* f;
+ Song* s;
+ int ll, lpl;
+ lpl = strlen(library_path);
+ get_playlist_dir(path);
+ strcat(path, name);
+ strcat(path, ".m3u");
+ f = fopen(path, "r");
+ if (!f) return 0;
+ strcpy(p->name, name);
+ p->cnt = 0;
+ while (fgets(line, sizeof line, f)) {
+ ll = strlen(line);
+ for (
+ ;
+ ll > 1 && line[ll - 1] == '\n';
+ ll--, line[ll] = 0
+ );
+ if (strstr(line, library_path) != line) {
+ strcpy(path, library_path);
+ if (path[lpl - 1] != '/')
+ strcat(path, "/");
+ strcat(path, line);
+ sp = path;
+ } else {
+ sp = line;
+ }
+ s = find_song(l, sp);
+ if (s && s->path[0])
+ playlist_add(p, s);
+ }
+ return 1;
+}
diff --git a/playlist.h b/playlist.h
new file mode 100644
index 0000000..83d532f
--- /dev/null
+++ b/playlist.h
@@ -0,0 +1,22 @@
+#ifndef playlist_h
+#define playlist_h
+
+#include "config.h"
+#include "library.h"
+
+typedef struct {
+ char name[32];
+ Song* songs[playlist_max];
+ int cnt;
+} Playlist;
+
+void init_playlist(Playlist* p);
+void playlist_rm(Playlist* p, int i);
+void playlist_add(Playlist* p, Song* s);
+void playlist_add_first(Playlist* p, Song* s);
+void playlist_shuffle(Playlist* p);
+void playlist_save(const Playlist* p);
+int playlist_load(Library* l, Playlist* p, const char* name);
+void get_playlist_dir(char* buf);
+
+#endif