Audacious $Id:Doxyfile42802007-03-2104:39:00Znenolod$

playback.c

Go to the documentation of this file.
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 }