Add support for VFS caching.
[herrie-working.git] / herrie / src / playq.c
blob0fc53afcc8e8bcd45f26567b1d65462cff23cc04
1 /*
2 * Copyright (c) 2006-2011 Ed Schouten <ed@80386.nl>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
26 /**
27 * @file playq.c
28 * @brief Playlist handling.
31 #include "stdinc.h"
33 #include "audio_file.h"
34 #include "audio_output.h"
35 #include "config.h"
36 #include "gui.h"
37 #include "playq.h"
38 #include "playq_modules.h"
39 #include "vfs.h"
41 /**
42 * @brief Routines that should be used to control the playlist.
44 struct playq_funcs {
45 /**
46 * @brief Give a song that should be played.
48 struct vfsref *(*give)(void);
49 /**
50 * @brief Report that playback thread is going idle.
52 void (*idle)(void);
53 /**
54 * @brief Select a specific song for playback.
56 int (*select)(struct vfsref *vr);
57 /**
58 * @brief Specify that the next song should be played.
60 int (*next)(void);
61 /**
62 * @brief Specify that the previous song should be played.
64 int (*prev)(void);
65 /**
66 * @brief Notify that a song is about to be removed.
68 void (*notify_pre_removal)(struct vfsref *vr);
71 /**
72 * @brief Traditional Herrie-style playlist handling.
74 static struct playq_funcs party_funcs = {
75 playq_party_give,
76 playq_party_idle,
77 playq_party_select,
78 playq_party_next,
79 playq_party_prev,
80 playq_party_notify_pre_removal,
82 /**
83 * @brief XMMS-style playlist handling.
85 static struct playq_funcs xmms_funcs = {
86 playq_xmms_give,
87 playq_xmms_idle,
88 playq_xmms_select,
89 playq_xmms_next,
90 playq_xmms_prev,
91 playq_xmms_notify_pre_removal,
93 /**
94 * @brief Currenty used playlist handling routines.
96 static struct playq_funcs *funcs = &party_funcs;
98 struct vfslist playq_list = VFSLIST_INITIALIZER;
99 GMutex *playq_mtx;
101 * @brief Conditional variable used to kick the playq alive when it was
102 * waiting for a new song to be added to the playlist or when
103 * paused.
105 static GCond *playq_wakeup;
107 * @brief Reference to playback thread.
109 static GThread *playq_runner;
111 * @brief Randomizer used for shuffling the playlist.
113 static GRand *playq_rand;
115 * @brief Quit the playback thread.
117 #define PF_QUIT 0x01
119 * @brief Pause the current song.
121 #define PF_PAUSE 0x02
123 * @brief Perform an absolute seek.
125 #define PF_SEEK_ABS 0x08
127 * @brief Perform a relative seek.
129 #define PF_SEEK_REL 0x10
131 * @brief Test whether a seek is performed at all.
133 #define PF_SEEK (PF_SEEK_ABS|PF_SEEK_REL)
135 * @brief Skip to the next song.
137 #define PF_SKIP 0x20
139 * @brief Don't start playback of the next song.
141 #define PF_STOP 0x40
143 * @brief Flags the playback thread should honour. Writing to them
144 * should be locked down.
146 static volatile int playq_flags = PF_STOP;
147 int playq_repeat = 0;
149 * @brief Amount of seconds which the current song should seek.
151 static volatile int playq_seek_time;
154 * @brief Infinitely play music in the playlist, honouring the
155 * playq_flags.
157 static void *
158 playq_runner_thread(void *unused)
160 struct vfsref *nvr;
161 struct audio_file *cur;
162 char *errmsg;
164 gui_input_sigmask();
166 do {
167 /* Wait until there's a song available */
168 playq_lock();
169 for (;;) {
170 /* Shut down when the user wants to */
171 if (playq_flags & PF_QUIT) {
172 playq_unlock();
173 goto done;
176 /* Try to start a new song when we're not stopped */
177 if (!(playq_flags & PF_STOP) &&
178 (nvr = funcs->give()) != NULL) {
179 /* We've got work to do */
180 break;
183 /* Wait for new events to occur */
184 playq_flags |= PF_STOP;
185 funcs->idle();
186 gui_playq_song_update(NULL, 0, 0);
187 g_cond_wait(playq_wakeup, playq_mtx);
189 playq_unlock();
191 cur = audio_file_open(nvr);
192 if (cur == NULL) {
193 /* Skip broken songs */
194 errmsg = g_strdup_printf(
195 _("Failed to open \"%s\" for playback."),
196 vfs_name(nvr));
197 gui_msgbar_warn(errmsg);
198 g_free(errmsg);
199 vfs_close(nvr);
200 /* Don't hog the CPU */
201 g_usleep(500000);
202 continue;
205 /* Trash it */
206 vfs_close(nvr);
207 gui_playq_song_update(cur, 0, 0);
209 playq_lock();
210 playq_flags &= ~(PF_PAUSE|PF_SKIP|PF_SEEK);
211 playq_unlock();
213 do {
214 if (playq_flags & PF_PAUSE && !cur->stream) {
215 gui_playq_song_update(cur, 1, 1);
217 /* Wait to be waken up */
218 playq_lock();
219 g_cond_wait(playq_wakeup, playq_mtx);
220 playq_unlock();
221 } else {
222 gui_playq_song_update(cur, 0, 1);
224 /* Play a part of the audio file */
225 if (audio_output_play(cur) != 0)
226 break;
229 if (playq_flags & PF_SEEK) {
230 audio_file_seek(cur, playq_seek_time,
231 playq_flags & PF_SEEK_REL);
232 playq_lock();
233 playq_flags &= ~PF_SEEK;
234 playq_unlock();
236 } while (!(playq_flags & (PF_QUIT|PF_SKIP)));
238 audio_file_close(cur);
239 } while (!(playq_flags & PF_QUIT));
240 done:
241 return (NULL);
244 void
245 playq_init(int autoplay, int xmms, int load_dumpfile)
247 const char *filename;
248 struct vfsref *vr;
250 playq_mtx = g_mutex_new();
251 playq_wakeup = g_cond_new();
252 playq_rand = g_rand_new(); /* XXX: /dev/urandom in chroot() */
254 if (autoplay || config_getopt_bool("playq.autoplay"))
255 playq_flags &= ~PF_STOP;
257 if (xmms || config_getopt_bool("playq.xmms")) {
258 funcs = &xmms_funcs;
259 playq_repeat = 1;
262 filename = config_getopt("playq.dumpfile");
263 if (load_dumpfile && filename[0] != '\0') {
264 /* Autoload playlist */
265 vr = vfs_lookup(filename, NULL, NULL, 0);
266 if (vr != NULL) {
267 vfs_unfold(&playq_list, vr);
268 vfs_close(vr);
273 void
274 playq_spawn(void)
276 playq_runner = g_thread_create_full(playq_runner_thread, NULL,
277 0, 1, TRUE, G_THREAD_PRIORITY_URGENT, NULL);
280 void
281 playq_shutdown(void)
283 struct vfsref *vr;
284 const char *filename;
286 playq_lock();
287 playq_flags = PF_QUIT;
288 playq_unlock();
289 g_cond_signal(playq_wakeup);
290 g_thread_join(playq_runner);
292 filename = config_getopt("playq.dumpfile");
293 if (filename[0] != '\0') {
294 if (vfs_list_empty(&playq_list)) {
295 /* Remove the autosave playlist */
296 vfs_delete(filename);
297 } else {
298 /* Flush the list back to the disk */
299 vr = vfs_write_playlist(&playq_list, NULL, filename);
300 if (vr != NULL)
301 vfs_close(vr);
307 * Public queue functions: playq_song_*
310 void
311 playq_song_add_head(struct vfsref *vr)
313 struct vfslist newlist = VFSLIST_INITIALIZER;
315 /* Recursively expand the item */
316 vfs_unfold(&newlist, vr);
317 if (vfs_list_empty(&newlist))
318 return;
320 playq_lock();
321 /* Copy the expanded contents to the playlist */
322 while ((vr = vfs_list_last(&newlist)) != NULL) {
323 vfs_list_remove(&newlist, vr);
324 vfs_list_insert_head(&playq_list, vr);
325 gui_playq_notify_post_insertion(1);
328 gui_playq_notify_done();
329 g_cond_signal(playq_wakeup);
330 playq_unlock();
333 void
334 playq_song_add_tail(struct vfsref *vr)
336 struct vfslist newlist = VFSLIST_INITIALIZER;
338 /* Recursively expand the item */
339 vfs_unfold(&newlist, vr);
340 if (vfs_list_empty(&newlist))
341 return;
343 playq_lock();
344 /* Copy the expanded contents to the playlist */
345 while ((vr = vfs_list_first(&newlist)) != NULL) {
346 vfs_list_remove(&newlist, vr);
347 vfs_list_insert_tail(&playq_list, vr);
348 gui_playq_notify_post_insertion(vfs_list_items(&playq_list));
351 gui_playq_notify_done();
352 g_cond_signal(playq_wakeup);
353 playq_unlock();
357 * @brief Seek the current song by a certain amount of time.
359 void
360 playq_cursong_seek(int len, int rel)
362 int fl;
364 fl = rel ? PF_SEEK_REL : PF_SEEK_ABS;
365 playq_lock();
366 playq_flags = (playq_flags & ~PF_SEEK) | fl;
367 playq_seek_time = len;
368 playq_unlock();
370 g_cond_signal(playq_wakeup);
373 void
374 playq_cursong_next(void)
376 playq_lock();
377 if (funcs->next() == 0) {
378 /* Unpause as well */
379 playq_flags |= PF_SKIP;
380 g_cond_signal(playq_wakeup);
382 playq_unlock();
385 void
386 playq_cursong_prev(void)
388 playq_lock();
389 if (funcs->prev() == 0) {
390 /* Unpause as well */
391 playq_flags |= PF_SKIP;
392 g_cond_signal(playq_wakeup);
394 playq_unlock();
397 void
398 playq_cursong_stop(void)
400 playq_lock();
401 /* Stop playback */
402 playq_flags |= (PF_SKIP|PF_STOP);
403 playq_unlock();
405 g_cond_signal(playq_wakeup);
408 void
409 playq_cursong_pause(void)
411 playq_lock();
412 /* Toggle the pause flag */
413 playq_flags ^= PF_PAUSE;
414 playq_unlock();
416 g_cond_signal(playq_wakeup);
419 void
420 playq_repeat_toggle(void)
422 char *msg;
424 playq_repeat = !playq_repeat;
426 msg = g_strdup_printf(_("Repeat: %s"),
427 playq_repeat ? _("on") : _("off"));
428 gui_msgbar_warn(msg);
429 g_free(msg);
432 void
433 playq_song_fast_remove(struct vfsref *vr, unsigned int index)
435 funcs->notify_pre_removal(vr);
436 gui_playq_notify_pre_removal(index);
437 vfs_list_remove(&playq_list, vr);
438 vfs_close(vr);
439 gui_playq_notify_done();
442 void
443 playq_song_fast_add_before(struct vfsref *nvr, struct vfsref *lvr,
444 unsigned int index)
446 struct vfslist newlist = VFSLIST_INITIALIZER;
448 /* Recursively expand the item */
449 vfs_unfold(&newlist, nvr);
450 if (vfs_list_empty(&newlist))
451 return;
453 /* Copy the expanded contents to the playlist */
454 while ((nvr = vfs_list_first(&newlist)) != NULL) {
455 vfs_list_remove(&newlist, nvr);
456 vfs_list_insert_before(&playq_list, nvr, lvr);
457 gui_playq_notify_post_insertion(index);
460 gui_playq_notify_done();
461 g_cond_signal(playq_wakeup);
464 void
465 playq_song_fast_add_after(struct vfsref *nvr, struct vfsref *lvr,
466 unsigned int index)
468 struct vfslist newlist = VFSLIST_INITIALIZER;
470 /* Recursively expand the item */
471 vfs_unfold(&newlist, nvr);
472 if (vfs_list_empty(&newlist))
473 return;
475 /* Copy the expanded contents to the playlist */
476 while ((nvr = vfs_list_last(&newlist)) != NULL) {
477 vfs_list_remove(&newlist, nvr);
478 vfs_list_insert_after(&playq_list, nvr, lvr);
479 gui_playq_notify_post_insertion(index + 1);
482 gui_playq_notify_done();
483 g_cond_signal(playq_wakeup);
486 void
487 playq_song_fast_move_up(struct vfsref *vr, unsigned int index)
489 struct vfsref *pvr;
491 /* Remove the item above */
492 pvr = vfs_list_prev(vr);
493 gui_playq_notify_pre_removal(index - 1);
494 vfs_list_remove(&playq_list, pvr);
496 /* Add it below */
497 vfs_list_insert_after(&playq_list, pvr, vr);
498 gui_playq_notify_post_insertion(index);
499 gui_playq_notify_done();
502 void
503 playq_song_fast_move_down(struct vfsref *vr, unsigned int index)
505 struct vfsref *nvr;
507 /* Remove the item below */
508 nvr = vfs_list_next(vr);
509 gui_playq_notify_pre_removal(index + 1);
510 vfs_list_remove(&playq_list, nvr);
512 /* Add it above */
513 vfs_list_insert_before(&playq_list, nvr, vr);
514 gui_playq_notify_post_insertion(index);
515 gui_playq_notify_done();
519 void
520 playq_song_fast_move_head(struct vfsref *vr, unsigned int index)
522 /* Remove the current item */
523 gui_playq_notify_pre_removal(index);
524 vfs_list_remove(&playq_list, vr);
526 /* Add it to the top */
527 vfs_list_insert_head(&playq_list, vr);
528 gui_playq_notify_post_insertion(1);
529 gui_playq_notify_done();
532 void
533 playq_song_fast_move_tail(struct vfsref *vr, unsigned int index)
535 /* Remove the current item */
536 gui_playq_notify_pre_removal(index);
537 vfs_list_remove(&playq_list, vr);
539 /* Add it to the bottom */
540 vfs_list_insert_tail(&playq_list, vr);
541 gui_playq_notify_post_insertion(vfs_list_items(&playq_list));
542 gui_playq_notify_done();
545 void
546 playq_song_fast_select(struct vfsref *vr)
548 if (funcs->select(vr) != 0)
549 return;
551 /* Now go to the next song */
552 playq_flags &= ~PF_STOP;
553 playq_flags |= PF_SKIP;
554 g_cond_signal(playq_wakeup);
557 void
558 playq_song_remove_all(void)
560 struct vfsref *vr;
562 playq_lock();
563 while ((vr = vfs_list_first(&playq_list)) != NULL) {
564 funcs->notify_pre_removal(vr);
565 gui_playq_notify_pre_removal(1);
566 vfs_list_remove(&playq_list, vr);
567 vfs_close(vr);
569 gui_playq_notify_done();
570 playq_unlock();
573 void
574 playq_song_randomize(void)
576 unsigned int remaining, idx = 0;
577 struct vfsref *vr, **vrlist;
580 * This code implements the Fisher-Yates algorithm to randomize
581 * the playlist. Only difference is that we already place items
582 * back in the list instead of actually swapping them.
585 playq_lock();
586 remaining = vfs_list_items(&playq_list);
587 if (remaining < 2)
588 goto done;
590 /* Generate a shadow list */
591 vrlist = g_new(struct vfsref *, remaining);
592 VFS_LIST_FOREACH(&playq_list, vr)
593 vrlist[idx++] = vr;
594 vfs_list_init(&playq_list);
596 do {
597 /* Pick a random item from the beginning */
598 idx = g_rand_int_range(playq_rand, 0, remaining);
600 /* Add it to the list */
601 vfs_list_insert_tail(&playq_list, vrlist[idx]);
602 /* Remove fragmentation */
603 vrlist[idx] = vrlist[--remaining];
604 } while (remaining != 0);
606 /* Trash the shadow list */
607 g_free(vrlist);
609 gui_playq_notify_post_randomization();
610 gui_playq_notify_done();
611 done: playq_unlock();