2 * Copyright (c) 2006-2011 Ed Schouten <ed@80386.nl>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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
28 * @brief Playlist handling.
33 #include "audio_file.h"
34 #include "audio_output.h"
38 #include "playq_modules.h"
42 * @brief Routines that should be used to control the playlist.
46 * @brief Give a song that should be played.
48 struct vfsref
*(*give
)(void);
50 * @brief Report that playback thread is going idle.
54 * @brief Select a specific song for playback.
56 int (*select
)(struct vfsref
*vr
);
58 * @brief Specify that the next song should be played.
62 * @brief Specify that the previous song should be played.
66 * @brief Notify that a song is about to be removed.
68 void (*notify_pre_removal
)(struct vfsref
*vr
);
72 * @brief Traditional Herrie-style playlist handling.
74 static struct playq_funcs party_funcs
= {
80 playq_party_notify_pre_removal
,
83 * @brief XMMS-style playlist handling.
85 static struct playq_funcs xmms_funcs
= {
91 playq_xmms_notify_pre_removal
,
94 * @brief Currenty used playlist handling routines.
96 static struct playq_funcs
*funcs
= &party_funcs
;
98 struct vfslist playq_list
= VFSLIST_INITIALIZER
;
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
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.
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.
139 * @brief Don't start playback of the next song.
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
158 playq_runner_thread(void *unused
)
161 struct audio_file
*cur
;
167 /* Wait until there's a song available */
170 /* Shut down when the user wants to */
171 if (playq_flags
& PF_QUIT
) {
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 */
183 /* Wait for new events to occur */
184 playq_flags
|= PF_STOP
;
186 gui_playq_song_update(NULL
, 0, 0);
187 g_cond_wait(playq_wakeup
, playq_mtx
);
191 cur
= audio_file_open(nvr
);
193 /* Skip broken songs */
194 errmsg
= g_strdup_printf(
195 _("Failed to open \"%s\" for playback."),
197 gui_msgbar_warn(errmsg
);
200 /* Don't hog the CPU */
207 gui_playq_song_update(cur
, 0, 0);
210 playq_flags
&= ~(PF_PAUSE
|PF_SKIP
|PF_SEEK
);
214 if (playq_flags
& PF_PAUSE
&& !cur
->stream
) {
215 gui_playq_song_update(cur
, 1, 1);
217 /* Wait to be waken up */
219 g_cond_wait(playq_wakeup
, playq_mtx
);
222 gui_playq_song_update(cur
, 0, 1);
224 /* Play a part of the audio file */
225 if (audio_output_play(cur
) != 0)
229 if (playq_flags
& PF_SEEK
) {
230 audio_file_seek(cur
, playq_seek_time
,
231 playq_flags
& PF_SEEK_REL
);
233 playq_flags
&= ~PF_SEEK
;
236 } while (!(playq_flags
& (PF_QUIT
|PF_SKIP
)));
238 audio_file_close(cur
);
239 } while (!(playq_flags
& PF_QUIT
));
245 playq_init(int autoplay
, int xmms
, int load_dumpfile
)
247 const char *filename
;
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")) {
262 filename
= config_getopt("playq.dumpfile");
263 if (load_dumpfile
&& filename
[0] != '\0') {
264 /* Autoload playlist */
265 vr
= vfs_lookup(filename
, NULL
, NULL
, 0);
267 vfs_unfold(&playq_list
, vr
);
276 playq_runner
= g_thread_create_full(playq_runner_thread
, NULL
,
277 0, 1, TRUE
, G_THREAD_PRIORITY_URGENT
, NULL
);
284 const char *filename
;
287 playq_flags
= PF_QUIT
;
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
);
298 /* Flush the list back to the disk */
299 vr
= vfs_write_playlist(&playq_list
, NULL
, filename
);
307 * Public queue functions: playq_song_*
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
))
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
);
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
))
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
);
357 * @brief Seek the current song by a certain amount of time.
360 playq_cursong_seek(int len
, int rel
)
364 fl
= rel
? PF_SEEK_REL
: PF_SEEK_ABS
;
366 playq_flags
= (playq_flags
& ~PF_SEEK
) | fl
;
367 playq_seek_time
= len
;
370 g_cond_signal(playq_wakeup
);
374 playq_cursong_next(void)
377 if (funcs
->next() == 0) {
378 /* Unpause as well */
379 playq_flags
|= PF_SKIP
;
380 g_cond_signal(playq_wakeup
);
386 playq_cursong_prev(void)
389 if (funcs
->prev() == 0) {
390 /* Unpause as well */
391 playq_flags
|= PF_SKIP
;
392 g_cond_signal(playq_wakeup
);
398 playq_cursong_stop(void)
402 playq_flags
|= (PF_SKIP
|PF_STOP
);
405 g_cond_signal(playq_wakeup
);
409 playq_cursong_pause(void)
412 /* Toggle the pause flag */
413 playq_flags
^= PF_PAUSE
;
416 g_cond_signal(playq_wakeup
);
420 playq_repeat_toggle(void)
424 playq_repeat
= !playq_repeat
;
426 msg
= g_strdup_printf(_("Repeat: %s"),
427 playq_repeat
? _("on") : _("off"));
428 gui_msgbar_warn(msg
);
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
);
439 gui_playq_notify_done();
443 playq_song_fast_add_before(struct vfsref
*nvr
, struct vfsref
*lvr
,
446 struct vfslist newlist
= VFSLIST_INITIALIZER
;
448 /* Recursively expand the item */
449 vfs_unfold(&newlist
, nvr
);
450 if (vfs_list_empty(&newlist
))
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
);
465 playq_song_fast_add_after(struct vfsref
*nvr
, struct vfsref
*lvr
,
468 struct vfslist newlist
= VFSLIST_INITIALIZER
;
470 /* Recursively expand the item */
471 vfs_unfold(&newlist
, nvr
);
472 if (vfs_list_empty(&newlist
))
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
);
487 playq_song_fast_move_up(struct vfsref
*vr
, unsigned int index
)
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
);
497 vfs_list_insert_after(&playq_list
, pvr
, vr
);
498 gui_playq_notify_post_insertion(index
);
499 gui_playq_notify_done();
503 playq_song_fast_move_down(struct vfsref
*vr
, unsigned int index
)
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
);
513 vfs_list_insert_before(&playq_list
, nvr
, vr
);
514 gui_playq_notify_post_insertion(index
);
515 gui_playq_notify_done();
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();
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();
546 playq_song_fast_select(struct vfsref
*vr
)
548 if (funcs
->select(vr
) != 0)
551 /* Now go to the next song */
552 playq_flags
&= ~PF_STOP
;
553 playq_flags
|= PF_SKIP
;
554 g_cond_signal(playq_wakeup
);
558 playq_song_remove_all(void)
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
);
569 gui_playq_notify_done();
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.
586 remaining
= vfs_list_items(&playq_list
);
590 /* Generate a shadow list */
591 vrlist
= g_new(struct vfsref
*, remaining
);
592 VFS_LIST_FOREACH(&playq_list
, vr
)
594 vfs_list_init(&playq_list
);
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 */
609 gui_playq_notify_post_randomization();
610 gui_playq_notify_done();
611 done
: playq_unlock();