Audacious $Id:Doxyfile42802007-03-2104:39:00Znenolod$
|
00001 /* Audacious - Cross-platform multimedia player 00002 * Copyright (C) 2005-2009 Audacious development team 00003 * 00004 * Based on BMP: 00005 * Copyright (C) 2003-2004 BMP development team. 00006 * 00007 * Based on XMMS: 00008 * Copyright (C) 1998-2003 XMMS development team. 00009 * 00010 * This program is free software; you can redistribute it and/or modify 00011 * it under the terms of the GNU General Public License as published by 00012 * the Free Software Foundation; under version 3 of the License. 00013 * 00014 * This program is distributed in the hope that it will be useful, 00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00017 * GNU General Public License for more details. 00018 * 00019 * You should have received a copy of the GNU General Public License 00020 * along with this program. If not, see <http://www.gnu.org/licenses>. 00021 * 00022 * The Audacious team does not consider modular code linking to 00023 * Audacious or using our public API to be a derived work. 00024 */ 00025 00026 #include <glib.h> 00027 00028 #include <libaudcore/audstrings.h> 00029 #include <libaudcore/eventqueue.h> 00030 #include <libaudcore/hook.h> 00031 00032 #include "audconfig.h" 00033 #include "config.h" 00034 #include "i18n.h" 00035 #include "interface.h" 00036 #include "main.h" 00037 #include "output.h" 00038 #include "playback.h" 00039 #include "playlist.h" 00040 00041 static void set_params (InputPlayback * playback, const gchar * title, gint 00042 length, gint bitrate, gint samplerate, gint channels); 00043 static void set_tuple (InputPlayback * playback, Tuple * tuple); 00044 static void set_gain_from_playlist (InputPlayback * playback); 00045 00046 static void playback_free (InputPlayback * playback); 00047 static gboolean playback_play_file (gint playlist, gint entry, gint seek_time, 00048 gboolean pause); 00049 00050 InputPlayback * current_playback = NULL; 00051 00052 static gint time_offset; 00053 static gboolean paused; 00054 static gboolean stopping; 00055 static gint ready_source; 00056 static gint failed_entries; 00057 static gint set_tuple_source = 0; 00058 static Tuple * tuple_to_be_set = NULL; 00059 static ReplayGainInfo gain_from_playlist; 00060 00061 static void cancel_set_tuple (void) 00062 { 00063 if (set_tuple_source != 0) 00064 { 00065 g_source_remove (set_tuple_source); 00066 set_tuple_source = 0; 00067 } 00068 00069 if (tuple_to_be_set != NULL) 00070 { 00071 tuple_free (tuple_to_be_set); 00072 tuple_to_be_set = NULL; 00073 } 00074 } 00075 00076 /* clears gain info if tuple == NULL */ 00077 static void read_gain_from_tuple (Tuple * tuple) 00078 { 00079 gint album_gain, album_peak, track_gain, track_peak, gain_unit, peak_unit; 00080 00081 memset (& gain_from_playlist, 0, sizeof gain_from_playlist); 00082 00083 if (tuple == NULL) 00084 return; 00085 00086 album_gain = tuple_get_int (tuple, FIELD_GAIN_ALBUM_GAIN, NULL); 00087 album_peak = tuple_get_int (tuple, FIELD_GAIN_ALBUM_PEAK, NULL); 00088 track_gain = tuple_get_int (tuple, FIELD_GAIN_TRACK_GAIN, NULL); 00089 track_peak = tuple_get_int (tuple, FIELD_GAIN_TRACK_PEAK, NULL); 00090 gain_unit = tuple_get_int (tuple, FIELD_GAIN_GAIN_UNIT, NULL); 00091 peak_unit = tuple_get_int (tuple, FIELD_GAIN_PEAK_UNIT, NULL); 00092 00093 if (gain_unit) 00094 { 00095 gain_from_playlist.album_gain = album_gain / (gfloat) gain_unit; 00096 gain_from_playlist.track_gain = track_gain / (gfloat) gain_unit; 00097 } 00098 00099 if (peak_unit) 00100 { 00101 gain_from_playlist.album_peak = album_peak / (gfloat) peak_unit; 00102 gain_from_playlist.track_peak = track_peak / (gfloat) peak_unit; 00103 } 00104 } 00105 00106 static gboolean ready_cb (void * unused) 00107 { 00108 g_return_val_if_fail (current_playback != NULL, FALSE); 00109 00110 g_mutex_lock (current_playback->pb_ready_mutex); 00111 ready_source = 0; 00112 g_mutex_unlock (current_playback->pb_ready_mutex); 00113 00114 hook_call ("title change", NULL); 00115 return FALSE; 00116 } 00117 00118 static gboolean playback_is_ready (void) 00119 { 00120 gboolean ready; 00121 00122 g_return_val_if_fail (current_playback != NULL, FALSE); 00123 00124 g_mutex_lock (current_playback->pb_ready_mutex); 00125 ready = (current_playback->pb_ready_val && ! ready_source); 00126 g_mutex_unlock (current_playback->pb_ready_mutex); 00127 return ready; 00128 } 00129 00130 static gint 00131 playback_set_pb_ready(InputPlayback *playback) 00132 { 00133 g_mutex_lock(playback->pb_ready_mutex); 00134 playback->pb_ready_val = 1; 00135 ready_source = g_timeout_add (0, ready_cb, NULL); 00136 g_cond_signal(playback->pb_ready_cond); 00137 g_mutex_unlock(playback->pb_ready_mutex); 00138 return 0; 00139 } 00140 00141 static void update_cb (void * hook_data, void * user_data) 00142 { 00143 gint playlist, entry, length; 00144 const gchar * title; 00145 00146 g_return_if_fail (current_playback != NULL); 00147 00148 if (GPOINTER_TO_INT (hook_data) < PLAYLIST_UPDATE_METADATA) 00149 return; 00150 00151 playlist = playlist_get_playing (); 00152 entry = playlist_get_position (playlist); 00153 00154 if ((title = playlist_entry_get_title (playlist, entry, FALSE)) == NULL) 00155 title = playlist_entry_get_filename (playlist, entry); 00156 00157 length = playlist_entry_get_length (playlist, entry, FALSE); 00158 00159 if (! strcmp (title, current_playback->title) && length == 00160 current_playback->length) 00161 return; 00162 00163 g_free (current_playback->title); 00164 current_playback->title = g_strdup (title); 00165 current_playback->length = length; 00166 00167 if (playback_is_ready ()) 00168 hook_call ("title change", NULL); 00169 } 00170 00171 gint playback_get_time (void) 00172 { 00173 if (! playback_is_ready ()) 00174 return 0; 00175 00176 gint time = -1; 00177 00178 if (current_playback->plugin->get_time != NULL) 00179 time = current_playback->plugin->get_time (current_playback); 00180 00181 if (time < 0) 00182 time = get_output_time (); 00183 00184 return time - time_offset; 00185 } 00186 00187 void playback_play (gint seek_time, gboolean pause) 00188 { 00189 gint playlist, entry; 00190 00191 playlist = playlist_get_playing (); 00192 00193 if (playlist == -1) 00194 { 00195 playlist = playlist_get_active (); 00196 playlist_set_playing (playlist); 00197 } 00198 00199 entry = playlist_get_position (playlist); 00200 00201 if (entry == -1) 00202 { 00203 playlist_next_song (playlist, TRUE); 00204 entry = playlist_get_position (playlist); 00205 00206 if (entry == -1) 00207 return; 00208 } 00209 00210 if (playback_get_playing()) 00211 playback_stop(); 00212 00213 failed_entries = 0; 00214 playback_play_file (playlist, entry, seek_time, pause); 00215 } 00216 00217 void playback_pause (void) 00218 { 00219 if (! playback_is_ready ()) 00220 return; 00221 00222 paused = ! paused; 00223 00224 g_return_if_fail (current_playback->plugin->pause != NULL); 00225 current_playback->plugin->pause (current_playback, paused); 00226 00227 if (paused) 00228 hook_call("playback pause", NULL); 00229 else 00230 hook_call("playback unpause", NULL); 00231 } 00232 00233 static void playback_finalize (void) 00234 { 00235 hook_dissociate ("playlist update", update_cb); 00236 00237 g_mutex_lock (current_playback->pb_ready_mutex); 00238 00239 while (! current_playback->pb_ready_val) 00240 g_cond_wait (current_playback->pb_ready_cond, 00241 current_playback->pb_ready_mutex); 00242 00243 if (ready_source) 00244 { 00245 g_source_remove (ready_source); 00246 ready_source = 0; 00247 } 00248 00249 g_mutex_unlock (current_playback->pb_ready_mutex); 00250 00251 current_playback->plugin->stop (current_playback); 00252 00253 /* some plugins do this themselves */ 00254 if (current_playback->thread != NULL) 00255 g_thread_join (current_playback->thread); 00256 00257 cancel_set_tuple (); 00258 playback_free (current_playback); 00259 current_playback = NULL; 00260 } 00261 00262 static void complete_stop (void) 00263 { 00264 output_drain (); 00265 hook_call ("playback stop", NULL); 00266 00267 if (cfg.stopaftersong) 00268 { 00269 cfg.stopaftersong = FALSE; 00270 hook_call ("toggle stop after song", NULL); 00271 } 00272 } 00273 00274 void playback_stop (void) 00275 { 00276 g_return_if_fail (current_playback != NULL); 00277 00278 stopping = TRUE; 00279 playback_finalize (); 00280 stopping = FALSE; 00281 00282 complete_stop (); 00283 } 00284 00285 static gboolean playback_ended (void * unused) 00286 { 00287 gint playlist = playlist_get_playing (); 00288 gboolean play; 00289 00290 g_return_val_if_fail (current_playback != NULL, FALSE); 00291 00292 hook_call ("playback end", NULL); 00293 00294 if (current_playback->error) 00295 failed_entries ++; 00296 else 00297 failed_entries = 0; 00298 00299 playback_finalize (); 00300 00301 while (1) 00302 { 00303 if (cfg.no_playlist_advance) 00304 play = cfg.repeat && ! failed_entries; 00305 else 00306 { 00307 if (! (play = playlist_next_song (playlist, cfg.repeat))) 00308 playlist_set_position (playlist, -1); 00309 00310 if (failed_entries >= 10) 00311 play = FALSE; 00312 } 00313 00314 if (cfg.stopaftersong) 00315 play = FALSE; 00316 00317 if (! play) 00318 { 00319 complete_stop (); 00320 hook_call ("playlist end reached", NULL); 00321 break; 00322 } 00323 00324 if (playback_play_file (playlist, playlist_get_position (playlist), 0, 00325 FALSE)) 00326 break; 00327 00328 failed_entries ++; 00329 } 00330 00331 return FALSE; 00332 } 00333 00334 typedef struct 00335 { 00336 gint start_time, stop_time; 00337 gboolean pause; 00338 } 00339 PlayParams; 00340 00341 static void * playback_monitor_thread (void * data) 00342 { 00343 if (current_playback->plugin->play != NULL) 00344 { 00345 PlayParams * params = data; 00346 VFSFile * file = vfs_fopen (current_playback->filename, "r"); 00347 00348 current_playback->error = ! current_playback->plugin->play 00349 (current_playback, current_playback->filename, file, 00350 params->start_time, params->stop_time, params->pause); 00351 00352 if (file != NULL) 00353 vfs_fclose (file); 00354 } 00355 else 00356 { 00357 fprintf (stderr, "%s should be updated to provide play().\n", 00358 current_playback->plugin->description); 00359 g_return_val_if_fail (current_playback->plugin->play_file != NULL, NULL); 00360 current_playback->plugin->play_file (current_playback); 00361 } 00362 00363 g_mutex_lock (current_playback->pb_ready_mutex); 00364 current_playback->pb_ready_val = TRUE; 00365 00366 if (ready_source != 0) 00367 { 00368 g_source_remove (ready_source); 00369 ready_source = 0; 00370 } 00371 00372 g_cond_signal (current_playback->pb_ready_cond); 00373 g_mutex_unlock (current_playback->pb_ready_mutex); 00374 00375 if (! stopping) 00376 g_timeout_add (0, playback_ended, NULL); 00377 00378 return NULL; 00379 } 00380 00381 /* compatibility */ 00382 static void playback_set_replaygain_info (InputPlayback * playback, 00383 ReplayGainInfo * info) 00384 { 00385 fprintf (stderr, "Plugin %s should be updated to use OutputAPI::" 00386 "set_replaygain_info or (better) InputPlayback::set_gain_from_playlist.\n", 00387 playback->plugin->description); 00388 00389 playback->output->set_replaygain_info (info); 00390 } 00391 00392 /* compatibility */ 00393 static void playback_pass_audio (InputPlayback * playback, gint format, gint 00394 channels, gint size, void * data, gint * going) 00395 { 00396 static gboolean warned = FALSE; 00397 00398 if (! warned) 00399 { 00400 fprintf (stderr, "Plugin %s should be updated to use OutputAPI::" 00401 "write_audio.\n", playback->plugin->description); 00402 warned = TRUE; 00403 } 00404 00405 playback->output->write_audio (data, size); 00406 } 00407 00408 static InputPlayback * playback_new (void) 00409 { 00410 InputPlayback *playback = (InputPlayback *) g_slice_new0(InputPlayback); 00411 00412 playback->pb_ready_mutex = g_mutex_new(); 00413 playback->pb_ready_cond = g_cond_new(); 00414 playback->pb_ready_val = 0; 00415 00416 playback->output = & output_api; 00417 00418 /* init vtable functors */ 00419 playback->set_pb_ready = playback_set_pb_ready; 00420 playback->set_params = set_params; 00421 playback->set_tuple = set_tuple; 00422 playback->set_gain_from_playlist = set_gain_from_playlist; 00423 00424 /* compatibility */ 00425 playback->set_replaygain_info = playback_set_replaygain_info; 00426 playback->pass_audio = playback_pass_audio; 00427 00428 return playback; 00429 } 00430 00438 static void playback_free (InputPlayback * playback) 00439 { 00440 g_free(playback->filename); 00441 g_free(playback->title); 00442 00443 g_mutex_free(playback->pb_ready_mutex); 00444 g_cond_free(playback->pb_ready_cond); 00445 00446 g_slice_free(InputPlayback, playback); 00447 } 00448 00449 static void playback_run (gint start_time, gint stop_time, gboolean pause) 00450 { 00451 current_playback->playing = FALSE; 00452 current_playback->eof = FALSE; 00453 current_playback->error = FALSE; 00454 00455 paused = pause; 00456 stopping = FALSE; 00457 ready_source = 0; 00458 00459 static PlayParams params; 00460 params.start_time = start_time; 00461 params.stop_time = stop_time; 00462 params.pause = pause; 00463 00464 current_playback->thread = g_thread_create (playback_monitor_thread, 00465 & params, TRUE, NULL); 00466 } 00467 00468 static gboolean playback_play_file (gint playlist, gint entry, gint seek_time, 00469 gboolean pause) 00470 { 00471 const gchar * filename = playlist_entry_get_filename (playlist, entry); 00472 const gchar * title = playlist_entry_get_title (playlist, entry, FALSE); 00473 InputPlugin * decoder = playlist_entry_get_decoder (playlist, entry); 00474 Tuple * tuple = (Tuple *) playlist_entry_get_tuple (playlist, entry, FALSE); 00475 00476 g_return_val_if_fail (current_playback == NULL, FALSE); 00477 00478 if (decoder == NULL) 00479 { 00480 gchar * error = g_strdup_printf (_("No decoder found for %s."), filename); 00481 /* The interface may not be up yet at this point. --jlindgren */ 00482 event_queue_with_data_free ("interface show error", error); 00483 return FALSE; 00484 } 00485 00486 read_gain_from_tuple (tuple); /* even if tuple == NULL */ 00487 00488 current_playback = playback_new (); 00489 current_playback->plugin = decoder; 00490 current_playback->filename = g_strdup (filename); 00491 current_playback->title = g_strdup ((title != NULL) ? title : filename); 00492 current_playback->length = playlist_entry_get_length (playlist, entry, FALSE); 00493 00494 if (playlist_entry_is_segmented (playlist, entry)) 00495 { 00496 time_offset = playlist_entry_get_start_time (playlist, entry); 00497 playback_run (time_offset + seek_time, playlist_entry_get_end_time 00498 (playlist, entry), pause); 00499 } 00500 else 00501 { 00502 time_offset = 0; 00503 playback_run (seek_time, -1, pause); 00504 } 00505 00506 #ifdef USE_DBUS /* Fix me: Use a "playback begin" hook in dbus.c. */ 00507 mpris_emit_track_change(mpris); 00508 #endif 00509 00510 hook_associate ("playlist update", update_cb, NULL); 00511 hook_call ("playback begin", NULL); 00512 return TRUE; 00513 } 00514 00515 gboolean playback_get_playing (void) 00516 { 00517 return (current_playback != NULL); 00518 } 00519 00520 gboolean playback_get_paused (void) 00521 { 00522 g_return_val_if_fail (current_playback != NULL, FALSE); 00523 00524 return paused; 00525 } 00526 00527 void playback_seek (gint time) 00528 { 00529 g_return_if_fail (current_playback != NULL); 00530 00531 if (! playback_is_ready ()) 00532 return; 00533 00534 time = CLAMP (time, 0, current_playback->length); 00535 time += time_offset; 00536 00537 if (current_playback->plugin->mseek != NULL) 00538 current_playback->plugin->mseek (current_playback, time); 00539 else if (current_playback->plugin->seek != NULL) 00540 { 00541 fprintf (stderr, "%s should be updated to provide mseek().\n", 00542 current_playback->plugin->description); 00543 current_playback->plugin->seek (current_playback, time / 1000); 00544 } 00545 00546 hook_call ("playback seek", NULL); 00547 } 00548 00549 static void set_params (InputPlayback * playback, const gchar * title, gint 00550 length, gint bitrate, gint samplerate, gint channels) 00551 { 00552 playback->rate = bitrate; 00553 playback->freq = samplerate; 00554 playback->nch = channels; 00555 00556 event_queue ("info change", NULL); 00557 } 00558 00559 static gboolean set_tuple_cb (void * unused) 00560 { 00561 gint playlist = playlist_get_playing (); 00562 00563 g_return_val_if_fail (current_playback != NULL, FALSE); 00564 g_mutex_lock (current_playback->pb_ready_mutex); 00565 00566 playlist_entry_set_tuple (playlist, playlist_get_position (playlist), 00567 tuple_to_be_set); 00568 set_tuple_source = 0; 00569 tuple_to_be_set = NULL; 00570 00571 g_mutex_unlock (current_playback->pb_ready_mutex); 00572 00573 return FALSE; 00574 } 00575 00576 static void set_tuple (InputPlayback * playback, Tuple * tuple) 00577 { 00578 g_mutex_lock (playback->pb_ready_mutex); 00579 00580 /* playlist_entry_set_tuple must execute in main thread */ 00581 cancel_set_tuple (); 00582 set_tuple_source = g_timeout_add (0, set_tuple_cb, NULL); 00583 tuple_to_be_set = tuple; 00584 00585 read_gain_from_tuple (tuple); 00586 00587 g_mutex_unlock (playback->pb_ready_mutex); 00588 } 00589 00590 static void set_gain_from_playlist (InputPlayback * playback) 00591 { 00592 playback->output->set_replaygain_info (& gain_from_playlist); 00593 } 00594 00595 gchar * playback_get_title (void) 00596 { 00597 gchar * suffix, * title; 00598 00599 g_return_val_if_fail (current_playback != NULL, NULL); 00600 00601 if (! playback_is_ready ()) 00602 return g_strdup (_("Buffering ...")); 00603 00604 suffix = (current_playback->length > 0) ? g_strdup_printf (" (%d:%02d)", 00605 current_playback->length / 60000, current_playback->length / 1000 % 60) : 00606 NULL; 00607 00608 if (cfg.show_numbers_in_pl) 00609 title = g_strdup_printf ("%d. %s%s", 1 + playlist_get_position 00610 (playlist_get_playing ()), current_playback->title, (suffix != NULL) ? 00611 suffix : ""); 00612 else 00613 title = g_strdup_printf ("%s%s", current_playback->title, (suffix != 00614 NULL) ? suffix : ""); 00615 00616 g_free (suffix); 00617 return title; 00618 } 00619 00620 gint playback_get_length (void) 00621 { 00622 g_return_val_if_fail (current_playback != NULL, 0); 00623 00624 return current_playback->length; 00625 } 00626 00627 void playback_get_info (gint * bitrate, gint * samplerate, gint * channels) 00628 { 00629 g_return_if_fail (current_playback != NULL); 00630 00631 * bitrate = current_playback->rate; 00632 * samplerate = current_playback->freq; 00633 * channels = current_playback->nch; 00634 } 00635 00636 void 00637 input_get_volume(gint * l, gint * r) 00638 { 00639 if (current_playback && current_playback->plugin->get_volume && 00640 current_playback->plugin->get_volume (l, r)) 00641 return; 00642 00643 output_get_volume (l, r); 00644 } 00645 00646 void 00647 input_set_volume(gint l, gint r) 00648 { 00649 gint h_vol[2] = {l, r}; 00650 00651 hook_call("volume set", h_vol); 00652 00653 if (current_playback && current_playback->plugin->set_volume && 00654 current_playback->plugin->set_volume (l, r)) 00655 return; 00656 00657 output_set_volume (l, r); 00658 }