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 *****************************************************************************/
26 #include <libxml/parser.h>
27 #include <libxml/xpath.h>
32 #define DISABLE_DEBUG_OUTPUT
35 #include "song_player.h"
37 #include "song_player.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
;
50 bool g_playback_toggle_flag
;
57 struct list_head siblings
;
59 char * song_filename_ptr
;
63 struct list_head g_playlist
;
65 struct playlist_entry
* g_current_song_ptr
;
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
;
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
);
94 struct list_head
* node_ptr
;
95 struct playlist_entry
* entry_ptr
;
98 OUTPUT_MESSAGE("-------- Playlist ---------");
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
);
112 OUTPUT_MESSAGE("%02i - %s", i
+1, entry_ptr
->song_name_ptr
);
118 OUTPUT_MESSAGE("------------------");
122 playlist_build(const char * playlist_filename_ptr
)
126 xmlXPathContextPtr xpath_ctx_ptr
;
127 xmlXPathObjectPtr xpath_obj_ptr
;
128 xmlXPathObjectPtr xpath_obj2_ptr
;
129 xmlBufferPtr content_buffer_ptr
;
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
;
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");
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
);
162 ERROR_OUT("Failed to parse playlist \"%s\"", playlist_filename_ptr
);
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");
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
);
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
);
192 content_buffer_ptr
= xmlBufferCreate();
193 if (content_buffer_ptr
== NULL
)
195 ERROR_OUT("xmlBufferCreate() failed.");
200 ret
= xml_get_string_dup(doc_ptr
, "/playlist/name", &playlist_name_ptr
);
203 ERROR_OUT("xml_get_string_dup() failed. Cannot get playlist name (%d)", ret
);
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);
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");
227 if (xpath_obj2_ptr
->nodesetval
== NULL
|| xpath_obj2_ptr
->nodesetval
->nodeNr
== 0)
229 DEBUG_OUT("XPath evaluation returned no data (%s)", xpath
);
231 else if (xpath_obj2_ptr
->nodesetval
->nodeNr
!= 1)
233 ERROR_OUT("XPath evaluation returned too many data");
237 ret
= xmlNodeBufGetContent(content_buffer_ptr
, xpath_obj2_ptr
->nodesetval
->nodeTab
[0]);
240 ERROR_OUT("xmlNodeBufGetContent() failed. (%d)", ret
);
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)
252 xmlBufferEmpty(content_buffer_ptr
);
257 ret
= xmlNodeBufGetContent(content_buffer_ptr
, xpath_obj_ptr
->nodesetval
->nodeTab
[i
]);
260 ERROR_OUT("xmlNodeBufGetContent() failed. (%d)", ret
);
265 entry_ptr
= (struct playlist_entry
*)malloc(sizeof(struct playlist_entry
));
266 if (entry_ptr
== NULL
)
268 ERROR_OUT("malloc() failed.");
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.");
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
);
299 ERROR_OUT("Cannot get song name from \"%s\"", xmlBufferContent(content_buffer_ptr
));
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
);
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");
325 /* Make first song current */
326 g_current_song_ptr
= list_entry(g_playlist
.next
, struct playlist_entry
, siblings
);
329 goto free_playlist_name
;
332 free(entry_ptr
->song_filename_ptr
);
341 free(playlist_name_ptr
);
344 xmlBufferFree(content_buffer_ptr
);
347 xmlXPathFreeObject(xpath_obj_ptr
);
350 xmlXPathFreeContext(xpath_ctx_ptr
);
355 free_playlist_filename_copy
:
356 free(playlist_filename_copy_ptr
);
362 end_of_song_callback()
364 NOTICE_OUT("End of song");
365 pthread_mutex_lock(&g_playback_mutex
);
367 pthread_cond_signal(&g_playback_cond
);
368 pthread_mutex_unlock(&g_playback_mutex
);
371 void * playback_thread(void * context
)
374 bool playback_toggle_flag
= !g_playback_toggle_flag
;
375 bool playing_flag
= false;
377 struct list_head
* node_ptr
;
382 pthread_mutex_lock(&g_playback_mutex
);
388 pthread_mutex_unlock(&g_playback_mutex
);
395 pthread_mutex_unlock(&g_playback_mutex
);
396 DEBUG_OUT("End of song detected.");
398 playing_flag
= false;
399 pthread_mutex_lock(&g_playback_mutex
);
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
);
410 OUTPUT_MESSAGE("End of last song.");
414 song_changed
= false;
420 list_for_each(node_ptr
, &g_playlist
)
426 g_current_song_ptr
= list_entry(node_ptr
, struct playlist_entry
, siblings
);
434 ERROR_OUT("there is no song %u (ignoring goto)", g_goto
);
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
);
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
);
465 OUTPUT_MESSAGE("Current song changed to \"%s\"", g_current_song_ptr
->song_name_ptr
);
469 pthread_mutex_unlock(&g_playback_mutex
);
470 DEBUG_OUT("Stopping because current song changed.");
472 playing_flag
= false;
473 playback_toggle_flag
= !g_playback_toggle_flag
; /* schedule play after song change */
474 pthread_mutex_lock(&g_playback_mutex
);
479 if (g_playback_toggle_flag
!= playback_toggle_flag
)
481 DEBUG_OUT("Playback toggle detected.");
484 playback_toggle_flag
= g_playback_toggle_flag
;
485 pthread_mutex_unlock(&g_playback_mutex
);
489 DEBUG_OUT("Ending playback.");
491 playing_flag
= false;
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)
502 if (g_playback_toggle_flag
!= playback_toggle_flag
||
507 OUTPUT_MESSAGE("Canceling intro silence.");
508 pthread_mutex_lock(&g_playback_mutex
);
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
);
520 ERROR_OUT("failed to play song");
528 pthread_mutex_lock(&g_playback_mutex
);
533 pthread_cond_wait(&g_playback_cond
, &g_playback_mutex
);
541 DEBUG_OUT("Ending playback.");
549 playback_init(const char * playlist_filename_ptr
)
553 /* parse and build playlist */
554 ret
= playlist_build(playlist_filename_ptr
);
557 ERROR_OUT("Failed to build playlist");
562 /* start playback thread */
563 ret
= pthread_mutex_init(&g_playback_mutex
, NULL
);
566 ERROR_OUT("Failed to init real mask mutex (%d)", ret
);
568 goto exit_playlist_cleanup
;
571 ret
= pthread_cond_init(&g_playback_cond
, NULL
);
574 ERROR_OUT("Failed to init real mask cond (%d)", ret
);
576 goto exit_destroy_mutex
;
579 ret
= pthread_create(&g_playback_thread
, NULL
, playback_thread
, NULL
);
582 ERROR_OUT("Failed to start feed thread (%d)", ret
);
584 goto exit_destroy_cond
;
591 ret1
= pthread_cond_destroy(&g_playback_cond
);
594 ERROR_OUT("Failed to destroy playback cond (%d)", ret1
);
598 ret1
= pthread_mutex_destroy(&g_playback_mutex
);
601 ERROR_OUT("Failed to destroy playback mutex (%d)", ret1
);
604 exit_playlist_cleanup
:
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
);
623 pthread_mutex_lock(&g_playback_mutex
);
625 pthread_cond_signal(&g_playback_cond
);
626 pthread_mutex_unlock(&g_playback_mutex
);
632 pthread_mutex_lock(&g_playback_mutex
);
634 pthread_cond_signal(&g_playback_cond
);
635 pthread_mutex_unlock(&g_playback_mutex
);
639 playback_goto(unsigned int song
)
641 pthread_mutex_lock(&g_playback_mutex
);
644 pthread_cond_signal(&g_playback_cond
);
645 pthread_mutex_unlock(&g_playback_mutex
);
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
);