/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * arch-tag: Implementation of main playback logic object * * Copyright (C) 2002, 2003 Jorn Baayen * Copyright (C) 2002,2003 Colin Walters * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "rb-property-view.h" #include "rb-shell-player.h" #include "rb-stock-icons.h" #include "rb-glade-helpers.h" #include "rb-file-helpers.h" #include "rb-cut-and-paste-code.h" #include "rb-dialog.h" #include "rb-preferences.h" #include "rb-debug.h" #include "rb-player.h" #include "rb-header.h" #include "totem-pl-parser.h" #include "rb-metadata.h" #include "rb-library-source.h" #include "eel-gconf-extensions.h" #include "rb-util.h" #include "rb-play-order.h" #include "rb-statusbar.h" #include "rb-playlist-source.h" #include "rb-play-queue-source.h" #include "rhythmdb.h" #include "rb-podcast-manager.h" #include "rb-marshal.h" #ifdef HAVE_XIDLE_EXTENSION #include #endif /* HAVE_XIDLE_EXTENSION */ static const char* const state_to_play_order[2][2] = {{"linear", "linear-loop"}, {"shuffle", "random-by-age-and-rating"}}; static void rb_shell_player_class_init (RBShellPlayerClass *klass); static void rb_shell_player_init (RBShellPlayer *shell_player); static GObject *rb_shell_player_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties); static void rb_shell_player_dispose (GObject *object); static void rb_shell_player_finalize (GObject *object); static void rb_shell_player_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void rb_shell_player_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void rb_shell_player_cmd_previous (GtkAction *action, RBShellPlayer *player); static void rb_shell_player_cmd_play (GtkAction *action, RBShellPlayer *player); static void rb_shell_player_cmd_next (GtkAction *action, RBShellPlayer *player); static void rb_shell_player_cmd_volume_up (GtkAction *action, RBShellPlayer *player); static void rb_shell_player_cmd_volume_down (GtkAction *action, RBShellPlayer *player); static void rb_shell_player_shuffle_changed_cb (GtkAction *action, RBShellPlayer *player); static void rb_shell_player_repeat_changed_cb (GtkAction *action, RBShellPlayer *player); static void rb_shell_player_view_song_position_slider_changed_cb (GtkAction *action, RBShellPlayer *player); static void rb_shell_player_set_playing_source_internal (RBShellPlayer *player, RBSource *source, gboolean sync_entry_view); static void rb_shell_player_sync_with_source (RBShellPlayer *player); static void rb_shell_player_sync_with_selected_source (RBShellPlayer *player); static void rb_shell_player_entry_changed_cb (RhythmDB *db, RhythmDBEntry *entry, GSList *changes, RBShellPlayer *player); static void rb_shell_player_entry_activated_cb (RBEntryView *view, RhythmDBEntry *entry, RBShellPlayer *playa); static void rb_shell_player_property_row_activated_cb (RBPropertyView *view, const char *name, RBShellPlayer *playa); static void rb_shell_player_sync_volume (RBShellPlayer *player, gboolean notify); static void rb_shell_player_sync_replaygain (RBShellPlayer *player, RhythmDBEntry *entry); static void tick_cb (RBPlayer *player, long elapsed, gpointer data); static void error_cb (RBPlayer *player, const GError *err, gpointer data); static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err); static void rb_shell_player_set_play_order (RBShellPlayer *player, const gchar *new_val); static void rb_shell_player_play_order_update_cb (RBPlayOrder *porder, gboolean has_next, gboolean has_previous, RBShellPlayer *player); static void rb_shell_player_sync_play_order (RBShellPlayer *player); static void rb_shell_player_sync_control_state (RBShellPlayer *player); static void rb_shell_player_sync_song_position_slider_visibility (RBShellPlayer *player); static void rb_shell_player_sync_buttons (RBShellPlayer *player); static void gconf_play_order_changed (GConfClient *client,guint cnxn_id, GConfEntry *entry, RBShellPlayer *player); static void gconf_song_position_slider_visibility_changed (GConfClient *client,guint cnxn_id, GConfEntry *entry, RBShellPlayer *player); static void rb_shell_player_playing_changed_cb (RBShellPlayer *player, GParamSpec *arg1, gpointer user_data); static void rb_shell_player_extra_metadata_cb (RhythmDB *db, RhythmDBEntry *entry, const char *field, GValue *metadata, RBShellPlayer *player); static gboolean rb_shell_player_jump_to_current_idle (RBShellPlayer *player); #define CONF_STATE CONF_PREFIX "/state" struct RBShellPlayerPrivate { RhythmDB *db; gboolean syncing_state; gboolean queue_only; RBSource *selected_source; RBSource *source; RBPlayQueueSource *queue_source; RBSource *current_playing_source; gboolean did_retry; GTimeVal last_retry; GtkUIManager *ui_manager; GtkActionGroup *actiongroup; gboolean handling_error; RBPlayer *mmplayer; char *song; char *url; guint elapsed; RBPlayOrder *play_order; RBPlayOrder *queue_play_order; GQueue *playlist_urls; RBHeader *header_widget; RBStatusbar *statusbar_widget; guint gconf_play_order_id; guint gconf_song_position_slider_visibility_id; gboolean mute; float volume; guint do_next_idle_id; }; #define RB_SHELL_PLAYER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_PLAYER, RBShellPlayerPrivate)) enum { PROP_0, PROP_SOURCE, PROP_DB, PROP_UI_MANAGER, PROP_ACTION_GROUP, PROP_PLAY_ORDER, PROP_PLAYING, PROP_VOLUME, PROP_STATUSBAR, PROP_QUEUE_SOURCE, PROP_QUEUE_ONLY, PROP_PLAYING_FROM_QUEUE, PROP_PLAYER, }; enum { WINDOW_TITLE_CHANGED, ELAPSED_CHANGED, PLAYING_SOURCE_CHANGED, PLAYING_CHANGED, PLAYING_SONG_CHANGED, PLAYING_URI_CHANGED, PLAYING_SONG_PROPERTY_CHANGED, LAST_SIGNAL }; static GtkActionEntry rb_shell_player_actions [] = { { "ControlPrevious", GTK_STOCK_MEDIA_PREVIOUS, N_("P_revious"), "Left", N_("Start playing the previous song"), G_CALLBACK (rb_shell_player_cmd_previous) }, { "ControlNext", GTK_STOCK_MEDIA_NEXT, N_("_Next"), "Right", N_("Start playing the next song"), G_CALLBACK (rb_shell_player_cmd_next) }, { "ControlVolumeUp", NULL, N_("_Increase Volume"), "Up", N_("Increase playback volume"), G_CALLBACK (rb_shell_player_cmd_volume_up) }, { "ControlVolumeDown", NULL, N_("_Decrease Volume"), "Down", N_("Decrease playback volume"), G_CALLBACK (rb_shell_player_cmd_volume_down) }, }; static guint rb_shell_player_n_actions = G_N_ELEMENTS (rb_shell_player_actions); static GtkToggleActionEntry rb_shell_player_toggle_entries [] = { { "ControlPlay", GTK_STOCK_MEDIA_PLAY, N_("_Play"), "space", N_("Start playback"), G_CALLBACK (rb_shell_player_cmd_play) }, { "ControlShuffle", GNOME_MEDIA_SHUFFLE, N_("Sh_uffle"), "U", N_("Play songs in a random order"), G_CALLBACK (rb_shell_player_shuffle_changed_cb) }, { "ControlRepeat", GNOME_MEDIA_REPEAT, N_("_Repeat"), "R", N_("Play first song again after all songs are played"), G_CALLBACK (rb_shell_player_repeat_changed_cb) }, { "ViewSongPositionSlider", NULL, N_("_Song Position Slider"), NULL, N_("Change the visibility of the song position slider"), G_CALLBACK (rb_shell_player_view_song_position_slider_changed_cb), TRUE }, }; static guint rb_shell_player_n_toggle_entries = G_N_ELEMENTS (rb_shell_player_toggle_entries); static guint rb_shell_player_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (RBShellPlayer, rb_shell_player, GTK_TYPE_HBOX) static void rb_shell_player_class_init (RBShellPlayerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = rb_shell_player_dispose; object_class->finalize = rb_shell_player_finalize; object_class->constructor = rb_shell_player_constructor; object_class->set_property = rb_shell_player_set_property; object_class->get_property = rb_shell_player_get_property; g_object_class_install_property (object_class, PROP_SOURCE, g_param_spec_object ("source", "RBSource", "RBSource object", RB_TYPE_SOURCE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_UI_MANAGER, g_param_spec_object ("ui-manager", "GtkUIManager", "GtkUIManager object", GTK_TYPE_UI_MANAGER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_DB, g_param_spec_object ("db", "RhythmDB", "RhythmDB object", RHYTHMDB_TYPE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_ACTION_GROUP, g_param_spec_object ("action-group", "GtkActionGroup", "GtkActionGroup object", GTK_TYPE_ACTION_GROUP, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_QUEUE_SOURCE, g_param_spec_object ("queue-source", "RBPlaylistSource", "RBPlaylistSource object", RB_TYPE_PLAYLIST_SOURCE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_QUEUE_ONLY, g_param_spec_boolean ("queue-only", "Queue only", "Activation only adds to queue", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_PLAYING_FROM_QUEUE, g_param_spec_boolean ("playing-from-queue", "Playing from queue", "Whether playing from the play queue or not", FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PLAYER, g_param_spec_object ("player", "RBPlayer", "RBPlayer object", G_TYPE_OBJECT, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PLAY_ORDER, g_param_spec_string ("play-order", "play-order", "What play order to use", "linear", G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PLAYING, g_param_spec_boolean ("playing", "playing", "Whether Rhythmbox is currently playing", FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_VOLUME, g_param_spec_float ("volume", "volume", "Current playback volume", 0.0f, 1.0f, 1.0f, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_STATUSBAR, g_param_spec_object ("statusbar", "RBStatusbar", "RBStatusbar object", RB_TYPE_STATUSBAR, G_PARAM_READWRITE)); rb_shell_player_signals[WINDOW_TITLE_CHANGED] = g_signal_new ("window_title_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBShellPlayerClass, window_title_changed), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); rb_shell_player_signals[ELAPSED_CHANGED] = g_signal_new ("elapsed_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_changed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); rb_shell_player_signals[PLAYING_SOURCE_CHANGED] = g_signal_new ("playing-source-changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBShellPlayerClass, playing_source_changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, RB_TYPE_SOURCE); rb_shell_player_signals[PLAYING_CHANGED] = g_signal_new ("playing-changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBShellPlayerClass, playing_changed), NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); rb_shell_player_signals[PLAYING_SONG_CHANGED] = g_signal_new ("playing-song-changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_changed), NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, RHYTHMDB_TYPE_ENTRY); rb_shell_player_signals[PLAYING_URI_CHANGED] = g_signal_new ("playing-uri-changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBShellPlayerClass, playing_uri_changed), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED] = g_signal_new ("playing-song-property-changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_property_changed), NULL, NULL, rb_marshal_VOID__STRING_STRING_POINTER_POINTER, G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_VALUE, G_TYPE_VALUE); g_type_class_add_private (klass, sizeof (RBShellPlayerPrivate)); } static GObject * rb_shell_player_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { RBShellPlayer *player; RBShellPlayerClass *klass; GtkAction *action; klass = RB_SHELL_PLAYER_CLASS (g_type_class_peek (RB_TYPE_SHELL_PLAYER)); player = RB_SHELL_PLAYER (G_OBJECT_CLASS (rb_shell_player_parent_class)-> constructor (type, n_construct_properties, construct_properties)); player->priv->header_widget = rb_header_new (player, player->priv->db); gtk_widget_show (GTK_WIDGET (player->priv->header_widget)); gtk_box_pack_start (GTK_BOX (player), GTK_WIDGET (player->priv->header_widget), TRUE, TRUE, 0); gtk_action_group_add_actions (player->priv->actiongroup, rb_shell_player_actions, rb_shell_player_n_actions, player); gtk_action_group_add_toggle_actions (player->priv->actiongroup, rb_shell_player_toggle_entries, rb_shell_player_n_toggle_entries, player); action = gtk_action_group_get_action (player->priv->actiongroup, "ControlPlay"); g_object_set (action, "is-important", TRUE, NULL); player->priv->syncing_state = TRUE; rb_shell_player_set_playing_source (player, NULL); rb_shell_player_sync_play_order (player); rb_shell_player_sync_control_state (player); rb_shell_player_sync_volume (player, FALSE); player->priv->syncing_state = FALSE; rb_shell_player_sync_song_position_slider_visibility (player); g_signal_connect (G_OBJECT (player), "notify::playing", G_CALLBACK (rb_shell_player_playing_changed_cb), NULL); return G_OBJECT (player); } static void volume_pre_unmount_cb (GnomeVFSVolumeMonitor *monitor, GnomeVFSVolume *volume, RBShellPlayer *player) { gchar *uri_mount_point; gchar *volume_mount_point; RhythmDBEntry *entry; const char *uri; gboolean playing; rb_shell_player_get_playing (player, &playing, NULL); if (playing) { return; } entry = rb_shell_player_get_playing_entry (player); if (entry == NULL) { /* At startup for example, playing path can be NULL */ return; } uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION); uri_mount_point = rb_uri_get_mount_point (uri); volume_mount_point = gnome_vfs_volume_get_activation_uri (volume); if (uri_mount_point && volume_mount_point && (strcmp (uri_mount_point, volume_mount_point) == 0)) { rb_shell_player_stop (player); } g_free (uri_mount_point); g_free (volume_mount_point); if (entry != NULL) { rhythmdb_entry_unref (entry); } } static void reemit_playing_signal (RBShellPlayer *player, GParamSpec *pspec, gpointer data) { g_signal_emit (player, rb_shell_player_signals[PLAYING_CHANGED], 0, rb_player_playing (player->priv->mmplayer)); } static gboolean notify_playing_idle (RBShellPlayer *player) { GDK_THREADS_ENTER (); rb_debug ("emitting playing notification: %d", rb_player_playing (player->priv->mmplayer)); g_object_notify (G_OBJECT (player), "playing"); rb_shell_player_sync_buttons (player); GDK_THREADS_LEAVE (); return FALSE; } static void rb_shell_player_open_playlist_url (RBShellPlayer *player, const char *location) { GError *error = NULL; rb_debug ("playing stream url %s", location); rb_player_open (player->priv->mmplayer, location, &error); if (error == NULL) rb_player_play (player->priv->mmplayer, &error); if (error) { GDK_THREADS_ENTER (); rb_shell_player_error (player, TRUE, error); g_error_free (error); GDK_THREADS_LEAVE (); } g_idle_add ((GSourceFunc) notify_playing_idle, player); } static void rb_shell_player_handle_eos_unlocked (RBShellPlayer *player) { RhythmDBEntry *entry; RBSource *source; source = player->priv->current_playing_source; /* nothing to do */ if (source == NULL) { return; } entry = rb_shell_player_get_playing_entry (player); switch (rb_source_handle_eos (source)) { case RB_SOURCE_EOF_ERROR: rb_error_dialog (NULL, _("Stream error"), _("Unexpected end of stream!")); rb_shell_player_set_playing_source (player, NULL); break; case RB_SOURCE_EOF_STOP: rb_shell_player_set_playing_source (player, NULL); break; case RB_SOURCE_EOF_RETRY: { GTimeVal current; gint diff; g_get_current_time (¤t); diff = current.tv_sec - player->priv->last_retry.tv_sec; player->priv->last_retry = current; if (rb_source_try_playlist (source) && !g_queue_is_empty (player->priv->playlist_urls)) { char *location = g_queue_pop_head (player->priv->playlist_urls); rb_debug ("trying next radio stream url: %s", location); rb_shell_player_open_playlist_url (player, location); g_free (location); break; } if (diff < 4) { rb_debug ("Last retry was less than 4 seconds ago...aborting retry playback"); rb_shell_player_set_playing_source (player, NULL); } else { rb_shell_player_play_entry (player, entry, NULL); } } break; case RB_SOURCE_EOF_NEXT: { GError *error = NULL; if (!rb_shell_player_do_next (player, &error)) { if (error->domain != RB_SHELL_PLAYER_ERROR || error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) g_warning ("Unhandled error: %s", error->message); else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) rb_shell_player_set_playing_source (player, NULL); } } break; } if (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) == NULL) { rb_debug ("updating play statistics"); rb_source_update_play_statistics (source, player->priv->db, entry); } if (entry != NULL) { rhythmdb_entry_unref (entry); } } static void rb_shell_player_handle_eos (RBShellPlayer *player) { rb_debug ("handling eos!"); GDK_THREADS_ENTER (); rb_shell_player_handle_eos_unlocked (player); GDK_THREADS_LEAVE (); } static void rb_shell_player_init (RBShellPlayer *player) { GError *error = NULL; player->priv = RB_SHELL_PLAYER_GET_PRIVATE (player); player->priv->mmplayer = rb_player_new (&error); if (error != NULL) { GtkWidget *dialog; dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("Failed to create the player: %s"), error->message); gtk_dialog_run (GTK_DIALOG (dialog)); exit (1); } gtk_box_set_spacing (GTK_BOX (player), 12); gtk_container_set_border_width (GTK_CONTAINER (player), 3); g_signal_connect_object (G_OBJECT (player->priv->mmplayer), "eos", G_CALLBACK (rb_shell_player_handle_eos), player, G_CONNECT_SWAPPED); g_signal_connect_object (G_OBJECT (player->priv->mmplayer), "tick", G_CALLBACK (tick_cb), player, 0); g_signal_connect_object (G_OBJECT (player->priv->mmplayer), "error", G_CALLBACK (error_cb), player, 0); g_signal_connect (G_OBJECT (gnome_vfs_get_volume_monitor ()), "volume-pre-unmount", G_CALLBACK (volume_pre_unmount_cb), player); player->priv->gconf_play_order_id = eel_gconf_notification_add (CONF_STATE_PLAY_ORDER, (GConfClientNotifyFunc)gconf_play_order_changed, player); player->priv->volume = eel_gconf_get_float (CONF_STATE_VOLUME); g_signal_connect (player, "notify::playing", G_CALLBACK (reemit_playing_signal), NULL); player->priv->gconf_song_position_slider_visibility_id = eel_gconf_notification_add (CONF_UI_SONG_POSITION_SLIDER_HIDDEN, (GConfClientNotifyFunc) gconf_song_position_slider_visibility_changed, player); } static void rb_shell_player_set_source_internal (RBShellPlayer *player, RBSource *source) { if (player->priv->selected_source != NULL) { RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source); GList *property_views = rb_source_get_property_views (player->priv->selected_source); GList *l; if (songs != NULL) { g_signal_handlers_disconnect_by_func (G_OBJECT (songs), G_CALLBACK (rb_shell_player_entry_activated_cb), player); } for (l = property_views; l != NULL; l = g_list_next (l)) { g_signal_handlers_disconnect_by_func (G_OBJECT (l->data), G_CALLBACK (rb_shell_player_property_row_activated_cb), player); } g_list_free (property_views); } player->priv->selected_source = source; rb_debug ("selected source %p", player->priv->selected_source); rb_shell_player_sync_with_selected_source (player); rb_shell_player_sync_buttons (player); if (player->priv->selected_source != NULL) { RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source); GList *property_views = rb_source_get_property_views (player->priv->selected_source); GList *l; if (songs) g_signal_connect_object (G_OBJECT (songs), "entry-activated", G_CALLBACK (rb_shell_player_entry_activated_cb), player, 0); for (l = property_views; l != NULL; l = g_list_next (l)) { g_signal_connect_object (G_OBJECT (l->data), "property-activated", G_CALLBACK (rb_shell_player_property_row_activated_cb), player, 0); } g_list_free (property_views); } /* If we're not playing, change the play order's view of the current source; * if the selected source is the queue, however, set it to NULL so it'll stop * once the queue is empty. */ if (player->priv->current_playing_source == NULL) { RBSource *source = player->priv->selected_source; if (source == RB_SOURCE (player->priv->queue_source)) source = NULL; rb_play_order_playing_source_changed (player->priv->play_order, source); } } static void rb_shell_player_set_db_internal (RBShellPlayer *player, RhythmDB *db) { if (player->priv->db != NULL) { g_signal_handlers_disconnect_by_func (player->priv->db, G_CALLBACK (rb_shell_player_entry_changed_cb), player); g_signal_handlers_disconnect_by_func (player->priv->db, G_CALLBACK (rb_shell_player_extra_metadata_cb), player); } player->priv->db = db; if (player->priv->db != NULL) { /* Listen for changed entries to update metadata display */ g_signal_connect_object (G_OBJECT (player->priv->db), "entry_changed", G_CALLBACK (rb_shell_player_entry_changed_cb), player, 0); g_signal_connect_object (G_OBJECT (player->priv->db), "entry_extra_metadata_notify", G_CALLBACK (rb_shell_player_extra_metadata_cb), player, 0); } } static void rb_shell_player_set_queue_source_internal (RBShellPlayer *player, RBPlayQueueSource *source) { if (player->priv->queue_source != NULL) { RBEntryView *sidebar; g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL); g_signal_handlers_disconnect_by_func (sidebar, G_CALLBACK (rb_shell_player_entry_activated_cb), player); g_object_unref (sidebar); if (player->priv->queue_play_order != NULL) { g_signal_handlers_disconnect_by_func (player->priv->queue_play_order, G_CALLBACK (rb_shell_player_play_order_update_cb), player); g_object_unref (player->priv->queue_play_order); } } player->priv->queue_source = source; if (player->priv->queue_source != NULL) { RBEntryView *sidebar; player->priv->queue_play_order = rb_play_order_new ("queue", player); g_signal_connect_object (G_OBJECT (player->priv->queue_play_order), "have_next_previous_changed", G_CALLBACK (rb_shell_player_play_order_update_cb), player, 0); rb_shell_player_play_order_update_cb (player->priv->play_order, FALSE, FALSE, player); rb_play_order_playing_source_changed (player->priv->queue_play_order, RB_SOURCE (player->priv->queue_source)); g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL); g_signal_connect_object (G_OBJECT (sidebar), "entry-activated", G_CALLBACK (rb_shell_player_entry_activated_cb), player, 0); g_object_unref (sidebar); } } static void rb_shell_player_dispose (GObject *object) { RBShellPlayer *player; g_return_if_fail (object != NULL); g_return_if_fail (RB_IS_SHELL_PLAYER (object)); player = RB_SHELL_PLAYER (object); g_return_if_fail (player->priv != NULL); if (player->priv->gconf_play_order_id != 0) { eel_gconf_notification_remove (player->priv->gconf_play_order_id); player->priv->gconf_play_order_id = 0; } if (player->priv->mmplayer != NULL) { g_object_unref (player->priv->mmplayer); player->priv->mmplayer = NULL; } if (player->priv->play_order != NULL) { g_object_unref (player->priv->play_order); player->priv->play_order = NULL; } if (player->priv->queue_play_order != NULL) { g_object_unref (player->priv->queue_play_order); player->priv->queue_play_order = NULL; } G_OBJECT_CLASS (rb_shell_player_parent_class)->dispose (object); } static void rb_shell_player_finalize (GObject *object) { RBShellPlayer *player; g_return_if_fail (object != NULL); g_return_if_fail (RB_IS_SHELL_PLAYER (object)); player = RB_SHELL_PLAYER (object); g_return_if_fail (player->priv != NULL); eel_gconf_set_float (CONF_STATE_VOLUME, player->priv->volume); G_OBJECT_CLASS (rb_shell_player_parent_class)->finalize (object); } static void rb_shell_player_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { RBShellPlayer *player = RB_SHELL_PLAYER (object); switch (prop_id) { case PROP_SOURCE: rb_shell_player_set_source_internal (player, g_value_get_object (value)); break; case PROP_UI_MANAGER: player->priv->ui_manager = g_value_get_object (value); break; case PROP_DB: rb_shell_player_set_db_internal (player, g_value_get_object (value)); break; case PROP_ACTION_GROUP: player->priv->actiongroup = g_value_get_object (value); break; case PROP_PLAY_ORDER: eel_gconf_set_string (CONF_STATE_PLAY_ORDER, g_value_get_string (value)); break; case PROP_VOLUME: player->priv->volume = g_value_get_float (value); rb_shell_player_sync_volume (player, FALSE); break; case PROP_STATUSBAR: player->priv->statusbar_widget = g_value_get_object (value); break; case PROP_QUEUE_SOURCE: rb_shell_player_set_queue_source_internal (player, g_value_get_object (value)); break; case PROP_QUEUE_ONLY: player->priv->queue_only = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void rb_shell_player_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { RBShellPlayer *player = RB_SHELL_PLAYER (object); switch (prop_id) { case PROP_SOURCE: g_value_set_object (value, player->priv->selected_source); break; case PROP_UI_MANAGER: g_value_set_object (value, player->priv->ui_manager); break; case PROP_DB: g_value_set_object (value, player->priv->db); break; case PROP_ACTION_GROUP: g_value_set_object (value, player->priv->actiongroup); break; case PROP_PLAY_ORDER: { char *play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER); if (!play_order) play_order = g_strdup ("linear"); g_value_set_string_take_ownership (value, play_order); break; } case PROP_PLAYING: if (player->priv->mmplayer != NULL) g_value_set_boolean (value, rb_player_playing (player->priv->mmplayer)); else g_value_set_boolean (value, FALSE); break; case PROP_VOLUME: g_value_set_float (value, player->priv->volume); break; case PROP_STATUSBAR: g_value_set_object (value, player->priv->statusbar_widget); break; case PROP_QUEUE_SOURCE: g_value_set_object (value, player->priv->queue_source); break; case PROP_QUEUE_ONLY: g_value_set_boolean (value, player->priv->queue_only); break; case PROP_PLAYING_FROM_QUEUE: g_value_set_boolean (value, player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source)); break; case PROP_PLAYER: g_value_set_object (value, player->priv->mmplayer); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } GQuark rb_shell_player_error_quark (void) { static GQuark quark = 0; if (!quark) quark = g_quark_from_static_string ("rb_shell_player_error"); return quark; } void rb_shell_player_set_selected_source (RBShellPlayer *player, RBSource *source) { g_return_if_fail (RB_IS_SHELL_PLAYER (player)); g_return_if_fail (RB_IS_SOURCE (source)); g_object_set (G_OBJECT (player), "source", source, NULL); } RBSource * rb_shell_player_get_playing_source (RBShellPlayer *player) { return player->priv->current_playing_source; } RBSource * rb_shell_player_get_active_source (RBShellPlayer *player) { return player->priv->source; } RBShellPlayer * rb_shell_player_new (RhythmDB *db, GtkUIManager *mgr, GtkActionGroup *actiongroup) { return g_object_new (RB_TYPE_SHELL_PLAYER, "ui-manager", mgr, "action-group", actiongroup, "db", db, NULL); } RhythmDBEntry * rb_shell_player_get_playing_entry (RBShellPlayer *player) { RBPlayOrder *porder; RhythmDBEntry *entry; if (player->priv->current_playing_source == NULL) return NULL; if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source)) porder = player->priv->queue_play_order; else porder = player->priv->play_order; if (porder == NULL) { return NULL; } entry = rb_play_order_get_playing_entry (porder); return entry; } typedef struct { RBShellPlayer *player; char *location; } OpenLocationThreadData; #if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0) static void playlist_entry_cb (TotemPlParser *playlist, const char *uri, GHashTable *metadata, RBShellPlayer *player) #else static void playlist_entry_cb (TotemPlParser *playlist, const char *uri, const char *title, const char *genre, RBShellPlayer *player) #endif { rb_debug ("adding stream url %s", uri); g_queue_push_tail (player->priv->playlist_urls, g_strdup (uri)); } static gpointer open_location_thread (OpenLocationThreadData *data) { TotemPlParser *playlist; TotemPlParserResult playlist_result; playlist = totem_pl_parser_new (); #if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0) g_signal_connect_data (G_OBJECT (playlist), "entry-parsed", G_CALLBACK (playlist_entry_cb), data->player, NULL, 0); #else g_signal_connect_data (G_OBJECT (playlist), "entry", G_CALLBACK (playlist_entry_cb), data->player, NULL, 0); #endif /* TOTEM_PL_PARSER_CHECK_VERSION */ totem_pl_parser_add_ignored_mimetype (playlist, "x-directory/normal"); playlist_result = totem_pl_parser_parse (playlist, data->location, FALSE); g_object_unref (playlist); if (playlist_result == TOTEM_PL_PARSER_RESULT_SUCCESS) { if (g_queue_is_empty (data->player->priv->playlist_urls)) { GError *error = g_error_new (RB_SHELL_PLAYER_ERROR, RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST, _("Playlist was empty")); GDK_THREADS_ENTER (); rb_shell_player_error (data->player, TRUE, error); g_error_free (error); GDK_THREADS_LEAVE (); } else { char *location; location = g_queue_pop_head (data->player->priv->playlist_urls); rb_debug ("playing first stream url %s", data->location); rb_shell_player_open_playlist_url (data->player, location); g_free (location); } } else { /* if we can't parse it as a playlist, just try playing it */ rb_debug ("playlist parser failed, playing %s directly", data->location); rb_shell_player_open_playlist_url (data->player, data->location); } g_free (data); return NULL; } static gboolean rb_shell_player_open_location (RBShellPlayer *player, const char *location, GError **error) { char *unescaped; gboolean was_playing; unescaped = gnome_vfs_unescape_string_for_display (location); rb_debug ("Opening %s...", unescaped); g_free (unescaped); was_playing = rb_player_playing (player->priv->mmplayer); if (rb_source_try_playlist (player->priv->source)) { OpenLocationThreadData *data; data = g_new0 (OpenLocationThreadData, 1); data->player = player; /* dispose of any existing playlist urls */ if (player->priv->playlist_urls) { g_queue_foreach (player->priv->playlist_urls, (GFunc) g_free, NULL); g_queue_free (player->priv->playlist_urls); player->priv->playlist_urls = NULL; } player->priv->playlist_urls = g_queue_new (); /* add http:// as a prefix, if it doesn't have a URI scheme */ if (strstr (location, "://")) data->location = g_strdup (location); else data->location = g_strconcat ("http://", location, NULL); g_thread_create ((GThreadFunc)open_location_thread, data, FALSE, NULL); return TRUE; } else { if (!rb_player_open (player->priv->mmplayer, location, error)) return FALSE; if (!rb_player_play (player->priv->mmplayer, error)) return FALSE; g_object_notify (G_OBJECT (player), "playing"); } return TRUE; } static gboolean rb_shell_player_open_entry (RBShellPlayer *player, RhythmDBEntry *entry, GError **error) { char *location; gboolean result; location = rhythmdb_entry_get_playback_uri (entry); if (location == NULL) return FALSE; result = rb_shell_player_open_location (player, location, error); g_free (location); return result; } /** * rb_shell_player_play: * @player: a #RBShellPlayer * @error: error return * * Starts playback, if it is not already playing. * * @return: whether playback is now occurring (TRUE when successfully started * or already playing). **/ gboolean rb_shell_player_play (RBShellPlayer *player, GError **error) { RBEntryView *songs; if (player->priv->current_playing_source == NULL) return FALSE; if (rb_player_playing (player->priv->mmplayer)) return TRUE; if (!rb_player_play (player->priv->mmplayer, error)) return FALSE; songs = rb_source_get_entry_view (player->priv->current_playing_source); if (songs) rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING); rb_shell_player_sync_with_source (player); rb_shell_player_sync_buttons (player); return TRUE; } static void rb_shell_player_set_entry_playback_error (RBShellPlayer *player, RhythmDBEntry *entry, char *message) { GValue value = { 0, }; g_return_if_fail (RB_IS_SHELL_PLAYER (player)); g_value_init (&value, G_TYPE_STRING); g_value_set_string (&value, message); rhythmdb_entry_set (player->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value); g_value_unset (&value); rhythmdb_commit (player->priv->db); } static gboolean do_next_idle (RBShellPlayer *player) { /* use the EOS callback, so that EOF_SOURCE_ conditions are handled properly */ rb_shell_player_handle_eos (player); player->priv->do_next_idle_id = 0; return FALSE; } static gboolean rb_shell_player_set_playing_entry (RBShellPlayer *player, RhythmDBEntry *entry, gboolean out_of_order, GError **error) { GError *tmp_error = NULL; const char *location; GValue val = {0,}; g_return_val_if_fail (player->priv->current_playing_source != NULL, TRUE); g_return_val_if_fail (entry != NULL, TRUE); if (out_of_order) { RBPlayOrder *porder; if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source)) porder = player->priv->queue_play_order; else porder = player->priv->play_order; rb_play_order_set_playing_entry (porder, entry); } if (!rb_shell_player_open_entry (player, entry, &tmp_error)) goto lose; rb_shell_player_sync_replaygain (player, entry); rb_debug ("Success!"); /* clear error on successful playback */ g_value_init (&val, G_TYPE_STRING); g_value_set_string (&val, NULL); rhythmdb_entry_set (player->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val); g_value_unset (&val); location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION); g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SONG_CHANGED], 0, entry); g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_URI_CHANGED], 0, location); rb_shell_player_sync_with_source (player); rb_shell_player_sync_buttons (player); return TRUE; lose: /* Ignore errors, shutdown the player */ rb_player_close (player->priv->mmplayer, NULL); if (tmp_error == NULL) tmp_error = g_error_new (RB_SHELL_PLAYER_ERROR, RB_SHELL_PLAYER_ERROR_NOT_PLAYING, "Problem occurred without error being set. " "This is a bug in Rhythmbox or GStreamer."); /* Mark this song as failed */ rb_shell_player_set_entry_playback_error (player, entry, tmp_error->message); g_propagate_error (error, tmp_error); rb_shell_player_sync_with_source (player); rb_shell_player_sync_buttons (player); g_object_notify (G_OBJECT (player), "playing"); return FALSE; } static void gconf_play_order_changed (GConfClient *client, guint cnxn_id, GConfEntry *entry, RBShellPlayer *player) { rb_debug ("gconf play order changed"); player->priv->syncing_state = TRUE; rb_shell_player_sync_play_order (player); rb_shell_player_sync_buttons (player); rb_shell_player_sync_control_state (player); g_object_notify (G_OBJECT (player), "play-order"); player->priv->syncing_state = FALSE; } gboolean rb_shell_player_get_playback_state (RBShellPlayer *player, gboolean *shuffle, gboolean *repeat) { int i, j; char *play_order; play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER); if (!play_order) { g_warning (CONF_STATE_PLAY_ORDER " gconf key not found!"); return FALSE; } for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++) for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++) if (!strcmp (play_order, state_to_play_order[i][j])) goto found; g_free (play_order); return FALSE; found: *shuffle = i > 0; *repeat = j > 0; g_free (play_order); return TRUE; } static void rb_shell_player_set_play_order (RBShellPlayer *player, const gchar *new_val) { char *old_val; g_object_get (player, "play-order", &old_val, NULL); if (strcmp (old_val, new_val) != 0) { /* The notify signal will be emitted by the gconf notifier */ eel_gconf_set_string (CONF_STATE_PLAY_ORDER, new_val); } g_free (old_val); } void rb_shell_player_set_playback_state (RBShellPlayer *player, gboolean shuffle, gboolean repeat) { const char *neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0]; rb_shell_player_set_play_order (player, neworder); } static void rb_shell_player_sync_play_order (RBShellPlayer *player) { char *new_play_order; RhythmDBEntry *playing_entry = NULL; RBSource *source; new_play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER); if (new_play_order == NULL) { g_warning (CONF_STATE_PLAY_ORDER " gconf key not found!"); new_play_order = g_strdup ("linear"); } if (player->priv->play_order != NULL) { playing_entry = rb_play_order_get_playing_entry (player->priv->play_order); g_signal_handlers_disconnect_by_func (player->priv->play_order, G_CALLBACK (rb_shell_player_play_order_update_cb), player); g_object_unref (player->priv->play_order); } player->priv->play_order = rb_play_order_new (new_play_order, player); g_signal_connect_object (player->priv->play_order, "have_next_previous_changed", G_CALLBACK (rb_shell_player_play_order_update_cb), player, 0); rb_shell_player_play_order_update_cb (player->priv->play_order, FALSE, FALSE, player); source = player->priv->current_playing_source; if (source == NULL) { source = player->priv->selected_source; } rb_play_order_playing_source_changed (player->priv->play_order, source); if (playing_entry != NULL) { rb_play_order_set_playing_entry (player->priv->play_order, playing_entry); rhythmdb_entry_unref (playing_entry); } g_free (new_play_order); } static void rb_shell_player_play_order_update_cb (RBPlayOrder *porder, gboolean has_next, gboolean has_previous, RBShellPlayer *player) { /* we cannot depend on the values of has_next, has_previous or porder * since this can be called for the main porder, queue porder, etc */ gboolean have_next = FALSE; gboolean have_previous = FALSE; GtkAction *action; RhythmDBEntry *entry; entry = rb_shell_player_get_playing_entry (player); if (entry != NULL) { have_next = TRUE; have_previous = TRUE; rhythmdb_entry_unref (entry); } else { if (player->priv->current_playing_source && (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_NEXT)) { have_next = rb_play_order_has_next (player->priv->play_order); have_previous = rb_play_order_has_previous (player->priv->play_order); } if (player->priv->queue_play_order) { have_next |= rb_play_order_has_next (player->priv->queue_play_order); have_previous |= rb_play_order_has_previous (player->priv->queue_play_order); } } action = gtk_action_group_get_action (player->priv->actiongroup, "ControlPrevious"); g_object_set (action, "sensitive", have_previous, NULL); action = gtk_action_group_get_action (player->priv->actiongroup, "ControlNext"); g_object_set (action, "sensitive", have_next, NULL); } static gboolean rb_shell_player_jump_to_current_idle (RBShellPlayer *player) { GDK_THREADS_ENTER (); rb_shell_player_jump_to_current (player); GDK_THREADS_LEAVE (); return FALSE; } static void rb_shell_player_sync_song_position_slider_visibility (RBShellPlayer *player) { gboolean visible; GtkAction *action; visible = !eel_gconf_get_boolean (CONF_UI_SONG_POSITION_SLIDER_HIDDEN); rb_header_set_show_position_slider (player->priv->header_widget, visible); action = gtk_action_group_get_action (player->priv->actiongroup, "ViewSongPositionSlider"); gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible); } void rb_shell_player_jump_to_current (RBShellPlayer *player) { RBSource *source; RhythmDBEntry *entry; RBEntryView *songs; source = player->priv->current_playing_source ? player->priv->current_playing_source : player->priv->selected_source; songs = rb_source_get_entry_view (source); entry = rb_shell_player_get_playing_entry (player); if (songs != NULL) { if (entry != NULL) { rb_entry_view_scroll_to_entry (songs, entry); rb_entry_view_select_entry (songs, entry); } else { rb_entry_view_select_none (songs); } } if (entry != NULL) { rhythmdb_entry_unref (entry); } } static void swap_playing_source (RBShellPlayer *player, RBSource *new_source) { if (player->priv->current_playing_source != NULL) { RBEntryView *old_songs = rb_source_get_entry_view (player->priv->current_playing_source); if (old_songs) rb_entry_view_set_state (old_songs, RB_ENTRY_VIEW_NOT_PLAYING); } if (new_source != NULL) { RBEntryView *new_songs = rb_source_get_entry_view (new_source); if (new_songs) { rb_entry_view_set_state (new_songs, RB_ENTRY_VIEW_PLAYING); rb_shell_player_set_playing_source (player, new_source); } } } gboolean rb_shell_player_do_previous (RBShellPlayer *player, GError **error) { RhythmDBEntry *entry = NULL; RBSource *new_source; if (player->priv->current_playing_source == NULL) { g_set_error (error, RB_SHELL_PLAYER_ERROR, RB_SHELL_PLAYER_ERROR_NOT_PLAYING, _("Not currently playing")); return FALSE; } rb_debug ("going to previous"); if (player->priv->queue_play_order) { entry = rb_play_order_get_previous (player->priv->queue_play_order); if (entry != NULL) { new_source = RB_SOURCE (player->priv->queue_source); rb_play_order_go_previous (player->priv->queue_play_order); } } if (entry == NULL) { new_source = player->priv->source; entry = rb_play_order_get_previous (player->priv->play_order); if (entry) rb_play_order_go_previous (player->priv->play_order); } if (entry != NULL) { rb_debug ("previous song found, doing previous"); if (new_source != player->priv->current_playing_source) swap_playing_source (player, new_source); if (!rb_shell_player_set_playing_entry (player, entry, FALSE, error)) { rhythmdb_entry_unref (entry); return FALSE; } rb_shell_player_jump_to_current (player); rhythmdb_entry_unref (entry); } else { rb_debug ("no previous song found, signaling error"); g_set_error (error, RB_SHELL_PLAYER_ERROR, RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST, _("No previous song")); rb_shell_player_set_playing_source (player, NULL); return FALSE; } return TRUE; } gboolean rb_shell_player_do_next (RBShellPlayer *player, GError **error) { RBSource *new_source = NULL; RhythmDBEntry *entry = NULL; gboolean rv = TRUE; if (player->priv->source == NULL) return TRUE; if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source)) { /* Look for another entry in the queue, and fall back to the playing * source if there isn't one. Always call _get_next on the queue * so the current entry is removed. */ entry = rb_play_order_get_next (player->priv->queue_play_order); rb_play_order_go_next (player->priv->queue_play_order); if (entry != NULL) { new_source = RB_SOURCE (player->priv->queue_source); } else { /* If we haven't played anything from the playing source yet, * _get_playing_entry will return NULL, so we'll have to advance * the play order to get an entry to play. */ entry = rb_play_order_get_playing_entry (player->priv->play_order); if (entry == NULL) { entry = rb_play_order_get_next (player->priv->play_order); rb_play_order_go_next (player->priv->play_order); } new_source = player->priv->source; } } else { /* Advance the play order, and then let the queue override it. */ entry = rb_play_order_get_next (player->priv->play_order); if (entry != NULL) { new_source = player->priv->source; rb_play_order_go_next (player->priv->play_order); } if (player->priv->queue_play_order) { RhythmDBEntry *queue_entry; queue_entry = rb_play_order_get_next (player->priv->queue_play_order); if (queue_entry != NULL) { if (entry != NULL) { rhythmdb_entry_unref (entry); } entry = queue_entry; new_source = RB_SOURCE (player->priv->queue_source); rb_play_order_go_next (player->priv->queue_play_order); } } } /* play the new entry */ if (entry != NULL) { /* if the entry view containing the playing entry changed, update it */ if (new_source != player->priv->current_playing_source) swap_playing_source (player, new_source); if (!rb_shell_player_set_playing_entry (player, entry, FALSE, error)) rv = FALSE; } else { g_set_error (error, RB_SHELL_PLAYER_ERROR, RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST, _("No next song")); rb_debug ("No next entry, stopping playback"); rb_shell_player_set_playing_source (player, NULL); rb_play_order_set_playing_entry (player->priv->play_order, NULL); g_object_notify (G_OBJECT (player), "playing"); rv = FALSE; } g_idle_add ((GSourceFunc)rb_shell_player_jump_to_current_idle, player); if (entry != NULL) { rhythmdb_entry_unref (entry); } return rv; } static gboolean rb_shell_player_do_previous_or_seek (RBShellPlayer *player, GError **error) { rb_debug ("previous"); /* If we're in the first 3 seconds go to the previous song, * else restart the current one. */ if (player->priv->current_playing_source != NULL && rb_source_can_pause (player->priv->source) && rb_player_get_time (player->priv->mmplayer) > 3) { /* see if there's anything to go back to */ gboolean have_previous; have_previous = rb_play_order_has_previous (player->priv->play_order); if (player->priv->queue_play_order) have_previous |= rb_play_order_has_previous (player->priv->queue_play_order); if (have_previous) { rb_debug ("after 3 second previous, restarting song"); rb_player_set_time (player->priv->mmplayer, 0); rb_header_sync_time (player->priv->header_widget); return TRUE; } } return rb_shell_player_do_previous (player, error); } static void rb_shell_player_cmd_previous (GtkAction *action, RBShellPlayer *player) { GError *error = NULL; if (!rb_shell_player_do_previous_or_seek (player, &error)) { if (error->domain != RB_SHELL_PLAYER_ERROR || error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) g_warning ("cmd_previous: Unhandled error: %s", error->message); else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) rb_shell_player_set_playing_source (player, NULL); } } static void rb_shell_player_cmd_next (GtkAction *action, RBShellPlayer *player) { GError *error = NULL; if (!rb_shell_player_do_next (player, &error)) { if (error->domain != RB_SHELL_PLAYER_ERROR || error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) g_warning ("cmd_next: Unhandled error: %s", error->message); else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) rb_shell_player_set_playing_source (player, NULL); } } void rb_shell_player_play_entry (RBShellPlayer *player, RhythmDBEntry *entry, RBSource *source) { GError *error = NULL; if (source == NULL) source = player->priv->selected_source; rb_shell_player_set_playing_source (player, source); if (!rb_shell_player_set_playing_entry (player, entry, TRUE, &error)) { rb_shell_player_error (player, FALSE, error); g_clear_error (&error); } } static void rb_shell_player_cmd_volume_up (GtkAction *action, RBShellPlayer *player) { rb_shell_player_set_volume_relative (player, 0.1, NULL); } static void rb_shell_player_cmd_volume_down (GtkAction *action, RBShellPlayer *player) { rb_shell_player_set_volume_relative (player, -0.1, NULL); } static void rb_shell_player_cmd_play (GtkAction *action, RBShellPlayer *player) { GError *error = NULL; rb_debug ("play!"); if (!rb_shell_player_playpause (player, FALSE, &error)) rb_error_dialog (NULL, _("Couldn't start playback"), "%s", (error) ? error->message : "(null)"); g_clear_error (&error); } /* unused parameter can't be removed without breaking dbus interface compatibility */ gboolean rb_shell_player_playpause (RBShellPlayer *player, gboolean unused, GError **error) { gboolean ret; RBEntryView *songs; rb_debug ("doing playpause"); g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), TRUE); ret = TRUE; if (rb_player_playing (player->priv->mmplayer)) { if (player->priv->source == NULL) { rb_debug ("playing source is already NULL"); } else if (rb_source_can_pause (player->priv->source)) { rb_debug ("pausing mm player"); rb_player_pause (player->priv->mmplayer); songs = rb_source_get_entry_view (player->priv->current_playing_source); if (songs) rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PAUSED); } else { rb_debug ("setting playing source to NULL"); rb_shell_player_set_playing_source (player, NULL); } } else { RhythmDBEntry *entry; RBSource *new_source; gboolean out_of_order = FALSE; if (player->priv->source == NULL) { /* no current stream, pull one in from the currently * selected source */ rb_debug ("no playing source, using selected source"); rb_shell_player_set_playing_source (player, player->priv->selected_source); } new_source = player->priv->current_playing_source; entry = rb_shell_player_get_playing_entry (player); if (entry == NULL) { /* queue takes precedence over selection */ if (player->priv->queue_play_order) { entry = rb_play_order_get_next (player->priv->queue_play_order); if (entry != NULL) { new_source = RB_SOURCE (player->priv->queue_source); rb_play_order_go_next (player->priv->queue_play_order); } } /* selection takes precedence over first item in play order */ if (entry == NULL) { GList *selection = NULL; songs = rb_source_get_entry_view (player->priv->source); if (songs) selection = rb_entry_view_get_selected_entries (songs); if (selection != NULL) { rb_debug ("choosing first selected entry"); entry = (RhythmDBEntry*) selection->data; if (entry) out_of_order = TRUE; g_list_free (selection); } } /* play order is last */ if (entry == NULL) { rb_debug ("getting entry from play order"); entry = rb_play_order_get_next (player->priv->play_order); if (entry != NULL) rb_play_order_go_next (player->priv->play_order); } if (entry != NULL) { /* if the entry view containing the playing entry changed, update it */ if (new_source != player->priv->current_playing_source) swap_playing_source (player, new_source); if (!rb_shell_player_set_playing_entry (player, entry, out_of_order, error)) ret = FALSE; rb_shell_player_jump_to_current (player); } } else { if (!rb_shell_player_play (player, error)) { rb_shell_player_set_playing_source (player, NULL); ret = FALSE; } } if (entry != NULL) { rhythmdb_entry_unref (entry); } } rb_shell_player_sync_with_source (player); rb_shell_player_sync_buttons (player); g_object_notify (G_OBJECT (player), "playing"); return ret; } static void rb_shell_player_sync_control_state (RBShellPlayer *player) { gboolean shuffle, repeat; GtkAction *action; rb_debug ("syncing control state"); if (!rb_shell_player_get_playback_state (player, &shuffle, &repeat)) return; action = gtk_action_group_get_action (player->priv->actiongroup, "ControlShuffle"); gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), shuffle); action = gtk_action_group_get_action (player->priv->actiongroup, "ControlRepeat"); gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), repeat); } static void rb_shell_player_sync_volume (RBShellPlayer *player, gboolean notify) { GtkAction *action; RhythmDBEntry *entry; if (player->priv->volume <= 0.0){ player->priv->volume = 0.0; } else if (player->priv->volume >= 1.0){ player->priv->volume = 1.0; } action = gtk_action_group_get_action (player->priv->actiongroup, "ControlVolumeUp"); g_object_set (G_OBJECT (action), "sensitive", player->priv->volume < 0.9999, NULL); action = gtk_action_group_get_action (player->priv->actiongroup, "ControlVolumeDown"); g_object_set (G_OBJECT (action), "sensitive", player->priv->volume > 0.0001, NULL); rb_player_set_volume (player->priv->mmplayer, player->priv->mute ? 0.0 : player->priv->volume); eel_gconf_set_float (CONF_STATE_VOLUME, player->priv->volume); entry = rb_shell_player_get_playing_entry (player); rb_shell_player_sync_replaygain (player, entry); if (entry != NULL) { rhythmdb_entry_unref (entry); } if (notify) g_object_notify (G_OBJECT (player), "volume"); } void rb_shell_player_toggle_mute (RBShellPlayer *player) { player->priv->mute = !player->priv->mute; rb_shell_player_sync_volume (player, FALSE); } static void rb_shell_player_sync_replaygain (RBShellPlayer *player, RhythmDBEntry *entry) { double entry_track_gain = 0; double entry_track_peak = 0; double entry_album_gain = 0; double entry_album_peak = 0; if (entry != NULL) { entry_track_gain = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_TRACK_GAIN); entry_track_peak = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_TRACK_PEAK); entry_album_gain = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_ALBUM_GAIN); entry_album_peak = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_ALBUM_PEAK); } if (eel_gconf_get_boolean (CONF_USE_REPLAYGAIN)) { rb_player_set_replaygain (player->priv->mmplayer, entry_track_gain, entry_track_peak, entry_album_gain, entry_album_peak); } } gboolean rb_shell_player_set_volume (RBShellPlayer *player, gdouble volume, GError **error) { player->priv->volume = volume; rb_shell_player_sync_volume (player, TRUE); return TRUE; } gboolean rb_shell_player_set_volume_relative (RBShellPlayer *player, gdouble delta, GError **error) { /* rb_shell_player_sync_volume does clipping */ player->priv->volume += delta; rb_shell_player_sync_volume (player, TRUE); return TRUE; } gboolean rb_shell_player_get_volume (RBShellPlayer *player, gdouble *volume, GError **error) { *volume = player->priv->volume; return TRUE; } gboolean rb_shell_player_set_mute (RBShellPlayer *player, gboolean mute, GError **error) { player->priv->mute = mute; rb_shell_player_sync_volume (player, FALSE); return TRUE; } gboolean rb_shell_player_get_mute (RBShellPlayer *player, gboolean *mute, GError **error) { *mute = player->priv->mute; return TRUE; } static void gconf_song_position_slider_visibility_changed (GConfClient *client, guint cnxn_id, GConfEntry *entry, RBShellPlayer *player) { rb_debug ("song position slider visibility visibility changed"); rb_shell_player_sync_song_position_slider_visibility (player); } static void rb_shell_player_shuffle_changed_cb (GtkAction *action, RBShellPlayer *player) { const char *neworder; gboolean shuffle = FALSE; gboolean repeat = FALSE; if (player->priv->syncing_state) return; rb_debug ("shuffle changed"); rb_shell_player_get_playback_state (player, &shuffle, &repeat); shuffle = !shuffle; neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0]; rb_shell_player_set_play_order (player, neworder); } static void rb_shell_player_repeat_changed_cb (GtkAction *action, RBShellPlayer *player) { const char *neworder; gboolean shuffle = FALSE; gboolean repeat = FALSE; rb_debug ("repeat changed"); if (player->priv->syncing_state) return; rb_shell_player_get_playback_state (player, &shuffle, &repeat); repeat = !repeat; neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0]; rb_shell_player_set_play_order (player, neworder); } static void rb_shell_player_view_song_position_slider_changed_cb (GtkAction *action, RBShellPlayer *player) { eel_gconf_set_boolean (CONF_UI_SONG_POSITION_SLIDER_HIDDEN, !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))); } static void rb_shell_player_entry_activated_cb (RBEntryView *view, RhythmDBEntry *entry, RBShellPlayer *playa) { gboolean was_from_queue = FALSE; RhythmDBEntry *prev_entry = NULL; GError *error = NULL; gboolean source_set = FALSE; char *playback_uri; g_return_if_fail (entry != NULL); rb_debug ("got entry %p activated", entry); /* don't play hidden entries */ if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) return; /* skip entries with no playback uri */ playback_uri = rhythmdb_entry_get_playback_uri (entry); if (playback_uri == NULL) { return; } g_free (playback_uri); /* figure out where the previous entry came from */ if ((playa->priv->queue_source != NULL) && (playa->priv->current_playing_source == RB_SOURCE (playa->priv->queue_source))) { prev_entry = rb_shell_player_get_playing_entry (playa); was_from_queue = TRUE; } if (playa->priv->queue_source) { RBEntryView *queue_sidebar; g_object_get (playa->priv->queue_source, "sidebar", &queue_sidebar, NULL); if (view == queue_sidebar || view == rb_source_get_entry_view (RB_SOURCE (playa->priv->queue_source))) { /* fall back to the current selected source once the queue is empty */ if (view == queue_sidebar && playa->priv->source == NULL) { rb_play_order_playing_source_changed (playa->priv->play_order, playa->priv->selected_source); playa->priv->source = playa->priv->selected_source; } /* queue entry activated: move it to the start of the queue */ rb_static_playlist_source_move_entry (RB_STATIC_PLAYLIST_SOURCE (playa->priv->queue_source), entry, 0); rb_shell_player_set_playing_source (playa, RB_SOURCE (playa->priv->queue_source)); /* since we just moved the entry, we should give it focus. * just calling rb_shell_player_jump_to_current here * looks terribly ugly, though. */ g_idle_add ((GSourceFunc)rb_shell_player_jump_to_current_idle, playa); was_from_queue = FALSE; source_set = TRUE; } else { if (playa->priv->queue_only) { rb_source_add_to_queue (playa->priv->selected_source, RB_SOURCE (playa->priv->queue_source)); rb_shell_player_set_playing_source (playa, RB_SOURCE (playa->priv->queue_source)); source_set = TRUE; } } g_object_unref (queue_sidebar); } /* bail out if queue only */ if (playa->priv->queue_only) { return; } if (!source_set) { rb_shell_player_set_playing_source (playa, playa->priv->selected_source); source_set = TRUE; } if (!rb_shell_player_set_playing_entry (playa, entry, TRUE, &error)) { rb_shell_player_error (playa, FALSE, error); g_clear_error (&error); } /* if we were previously playing from the queue, clear its playing entry, * so we'll start again from the start. */ if (was_from_queue && prev_entry != NULL) { rb_play_order_set_playing_entry (playa->priv->queue_play_order, NULL); } if (prev_entry != NULL) { rhythmdb_entry_unref (prev_entry); } } static void rb_shell_player_property_row_activated_cb (RBPropertyView *view, const char *name, RBShellPlayer *player) { RhythmDBEntry *entry = NULL; GError *error = NULL; rb_debug ("got property activated"); rb_shell_player_set_playing_source (player, player->priv->selected_source); /* RHYTHMDBFIXME - do we need to wait here until the query is finished? * in theory, yes, but in practice the query is started when the row is * selected (on the first click when doubleclicking, or when using the * keyboard to select then activate) and is pretty much always done by * the time we get in here. */ entry = rb_play_order_get_next (player->priv->play_order); if (entry == NULL) { return; } rb_play_order_go_next (player->priv->play_order); if (!rb_shell_player_set_playing_entry (player, entry, TRUE, &error)) { rb_shell_player_error (player, FALSE, error); g_clear_error (&error); } if (entry != NULL) { rhythmdb_entry_unref (entry); } } static void rb_shell_player_entry_changed_cb (RhythmDB *db, RhythmDBEntry *entry, GSList *changes, RBShellPlayer *player) { GSList *t; gboolean synced = FALSE; const char *location; RhythmDBEntry *playing_entry; playing_entry = rb_shell_player_get_playing_entry (player); /* We try to update only if the changed entry is currently playing */ if (entry != playing_entry) { if (playing_entry != NULL) { rhythmdb_entry_unref (playing_entry); } return; } location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION); for (t = changes; t; t = t->next) { RhythmDBEntryChange *change = t->data; /* update UI if the artist, title or album has changed */ switch (change->prop) { case RHYTHMDB_PROP_TITLE: case RHYTHMDB_PROP_ARTIST: case RHYTHMDB_PROP_ALBUM: if (!synced) { rb_shell_player_sync_with_source (player); synced = TRUE; } break; default: break; } /* emit dbus signals for changes with easily marshallable types */ switch (rhythmdb_get_property_type (db, change->prop)) { case G_TYPE_STRING: case G_TYPE_BOOLEAN: case G_TYPE_ULONG: case G_TYPE_UINT64: case G_TYPE_DOUBLE: g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0, location, rhythmdb_nice_elt_name_from_propid (db, change->prop), &change->old, &change->new); break; default: break; } } if (playing_entry != NULL) { rhythmdb_entry_unref (playing_entry); } } static void rb_shell_player_extra_metadata_cb (RhythmDB *db, RhythmDBEntry *entry, const char *field, GValue *metadata, RBShellPlayer *player) { RhythmDBEntry *playing_entry; playing_entry = rb_shell_player_get_playing_entry (player); if (entry != playing_entry) { if (playing_entry != NULL) { rhythmdb_entry_unref (playing_entry); } return; } rb_shell_player_sync_with_source (player); /* emit dbus signals for changes with easily marshallable types */ switch (G_VALUE_TYPE (metadata)) { case G_TYPE_STRING: case G_TYPE_BOOLEAN: case G_TYPE_ULONG: case G_TYPE_UINT64: case G_TYPE_DOUBLE: g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION), field, metadata, /* slightly silly */ metadata); break; default: break; } } static void rb_shell_player_sync_with_source (RBShellPlayer *player) { const char *entry_title = NULL; const char *artist = NULL; const char *stream_name = NULL; char *streaming_title = NULL; char *streaming_artist = NULL; RhythmDBEntry *entry; char *title = NULL; long elapsed; entry = rb_shell_player_get_playing_entry (player); rb_debug ("playing source: %p, active entry: %p", player->priv->current_playing_source, entry); if (entry != NULL) { GValue *value; entry_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE); artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST); value = rhythmdb_entry_request_extra_metadata (player->priv->db, entry, RHYTHMDB_PROP_STREAM_SONG_TITLE); if (value != NULL) { streaming_title = g_value_dup_string (value); g_value_unset (value); g_free (value); rb_debug ("got streaming title \"%s\"", streaming_title); /* use entry title for stream name */ stream_name = entry_title; entry_title = streaming_title; } value = rhythmdb_entry_request_extra_metadata (player->priv->db, entry, RHYTHMDB_PROP_STREAM_SONG_ARTIST); if (value != NULL) { streaming_artist = g_value_dup_string (value); g_value_unset (value); g_free (value); rb_debug ("got streaming artist \"%s\"", streaming_artist); /* override artist from entry */ artist = streaming_artist; } } if ((artist && artist[0] != '\0') || entry_title || stream_name) { GString *title_str = g_string_sized_new (100); if (artist && artist[0] != '\0') { g_string_append (title_str, artist); g_string_append (title_str, " - "); } if (entry_title != NULL) g_string_append (title_str, entry_title); if (stream_name != NULL) g_string_append_printf (title_str, " (%s)", stream_name); title = g_string_free (title_str, FALSE); } elapsed = rb_player_get_time (player->priv->mmplayer); if (elapsed < 0) elapsed = 0; player->priv->elapsed = elapsed; g_signal_emit (G_OBJECT (player), rb_shell_player_signals[WINDOW_TITLE_CHANGED], 0, title); g_free (title); g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED], 0, player->priv->elapsed); /* Sync the header */ rb_header_set_playing_entry (player->priv->header_widget, entry, TRUE /*rb_player_seekable (player->priv->mmplayer)*/); rb_header_sync (player->priv->header_widget); if (entry != NULL) { rhythmdb_entry_unref (entry); } g_free (streaming_artist); g_free (streaming_title); } static void rb_shell_player_sync_buttons (RBShellPlayer *player) { GtkAction *action; RBSource *source; gboolean not_small; gboolean playing_from_queue; RBEntryView *view; int entry_view_state; RhythmDBEntry *entry; entry = rb_shell_player_get_playing_entry (player); if (entry != NULL) { source = player->priv->current_playing_source; entry_view_state = rb_player_playing (player->priv->mmplayer) ? RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_PAUSED; } else { source = player->priv->selected_source; entry_view_state = RB_ENTRY_VIEW_NOT_PLAYING; } source = (entry == NULL) ? player->priv->selected_source : player->priv->current_playing_source; playing_from_queue = (source == RB_SOURCE (player->priv->queue_source)); rb_debug ("syncing with source %p", source); not_small = !eel_gconf_get_boolean (CONF_UI_SMALL_DISPLAY); action = gtk_action_group_get_action (player->priv->actiongroup, "ViewJumpToPlaying"); g_object_set (action, "sensitive", entry != NULL && not_small, NULL); if (source != NULL) { view = rb_source_get_entry_view (source); if (view) rb_entry_view_set_state (view, entry_view_state); } if (entry != NULL) { rhythmdb_entry_unref (entry); } } void rb_shell_player_set_playing_source (RBShellPlayer *player, RBSource *source) { rb_shell_player_set_playing_source_internal (player, source, TRUE); } static void actually_set_playing_source (RBShellPlayer *player, RBSource *source, gboolean sync_entry_view) { player->priv->source = source; player->priv->current_playing_source = source; if (source != NULL) { RBEntryView *songs = rb_source_get_entry_view (player->priv->source); if (sync_entry_view && songs) { rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING); } } if (player->priv->play_order && source != RB_SOURCE (player->priv->queue_source)) { if (source == NULL) source = player->priv->selected_source; rb_play_order_playing_source_changed (player->priv->play_order, source); } rb_shell_player_play_order_update_cb (player->priv->play_order, FALSE, FALSE, player); } static void rb_shell_player_set_playing_source_internal (RBShellPlayer *player, RBSource *source, gboolean sync_entry_view) { gboolean emit_source_changed = TRUE; gboolean emit_playing_from_queue_changed = FALSE; if (player->priv->source == source && player->priv->current_playing_source == source && source != NULL) return; rb_debug ("setting playing source to %p", source); if (RB_SOURCE (player->priv->queue_source) == source) { if (player->priv->current_playing_source != source) emit_playing_from_queue_changed = TRUE; if (player->priv->source == NULL) { actually_set_playing_source (player, source, sync_entry_view); } else { emit_source_changed = FALSE; player->priv->current_playing_source = source; } } else { if (player->priv->current_playing_source != source) { if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source)) emit_playing_from_queue_changed = TRUE; /* stop the old source */ if (player->priv->current_playing_source != NULL) { if (sync_entry_view) { RBEntryView *songs = rb_source_get_entry_view (player->priv->current_playing_source); rb_debug ("source is already playing, stopping it"); /* clear the playing entry if we're switching between non-queue sources */ if (player->priv->current_playing_source != RB_SOURCE (player->priv->queue_source)) rb_play_order_set_playing_entry (player->priv->play_order, NULL); if (songs) rb_entry_view_set_state (songs, RB_ENTRY_VIEW_NOT_PLAYING); } } } actually_set_playing_source (player, source, sync_entry_view); } g_free (player->priv->url); player->priv->url = NULL; if (player->priv->current_playing_source == NULL) rb_shell_player_stop (player); rb_shell_player_sync_with_source (player); g_object_notify (G_OBJECT (player), "playing"); if (player->priv->selected_source) rb_shell_player_sync_buttons (player); if (emit_source_changed) { g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SOURCE_CHANGED], 0, player->priv->source); } if (emit_playing_from_queue_changed) { g_object_notify (G_OBJECT (player), "playing-from-queue"); } } /** * rb_shell_player_stop: * @player: a #RBShellPlayer. * * Completely stops playback, freeing resources and unloading the file. * * In general rb_shell_player_pause() should be used instead, as it stops the * audio, but does not completely free resources. **/ void rb_shell_player_stop (RBShellPlayer *player) { GError *error = NULL; rb_debug ("stopping"); g_return_if_fail (RB_IS_SHELL_PLAYER (player)); if (error == NULL) rb_player_close (player->priv->mmplayer, &error); if (error) { rb_error_dialog (NULL, _("Couldn't stop playback"), "%s", error->message); g_error_free (error); } rb_shell_player_sync_with_source (player); g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SONG_CHANGED], 0, NULL); g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_URI_CHANGED], 0, NULL); g_object_notify (G_OBJECT (player), "playing"); rb_shell_player_sync_buttons (player); } /** * rb_shell_player_pause: * @player: a #RBShellPlayer * @error: error return * * Pauses playback if possible, completely stopping if not. * * @return: whether playback is not occurring (TRUE when successfully * paused/stopped or playback was not occurring). **/ gboolean rb_shell_player_pause (RBShellPlayer *player, GError **error) { if (rb_player_playing (player->priv->mmplayer)) return rb_shell_player_playpause (player, FALSE, error); else return TRUE; } /** * rb_shell_player_get_playing: * @player: a #RBShellPlayer * @playing: playback state return * @error: error return * * Reports whether playback is occuring by setting playing. * * @return: whether the playback state could be reported successfully. **/ gboolean rb_shell_player_get_playing (RBShellPlayer *player, gboolean *playing, GError **error) { if (playing != NULL) *playing = rb_player_playing (player->priv->mmplayer); return TRUE; } char * rb_shell_player_get_playing_time_string (RBShellPlayer *player) { return rb_make_elapsed_time_string (player->priv->elapsed, rb_shell_player_get_playing_song_duration (player), !eel_gconf_get_boolean (CONF_UI_TIME_DISPLAY)); } gboolean rb_shell_player_get_playing_time (RBShellPlayer *player, guint *time, GError **error) { if (time != NULL) *time = (guint) rb_player_get_time (player->priv->mmplayer); return TRUE; } gboolean rb_shell_player_set_playing_time (RBShellPlayer *player, guint time, GError **error) { if (rb_player_seekable (player->priv->mmplayer)) { rb_player_set_time (player->priv->mmplayer, (long) time); return TRUE; } else { g_set_error (error, RB_SHELL_PLAYER_ERROR, RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE, _("Current song is not seekable")); return FALSE; } } void rb_shell_player_seek (RBShellPlayer *player, long offset) { g_return_if_fail (RB_IS_SHELL_PLAYER (player)); if (rb_player_seekable (player->priv->mmplayer)) { long t = rb_player_get_time (player->priv->mmplayer); if (t < 0) t = 0; rb_player_set_time (player->priv->mmplayer, t + offset); } } long rb_shell_player_get_playing_song_duration (RBShellPlayer *player) { RhythmDBEntry *current_entry; long val; g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), -1); current_entry = rb_shell_player_get_playing_entry (player); if (current_entry == NULL) { rb_debug ("Did not get playing entry : return -1 as length"); return -1; } val = rhythmdb_entry_get_ulong (current_entry, RHYTHMDB_PROP_DURATION); rhythmdb_entry_unref (current_entry); return val; } static void rb_shell_player_sync_with_selected_source (RBShellPlayer *player) { rb_debug ("syncing with selected source: %p", player->priv->selected_source); if (player->priv->source == NULL) { rb_debug ("no playing source, new source is %p", player->priv->selected_source); rb_shell_player_sync_with_source (player); } } static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err) { RhythmDBEntry *entry; gboolean do_next; g_return_if_fail (player->priv->handling_error == FALSE); player->priv->handling_error = TRUE; entry = rb_shell_player_get_playing_entry (player); rb_debug ("playback error while playing: %s", err->message); /* For synchronous errors the entry playback error has already been set */ if (entry && async) rb_shell_player_set_entry_playback_error (player, entry, err->message); if (err->code == RB_PLAYER_ERROR_NO_AUDIO) { /* stream has completely ended */ rb_shell_player_set_playing_source (player, NULL); do_next = FALSE; } else if ((player->priv->current_playing_source != NULL) && (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_RETRY)) { /* receiving an error means a broken stream or non-audio stream, so abort * unless we've got more URLs to try */ if (g_queue_is_empty (player->priv->playlist_urls)) { rb_error_dialog (NULL, _("Couldn't start playback"), "%s", (err) ? err->message : "(null)"); rb_shell_player_set_playing_source (player, NULL); do_next = FALSE; } else { rb_debug ("haven't yet exhausted the URLs from the playlist"); do_next = TRUE; } } else { do_next = TRUE; } if (do_next && player->priv->do_next_idle_id == 0) { player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_idle, player); } player->priv->handling_error = FALSE; if (entry != NULL) { rhythmdb_entry_unref (entry); } } static void error_cb (RBPlayer *mmplayer, const GError *err, gpointer data) { RBShellPlayer *player = RB_SHELL_PLAYER (data); if (player->priv->handling_error) return; if (player->priv->source == NULL) { rb_debug ("ignoring error (no source): %s", err->message); return; } GDK_THREADS_ENTER (); rb_shell_player_error (player, TRUE, err); rb_debug ("exiting error hander"); GDK_THREADS_LEAVE (); } static void tick_cb (RBPlayer *mmplayer, long elapsed, gpointer data) { RBShellPlayer *player = RB_SHELL_PLAYER (data); GDK_THREADS_ENTER (); if (rb_player_playing (mmplayer)) { if (elapsed < 0) elapsed = 0; if (player->priv->elapsed != elapsed) { player->priv->elapsed = elapsed; g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED], 0, player->priv->elapsed); } } GDK_THREADS_LEAVE (); } gboolean rb_shell_player_get_playing_path (RBShellPlayer *shell_player, const gchar **path, GError **error) { RhythmDBEntry *entry; entry = rb_shell_player_get_playing_entry (shell_player); if (entry != NULL) { *path = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION); } else { *path = NULL; } if (entry != NULL) { rhythmdb_entry_unref (entry); } return TRUE; } static gboolean _idle_unblock_signal_cb (gpointer data) { RBShellPlayer *player = (RBShellPlayer *)data; GtkAction *action; gboolean playing; GDK_THREADS_ENTER (); action = gtk_action_group_get_action (player->priv->actiongroup, "ControlPlay"); /* sync the active state of the action again */ g_object_get (player, "playing", &playing, NULL); gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing); g_signal_handlers_unblock_by_func (action, rb_shell_player_cmd_play, player); GDK_THREADS_LEAVE (); return FALSE; } static void rb_shell_player_playing_changed_cb (RBShellPlayer *player, GParamSpec *arg1, gpointer user_data) { GtkAction *action; gboolean playing; char *tooltip; g_object_get (player, "playing", &playing, NULL); action = gtk_action_group_get_action (player->priv->actiongroup, "ControlPlay"); if (playing) { tooltip = g_strdup (_("Stop playback")); } else { tooltip = g_strdup (_("Start playback")); } g_object_set (action, "tooltip", tooltip, NULL); g_free (tooltip); /* block the signal, so that it doesn't get stuck by triggering recursively, * and don't unblock it until whatever else is happening has finished. */ g_signal_handlers_block_by_func (action, rb_shell_player_cmd_play, player); gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing); g_idle_add (_idle_unblock_signal_cb, player); } /* This should really be standard. */ #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } GType rb_shell_player_error_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_PLAYLIST_PARSE_ERROR, "Playing parsing error"), ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST, "End of playlist reached"), ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_PLAYING, "Not playing"), ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE, "Not seekable"), { 0, 0, 0 } }; etype = g_enum_register_static ("RBShellPlayerError", values); } return etype; }