Fix bug in end of song detection.
[monster.git] / playback.c
blob04f2db983d420d4eab2cf36dd18c96f562f73a57
1 /* -*- Mode: C ; c-basic-offset: 2 -*- */
2 /*****************************************************************************
4 * Playback control and playlist handling
5 * This file is part of monster
7 * Copyright (C) 2006,2007 Nedko Arnaudov <nedko@arnaudov.name>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; version 2 of the License
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22 *****************************************************************************/
24 #include <stdlib.h>
25 #include <pthread.h>
26 #include <libxml/parser.h>
27 #include <libxml/xpath.h>
28 #include <string.h>
29 #include <libgen.h>
30 #include <unistd.h>
32 #define DISABLE_DEBUG_OUTPUT
34 #include "playback.h"
35 #include "song_player.h"
36 #include "log.h"
37 #include "song_player.h"
38 #include "types.h"
39 #include "list.h"
40 #include "xml.h"
41 #include "path.h"
43 /* XPath expression used for song path selection */
44 #define XPATH_SONG_PATH_EXPRESSION "/playlist/song/path"
46 pthread_mutex_t g_playback_mutex;
47 pthread_cond_t g_playback_cond;
48 pthread_t g_playback_thread;
49 bool g_destroy_flag;
50 bool g_playback_toggle_flag;
51 bool g_eos_flag;
52 signed int g_roll;
53 unsigned int g_goto;
55 struct playlist_entry
57 struct list_head siblings;
58 char * song_name_ptr;
59 char * song_filename_ptr;
60 int intro_silence;
63 struct list_head g_playlist;
65 struct playlist_entry * g_current_song_ptr;
67 void
68 playlist_free()
70 struct list_head * node_ptr;
71 struct playlist_entry * entry_ptr;
73 DEBUG_OUT("playlist_free() called.");
75 while (!list_empty(&g_playlist))
77 node_ptr = g_playlist.next;
79 list_del(node_ptr);
81 entry_ptr = list_entry(node_ptr, struct playlist_entry, siblings);
83 free(entry_ptr->song_name_ptr);
85 free(entry_ptr->song_filename_ptr);
87 free(entry_ptr);
91 void
92 playlist_display()
94 struct list_head * node_ptr;
95 struct playlist_entry * entry_ptr;
96 int i;
98 OUTPUT_MESSAGE("-------- Playlist ---------");
100 i = 0;
102 list_for_each(node_ptr, &g_playlist)
104 entry_ptr = list_entry(node_ptr, struct playlist_entry, siblings);
106 if (entry_ptr->intro_silence != 0)
108 OUTPUT_MESSAGE("%02i - %s (%d seconds intro silence)", i+1, entry_ptr->song_name_ptr, entry_ptr->intro_silence);
110 else
112 OUTPUT_MESSAGE("%02i - %s", i+1, entry_ptr->song_name_ptr);
115 i++;
118 OUTPUT_MESSAGE("------------------");
122 playlist_build(const char * playlist_filename_ptr)
124 int ret;
125 xmlDocPtr doc_ptr;
126 xmlXPathContextPtr xpath_ctx_ptr;
127 xmlXPathObjectPtr xpath_obj_ptr;
128 xmlXPathObjectPtr xpath_obj2_ptr;
129 xmlBufferPtr content_buffer_ptr;
130 int i;
131 char * playlist_name_ptr;
132 char * playlist_filename_copy_ptr;
133 const char * playlist_dirname_ptr;
134 size_t playlist_dirname_size;
135 struct playlist_entry * entry_ptr;
136 const char * song_filename_ptr;
137 size_t temp_size;
138 int intro_silence;
139 char xpath[1000];
141 DEBUG_OUT("Loading playlist from \"%s\"", playlist_filename_ptr);
143 INIT_LIST_HEAD(&g_playlist);
145 playlist_filename_copy_ptr = strdup(playlist_filename_ptr);
146 if (playlist_filename_copy_ptr == NULL)
148 ERROR_OUT("strdup() failed");
149 ret = -1;
150 goto exit;
153 playlist_dirname_ptr = dirname(playlist_filename_copy_ptr);
155 DEBUG_OUT("playlist dirname is \"%s\"", playlist_dirname_ptr);
157 playlist_dirname_size = strlen(playlist_dirname_ptr);
159 doc_ptr = xmlParseFile(playlist_filename_ptr);
160 if (doc_ptr == NULL)
162 ERROR_OUT("Failed to parse playlist \"%s\"", playlist_filename_ptr);
163 ret = -1;
164 goto free_playlist_filename_copy;
167 /* Create xpath evaluation context */
168 xpath_ctx_ptr = xmlXPathNewContext(doc_ptr);
169 if (xpath_ctx_ptr == NULL)
171 ERROR_OUT("Unable to create new XPath context");
172 ret = -1;
173 goto free_doc;
176 /* Evaluate xpath expression */
177 xpath_obj_ptr = xmlXPathEvalExpression((const xmlChar *)XPATH_SONG_PATH_EXPRESSION, xpath_ctx_ptr);
178 if (xpath_obj_ptr == NULL)
180 ERROR_OUT("Unable to evaluate XPath expression \"%s\"", XPATH_SONG_PATH_EXPRESSION);
181 ret = -1;
182 goto free_xpath_ctx;
185 if (xpath_obj_ptr->nodesetval == NULL || xpath_obj_ptr->nodesetval->nodeNr == 0)
187 ERROR_OUT("XPath \"%s\" evaluation returned no data", XPATH_SONG_PATH_EXPRESSION);
188 ret = -1;
189 goto free_xpath_obj;
192 content_buffer_ptr = xmlBufferCreate();
193 if (content_buffer_ptr == NULL)
195 ERROR_OUT("xmlBufferCreate() failed.");
196 ret = -1;
197 goto free_xpath_obj;
200 ret = xml_get_string_dup(doc_ptr, "/playlist/name", &playlist_name_ptr);
201 if (ret != 0)
203 ERROR_OUT("xml_get_string_dup() failed. Cannot get playlist name (%d)", ret);
204 ret = -1;
205 goto free_buffer;
208 OUTPUT_MESSAGE("-------- Playlist \"%s\" ---------", playlist_name_ptr);
210 for (i = 0 ; i < xpath_obj_ptr->nodesetval->nodeNr ; i++)
212 //DEBUG_OUT("song at index %d", i);
215 intro_silence = 0;
217 sprintf(xpath, "/playlist/song[%d]/intro_silence", i + 1);
219 /* Evaluate xpath expression */
220 xpath_obj2_ptr = xmlXPathEvalExpression((const xmlChar *)xpath, xpath_ctx_ptr);
221 if (xpath_obj2_ptr == NULL)
223 ERROR_OUT("Unable to evaluate xpath expression for selecting intro_silence");
225 else
227 if (xpath_obj2_ptr->nodesetval == NULL || xpath_obj2_ptr->nodesetval->nodeNr == 0)
229 ERROR_OUT("XPath evaluation returned no data");
231 else if (xpath_obj2_ptr->nodesetval->nodeNr != 1)
233 ERROR_OUT("XPath evaluation returned too many data");
235 else
237 ret = xmlNodeBufGetContent(content_buffer_ptr, xpath_obj2_ptr->nodesetval->nodeTab[0]);
238 if (ret != 0)
240 ERROR_OUT("xmlNodeBufGetContent() failed. (%d)", ret);
242 else
244 DEBUG_OUT("Intro silence is %s seconds", (const char *)xmlBufferContent(content_buffer_ptr));
245 intro_silence = atoi((const char *)xmlBufferContent(content_buffer_ptr));
246 if (intro_silence < 0)
248 intro_silence = 0;
252 xmlBufferEmpty(content_buffer_ptr);
257 ret = xmlNodeBufGetContent(content_buffer_ptr, xpath_obj_ptr->nodesetval->nodeTab[i]);
258 if (ret != 0)
260 ERROR_OUT("xmlNodeBufGetContent() failed. (%d)", ret);
261 ret = -1;
262 goto free_playlist;
265 entry_ptr = (struct playlist_entry *)malloc(sizeof(struct playlist_entry));
266 if (entry_ptr == NULL)
268 ERROR_OUT("malloc() failed.");
269 ret = -1;
270 goto free_playlist;
273 entry_ptr->intro_silence = intro_silence;
275 song_filename_ptr = (const char *)xmlBufferContent(content_buffer_ptr);
276 DEBUG_OUT("Song filename \"%s\"", song_filename_ptr);
277 temp_size = xmlBufferLength(content_buffer_ptr);
279 entry_ptr->song_filename_ptr = (char *)malloc(playlist_dirname_size + 1 + temp_size + 1);
280 if (entry_ptr->song_filename_ptr == NULL)
282 ERROR_OUT("malloc() failed.");
283 ret = -1;
284 goto free_entry;
287 memcpy(entry_ptr->song_filename_ptr, playlist_dirname_ptr, playlist_dirname_size);
288 entry_ptr->song_filename_ptr[playlist_dirname_size] = '/';
289 memcpy(entry_ptr->song_filename_ptr + playlist_dirname_size + 1, song_filename_ptr, temp_size);
290 entry_ptr->song_filename_ptr[playlist_dirname_size + 1 + temp_size] = 0;
292 xmlBufferEmpty(content_buffer_ptr);
294 path_normalize(entry_ptr->song_filename_ptr);
296 ret = get_song_name(entry_ptr->song_filename_ptr, &entry_ptr->song_name_ptr);
297 if (ret != 0)
299 ERROR_OUT("Cannot get song name from \"%s\"", xmlBufferContent(content_buffer_ptr));
300 ret = -1;
301 goto free_song_filename;
304 if (entry_ptr->intro_silence != 0)
306 OUTPUT_MESSAGE("%02i - %s (%d seconds intro silence)", i+1, entry_ptr->song_name_ptr, entry_ptr->intro_silence);
308 else
310 OUTPUT_MESSAGE("%02i - %s", i+1, entry_ptr->song_name_ptr);
313 list_add_tail(&entry_ptr->siblings, &g_playlist);
316 OUTPUT_MESSAGE("------------------");
318 if (list_empty(&g_playlist))
320 ERROR_OUT("There is no point to play empty playlist");
321 ret = -1;
322 goto free_playlist;
325 /* Make first song current */
326 g_current_song_ptr = list_entry(g_playlist.next, struct playlist_entry, siblings);
328 ret = 0;
329 goto free_playlist_name;
331 free_song_filename:
332 free(entry_ptr->song_filename_ptr);
334 free_entry:
335 free(entry_ptr);
337 free_playlist:
338 playlist_free();
340 free_playlist_name:
341 free(playlist_name_ptr);
343 free_buffer:
344 xmlBufferFree(content_buffer_ptr);
346 free_xpath_obj:
347 xmlXPathFreeObject(xpath_obj_ptr);
349 free_xpath_ctx:
350 xmlXPathFreeContext(xpath_ctx_ptr);
352 free_doc:
353 xmlFreeDoc(doc_ptr);
355 free_playlist_filename_copy:
356 free(playlist_filename_copy_ptr);
357 exit:
358 return ret;
361 void
362 end_of_song_callback()
364 NOTICE_OUT("End of song");
365 pthread_mutex_lock(&g_playback_mutex);
366 g_eos_flag = true;
367 pthread_cond_signal(&g_playback_cond);
368 pthread_mutex_unlock(&g_playback_mutex);
371 void * playback_thread(void * context)
373 int ret;
374 bool playback_toggle_flag = !g_playback_toggle_flag;
375 bool playing_flag = false;
376 bool song_changed;
377 struct list_head * node_ptr;
378 unsigned int index;
379 bool acted;
380 int silence_counter;
382 pthread_mutex_lock(&g_playback_mutex);
383 loop:
384 acted = false;
386 if (g_destroy_flag)
388 pthread_mutex_unlock(&g_playback_mutex);
389 goto exit;
392 if (g_eos_flag)
394 g_eos_flag = false;
395 pthread_mutex_unlock(&g_playback_mutex);
396 DEBUG_OUT("End of song detected.");
397 end_song();
398 playing_flag = false;
399 pthread_mutex_lock(&g_playback_mutex);
400 acted = true;
402 if (g_current_song_ptr->siblings.next != &g_playlist)
404 OUTPUT_MESSAGE("End of song, moving to next in playlist.");
405 g_current_song_ptr = list_entry(g_current_song_ptr->siblings.next, struct playlist_entry, siblings);
406 goto play;
408 else
410 OUTPUT_MESSAGE("End of last song.");
414 song_changed = false;
416 if (g_goto > 0)
418 index = 0;
420 list_for_each(node_ptr, &g_playlist)
422 index++;
424 if (index == g_goto)
426 g_current_song_ptr = list_entry(node_ptr, struct playlist_entry, siblings);
427 song_changed = true;
428 break;
432 if (!song_changed)
434 ERROR_OUT("there is no song %u (ignoring goto)", g_goto);
437 g_goto = 0;
440 if (g_roll > 0)
442 while (g_roll > 0 && g_current_song_ptr->siblings.next != &g_playlist)
444 g_current_song_ptr = list_entry(g_current_song_ptr->siblings.next, struct playlist_entry, siblings);
445 g_roll--;
446 song_changed = true;
449 g_roll = 0;
451 else if (g_roll < 0)
453 while (g_roll < 0 && g_current_song_ptr->siblings.prev != &g_playlist)
455 g_current_song_ptr = list_entry(g_current_song_ptr->siblings.prev, struct playlist_entry, siblings);
456 g_roll++;
457 song_changed = true;
460 g_roll = 0;
463 if (song_changed)
465 OUTPUT_MESSAGE("Current song changed to \"%s\"", g_current_song_ptr->song_name_ptr);
467 if (playing_flag)
469 pthread_mutex_unlock(&g_playback_mutex);
470 DEBUG_OUT("Stopping because current song changed.");
471 end_song();
472 playing_flag = false;
473 playback_toggle_flag = !g_playback_toggle_flag; /* schedule play after song change */
474 pthread_mutex_lock(&g_playback_mutex);
475 acted = true;
479 if (g_playback_toggle_flag != playback_toggle_flag)
481 DEBUG_OUT("Playback toggle detected.");
482 play:
483 acted = true;
484 playback_toggle_flag = g_playback_toggle_flag;
485 pthread_mutex_unlock(&g_playback_mutex);
487 if (playing_flag)
489 DEBUG_OUT("Ending playback.");
490 end_song();
491 playing_flag = false;
493 else
495 if (g_current_song_ptr->intro_silence != 0)
497 OUTPUT_MESSAGE("Intro silence %d seconds...", g_current_song_ptr->intro_silence);
498 silence_counter = g_current_song_ptr->intro_silence;
499 while (silence_counter != 0)
501 sleep(1);
502 if (g_playback_toggle_flag != playback_toggle_flag ||
503 g_goto != 0 ||
504 g_roll != 0 ||
505 g_destroy_flag)
507 OUTPUT_MESSAGE("Canceling intro silence.");
508 pthread_mutex_lock(&g_playback_mutex);
509 goto loop;
511 silence_counter--;
512 DEBUG_OUT("Intro silence: tick");
516 DEBUG_OUT("Starting playback of \"%s\"", g_current_song_ptr->song_name_ptr);
517 ret = play_song(g_current_song_ptr->song_filename_ptr, end_of_song_callback);
518 if (ret != 0)
520 ERROR_OUT("failed to play song");
522 else
524 playing_flag = true;
528 pthread_mutex_lock(&g_playback_mutex);
531 if (!acted)
533 pthread_cond_wait(&g_playback_cond, &g_playback_mutex);
536 goto loop;
538 exit:
539 if (playing_flag)
541 DEBUG_OUT("Ending playback.");
542 end_song();
545 return NULL;
549 playback_init(const char * playlist_filename_ptr)
551 int ret, ret1;
553 /* parse and build playlist */
554 ret = playlist_build(playlist_filename_ptr);
555 if (ret != 0)
557 ERROR_OUT("Failed to build playlist");
558 ret = -1;
559 goto exit;
562 /* start playback thread */
563 ret = pthread_mutex_init(&g_playback_mutex, NULL);
564 if (ret != 0)
566 ERROR_OUT("Failed to init real mask mutex (%d)", ret);
567 ret = -1;
568 goto exit_playlist_cleanup;
571 ret = pthread_cond_init(&g_playback_cond, NULL);
572 if (ret != 0)
574 ERROR_OUT("Failed to init real mask cond (%d)", ret);
575 ret = -1;
576 goto exit_destroy_mutex;
579 ret = pthread_create(&g_playback_thread, NULL, playback_thread, NULL);
580 if (ret != 0)
582 ERROR_OUT("Failed to start feed thread (%d)", ret);
583 ret = -1;
584 goto exit_destroy_cond;
587 ret = 0;
588 goto exit;
590 exit_destroy_cond:
591 ret1 = pthread_cond_destroy(&g_playback_cond);
592 if (ret1 != 0)
594 ERROR_OUT("Failed to destroy playback cond (%d)", ret1);
597 exit_destroy_mutex:
598 ret1 = pthread_mutex_destroy(&g_playback_mutex);
599 if (ret1 != 0)
601 ERROR_OUT("Failed to destroy playback mutex (%d)", ret1);
604 exit_playlist_cleanup:
605 playlist_free();
607 exit:
608 return ret;
611 void
612 playback_toggle()
614 pthread_mutex_lock(&g_playback_mutex);
615 g_playback_toggle_flag = !g_playback_toggle_flag;
616 pthread_cond_signal(&g_playback_cond);
617 pthread_mutex_unlock(&g_playback_mutex);
620 void
621 playback_next()
623 pthread_mutex_lock(&g_playback_mutex);
624 g_roll++;
625 pthread_cond_signal(&g_playback_cond);
626 pthread_mutex_unlock(&g_playback_mutex);
629 void
630 playback_prev()
632 pthread_mutex_lock(&g_playback_mutex);
633 g_roll--;
634 pthread_cond_signal(&g_playback_cond);
635 pthread_mutex_unlock(&g_playback_mutex);
638 void
639 playback_goto(unsigned int song)
641 pthread_mutex_lock(&g_playback_mutex);
642 g_roll = 0;
643 g_goto = song;
644 pthread_cond_signal(&g_playback_cond);
645 pthread_mutex_unlock(&g_playback_mutex);
648 void
649 playback_destroy()
651 pthread_mutex_lock(&g_playback_mutex);
652 g_destroy_flag = true;
653 pthread_cond_signal(&g_playback_cond);
654 pthread_mutex_unlock(&g_playback_mutex);
656 pthread_join(g_playback_thread, NULL);
658 pthread_cond_destroy(&g_playback_cond);
659 pthread_mutex_destroy(&g_playback_mutex);
661 playlist_free();