2006-12-14 Francisco Javier F. Serrador <serrador@openshine.com>
[rhythmbox.git] / shell / rb-shell-player.c
blobc91340ffeda72f189249692e4eb75b9661bf41ee
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of main playback logic object
5 * Copyright (C) 2002, 2003 Jorn Baayen <jorn@nl.linux.org>
6 * Copyright (C) 2002,2003 Colin Walters <walters@debian.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "config.h"
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <time.h>
29 #include <string.h>
31 #include <glib.h>
32 #include <glib/gi18n.h>
33 #include <gtk/gtk.h>
34 #include <glade/glade.h>
35 #include <libgnomevfs/gnome-vfs-utils.h>
36 #include <libgnomevfs/gnome-vfs-uri.h>
38 #ifdef HAVE_MMKEYS
39 #include <X11/Xlib.h>
40 #include <X11/XF86keysym.h>
41 #include <gdk/gdkx.h>
42 #endif /* HAVE_MMKEYS */
44 #include "rb-property-view.h"
45 #include "rb-shell-player.h"
46 #include "rb-stock-icons.h"
47 #include "rb-glade-helpers.h"
48 #include "rb-file-helpers.h"
49 #include "rb-cut-and-paste-code.h"
50 #include "rb-dialog.h"
51 #include "rb-preferences.h"
52 #include "rb-debug.h"
53 #include "rb-player.h"
54 #include "rb-header.h"
55 #include "totem-pl-parser.h"
56 #include "rb-metadata.h"
57 #include "rb-library-source.h"
58 #include "eel-gconf-extensions.h"
59 #include "rb-util.h"
60 #include "rb-play-order.h"
61 #include "rb-statusbar.h"
62 #include "rb-playlist-source.h"
63 #include "rb-play-queue-source.h"
64 #include "rhythmdb.h"
65 #include "rb-podcast-manager.h"
66 #include "rb-marshal.h"
68 #ifdef HAVE_XIDLE_EXTENSION
69 #include <X11/extensions/xidle.h>
70 #endif /* HAVE_XIDLE_EXTENSION */
72 static const char* const state_to_play_order[2][2] =
73 {{"linear", "linear-loop"},
74 {"shuffle", "random-by-age-and-rating"}};
76 static void rb_shell_player_class_init (RBShellPlayerClass *klass);
77 static void rb_shell_player_init (RBShellPlayer *shell_player);
78 static GObject *rb_shell_player_constructor (GType type, guint n_construct_properties,
79 GObjectConstructParam *construct_properties);
80 static void rb_shell_player_finalize (GObject *object);
81 static void rb_shell_player_set_property (GObject *object,
82 guint prop_id,
83 const GValue *value,
84 GParamSpec *pspec);
85 static void rb_shell_player_get_property (GObject *object,
86 guint prop_id,
87 GValue *value,
88 GParamSpec *pspec);
90 static void rb_shell_player_cmd_previous (GtkAction *action,
91 RBShellPlayer *player);
92 static void rb_shell_player_cmd_play (GtkAction *action,
93 RBShellPlayer *player);
94 static void rb_shell_player_cmd_next (GtkAction *action,
95 RBShellPlayer *player);
96 static void rb_shell_player_cmd_volume_up (GtkAction *action,
97 RBShellPlayer *player);
98 static void rb_shell_player_cmd_volume_down (GtkAction *action,
99 RBShellPlayer *player);
100 static void rb_shell_player_shuffle_changed_cb (GtkAction *action,
101 RBShellPlayer *player);
102 static void rb_shell_player_repeat_changed_cb (GtkAction *action,
103 RBShellPlayer *player);
104 static void rb_shell_player_view_song_position_slider_changed_cb (GtkAction *action,
105 RBShellPlayer *player);
106 static void rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
107 RBSource *source,
108 gboolean sync_entry_view);
109 static void rb_shell_player_sync_with_source (RBShellPlayer *player);
110 static void rb_shell_player_sync_with_selected_source (RBShellPlayer *player);
111 static void rb_shell_player_entry_changed_cb (RhythmDB *db,
112 RhythmDBEntry *entry,
113 GSList *changes,
114 RBShellPlayer *player);
116 static void rb_shell_player_entry_activated_cb (RBEntryView *view,
117 RhythmDBEntry *entry,
118 RBShellPlayer *playa);
119 static void rb_shell_player_property_row_activated_cb (RBPropertyView *view,
120 const char *name,
121 RBShellPlayer *playa);
122 static void rb_shell_player_sync_volume (RBShellPlayer *player, gboolean notify);
123 static void rb_shell_player_sync_replaygain (RBShellPlayer *player,
124 RhythmDBEntry *entry);
125 static void tick_cb (RBPlayer *player, long elapsed, gpointer data);
126 static void error_cb (RBPlayer *player, const GError *err, gpointer data);
127 static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err);
129 static void rb_shell_player_set_play_order (RBShellPlayer *player,
130 const gchar *new_val);
131 static void rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
132 gboolean has_next,
133 gboolean has_previous,
134 RBShellPlayer *player);
136 static void rb_shell_player_sync_play_order (RBShellPlayer *player);
137 static void rb_shell_player_sync_control_state (RBShellPlayer *player);
138 static void rb_shell_player_sync_song_position_slider_visibility (RBShellPlayer *player);
139 static void rb_shell_player_sync_buttons (RBShellPlayer *player);
141 static void gconf_play_order_changed (GConfClient *client,guint cnxn_id,
142 GConfEntry *entry, RBShellPlayer *player);
143 static void gconf_song_position_slider_visibility_changed (GConfClient *client,guint cnxn_id,
144 GConfEntry *entry, RBShellPlayer *player);
145 static void rb_shell_player_playing_changed_cb (RBShellPlayer *player,
146 GParamSpec *arg1,
147 gpointer user_data);
148 static void rb_shell_player_extra_metadata_cb (RhythmDB *db,
149 RhythmDBEntry *entry,
150 const char *field,
151 GValue *metadata,
152 RBShellPlayer *player);
155 static gboolean rb_shell_player_jump_to_current_idle (RBShellPlayer *player);
157 #ifdef HAVE_MMKEYS
158 static void grab_mmkey (int key_code, GdkWindow *root);
159 static GdkFilterReturn filter_mmkeys (GdkXEvent *xevent,
160 GdkEvent *event,
161 gpointer data);
162 static void rb_shell_player_init_mmkeys (RBShellPlayer *shell_player);
163 #endif /* HAVE_MMKEYS */
165 #define CONF_STATE CONF_PREFIX "/state"
167 struct RBShellPlayerPrivate
169 RhythmDB *db;
171 gboolean syncing_state;
172 gboolean queue_only;
174 RBSource *selected_source;
175 RBSource *source;
176 RBPlayQueueSource *queue_source;
177 RBSource *current_playing_source;
179 gboolean did_retry;
180 GTimeVal last_retry;
182 GtkUIManager *ui_manager;
183 GtkActionGroup *actiongroup;
185 gboolean handling_error;
187 RBPlayer *mmplayer;
189 char *song;
190 char *url;
191 guint elapsed;
193 RBPlayOrder *play_order;
194 RBPlayOrder *queue_play_order;
196 GQueue *playlist_urls;
198 RBHeader *header_widget;
199 RBStatusbar *statusbar_widget;
201 guint gconf_play_order_id;
202 guint gconf_song_position_slider_visibility_id;
204 gboolean mute;
205 float volume;
207 guint do_next_idle_id;
210 #define RB_SHELL_PLAYER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_PLAYER, RBShellPlayerPrivate))
212 enum
214 PROP_0,
215 PROP_SOURCE,
216 PROP_DB,
217 PROP_UI_MANAGER,
218 PROP_ACTION_GROUP,
219 PROP_PLAY_ORDER,
220 PROP_PLAYING,
221 PROP_VOLUME,
222 PROP_STATUSBAR,
223 PROP_QUEUE_SOURCE,
224 PROP_QUEUE_ONLY,
225 PROP_PLAYING_FROM_QUEUE,
226 PROP_PLAYER,
229 enum
231 WINDOW_TITLE_CHANGED,
232 ELAPSED_CHANGED,
233 PLAYING_SOURCE_CHANGED,
234 PLAYING_CHANGED,
235 PLAYING_SONG_CHANGED,
236 PLAYING_URI_CHANGED,
237 PLAYING_SONG_PROPERTY_CHANGED,
238 LAST_SIGNAL
241 static GtkActionEntry rb_shell_player_actions [] =
243 { "ControlPrevious", GTK_STOCK_MEDIA_PREVIOUS, N_("P_revious"), "<alt>Left",
244 N_("Start playing the previous song"),
245 G_CALLBACK (rb_shell_player_cmd_previous) },
246 { "ControlNext", GTK_STOCK_MEDIA_NEXT, N_("_Next"), "<alt>Right",
247 N_("Start playing the next song"),
248 G_CALLBACK (rb_shell_player_cmd_next) },
249 { "ControlVolumeUp", NULL, N_("_Increase Volume"), "<control>Up",
250 N_("Increase playback volume"),
251 G_CALLBACK (rb_shell_player_cmd_volume_up) },
252 { "ControlVolumeDown", NULL, N_("_Decrease Volume"), "<control>Down",
253 N_("Decrease playback volume"),
254 G_CALLBACK (rb_shell_player_cmd_volume_down) },
256 static guint rb_shell_player_n_actions = G_N_ELEMENTS (rb_shell_player_actions);
258 static GtkToggleActionEntry rb_shell_player_toggle_entries [] =
260 { "ControlPlay", GTK_STOCK_MEDIA_PLAY, N_("_Play"), "<control>space",
261 N_("Start playback"),
262 G_CALLBACK (rb_shell_player_cmd_play) },
263 { "ControlShuffle", GNOME_MEDIA_SHUFFLE, N_("Sh_uffle"), "<control>U",
264 N_("Play songs in a random order"),
265 G_CALLBACK (rb_shell_player_shuffle_changed_cb) },
266 { "ControlRepeat", GNOME_MEDIA_REPEAT, N_("_Repeat"), "<control>R",
267 N_("Play first song again after all songs are played"),
268 G_CALLBACK (rb_shell_player_repeat_changed_cb) },
269 { "ViewSongPositionSlider", NULL, N_("_Song Position Slider"), NULL,
270 N_("Change the visibility of the song position slider"),
271 G_CALLBACK (rb_shell_player_view_song_position_slider_changed_cb), TRUE },
273 static guint rb_shell_player_n_toggle_entries = G_N_ELEMENTS (rb_shell_player_toggle_entries);
275 static guint rb_shell_player_signals[LAST_SIGNAL] = { 0 };
277 G_DEFINE_TYPE (RBShellPlayer, rb_shell_player, GTK_TYPE_HBOX)
279 static void
280 rb_shell_player_class_init (RBShellPlayerClass *klass)
282 GObjectClass *object_class = G_OBJECT_CLASS (klass);
284 object_class->finalize = rb_shell_player_finalize;
285 object_class->constructor = rb_shell_player_constructor;
287 object_class->set_property = rb_shell_player_set_property;
288 object_class->get_property = rb_shell_player_get_property;
290 g_object_class_install_property (object_class,
291 PROP_SOURCE,
292 g_param_spec_object ("source",
293 "RBSource",
294 "RBSource object",
295 RB_TYPE_SOURCE,
296 G_PARAM_READWRITE));
298 g_object_class_install_property (object_class,
299 PROP_UI_MANAGER,
300 g_param_spec_object ("ui-manager",
301 "GtkUIManager",
302 "GtkUIManager object",
303 GTK_TYPE_UI_MANAGER,
304 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
306 g_object_class_install_property (object_class,
307 PROP_DB,
308 g_param_spec_object ("db",
309 "RhythmDB",
310 "RhythmDB object",
311 RHYTHMDB_TYPE,
312 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
314 g_object_class_install_property (object_class,
315 PROP_ACTION_GROUP,
316 g_param_spec_object ("action-group",
317 "GtkActionGroup",
318 "GtkActionGroup object",
319 GTK_TYPE_ACTION_GROUP,
320 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
322 g_object_class_install_property (object_class,
323 PROP_QUEUE_SOURCE,
324 g_param_spec_object ("queue-source",
325 "RBPlaylistSource",
326 "RBPlaylistSource object",
327 RB_TYPE_PLAYLIST_SOURCE,
328 G_PARAM_READWRITE));
330 g_object_class_install_property (object_class,
331 PROP_QUEUE_ONLY,
332 g_param_spec_boolean ("queue-only",
333 "Queue only",
334 "Activation only adds to queue",
335 FALSE,
336 G_PARAM_READWRITE));
338 g_object_class_install_property (object_class,
339 PROP_PLAYING_FROM_QUEUE,
340 g_param_spec_boolean ("playing-from-queue",
341 "Playing from queue",
342 "Whether playing from the play queue or not",
343 FALSE,
344 G_PARAM_READABLE));
346 g_object_class_install_property (object_class,
347 PROP_PLAYER,
348 g_param_spec_object ("player",
349 "RBPlayer",
350 "RBPlayer object",
351 G_TYPE_OBJECT,
352 G_PARAM_READABLE));
354 g_object_class_install_property (object_class,
355 PROP_PLAY_ORDER,
356 g_param_spec_string ("play-order",
357 "play-order",
358 "What play order to use",
359 "linear",
360 G_PARAM_READABLE));
361 g_object_class_install_property (object_class,
362 PROP_PLAYING,
363 g_param_spec_boolean ("playing",
364 "playing",
365 "Whether Rhythmbox is currently playing",
366 FALSE,
367 G_PARAM_READABLE));
369 g_object_class_install_property (object_class,
370 PROP_VOLUME,
371 g_param_spec_float ("volume",
372 "volume",
373 "Current playback volume",
374 0.0f, 1.0f, 1.0f,
375 G_PARAM_READWRITE));
377 g_object_class_install_property (object_class,
378 PROP_STATUSBAR,
379 g_param_spec_object ("statusbar",
380 "RBStatusbar",
381 "RBStatusbar object",
382 RB_TYPE_STATUSBAR,
383 G_PARAM_READWRITE));
385 rb_shell_player_signals[WINDOW_TITLE_CHANGED] =
386 g_signal_new ("window_title_changed",
387 G_OBJECT_CLASS_TYPE (object_class),
388 G_SIGNAL_RUN_LAST,
389 G_STRUCT_OFFSET (RBShellPlayerClass, window_title_changed),
390 NULL, NULL,
391 g_cclosure_marshal_VOID__STRING,
392 G_TYPE_NONE,
394 G_TYPE_STRING);
396 rb_shell_player_signals[ELAPSED_CHANGED] =
397 g_signal_new ("elapsed_changed",
398 G_OBJECT_CLASS_TYPE (object_class),
399 G_SIGNAL_RUN_LAST,
400 G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_changed),
401 NULL, NULL,
402 g_cclosure_marshal_VOID__UINT,
403 G_TYPE_NONE,
405 G_TYPE_UINT);
407 rb_shell_player_signals[PLAYING_SOURCE_CHANGED] =
408 g_signal_new ("playing-source-changed",
409 G_OBJECT_CLASS_TYPE (object_class),
410 G_SIGNAL_RUN_LAST,
411 G_STRUCT_OFFSET (RBShellPlayerClass, playing_source_changed),
412 NULL, NULL,
413 g_cclosure_marshal_VOID__OBJECT,
414 G_TYPE_NONE,
416 RB_TYPE_SOURCE);
418 rb_shell_player_signals[PLAYING_CHANGED] =
419 g_signal_new ("playing-changed",
420 G_OBJECT_CLASS_TYPE (object_class),
421 G_SIGNAL_RUN_LAST,
422 G_STRUCT_OFFSET (RBShellPlayerClass, playing_changed),
423 NULL, NULL,
424 g_cclosure_marshal_VOID__BOOLEAN,
425 G_TYPE_NONE,
427 G_TYPE_BOOLEAN);
429 rb_shell_player_signals[PLAYING_SONG_CHANGED] =
430 g_signal_new ("playing-song-changed",
431 G_OBJECT_CLASS_TYPE (object_class),
432 G_SIGNAL_RUN_LAST,
433 G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_changed),
434 NULL, NULL,
435 g_cclosure_marshal_VOID__BOXED,
436 G_TYPE_NONE,
438 RHYTHMDB_TYPE_ENTRY);
440 rb_shell_player_signals[PLAYING_URI_CHANGED] =
441 g_signal_new ("playing-uri-changed",
442 G_OBJECT_CLASS_TYPE (object_class),
443 G_SIGNAL_RUN_LAST,
444 G_STRUCT_OFFSET (RBShellPlayerClass, playing_uri_changed),
445 NULL, NULL,
446 g_cclosure_marshal_VOID__STRING,
447 G_TYPE_NONE,
449 G_TYPE_STRING);
451 rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED] =
452 g_signal_new ("playing-song-property-changed",
453 G_OBJECT_CLASS_TYPE (object_class),
454 G_SIGNAL_RUN_LAST,
455 G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_property_changed),
456 NULL, NULL,
457 rb_marshal_VOID__STRING_STRING_POINTER_POINTER,
458 G_TYPE_NONE,
460 G_TYPE_STRING, G_TYPE_STRING,
461 G_TYPE_VALUE, G_TYPE_VALUE);
463 g_type_class_add_private (klass, sizeof (RBShellPlayerPrivate));
466 static GObject *
467 rb_shell_player_constructor (GType type,
468 guint n_construct_properties,
469 GObjectConstructParam *construct_properties)
471 RBShellPlayer *player;
472 RBShellPlayerClass *klass;
473 GtkAction *action;
475 klass = RB_SHELL_PLAYER_CLASS (g_type_class_peek (RB_TYPE_SHELL_PLAYER));
477 player = RB_SHELL_PLAYER (G_OBJECT_CLASS (rb_shell_player_parent_class)->
478 constructor (type, n_construct_properties, construct_properties));
480 player->priv->header_widget = rb_header_new (player, player->priv->db);
481 gtk_widget_show (GTK_WIDGET (player->priv->header_widget));
482 gtk_box_pack_start (GTK_BOX (player), GTK_WIDGET (player->priv->header_widget), TRUE, TRUE, 0);
484 gtk_action_group_add_actions (player->priv->actiongroup,
485 rb_shell_player_actions,
486 rb_shell_player_n_actions,
487 player);
488 gtk_action_group_add_toggle_actions (player->priv->actiongroup,
489 rb_shell_player_toggle_entries,
490 rb_shell_player_n_toggle_entries,
491 player);
493 action = gtk_action_group_get_action (player->priv->actiongroup,
494 "ControlPlay");
495 g_object_set (action, "is-important", TRUE, NULL);
497 player->priv->syncing_state = TRUE;
498 rb_shell_player_set_playing_source (player, NULL);
499 rb_shell_player_sync_play_order (player);
500 rb_shell_player_sync_control_state (player);
501 rb_shell_player_sync_volume (player, FALSE);
502 player->priv->syncing_state = FALSE;
504 rb_shell_player_sync_song_position_slider_visibility (player);
506 g_signal_connect (G_OBJECT (player),
507 "notify::playing",
508 G_CALLBACK (rb_shell_player_playing_changed_cb),
509 NULL);
511 return G_OBJECT (player);
514 static void
515 volume_pre_unmount_cb (GnomeVFSVolumeMonitor *monitor,
516 GnomeVFSVolume *volume,
517 RBShellPlayer *player)
519 gchar *uri_mount_point;
520 gchar *volume_mount_point;
521 RhythmDBEntry *entry;
522 const char *uri;
523 gboolean playing;
525 rb_shell_player_get_playing (player, &playing, NULL);
526 if (playing) {
527 return;
530 entry = rb_shell_player_get_playing_entry (player);
531 if (entry == NULL) {
532 /* At startup for example, playing path can be NULL */
533 return;
536 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
537 uri_mount_point = rb_uri_get_mount_point (uri);
538 volume_mount_point = gnome_vfs_volume_get_activation_uri (volume);
540 if (uri_mount_point && volume_mount_point &&
541 (strcmp (uri_mount_point, volume_mount_point) == 0)) {
542 rb_shell_player_stop (player);
544 g_free (uri_mount_point);
545 g_free (volume_mount_point);
547 if (entry != NULL) {
548 rhythmdb_entry_unref (entry);
552 static void
553 reemit_playing_signal (RBShellPlayer *player,
554 GParamSpec *pspec,
555 gpointer data)
557 g_signal_emit (player, rb_shell_player_signals[PLAYING_CHANGED], 0,
558 rb_player_playing (player->priv->mmplayer));
561 static gboolean
562 notify_playing_idle (RBShellPlayer *player)
564 GDK_THREADS_ENTER ();
565 rb_debug ("emitting playing notification: %d", rb_player_playing (player->priv->mmplayer));
566 g_object_notify (G_OBJECT (player), "playing");
567 rb_shell_player_sync_buttons (player);
569 GDK_THREADS_LEAVE ();
570 return FALSE;
573 static void
574 rb_shell_player_open_playlist_url (RBShellPlayer *player,
575 const char *location)
577 GError *error = NULL;
579 rb_debug ("playing stream url %s", location);
580 rb_player_open (player->priv->mmplayer, location, &error);
581 if (error == NULL)
582 rb_player_play (player->priv->mmplayer, &error);
584 if (error) {
585 GDK_THREADS_ENTER ();
586 rb_shell_player_error (player, TRUE, error);
587 g_error_free (error);
588 GDK_THREADS_LEAVE ();
590 g_idle_add ((GSourceFunc) notify_playing_idle, player);
593 static void
594 rb_shell_player_handle_eos_unlocked (RBShellPlayer *player)
596 RhythmDBEntry *entry;
597 RBSource *source;
599 source = player->priv->current_playing_source;
601 /* nothing to do */
602 if (source == NULL) {
603 return;
606 entry = rb_shell_player_get_playing_entry (player);
608 switch (rb_source_handle_eos (source)) {
609 case RB_SOURCE_EOF_ERROR:
610 rb_error_dialog (NULL, _("Stream error"),
611 _("Unexpected end of stream!"));
612 rb_shell_player_set_playing_source (player, NULL);
613 break;
614 case RB_SOURCE_EOF_STOP:
615 rb_shell_player_set_playing_source (player, NULL);
616 break;
617 case RB_SOURCE_EOF_RETRY: {
618 GTimeVal current;
619 gint diff;
621 g_get_current_time (&current);
622 diff = current.tv_sec - player->priv->last_retry.tv_sec;
623 player->priv->last_retry = current;
625 if (rb_source_try_playlist (source) &&
626 !g_queue_is_empty (player->priv->playlist_urls)) {
627 char *location = g_queue_pop_head (player->priv->playlist_urls);
628 rb_debug ("trying next radio stream url: %s", location);
630 rb_shell_player_open_playlist_url (player, location);
631 g_free (location);
632 break;
635 if (diff < 4) {
636 rb_debug ("Last retry was less than 4 seconds ago...aborting retry playback");
637 rb_shell_player_set_playing_source (player, NULL);
638 } else {
639 rb_shell_player_play_entry (player, entry, NULL);
642 break;
643 case RB_SOURCE_EOF_NEXT:
645 GError *error = NULL;
647 if (!rb_shell_player_do_next (player, &error)) {
648 if (error->domain != RB_SHELL_PLAYER_ERROR ||
649 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
650 g_warning ("Unhandled error: %s", error->message);
651 else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
652 rb_shell_player_set_playing_source (player, NULL);
655 break;
658 if (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) == NULL) {
659 rb_debug ("updating play statistics");
660 rb_source_update_play_statistics (source,
661 player->priv->db,
662 entry);
665 if (entry != NULL) {
666 rhythmdb_entry_unref (entry);
670 static void
671 rb_shell_player_handle_eos (RBShellPlayer *player)
673 rb_debug ("handling eos!");
675 GDK_THREADS_ENTER ();
677 rb_shell_player_handle_eos_unlocked (player);
679 GDK_THREADS_LEAVE ();
682 static void
683 rb_shell_player_init (RBShellPlayer *player)
685 GError *error = NULL;
687 player->priv = RB_SHELL_PLAYER_GET_PRIVATE (player);
689 player->priv->mmplayer = rb_player_new (&error);
690 if (error != NULL) {
691 GtkWidget *dialog;
692 dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
693 GTK_MESSAGE_ERROR,
694 GTK_BUTTONS_CLOSE,
695 _("Failed to create the player: %s"),
696 error->message);
697 gtk_dialog_run (GTK_DIALOG (dialog));
698 exit (1);
701 gtk_box_set_spacing (GTK_BOX (player), 12);
702 gtk_container_set_border_width (GTK_CONTAINER (player), 3);
704 g_signal_connect_object (G_OBJECT (player->priv->mmplayer),
705 "eos",
706 G_CALLBACK (rb_shell_player_handle_eos),
707 player, G_CONNECT_SWAPPED);
709 g_signal_connect_object (G_OBJECT (player->priv->mmplayer),
710 "tick",
711 G_CALLBACK (tick_cb),
712 player, 0);
714 g_signal_connect_object (G_OBJECT (player->priv->mmplayer),
715 "error",
716 G_CALLBACK (error_cb),
717 player, 0);
719 g_signal_connect (G_OBJECT (gnome_vfs_get_volume_monitor ()),
720 "volume-pre-unmount",
721 G_CALLBACK (volume_pre_unmount_cb),
722 player);
724 player->priv->gconf_play_order_id =
725 eel_gconf_notification_add (CONF_STATE_PLAY_ORDER,
726 (GConfClientNotifyFunc)gconf_play_order_changed,
727 player);
729 player->priv->volume = eel_gconf_get_float (CONF_STATE_VOLUME);
731 g_signal_connect (player, "notify::playing",
732 G_CALLBACK (reemit_playing_signal), NULL);
734 player->priv->gconf_song_position_slider_visibility_id =
735 eel_gconf_notification_add (CONF_UI_SONG_POSITION_SLIDER_HIDDEN,
736 (GConfClientNotifyFunc) gconf_song_position_slider_visibility_changed,
737 player);
739 #ifdef HAVE_MMKEYS
740 /* Enable Multimedia Keys */
741 rb_shell_player_init_mmkeys (player);
742 #endif /* HAVE_MMKEYS */
745 static void
746 rb_shell_player_set_source_internal (RBShellPlayer *player,
747 RBSource *source)
749 if (player->priv->selected_source != NULL) {
750 RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
751 GList *property_views = rb_source_get_property_views (player->priv->selected_source);
753 if (songs != NULL) {
754 g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
755 G_CALLBACK (rb_shell_player_entry_activated_cb),
756 player);
759 for (; property_views; property_views = property_views->next) {
760 g_signal_handlers_disconnect_by_func (G_OBJECT (property_views->data),
761 G_CALLBACK (rb_shell_player_property_row_activated_cb),
762 player);
765 g_list_free (property_views);
768 player->priv->selected_source = source;
770 rb_debug ("selected source %p", player->priv->selected_source);
772 rb_shell_player_sync_with_selected_source (player);
773 rb_shell_player_sync_buttons (player);
775 if (player->priv->selected_source != NULL) {
776 RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
777 GList *property_views = rb_source_get_property_views (player->priv->selected_source);
779 if (songs)
780 g_signal_connect_object (G_OBJECT (songs),
781 "entry-activated",
782 G_CALLBACK (rb_shell_player_entry_activated_cb),
783 player, 0);
784 for (; property_views; property_views = property_views->next)
785 g_signal_connect_object (G_OBJECT (property_views->data),
786 "property-activated",
787 G_CALLBACK (rb_shell_player_property_row_activated_cb),
788 player, 0);
790 g_list_free (property_views);
793 /* If we're not playing, change the play order's view of the current source;
794 * if the selected source is the queue, however, set it to NULL so it'll stop
795 * once the queue is empty.
797 if (player->priv->current_playing_source == NULL) {
798 RBSource *source = player->priv->selected_source;
799 if (source == RB_SOURCE (player->priv->queue_source))
800 source = NULL;
802 rb_play_order_playing_source_changed (player->priv->play_order, source);
806 static void
807 rb_shell_player_set_db_internal (RBShellPlayer *player,
808 RhythmDB *db)
810 if (player->priv->db != NULL) {
811 g_signal_handlers_disconnect_by_func (player->priv->db,
812 G_CALLBACK (rb_shell_player_entry_changed_cb),
813 player);
814 g_signal_handlers_disconnect_by_func (player->priv->db,
815 G_CALLBACK (rb_shell_player_extra_metadata_cb),
816 player);
819 player->priv->db = db;
821 if (player->priv->db != NULL) {
822 /* Listen for changed entries to update metadata display */
823 g_signal_connect_object (G_OBJECT (player->priv->db),
824 "entry_changed",
825 G_CALLBACK (rb_shell_player_entry_changed_cb),
826 player, 0);
827 g_signal_connect_object (G_OBJECT (player->priv->db),
828 "entry_extra_metadata_notify",
829 G_CALLBACK (rb_shell_player_extra_metadata_cb),
830 player, 0);
834 static void
835 rb_shell_player_set_queue_source_internal (RBShellPlayer *player,
836 RBPlayQueueSource *source)
838 if (player->priv->queue_source != NULL) {
839 RBEntryView *sidebar;
841 g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
842 g_signal_handlers_disconnect_by_func (sidebar,
843 G_CALLBACK (rb_shell_player_entry_activated_cb),
844 player);
845 g_object_unref (sidebar);
847 if (player->priv->queue_play_order != NULL) {
848 g_signal_handlers_disconnect_by_func (player->priv->queue_play_order,
849 G_CALLBACK (rb_shell_player_play_order_update_cb),
850 player);
851 g_object_unref (player->priv->queue_play_order);
856 player->priv->queue_source = source;
858 if (player->priv->queue_source != NULL) {
859 RBEntryView *sidebar;
861 player->priv->queue_play_order = rb_play_order_new ("queue", player);
862 g_signal_connect_object (G_OBJECT (player->priv->queue_play_order),
863 "have_next_previous_changed",
864 G_CALLBACK (rb_shell_player_play_order_update_cb),
865 player, 0);
866 rb_shell_player_play_order_update_cb (player->priv->play_order,
867 FALSE, FALSE,
868 player);
869 rb_play_order_playing_source_changed (player->priv->queue_play_order,
870 RB_SOURCE (player->priv->queue_source));
872 g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
873 g_signal_connect_object (G_OBJECT (sidebar),
874 "entry-activated",
875 G_CALLBACK (rb_shell_player_entry_activated_cb),
876 player, 0);
877 g_object_unref (sidebar);
881 static void
882 rb_shell_player_finalize (GObject *object)
884 RBShellPlayer *player;
886 g_return_if_fail (object != NULL);
887 g_return_if_fail (RB_IS_SHELL_PLAYER (object));
889 player = RB_SHELL_PLAYER (object);
891 g_return_if_fail (player->priv != NULL);
893 eel_gconf_notification_remove (player->priv->gconf_play_order_id);
895 eel_gconf_set_float (CONF_STATE_VOLUME, player->priv->volume);
897 g_object_unref (player->priv->mmplayer);
898 g_object_unref (player->priv->play_order);
899 g_object_unref (player->priv->queue_play_order);
901 G_OBJECT_CLASS (rb_shell_player_parent_class)->finalize (object);
904 static void
905 rb_shell_player_set_property (GObject *object,
906 guint prop_id,
907 const GValue *value,
908 GParamSpec *pspec)
910 RBShellPlayer *player = RB_SHELL_PLAYER (object);
912 switch (prop_id) {
913 case PROP_SOURCE:
914 rb_shell_player_set_source_internal (player, g_value_get_object (value));
915 break;
916 case PROP_UI_MANAGER:
917 player->priv->ui_manager = g_value_get_object (value);
918 break;
919 case PROP_DB:
920 rb_shell_player_set_db_internal (player, g_value_get_object (value));
921 break;
922 case PROP_ACTION_GROUP:
923 player->priv->actiongroup = g_value_get_object (value);
924 break;
925 case PROP_PLAY_ORDER:
926 eel_gconf_set_string (CONF_STATE_PLAY_ORDER,
927 g_value_get_string (value));
928 break;
929 case PROP_VOLUME:
930 player->priv->volume = g_value_get_float (value);
931 rb_shell_player_sync_volume (player, FALSE);
932 break;
933 case PROP_STATUSBAR:
934 player->priv->statusbar_widget = g_value_get_object (value);
935 break;
936 case PROP_QUEUE_SOURCE:
937 rb_shell_player_set_queue_source_internal (player, g_value_get_object (value));
938 break;
939 case PROP_QUEUE_ONLY:
940 player->priv->queue_only = g_value_get_boolean (value);
941 break;
942 default:
943 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
944 break;
948 static void
949 rb_shell_player_get_property (GObject *object,
950 guint prop_id,
951 GValue *value,
952 GParamSpec *pspec)
954 RBShellPlayer *player = RB_SHELL_PLAYER (object);
956 switch (prop_id) {
957 case PROP_SOURCE:
958 g_value_set_object (value, player->priv->selected_source);
959 break;
960 case PROP_UI_MANAGER:
961 g_value_set_object (value, player->priv->ui_manager);
962 break;
963 case PROP_DB:
964 g_value_set_object (value, player->priv->db);
965 break;
966 case PROP_ACTION_GROUP:
967 g_value_set_object (value, player->priv->actiongroup);
968 break;
969 case PROP_PLAY_ORDER:
971 char *play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER);
972 if (!play_order)
973 play_order = g_strdup ("linear");
974 g_value_set_string_take_ownership (value, play_order);
975 break;
977 case PROP_PLAYING:
978 g_value_set_boolean (value, rb_player_playing (player->priv->mmplayer));
979 break;
980 case PROP_VOLUME:
981 g_value_set_float (value, player->priv->volume);
982 break;
983 case PROP_STATUSBAR:
984 g_value_set_object (value, player->priv->statusbar_widget);
985 break;
986 case PROP_QUEUE_SOURCE:
987 g_value_set_object (value, player->priv->queue_source);
988 break;
989 case PROP_QUEUE_ONLY:
990 g_value_set_boolean (value, player->priv->queue_only);
991 break;
992 case PROP_PLAYING_FROM_QUEUE:
993 g_value_set_boolean (value, player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source));
994 break;
995 case PROP_PLAYER:
996 g_value_set_object (value, player->priv->mmplayer);
997 break;
998 default:
999 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1000 break;
1004 GQuark
1005 rb_shell_player_error_quark (void)
1007 static GQuark quark = 0;
1008 if (!quark)
1009 quark = g_quark_from_static_string ("rb_shell_player_error");
1011 return quark;
1014 void
1015 rb_shell_player_set_selected_source (RBShellPlayer *player,
1016 RBSource *source)
1018 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
1019 g_return_if_fail (RB_IS_SOURCE (source));
1021 g_object_set (G_OBJECT (player),
1022 "source", source,
1023 NULL);
1026 RBSource *
1027 rb_shell_player_get_playing_source (RBShellPlayer *player)
1029 return player->priv->current_playing_source;
1032 RBSource *
1033 rb_shell_player_get_active_source (RBShellPlayer *player)
1035 return player->priv->source;
1038 RBShellPlayer *
1039 rb_shell_player_new (RhythmDB *db,
1040 GtkUIManager *mgr,
1041 GtkActionGroup *actiongroup)
1043 return g_object_new (RB_TYPE_SHELL_PLAYER,
1044 "ui-manager", mgr,
1045 "action-group", actiongroup,
1046 "db", db,
1047 NULL);
1050 RhythmDBEntry *
1051 rb_shell_player_get_playing_entry (RBShellPlayer *player)
1053 RBPlayOrder *porder;
1054 RhythmDBEntry *entry;
1056 if (player->priv->current_playing_source == NULL)
1057 return NULL;
1059 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
1060 porder = player->priv->queue_play_order;
1061 else
1062 porder = player->priv->play_order;
1064 if (porder == NULL) {
1065 return NULL;
1068 entry = rb_play_order_get_playing_entry (porder);
1070 return entry;
1073 typedef struct {
1074 RBShellPlayer *player;
1075 char *location;
1076 } OpenLocationThreadData;
1078 static void
1079 playlist_entry_cb (TotemPlParser *playlist,
1080 const char *uri,
1081 const char *title,
1082 const char *genre,
1083 RBShellPlayer *player)
1085 rb_debug ("adding stream url %s", uri);
1086 g_queue_push_tail (player->priv->playlist_urls, g_strdup (uri));
1089 static gpointer
1090 open_location_thread (OpenLocationThreadData *data)
1092 TotemPlParser *playlist;
1093 TotemPlParserResult playlist_result;
1095 playlist = totem_pl_parser_new ();
1096 g_signal_connect_data (G_OBJECT (playlist), "entry",
1097 G_CALLBACK (playlist_entry_cb),
1098 data->player, NULL, 0);
1099 totem_pl_parser_add_ignored_mimetype (playlist, "x-directory/normal");
1101 playlist_result = totem_pl_parser_parse (playlist, data->location, FALSE);
1102 g_object_unref (playlist);
1104 if (playlist_result == TOTEM_PL_PARSER_RESULT_SUCCESS) {
1105 if (g_queue_is_empty (data->player->priv->playlist_urls)) {
1106 GError *error = g_error_new (RB_SHELL_PLAYER_ERROR,
1107 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
1108 _("Playlist was empty"));
1109 GDK_THREADS_ENTER ();
1110 rb_shell_player_error (data->player, TRUE, error);
1111 g_error_free (error);
1112 GDK_THREADS_LEAVE ();
1113 } else {
1114 char *location;
1116 location = g_queue_pop_head (data->player->priv->playlist_urls);
1117 rb_debug ("playing first stream url %s", data->location);
1118 rb_shell_player_open_playlist_url (data->player, location);
1119 g_free (location);
1121 } else {
1122 /* if we can't parse it as a playlist, just try playing it */
1123 rb_debug ("playlist parser failed, playing %s directly", data->location);
1124 rb_shell_player_open_playlist_url (data->player, data->location);
1127 g_free (data);
1128 return NULL;
1131 static gboolean
1132 rb_shell_player_open_location (RBShellPlayer *player,
1133 const char *location,
1134 GError **error)
1136 char *unescaped;
1137 gboolean was_playing;
1139 unescaped = gnome_vfs_unescape_string_for_display (location);
1140 rb_debug ("Opening %s...", unescaped);
1141 g_free (unescaped);
1143 was_playing = rb_player_playing (player->priv->mmplayer);
1145 if (rb_source_try_playlist (player->priv->source)) {
1146 OpenLocationThreadData *data;
1148 data = g_new0 (OpenLocationThreadData, 1);
1149 data->player = player;
1151 /* dispose of any existing playlist urls */
1152 if (player->priv->playlist_urls) {
1153 g_queue_foreach (player->priv->playlist_urls,
1154 (GFunc) g_free,
1155 NULL);
1156 g_queue_free (player->priv->playlist_urls);
1157 player->priv->playlist_urls = NULL;
1159 player->priv->playlist_urls = g_queue_new ();
1161 /* add http:// as a prefix, if it doesn't have a URI scheme */
1162 if (strstr (location, "://"))
1163 data->location = g_strdup (location);
1164 else
1165 data->location = g_strconcat ("http://", location, NULL);
1167 g_thread_create ((GThreadFunc)open_location_thread, data, FALSE, NULL);
1168 return TRUE;
1169 } else {
1170 if (!rb_player_open (player->priv->mmplayer, location, error))
1171 return FALSE;
1173 if (!rb_player_play (player->priv->mmplayer, error))
1174 return FALSE;
1176 g_object_notify (G_OBJECT (player), "playing");
1179 return TRUE;
1182 static gboolean
1183 rb_shell_player_open_entry (RBShellPlayer *player,
1184 RhythmDBEntry *entry,
1185 GError **error)
1187 char *location;
1188 gboolean result;
1190 location = rhythmdb_entry_get_playback_uri (entry);
1191 if (location == NULL)
1192 return FALSE;
1194 result = rb_shell_player_open_location (player, location, error);
1195 g_free (location);
1197 return result;
1201 * rb_shell_player_play:
1202 * @player: a #RBShellPlayer
1203 * @error: error return
1205 * Starts playback, if it is not already playing.
1207 * @return: whether playback is now occurring (TRUE when successfully started
1208 * or already playing).
1210 gboolean
1211 rb_shell_player_play (RBShellPlayer *player,
1212 GError **error)
1214 RBEntryView *songs;
1216 if (player->priv->current_playing_source == NULL)
1217 return FALSE;
1219 if (rb_player_playing (player->priv->mmplayer))
1220 return TRUE;
1222 if (!rb_player_play (player->priv->mmplayer, error))
1223 return FALSE;
1225 songs = rb_source_get_entry_view (player->priv->current_playing_source);
1226 if (songs)
1227 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
1229 rb_shell_player_sync_with_source (player);
1230 rb_shell_player_sync_buttons (player);
1232 return TRUE;
1235 static void
1236 rb_shell_player_set_entry_playback_error (RBShellPlayer *player,
1237 RhythmDBEntry *entry,
1238 char *message)
1240 GValue value = { 0, };
1242 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
1244 g_value_init (&value, G_TYPE_STRING);
1245 g_value_set_string (&value, message);
1246 rhythmdb_entry_set (player->priv->db,
1247 entry,
1248 RHYTHMDB_PROP_PLAYBACK_ERROR,
1249 &value);
1250 g_value_unset (&value);
1251 rhythmdb_commit (player->priv->db);
1254 static gboolean
1255 do_next_idle (RBShellPlayer *player)
1257 /* use the EOS callback, so that EOF_SOURCE_ conditions are handled properly */
1258 rb_shell_player_handle_eos (player);
1259 player->priv->do_next_idle_id = 0;
1261 return FALSE;
1264 static gboolean
1265 rb_shell_player_set_playing_entry (RBShellPlayer *player,
1266 RhythmDBEntry *entry,
1267 gboolean out_of_order,
1268 GError **error)
1270 GError *tmp_error = NULL;
1271 const char *location;
1272 GValue val = {0,};
1274 g_return_val_if_fail (player->priv->current_playing_source != NULL, TRUE);
1275 g_return_val_if_fail (entry != NULL, TRUE);
1277 if (out_of_order) {
1278 RBPlayOrder *porder;
1279 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
1280 porder = player->priv->queue_play_order;
1281 else
1282 porder = player->priv->play_order;
1283 rb_play_order_set_playing_entry (porder, entry);
1286 if (!rb_shell_player_open_entry (player, entry, &tmp_error))
1287 goto lose;
1288 rb_shell_player_sync_replaygain (player, entry);
1290 rb_debug ("Success!");
1291 /* clear error on successful playback */
1292 g_value_init (&val, G_TYPE_STRING);
1293 g_value_set_string (&val, NULL);
1294 rhythmdb_entry_set (player->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
1295 g_value_unset (&val);
1297 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1298 g_signal_emit (G_OBJECT (player),
1299 rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
1300 entry);
1301 g_signal_emit (G_OBJECT (player),
1302 rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
1303 location);
1305 rb_shell_player_sync_with_source (player);
1306 rb_shell_player_sync_buttons (player);
1308 return TRUE;
1309 lose:
1310 /* Ignore errors, shutdown the player */
1311 rb_player_close (player->priv->mmplayer, NULL);
1312 if (tmp_error == NULL)
1313 tmp_error = g_error_new (RB_SHELL_PLAYER_ERROR,
1314 RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
1315 "Problem occurred without error being set. "
1316 "This is a bug in Rhythmbox or GStreamer.");
1317 /* Mark this song as failed */
1318 rb_shell_player_set_entry_playback_error (player, entry, tmp_error->message);
1319 g_propagate_error (error, tmp_error);
1321 rb_shell_player_sync_with_source (player);
1322 rb_shell_player_sync_buttons (player);
1323 g_object_notify (G_OBJECT (player), "playing");
1325 return FALSE;
1328 static void
1329 gconf_play_order_changed (GConfClient *client,
1330 guint cnxn_id,
1331 GConfEntry *entry,
1332 RBShellPlayer *player)
1334 rb_debug ("gconf play order changed");
1335 player->priv->syncing_state = TRUE;
1336 rb_shell_player_sync_play_order (player);
1337 rb_shell_player_sync_buttons (player);
1338 rb_shell_player_sync_control_state (player);
1339 g_object_notify (G_OBJECT (player), "play-order");
1340 player->priv->syncing_state = FALSE;
1343 gboolean
1344 rb_shell_player_get_playback_state (RBShellPlayer *player,
1345 gboolean *shuffle,
1346 gboolean *repeat)
1348 int i, j;
1349 char *play_order;
1351 play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER);
1352 if (!play_order) {
1353 g_warning (CONF_STATE_PLAY_ORDER " gconf key not found!");
1354 return FALSE;
1357 for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
1358 for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
1359 if (!strcmp (play_order, state_to_play_order[i][j]))
1360 goto found;
1362 g_free (play_order);
1363 return FALSE;
1365 found:
1366 *shuffle = i > 0;
1367 *repeat = j > 0;
1368 g_free (play_order);
1369 return TRUE;
1372 static void
1373 rb_shell_player_set_play_order (RBShellPlayer *player,
1374 const gchar *new_val)
1376 char *old_val;
1378 g_object_get (player, "play-order", &old_val, NULL);
1379 if (strcmp (old_val, new_val) != 0) {
1380 /* The notify signal will be emitted by the gconf notifier */
1381 eel_gconf_set_string (CONF_STATE_PLAY_ORDER, new_val);
1383 g_free (old_val);
1386 void
1387 rb_shell_player_set_playback_state (RBShellPlayer *player,
1388 gboolean shuffle,
1389 gboolean repeat)
1391 const char *neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
1392 rb_shell_player_set_play_order (player, neworder);
1395 static void
1396 rb_shell_player_sync_play_order (RBShellPlayer *player)
1398 char *new_play_order;
1399 RhythmDBEntry *playing_entry = NULL;
1400 RBSource *source;
1402 new_play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER);
1403 if (new_play_order == NULL) {
1404 g_warning (CONF_STATE_PLAY_ORDER " gconf key not found!");
1405 new_play_order = g_strdup ("linear");
1408 if (player->priv->play_order != NULL) {
1409 playing_entry = rb_play_order_get_playing_entry (player->priv->play_order);
1410 g_signal_handlers_disconnect_by_func (player->priv->play_order,
1411 G_CALLBACK (rb_shell_player_play_order_update_cb),
1412 player);
1413 g_object_unref (player->priv->play_order);
1416 player->priv->play_order = rb_play_order_new (new_play_order, player);
1417 g_signal_connect_object (player->priv->play_order,
1418 "have_next_previous_changed",
1419 G_CALLBACK (rb_shell_player_play_order_update_cb),
1420 player, 0);
1421 rb_shell_player_play_order_update_cb (player->priv->play_order,
1422 FALSE, FALSE,
1423 player);
1425 source = player->priv->current_playing_source;
1426 if (source == NULL) {
1427 source = player->priv->selected_source;
1429 rb_play_order_playing_source_changed (player->priv->play_order, source);
1431 if (playing_entry != NULL) {
1432 rb_play_order_set_playing_entry (player->priv->play_order, playing_entry);
1433 rhythmdb_entry_unref (playing_entry);
1436 g_free (new_play_order);
1439 static void
1440 rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
1441 gboolean has_next,
1442 gboolean has_previous,
1443 RBShellPlayer *player)
1445 /* we cannot depend on the values of has_next, has_previous or porder
1446 * since this can be called for the main porder, queue porder, etc
1448 gboolean have_next = FALSE;
1449 gboolean have_previous = FALSE;
1450 GtkAction *action;
1451 RhythmDBEntry *entry;
1453 entry = rb_shell_player_get_playing_entry (player);
1454 if (entry != NULL) {
1455 have_next = TRUE;
1456 have_previous = TRUE;
1457 rhythmdb_entry_unref (entry);
1458 } else {
1459 if (player->priv->current_playing_source &&
1460 (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_NEXT)) {
1461 have_next = rb_play_order_has_next (player->priv->play_order);
1462 have_previous = rb_play_order_has_previous (player->priv->play_order);
1464 if (player->priv->queue_play_order) {
1465 have_next |= rb_play_order_has_next (player->priv->queue_play_order);
1466 have_previous |= rb_play_order_has_previous (player->priv->queue_play_order);
1470 action = gtk_action_group_get_action (player->priv->actiongroup,
1471 "ControlPrevious");
1472 g_object_set (action, "sensitive", have_previous, NULL);
1473 action = gtk_action_group_get_action (player->priv->actiongroup,
1474 "ControlNext");
1475 g_object_set (action, "sensitive", have_next, NULL);
1478 static gboolean
1479 rb_shell_player_jump_to_current_idle (RBShellPlayer *player)
1481 GDK_THREADS_ENTER ();
1482 rb_shell_player_jump_to_current (player);
1483 GDK_THREADS_LEAVE ();
1484 return FALSE;
1487 static void
1488 rb_shell_player_sync_song_position_slider_visibility (RBShellPlayer *player)
1490 gboolean visible;
1491 GtkAction *action;
1493 visible = !eel_gconf_get_boolean (CONF_UI_SONG_POSITION_SLIDER_HIDDEN);
1495 rb_header_set_show_position_slider (player->priv->header_widget,
1496 visible);
1498 action = gtk_action_group_get_action (player->priv->actiongroup,
1499 "ViewSongPositionSlider");
1500 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
1501 visible);
1504 void
1505 rb_shell_player_jump_to_current (RBShellPlayer *player)
1507 RBSource *source;
1508 RhythmDBEntry *entry;
1509 RBEntryView *songs;
1511 source = player->priv->current_playing_source ? player->priv->current_playing_source :
1512 player->priv->selected_source;
1514 songs = rb_source_get_entry_view (source);
1515 entry = rb_shell_player_get_playing_entry (player);
1516 if (songs != NULL) {
1517 if (entry != NULL) {
1518 rb_entry_view_scroll_to_entry (songs, entry);
1519 rb_entry_view_select_entry (songs, entry);
1520 } else {
1521 rb_entry_view_select_none (songs);
1525 if (entry != NULL) {
1526 rhythmdb_entry_unref (entry);
1530 static void
1531 swap_playing_source (RBShellPlayer *player,
1532 RBSource *new_source)
1534 if (player->priv->current_playing_source != NULL) {
1535 RBEntryView *old_songs = rb_source_get_entry_view (player->priv->current_playing_source);
1536 if (old_songs)
1537 rb_entry_view_set_state (old_songs, RB_ENTRY_VIEW_NOT_PLAYING);
1539 if (new_source != NULL) {
1540 RBEntryView *new_songs = rb_source_get_entry_view (new_source);
1542 if (new_songs) {
1543 rb_entry_view_set_state (new_songs, RB_ENTRY_VIEW_PLAYING);
1544 rb_shell_player_set_playing_source (player, new_source);
1549 gboolean
1550 rb_shell_player_do_previous (RBShellPlayer *player,
1551 GError **error)
1553 RhythmDBEntry *entry = NULL;
1554 RBSource *new_source;
1556 if (player->priv->current_playing_source == NULL) {
1557 g_set_error (error,
1558 RB_SHELL_PLAYER_ERROR,
1559 RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
1560 _("Not currently playing"));
1561 return FALSE;
1564 rb_debug ("going to previous");
1566 if (player->priv->queue_play_order) {
1567 entry = rb_play_order_get_previous (player->priv->queue_play_order);
1568 if (entry != NULL) {
1569 new_source = RB_SOURCE (player->priv->queue_source);
1570 rb_play_order_go_previous (player->priv->queue_play_order);
1574 if (entry == NULL) {
1575 new_source = player->priv->source;
1576 entry = rb_play_order_get_previous (player->priv->play_order);
1577 if (entry)
1578 rb_play_order_go_previous (player->priv->play_order);
1581 if (entry != NULL) {
1582 rb_debug ("previous song found, doing previous");
1583 if (new_source != player->priv->current_playing_source)
1584 swap_playing_source (player, new_source);
1586 if (!rb_shell_player_set_playing_entry (player, entry, FALSE, error)) {
1587 rhythmdb_entry_unref (entry);
1588 return FALSE;
1591 rb_shell_player_jump_to_current (player);
1592 rhythmdb_entry_unref (entry);
1593 } else {
1594 rb_debug ("no previous song found, signaling error");
1595 g_set_error (error,
1596 RB_SHELL_PLAYER_ERROR,
1597 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
1598 _("No previous song"));
1599 rb_shell_player_set_playing_source (player, NULL);
1600 return FALSE;
1603 return TRUE;
1606 gboolean
1607 rb_shell_player_do_next (RBShellPlayer *player,
1608 GError **error)
1610 RBSource *new_source = NULL;
1611 RhythmDBEntry *entry = NULL;
1612 gboolean rv = TRUE;
1614 if (player->priv->source == NULL)
1615 return TRUE;
1617 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source)) {
1618 /* Look for another entry in the queue, and fall back to the playing
1619 * source if there isn't one. Always call _get_next on the queue
1620 * so the current entry is removed.
1622 entry = rb_play_order_get_next (player->priv->queue_play_order);
1623 rb_play_order_go_next (player->priv->queue_play_order);
1625 if (entry != NULL) {
1626 new_source = RB_SOURCE (player->priv->queue_source);
1627 } else {
1628 /* If we haven't played anything from the playing source yet,
1629 * _get_playing_entry will return NULL, so we'll have to advance
1630 * the play order to get an entry to play.
1632 entry = rb_play_order_get_playing_entry (player->priv->play_order);
1633 if (entry == NULL) {
1634 entry = rb_play_order_get_next (player->priv->play_order);
1635 rb_play_order_go_next (player->priv->play_order);
1638 new_source = player->priv->source;
1640 } else {
1641 /* Advance the play order, and then let the queue override it. */
1642 entry = rb_play_order_get_next (player->priv->play_order);
1643 if (entry != NULL) {
1644 new_source = player->priv->source;
1645 rb_play_order_go_next (player->priv->play_order);
1648 if (player->priv->queue_play_order) {
1649 RhythmDBEntry *queue_entry;
1651 queue_entry = rb_play_order_get_next (player->priv->queue_play_order);
1652 if (queue_entry != NULL) {
1653 if (entry != NULL) {
1654 rhythmdb_entry_unref (entry);
1656 entry = queue_entry;
1657 new_source = RB_SOURCE (player->priv->queue_source);
1658 rb_play_order_go_next (player->priv->queue_play_order);
1663 /* play the new entry */
1664 if (entry != NULL) {
1665 /* if the entry view containing the playing entry changed, update it */
1666 if (new_source != player->priv->current_playing_source)
1667 swap_playing_source (player, new_source);
1669 if (!rb_shell_player_set_playing_entry (player, entry, FALSE, error))
1670 rv = FALSE;
1671 } else {
1672 g_set_error (error,
1673 RB_SHELL_PLAYER_ERROR,
1674 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
1675 _("No next song"));
1676 rb_debug ("No next entry, stopping playback");
1677 rb_shell_player_set_playing_source (player, NULL);
1678 rb_play_order_set_playing_entry (player->priv->play_order, NULL);
1679 g_object_notify (G_OBJECT (player), "playing");
1680 rv = FALSE;
1683 g_idle_add ((GSourceFunc)rb_shell_player_jump_to_current_idle, player);
1685 if (entry != NULL) {
1686 rhythmdb_entry_unref (entry);
1689 return rv;
1692 static gboolean
1693 rb_shell_player_do_previous_or_seek (RBShellPlayer *player,
1694 GError **error)
1696 rb_debug ("previous");
1697 /* If we're in the first 3 seconds go to the previous song,
1698 * else restart the current one.
1700 if (player->priv->current_playing_source != NULL
1701 && rb_source_can_pause (player->priv->source)
1702 && rb_player_get_time (player->priv->mmplayer) > 3) {
1704 /* see if there's anything to go back to */
1705 gboolean have_previous;
1706 have_previous = rb_play_order_has_previous (player->priv->play_order);
1707 if (player->priv->queue_play_order)
1708 have_previous |= rb_play_order_has_previous (player->priv->queue_play_order);
1710 if (have_previous) {
1711 rb_debug ("after 3 second previous, restarting song");
1712 rb_player_set_time (player->priv->mmplayer, 0);
1713 rb_header_sync_time (player->priv->header_widget);
1714 return TRUE;
1718 return rb_shell_player_do_previous (player, error);
1721 static void
1722 rb_shell_player_cmd_previous (GtkAction *action,
1723 RBShellPlayer *player)
1725 GError *error = NULL;
1727 if (!rb_shell_player_do_previous_or_seek (player, &error)) {
1728 if (error->domain != RB_SHELL_PLAYER_ERROR ||
1729 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
1730 g_warning ("cmd_previous: Unhandled error: %s", error->message);
1731 else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
1732 rb_shell_player_set_playing_source (player, NULL);
1736 static void
1737 rb_shell_player_cmd_next (GtkAction *action,
1738 RBShellPlayer *player)
1740 GError *error = NULL;
1742 if (!rb_shell_player_do_next (player, &error)) {
1743 if (error->domain != RB_SHELL_PLAYER_ERROR ||
1744 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
1745 g_warning ("cmd_next: Unhandled error: %s", error->message);
1746 else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
1747 rb_shell_player_set_playing_source (player, NULL);
1751 void
1752 rb_shell_player_play_entry (RBShellPlayer *player,
1753 RhythmDBEntry *entry,
1754 RBSource *source)
1756 GError *error = NULL;
1758 if (source == NULL)
1759 source = player->priv->selected_source;
1760 rb_shell_player_set_playing_source (player, source);
1762 if (!rb_shell_player_set_playing_entry (player, entry, TRUE, &error)) {
1763 rb_shell_player_error (player, FALSE, error);
1764 g_clear_error (&error);
1768 static void
1769 rb_shell_player_cmd_volume_up (GtkAction *action,
1770 RBShellPlayer *player)
1772 rb_shell_player_set_volume_relative (player, 0.1, NULL);
1775 static void
1776 rb_shell_player_cmd_volume_down (GtkAction *action,
1777 RBShellPlayer *player)
1779 rb_shell_player_set_volume_relative (player, -0.1, NULL);
1782 static void
1783 rb_shell_player_cmd_play (GtkAction *action,
1784 RBShellPlayer *player)
1786 GError *error = NULL;
1787 rb_debug ("play!");
1788 if (!rb_shell_player_playpause (player, FALSE, &error))
1789 rb_error_dialog (NULL,
1790 _("Couldn't start playback"),
1791 "%s", (error) ? error->message : "(null)");
1792 g_clear_error (&error);
1795 /* unused parameter can't be removed without breaking dbus interface compatibility */
1796 gboolean
1797 rb_shell_player_playpause (RBShellPlayer *player,
1798 gboolean unused,
1799 GError **error)
1801 gboolean ret;
1802 RBEntryView *songs;
1804 rb_debug ("doing playpause");
1806 g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), TRUE);
1808 ret = TRUE;
1810 if (rb_player_playing (player->priv->mmplayer)) {
1811 if (player->priv->source == NULL) {
1812 rb_debug ("playing source is already NULL");
1813 } else if (rb_source_can_pause (player->priv->source)) {
1814 rb_debug ("pausing mm player");
1815 rb_player_pause (player->priv->mmplayer);
1816 songs = rb_source_get_entry_view (player->priv->current_playing_source);
1817 if (songs)
1818 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PAUSED);
1819 } else {
1820 rb_debug ("setting playing source to NULL");
1821 rb_shell_player_set_playing_source (player, NULL);
1823 } else {
1824 RhythmDBEntry *entry;
1825 RBSource *new_source;
1826 gboolean out_of_order = FALSE;
1828 if (player->priv->source == NULL) {
1829 /* no current stream, pull one in from the currently
1830 * selected source */
1831 rb_debug ("no playing source, using selected source");
1832 rb_shell_player_set_playing_source (player, player->priv->selected_source);
1834 new_source = player->priv->current_playing_source;
1836 entry = rb_shell_player_get_playing_entry (player);
1837 if (entry == NULL) {
1838 /* queue takes precedence over selection */
1839 if (player->priv->queue_play_order) {
1840 entry = rb_play_order_get_next (player->priv->queue_play_order);
1841 if (entry != NULL) {
1842 new_source = RB_SOURCE (player->priv->queue_source);
1843 rb_play_order_go_next (player->priv->queue_play_order);
1847 /* selection takes precedence over first item in play order */
1848 if (entry == NULL) {
1849 GList *selection = NULL;
1851 songs = rb_source_get_entry_view (player->priv->source);
1852 if (songs)
1853 selection = rb_entry_view_get_selected_entries (songs);
1855 if (selection != NULL) {
1856 rb_debug ("choosing first selected entry");
1857 entry = (RhythmDBEntry*) selection->data;
1858 if (entry)
1859 out_of_order = TRUE;
1861 g_list_free (selection);
1865 /* play order is last */
1866 if (entry == NULL) {
1867 rb_debug ("getting entry from play order");
1868 entry = rb_play_order_get_next (player->priv->play_order);
1869 if (entry != NULL)
1870 rb_play_order_go_next (player->priv->play_order);
1873 if (entry != NULL) {
1874 /* if the entry view containing the playing entry changed, update it */
1875 if (new_source != player->priv->current_playing_source)
1876 swap_playing_source (player, new_source);
1878 if (!rb_shell_player_set_playing_entry (player, entry, out_of_order, error))
1879 ret = FALSE;
1880 rb_shell_player_jump_to_current (player);
1882 } else {
1883 if (!rb_shell_player_play (player, error)) {
1884 rb_shell_player_set_playing_source (player, NULL);
1885 ret = FALSE;
1889 if (entry != NULL) {
1890 rhythmdb_entry_unref (entry);
1894 rb_shell_player_sync_with_source (player);
1895 rb_shell_player_sync_buttons (player);
1896 g_object_notify (G_OBJECT (player), "playing");
1898 return ret;
1901 static void
1902 rb_shell_player_sync_control_state (RBShellPlayer *player)
1904 gboolean shuffle, repeat;
1905 GtkAction *action;
1906 rb_debug ("syncing control state");
1908 if (!rb_shell_player_get_playback_state (player, &shuffle,
1909 &repeat))
1910 return;
1912 action = gtk_action_group_get_action (player->priv->actiongroup,
1913 "ControlShuffle");
1914 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), shuffle);
1915 action = gtk_action_group_get_action (player->priv->actiongroup,
1916 "ControlRepeat");
1917 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), repeat);
1920 static void
1921 rb_shell_player_sync_volume (RBShellPlayer *player,
1922 gboolean notify)
1924 GtkAction *action;
1925 RhythmDBEntry *entry;
1927 if (player->priv->volume <= 0.0){
1928 player->priv->volume = 0.0;
1929 } else if (player->priv->volume >= 1.0){
1930 player->priv->volume = 1.0;
1933 action = gtk_action_group_get_action (player->priv->actiongroup,
1934 "ControlVolumeUp");
1935 g_object_set (G_OBJECT (action), "sensitive", player->priv->volume < 0.9999, NULL);
1937 action = gtk_action_group_get_action (player->priv->actiongroup,
1938 "ControlVolumeDown");
1939 g_object_set (G_OBJECT (action), "sensitive", player->priv->volume > 0.0001, NULL);
1941 rb_player_set_volume (player->priv->mmplayer,
1942 player->priv->mute ? 0.0 : player->priv->volume);
1944 eel_gconf_set_float (CONF_STATE_VOLUME, player->priv->volume);
1947 entry = rb_shell_player_get_playing_entry (player);
1948 rb_shell_player_sync_replaygain (player, entry);
1949 if (entry != NULL) {
1950 rhythmdb_entry_unref (entry);
1953 if (notify)
1954 g_object_notify (G_OBJECT (player), "volume");
1957 void
1958 rb_shell_player_toggle_mute (RBShellPlayer *player)
1960 player->priv->mute = !player->priv->mute;
1961 rb_shell_player_sync_volume (player, FALSE);
1964 static void
1965 rb_shell_player_sync_replaygain (RBShellPlayer *player,
1966 RhythmDBEntry *entry)
1968 double entry_track_gain = 0;
1969 double entry_track_peak = 0;
1970 double entry_album_gain = 0;
1971 double entry_album_peak = 0;
1973 if (entry != NULL) {
1974 entry_track_gain = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_TRACK_GAIN);
1975 entry_track_peak = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_TRACK_PEAK);
1976 entry_album_gain = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_ALBUM_GAIN);
1977 entry_album_peak = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_ALBUM_PEAK);
1980 rb_player_set_replaygain (player->priv->mmplayer, entry_track_gain,
1981 entry_track_peak, entry_album_gain, entry_album_peak);
1984 gboolean
1985 rb_shell_player_set_volume (RBShellPlayer *player,
1986 gdouble volume,
1987 GError **error)
1989 player->priv->volume = volume;
1990 rb_shell_player_sync_volume (player, TRUE);
1991 return TRUE;
1994 gboolean
1995 rb_shell_player_set_volume_relative (RBShellPlayer *player,
1996 gdouble delta,
1997 GError **error)
1999 /* rb_shell_player_sync_volume does clipping */
2000 player->priv->volume += delta;
2001 rb_shell_player_sync_volume (player, TRUE);
2002 return TRUE;
2005 gboolean
2006 rb_shell_player_get_volume (RBShellPlayer *player,
2007 gdouble *volume,
2008 GError **error)
2010 *volume = player->priv->volume;
2011 return TRUE;
2014 gboolean
2015 rb_shell_player_set_mute (RBShellPlayer *player,
2016 gboolean mute,
2017 GError **error)
2019 player->priv->mute = mute;
2020 rb_shell_player_sync_volume (player, FALSE);
2021 return TRUE;
2024 gboolean
2025 rb_shell_player_get_mute (RBShellPlayer *player,
2026 gboolean *mute,
2027 GError **error)
2029 *mute = player->priv->mute;
2030 return TRUE;
2033 static void
2034 gconf_song_position_slider_visibility_changed (GConfClient *client,
2035 guint cnxn_id,
2036 GConfEntry *entry,
2037 RBShellPlayer *player)
2039 rb_debug ("song position slider visibility visibility changed");
2040 rb_shell_player_sync_song_position_slider_visibility (player);
2043 static void
2044 rb_shell_player_shuffle_changed_cb (GtkAction *action,
2045 RBShellPlayer *player)
2047 const char *neworder;
2048 gboolean shuffle = FALSE;
2049 gboolean repeat = FALSE;
2051 if (player->priv->syncing_state)
2052 return;
2054 rb_debug ("shuffle changed");
2056 rb_shell_player_get_playback_state (player, &shuffle, &repeat);
2058 shuffle = !shuffle;
2059 neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
2060 rb_shell_player_set_play_order (player, neworder);
2063 static void
2064 rb_shell_player_repeat_changed_cb (GtkAction *action,
2065 RBShellPlayer *player)
2067 const char *neworder;
2068 gboolean shuffle = FALSE;
2069 gboolean repeat = FALSE;
2070 rb_debug ("repeat changed");
2072 if (player->priv->syncing_state)
2073 return;
2075 rb_shell_player_get_playback_state (player, &shuffle, &repeat);
2077 repeat = !repeat;
2078 neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
2079 rb_shell_player_set_play_order (player, neworder);
2082 static void
2083 rb_shell_player_view_song_position_slider_changed_cb (GtkAction *action,
2084 RBShellPlayer *player)
2086 eel_gconf_set_boolean (CONF_UI_SONG_POSITION_SLIDER_HIDDEN,
2087 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
2090 static void
2091 rb_shell_player_entry_activated_cb (RBEntryView *view,
2092 RhythmDBEntry *entry,
2093 RBShellPlayer *playa)
2095 gboolean was_from_queue = FALSE;
2096 RhythmDBEntry *prev_entry = NULL;
2097 GError *error = NULL;
2098 gboolean source_set = FALSE;
2099 char *playback_uri;
2101 g_return_if_fail (entry != NULL);
2103 rb_debug ("got entry %p activated", entry);
2105 /* don't play hidden entries */
2106 if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
2107 return;
2109 /* skip entries with no playback uri */
2110 playback_uri = rhythmdb_entry_get_playback_uri (entry);
2111 if (playback_uri == NULL) {
2112 return;
2114 g_free (playback_uri);
2116 /* figure out where the previous entry came from */
2117 if ((playa->priv->queue_source != NULL) &&
2118 (playa->priv->current_playing_source == RB_SOURCE (playa->priv->queue_source))) {
2119 prev_entry = rb_shell_player_get_playing_entry (playa);
2120 was_from_queue = TRUE;
2123 if (playa->priv->queue_source) {
2124 RBEntryView *queue_sidebar;
2126 g_object_get (playa->priv->queue_source, "sidebar", &queue_sidebar, NULL);
2128 if (view == queue_sidebar || view == rb_source_get_entry_view (RB_SOURCE (playa->priv->queue_source))) {
2130 /* fall back to the current selected source once the queue is empty */
2131 if (view == queue_sidebar && playa->priv->source == NULL) {
2132 rb_play_order_playing_source_changed (playa->priv->play_order,
2133 playa->priv->selected_source);
2134 playa->priv->source = playa->priv->selected_source;
2137 /* queue entry activated: move it to the start of the queue */
2138 rb_static_playlist_source_move_entry (RB_STATIC_PLAYLIST_SOURCE (playa->priv->queue_source), entry, 0);
2139 rb_shell_player_set_playing_source (playa, RB_SOURCE (playa->priv->queue_source));
2141 /* since we just moved the entry, we should give it focus.
2142 * just calling rb_shell_player_jump_to_current here
2143 * looks terribly ugly, though. */
2144 g_idle_add ((GSourceFunc)rb_shell_player_jump_to_current_idle, playa);
2145 was_from_queue = FALSE;
2146 source_set = TRUE;
2147 } else {
2148 if (playa->priv->queue_only) {
2149 rb_source_add_to_queue (playa->priv->selected_source,
2150 RB_SOURCE (playa->priv->queue_source));
2151 rb_shell_player_set_playing_source (playa, RB_SOURCE (playa->priv->queue_source));
2152 source_set = TRUE;
2156 g_object_unref (queue_sidebar);
2159 /* bail out if queue only */
2160 if (playa->priv->queue_only) {
2161 return;
2164 if (!source_set) {
2165 rb_shell_player_set_playing_source (playa, playa->priv->selected_source);
2166 source_set = TRUE;
2169 if (!rb_shell_player_set_playing_entry (playa, entry, TRUE, &error)) {
2170 rb_shell_player_error (playa, FALSE, error);
2171 g_clear_error (&error);
2174 /* if we were previously playing from the queue, clear its playing entry,
2175 * so we'll start again from the start.
2177 if (was_from_queue && prev_entry != NULL) {
2178 rb_play_order_set_playing_entry (playa->priv->queue_play_order, NULL);
2181 if (prev_entry != NULL) {
2182 rhythmdb_entry_unref (prev_entry);
2186 static void
2187 rb_shell_player_property_row_activated_cb (RBPropertyView *view,
2188 const char *name,
2189 RBShellPlayer *player)
2191 RhythmDBEntry *entry = NULL;
2192 GError *error = NULL;
2194 rb_debug ("got property activated");
2196 rb_shell_player_set_playing_source (player, player->priv->selected_source);
2198 /* RHYTHMDBFIXME - do we need to wait here until the query is finished?
2199 * in theory, yes, but in practice the query is started when the row is
2200 * selected (on the first click when doubleclicking, or when using the
2201 * keyboard to select then activate) and is pretty much always done by
2202 * the time we get in here.
2205 entry = rb_play_order_get_next (player->priv->play_order);
2206 if (entry == NULL) {
2207 return;
2210 rb_play_order_go_next (player->priv->play_order);
2212 if (!rb_shell_player_set_playing_entry (player, entry, TRUE, &error)) {
2213 rb_shell_player_error (player, FALSE, error);
2214 g_clear_error (&error);
2217 if (entry != NULL) {
2218 rhythmdb_entry_unref (entry);
2222 static void
2223 rb_shell_player_entry_changed_cb (RhythmDB *db,
2224 RhythmDBEntry *entry,
2225 GSList *changes,
2226 RBShellPlayer *player)
2228 GSList *t;
2229 gboolean synced = FALSE;
2230 const char *location;
2231 RhythmDBEntry *playing_entry;
2233 playing_entry = rb_shell_player_get_playing_entry (player);
2235 /* We try to update only if the changed entry is currently playing */
2236 if (entry != playing_entry) {
2237 if (playing_entry != NULL) {
2238 rhythmdb_entry_unref (playing_entry);
2240 return;
2243 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
2244 for (t = changes; t; t = t->next) {
2245 RhythmDBEntryChange *change = t->data;
2247 /* update UI if the artist, title or album has changed */
2248 switch (change->prop) {
2249 case RHYTHMDB_PROP_TITLE:
2250 case RHYTHMDB_PROP_ARTIST:
2251 case RHYTHMDB_PROP_ALBUM:
2252 if (!synced) {
2253 rb_shell_player_sync_with_source (player);
2254 synced = TRUE;
2256 break;
2257 default:
2258 break;
2261 /* emit dbus signals for changes with easily marshallable types */
2262 switch (rhythmdb_get_property_type (db, change->prop)) {
2263 case G_TYPE_STRING:
2264 case G_TYPE_BOOLEAN:
2265 case G_TYPE_ULONG:
2266 case G_TYPE_UINT64:
2267 case G_TYPE_DOUBLE:
2268 g_signal_emit (G_OBJECT (player),
2269 rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
2270 location,
2271 rhythmdb_nice_elt_name_from_propid (db, change->prop),
2272 &change->old,
2273 &change->new);
2274 break;
2275 default:
2276 break;
2280 if (playing_entry != NULL) {
2281 rhythmdb_entry_unref (playing_entry);
2285 static void
2286 rb_shell_player_extra_metadata_cb (RhythmDB *db,
2287 RhythmDBEntry *entry,
2288 const char *field,
2289 GValue *metadata,
2290 RBShellPlayer *player)
2293 RhythmDBEntry *playing_entry;
2295 playing_entry = rb_shell_player_get_playing_entry (player);
2296 if (entry != playing_entry) {
2297 if (playing_entry != NULL) {
2298 rhythmdb_entry_unref (playing_entry);
2300 return;
2303 rb_shell_player_sync_with_source (player);
2305 /* emit dbus signals for changes with easily marshallable types */
2306 switch (G_VALUE_TYPE (metadata)) {
2307 case G_TYPE_STRING:
2308 case G_TYPE_BOOLEAN:
2309 case G_TYPE_ULONG:
2310 case G_TYPE_UINT64:
2311 case G_TYPE_DOUBLE:
2312 g_signal_emit (G_OBJECT (player),
2313 rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
2314 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
2315 field,
2316 metadata, /* slightly silly */
2317 metadata);
2318 break;
2319 default:
2320 break;
2325 static void
2326 rb_shell_player_sync_with_source (RBShellPlayer *player)
2328 const char *entry_title = NULL;
2329 const char *artist = NULL;
2330 const char *stream_name = NULL;
2331 char *streaming_title = NULL;
2332 char *streaming_artist = NULL;
2333 RhythmDBEntry *entry;
2334 char *title = NULL;
2335 long elapsed;
2337 entry = rb_shell_player_get_playing_entry (player);
2338 rb_debug ("playing source: %p, active entry: %p", player->priv->current_playing_source, entry);
2340 if (entry != NULL) {
2341 GValue *value;
2343 entry_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
2344 artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
2346 value = rhythmdb_entry_request_extra_metadata (player->priv->db,
2347 entry,
2348 RHYTHMDB_PROP_STREAM_SONG_TITLE);
2349 if (value != NULL) {
2350 streaming_title = g_value_dup_string (value);
2351 g_value_unset (value);
2352 g_free (value);
2354 rb_debug ("got streaming title \"%s\"", streaming_title);
2355 /* use entry title for stream name */
2356 stream_name = entry_title;
2357 entry_title = streaming_title;
2360 value = rhythmdb_entry_request_extra_metadata (player->priv->db,
2361 entry,
2362 RHYTHMDB_PROP_STREAM_SONG_ARTIST);
2363 if (value != NULL) {
2364 streaming_artist = g_value_dup_string (value);
2365 g_value_unset (value);
2366 g_free (value);
2368 rb_debug ("got streaming artist \"%s\"", streaming_artist);
2369 /* override artist from entry */
2370 artist = streaming_artist;
2374 if ((artist && artist[0] != '\0') || entry_title || stream_name) {
2376 GString *title_str = g_string_sized_new (100);
2377 if (artist && artist[0] != '\0') {
2378 g_string_append (title_str, artist);
2379 g_string_append (title_str, " - ");
2381 if (entry_title != NULL)
2382 g_string_append (title_str, entry_title);
2384 if (stream_name != NULL)
2385 g_string_append_printf (title_str, " (%s)", stream_name);
2387 title = g_string_free (title_str, FALSE);
2390 elapsed = rb_player_get_time (player->priv->mmplayer);
2391 if (elapsed < 0)
2392 elapsed = 0;
2393 player->priv->elapsed = elapsed;
2395 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[WINDOW_TITLE_CHANGED], 0,
2396 title);
2397 g_free (title);
2399 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED], 0,
2400 player->priv->elapsed);
2402 /* Sync the header */
2403 rb_header_set_playing_entry (player->priv->header_widget,
2404 entry,
2405 TRUE /*rb_player_seekable (player->priv->mmplayer)*/);
2406 rb_header_sync (player->priv->header_widget);
2408 if (entry != NULL) {
2409 rhythmdb_entry_unref (entry);
2412 g_free (streaming_artist);
2413 g_free (streaming_title);
2416 static void
2417 rb_shell_player_sync_buttons (RBShellPlayer *player)
2419 GtkAction *action;
2420 RBSource *source;
2421 gboolean not_small;
2422 gboolean playing_from_queue;
2423 RBEntryView *view;
2424 int entry_view_state;
2425 RhythmDBEntry *entry;
2427 entry = rb_shell_player_get_playing_entry (player);
2428 if (entry != NULL) {
2429 source = player->priv->current_playing_source;
2430 entry_view_state = rb_player_playing (player->priv->mmplayer) ?
2431 RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_PAUSED;
2432 } else {
2433 source = player->priv->selected_source;
2434 entry_view_state = RB_ENTRY_VIEW_NOT_PLAYING;
2437 source = (entry == NULL) ? player->priv->selected_source : player->priv->current_playing_source;
2439 playing_from_queue = (source == RB_SOURCE (player->priv->queue_source));
2441 rb_debug ("syncing with source %p", source);
2443 not_small = !eel_gconf_get_boolean (CONF_UI_SMALL_DISPLAY);
2444 action = gtk_action_group_get_action (player->priv->actiongroup,
2445 "ViewJumpToPlaying");
2446 g_object_set (action,
2447 "sensitive", entry != NULL && not_small, NULL);
2449 if (source != NULL) {
2450 view = rb_source_get_entry_view (source);
2451 if (view)
2452 rb_entry_view_set_state (view, entry_view_state);
2455 if (entry != NULL) {
2456 rhythmdb_entry_unref (entry);
2460 void
2461 rb_shell_player_set_playing_source (RBShellPlayer *player,
2462 RBSource *source)
2464 rb_shell_player_set_playing_source_internal (player, source, TRUE);
2467 static void
2468 actually_set_playing_source (RBShellPlayer *player,
2469 RBSource *source,
2470 gboolean sync_entry_view)
2472 player->priv->source = source;
2473 player->priv->current_playing_source = source;
2475 if (source != NULL) {
2476 RBEntryView *songs = rb_source_get_entry_view (player->priv->source);
2477 if (sync_entry_view && songs) {
2478 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
2482 if (player->priv->play_order && source != RB_SOURCE (player->priv->queue_source)) {
2483 if (source == NULL)
2484 source = player->priv->selected_source;
2485 rb_play_order_playing_source_changed (player->priv->play_order, source);
2488 rb_shell_player_play_order_update_cb (player->priv->play_order,
2489 FALSE, FALSE,
2490 player);
2493 static void
2494 rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
2495 RBSource *source,
2496 gboolean sync_entry_view)
2499 gboolean emit_source_changed = TRUE;
2500 gboolean emit_playing_from_queue_changed = FALSE;
2502 if (player->priv->source == source &&
2503 player->priv->current_playing_source == source &&
2504 source != NULL)
2505 return;
2507 rb_debug ("setting playing source to %p", source);
2509 if (RB_SOURCE (player->priv->queue_source) == source) {
2511 if (player->priv->current_playing_source != source)
2512 emit_playing_from_queue_changed = TRUE;
2514 if (player->priv->source == NULL) {
2515 actually_set_playing_source (player, source, sync_entry_view);
2516 } else {
2517 emit_source_changed = FALSE;
2518 player->priv->current_playing_source = source;
2521 } else {
2522 if (player->priv->current_playing_source != source) {
2523 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
2524 emit_playing_from_queue_changed = TRUE;
2526 /* stop the old source */
2527 if (player->priv->current_playing_source != NULL) {
2528 if (sync_entry_view) {
2529 RBEntryView *songs = rb_source_get_entry_view (player->priv->current_playing_source);
2530 rb_debug ("source is already playing, stopping it");
2532 /* clear the playing entry if we're switching between non-queue sources */
2533 if (player->priv->current_playing_source != RB_SOURCE (player->priv->queue_source))
2534 rb_play_order_set_playing_entry (player->priv->play_order, NULL);
2536 if (songs)
2537 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_NOT_PLAYING);
2541 actually_set_playing_source (player, source, sync_entry_view);
2544 g_free (player->priv->url);
2545 player->priv->url = NULL;
2547 if (player->priv->current_playing_source == NULL)
2548 rb_shell_player_stop (player);
2550 rb_shell_player_sync_with_source (player);
2551 g_object_notify (G_OBJECT (player), "playing");
2552 if (player->priv->selected_source)
2553 rb_shell_player_sync_buttons (player);
2555 if (emit_source_changed) {
2556 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SOURCE_CHANGED],
2557 0, player->priv->source);
2559 if (emit_playing_from_queue_changed) {
2560 g_object_notify (G_OBJECT (player), "playing-from-queue");
2565 * rb_shell_player_stop:
2566 * @player: a #RBShellPlayer.
2568 * Completely stops playback, freeing resources and unloading the file.
2570 * In general rb_shell_player_pause() should be used instead, as it stops the
2571 * audio, but does not completely free resources.
2574 void
2575 rb_shell_player_stop (RBShellPlayer *player)
2577 GError *error = NULL;
2578 rb_debug ("stopping");
2580 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
2582 if (error == NULL)
2583 rb_player_close (player->priv->mmplayer, &error);
2584 if (error) {
2585 rb_error_dialog (NULL,
2586 _("Couldn't stop playback"),
2587 "%s", error->message);
2588 g_error_free (error);
2591 rb_shell_player_sync_with_source (player);
2592 g_signal_emit (G_OBJECT (player),
2593 rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
2594 NULL);
2595 g_signal_emit (G_OBJECT (player),
2596 rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
2597 NULL);
2598 g_object_notify (G_OBJECT (player), "playing");
2599 rb_shell_player_sync_buttons (player);
2603 * rb_shell_player_pause:
2604 * @player: a #RBShellPlayer
2605 * @error: error return
2607 * Pauses playback if possible, completely stopping if not.
2609 * @return: whether playback is not occurring (TRUE when successfully
2610 * paused/stopped or playback was not occurring).
2613 gboolean
2614 rb_shell_player_pause (RBShellPlayer *player,
2615 GError **error)
2617 if (rb_player_playing (player->priv->mmplayer))
2618 return rb_shell_player_playpause (player, FALSE, error);
2619 else
2620 return TRUE;
2624 * rb_shell_player_get_playing:
2625 * @player: a #RBShellPlayer
2626 * @playing: playback state return
2627 * @error: error return
2629 * Reports whether playback is occuring by setting playing.
2631 * @return: whether the playback state could be reported successfully.
2633 gboolean
2634 rb_shell_player_get_playing (RBShellPlayer *player,
2635 gboolean *playing,
2636 GError **error)
2638 if (playing != NULL)
2639 *playing = rb_player_playing (player->priv->mmplayer);
2641 return TRUE;
2644 char *
2645 rb_shell_player_get_playing_time_string (RBShellPlayer *player)
2647 return rb_make_elapsed_time_string (player->priv->elapsed,
2648 rb_shell_player_get_playing_song_duration (player),
2649 !eel_gconf_get_boolean (CONF_UI_TIME_DISPLAY));
2652 gboolean
2653 rb_shell_player_get_playing_time (RBShellPlayer *player,
2654 guint *time,
2655 GError **error)
2657 if (time != NULL)
2658 *time = (guint) rb_player_get_time (player->priv->mmplayer);
2660 return TRUE;
2663 gboolean
2664 rb_shell_player_set_playing_time (RBShellPlayer *player,
2665 guint time,
2666 GError **error)
2668 if (rb_player_seekable (player->priv->mmplayer)) {
2669 rb_player_set_time (player->priv->mmplayer, (long) time);
2670 return TRUE;
2671 } else {
2672 g_set_error (error,
2673 RB_SHELL_PLAYER_ERROR,
2674 RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
2675 _("Current song is not seekable"));
2676 return FALSE;
2680 void
2681 rb_shell_player_seek (RBShellPlayer *player, long offset)
2683 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
2685 if (rb_player_seekable (player->priv->mmplayer)) {
2686 long t = rb_player_get_time (player->priv->mmplayer);
2687 if (t < 0)
2688 t = 0;
2689 rb_player_set_time (player->priv->mmplayer, t + offset);
2693 long
2694 rb_shell_player_get_playing_song_duration (RBShellPlayer *player)
2696 RhythmDBEntry *current_entry;
2697 long val;
2699 g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), -1);
2701 current_entry = rb_shell_player_get_playing_entry (player);
2703 if (current_entry == NULL) {
2704 rb_debug ("Did not get playing entry : return -1 as length");
2705 return -1;
2708 val = rhythmdb_entry_get_ulong (current_entry, RHYTHMDB_PROP_DURATION);
2710 rhythmdb_entry_unref (current_entry);
2712 return val;
2715 static void
2716 rb_shell_player_sync_with_selected_source (RBShellPlayer *player)
2718 rb_debug ("syncing with selected source: %p", player->priv->selected_source);
2719 if (player->priv->source == NULL)
2721 rb_debug ("no playing source, new source is %p", player->priv->selected_source);
2722 rb_shell_player_sync_with_source (player);
2726 static void
2727 rb_shell_player_error (RBShellPlayer *player,
2728 gboolean async,
2729 const GError *err)
2731 RhythmDBEntry *entry;
2732 gboolean do_next;
2734 g_return_if_fail (player->priv->handling_error == FALSE);
2736 player->priv->handling_error = TRUE;
2738 entry = rb_shell_player_get_playing_entry (player);
2740 rb_debug ("playback error while playing: %s", err->message);
2741 /* For synchronous errors the entry playback error has already been set */
2742 if (entry && async)
2743 rb_shell_player_set_entry_playback_error (player, entry, err->message);
2745 if (err->code == RB_PLAYER_ERROR_NO_AUDIO) {
2746 /* stream has completely ended */
2747 rb_shell_player_set_playing_source (player, NULL);
2748 do_next = FALSE;
2749 } else if ((player->priv->current_playing_source != NULL) &&
2750 (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_RETRY)) {
2751 /* receiving an error means a broken stream or non-audio stream, so abort
2752 * unless we've got more URLs to try */
2753 if (g_queue_is_empty (player->priv->playlist_urls)) {
2754 rb_error_dialog (NULL,
2755 _("Couldn't start playback"),
2756 "%s", (err) ? err->message : "(null)");
2757 rb_shell_player_set_playing_source (player, NULL);
2758 do_next = FALSE;
2759 } else {
2760 rb_debug ("haven't yet exhausted the URLs from the playlist");
2761 do_next = TRUE;
2763 } else {
2764 do_next = TRUE;
2767 if (do_next && player->priv->do_next_idle_id == 0) {
2768 player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_idle, player);
2771 player->priv->handling_error = FALSE;
2773 if (entry != NULL) {
2774 rhythmdb_entry_unref (entry);
2778 static void
2779 error_cb (RBPlayer *mmplayer,
2780 const GError *err,
2781 gpointer data)
2783 RBShellPlayer *player = RB_SHELL_PLAYER (data);
2785 if (player->priv->handling_error)
2786 return;
2788 if (player->priv->source == NULL) {
2789 rb_debug ("ignoring error (no source): %s", err->message);
2790 return;
2793 GDK_THREADS_ENTER ();
2795 rb_shell_player_error (player, TRUE, err);
2797 rb_debug ("exiting error hander");
2798 GDK_THREADS_LEAVE ();
2801 static void
2802 tick_cb (RBPlayer *mmplayer,
2803 long elapsed,
2804 gpointer data)
2806 RBShellPlayer *player = RB_SHELL_PLAYER (data);
2808 GDK_THREADS_ENTER ();
2810 if (rb_player_playing (mmplayer)) {
2811 if (elapsed < 0)
2812 elapsed = 0;
2814 if (player->priv->elapsed != elapsed) {
2815 player->priv->elapsed = elapsed;
2816 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED],
2817 0, player->priv->elapsed);
2821 GDK_THREADS_LEAVE ();
2824 gboolean
2825 rb_shell_player_get_playing_path (RBShellPlayer *shell_player,
2826 const gchar **path,
2827 GError **error)
2829 RhythmDBEntry *entry;
2831 entry = rb_shell_player_get_playing_entry (shell_player);
2832 if (entry != NULL) {
2833 *path = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
2834 } else {
2835 *path = NULL;
2838 if (entry != NULL) {
2839 rhythmdb_entry_unref (entry);
2842 return TRUE;
2845 #ifdef HAVE_MMKEYS
2846 static void
2847 grab_mmkey (int key_code,
2848 GdkWindow *root)
2850 gdk_error_trap_push ();
2852 XGrabKey (GDK_DISPLAY (), key_code,
2854 GDK_WINDOW_XID (root), True,
2855 GrabModeAsync, GrabModeAsync);
2856 XGrabKey (GDK_DISPLAY (), key_code,
2857 Mod2Mask,
2858 GDK_WINDOW_XID (root), True,
2859 GrabModeAsync, GrabModeAsync);
2860 XGrabKey (GDK_DISPLAY (), key_code,
2861 Mod5Mask,
2862 GDK_WINDOW_XID (root), True,
2863 GrabModeAsync, GrabModeAsync);
2864 XGrabKey (GDK_DISPLAY (), key_code,
2865 LockMask,
2866 GDK_WINDOW_XID (root), True,
2867 GrabModeAsync, GrabModeAsync);
2868 XGrabKey (GDK_DISPLAY (), key_code,
2869 Mod2Mask | Mod5Mask,
2870 GDK_WINDOW_XID (root), True,
2871 GrabModeAsync, GrabModeAsync);
2872 XGrabKey (GDK_DISPLAY (), key_code,
2873 Mod2Mask | LockMask,
2874 GDK_WINDOW_XID (root), True,
2875 GrabModeAsync, GrabModeAsync);
2876 XGrabKey (GDK_DISPLAY (), key_code,
2877 Mod5Mask | LockMask,
2878 GDK_WINDOW_XID (root), True,
2879 GrabModeAsync, GrabModeAsync);
2880 XGrabKey (GDK_DISPLAY (), key_code,
2881 Mod2Mask | Mod5Mask | LockMask,
2882 GDK_WINDOW_XID (root), True,
2883 GrabModeAsync, GrabModeAsync);
2885 gdk_flush ();
2886 if (gdk_error_trap_pop ()) {
2887 rb_debug ("Error grabbing key");
2891 static GdkFilterReturn
2892 filter_mmkeys (GdkXEvent *xevent,
2893 GdkEvent *event,
2894 gpointer data)
2896 XEvent *xev;
2897 XKeyEvent *key;
2898 RBShellPlayer *player;
2899 xev = (XEvent *) xevent;
2900 if (xev->type != KeyPress) {
2901 return GDK_FILTER_CONTINUE;
2904 key = (XKeyEvent *) xevent;
2906 player = (RBShellPlayer *)data;
2908 if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPlay) == key->keycode) {
2909 rb_shell_player_playpause (player, FALSE, NULL);
2910 return GDK_FILTER_REMOVE;
2911 } else if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPause) == key->keycode) {
2912 rb_shell_player_pause (player, NULL);
2913 return GDK_FILTER_REMOVE;
2914 } else if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioStop) == key->keycode) {
2915 rb_shell_player_stop (player);
2916 return GDK_FILTER_REMOVE;
2917 } else if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPrev) == key->keycode) {
2918 rb_shell_player_cmd_previous (NULL, player);
2919 return GDK_FILTER_REMOVE;
2920 } else if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioNext) == key->keycode) {
2921 rb_shell_player_cmd_next (NULL, player);
2922 return GDK_FILTER_REMOVE;
2923 } else {
2924 return GDK_FILTER_CONTINUE;
2928 static void
2929 rb_shell_player_init_mmkeys (RBShellPlayer *shell_player)
2931 gint keycodes[] = {0, 0, 0, 0, 0};
2932 GdkDisplay *display;
2933 GdkScreen *screen;
2934 GdkWindow *root;
2935 guint i, j;
2937 keycodes[0] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPlay);
2938 keycodes[1] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioStop);
2939 keycodes[2] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPrev);
2940 keycodes[3] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioNext);
2941 keycodes[4] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPause);
2943 display = gdk_display_get_default ();
2945 for (i = 0; i < gdk_display_get_n_screens (display); i++) {
2946 screen = gdk_display_get_screen (display, i);
2948 if (screen != NULL) {
2949 root = gdk_screen_get_root_window (screen);
2951 for (j = 0; j < G_N_ELEMENTS (keycodes) ; j++) {
2952 if (keycodes[j] != 0)
2953 grab_mmkey (keycodes[j], root);
2956 gdk_window_add_filter (root, filter_mmkeys,
2957 (gpointer) shell_player);
2961 #endif /* HAVE_MMKEYS */
2963 static gboolean
2964 _idle_unblock_signal_cb (gpointer data)
2966 RBShellPlayer *player = (RBShellPlayer *)data;
2967 GtkAction *action;
2968 gboolean playing;
2970 GDK_THREADS_ENTER ();
2972 action = gtk_action_group_get_action (player->priv->actiongroup,
2973 "ControlPlay");
2975 /* sync the active state of the action again */
2976 g_object_get (player, "playing", &playing, NULL);
2977 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing);
2979 g_signal_handlers_unblock_by_func (action, rb_shell_player_cmd_play, player);
2981 GDK_THREADS_LEAVE ();
2982 return FALSE;
2985 static void
2986 rb_shell_player_playing_changed_cb (RBShellPlayer *player,
2987 GParamSpec *arg1,
2988 gpointer user_data)
2990 GtkAction *action;
2991 gboolean playing;
2992 char *tooltip;
2994 g_object_get (player, "playing", &playing, NULL);
2995 action = gtk_action_group_get_action (player->priv->actiongroup,
2996 "ControlPlay");
2997 if (playing) {
2998 tooltip = g_strdup (_("Stop playback"));
2999 } else {
3000 tooltip = g_strdup (_("Start playback"));
3002 g_object_set (action, "tooltip", tooltip, NULL);
3003 g_free (tooltip);
3005 /* block the signal, so that it doesn't get stuck by triggering recursively,
3006 * and don't unblock it until whatever else is happening has finished.
3008 g_signal_handlers_block_by_func (action, rb_shell_player_cmd_play, player);
3009 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing);
3010 g_idle_add (_idle_unblock_signal_cb, player);
3013 /* This should really be standard. */
3014 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3016 GType
3017 rb_shell_player_error_get_type (void)
3019 static GType etype = 0;
3021 if (etype == 0) {
3022 static const GEnumValue values[] = {
3023 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_PLAYLIST_PARSE_ERROR, "Playing parsing error"),
3024 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST, "End of playlist reached"),
3025 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_PLAYING, "Not playing"),
3026 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE, "Not seekable"),
3027 { 0, 0, 0 }
3030 etype = g_enum_register_static ("RBShellPlayerError", values);
3033 return etype;