Updated Macedonian Translation <ufo@linux.net.mk>
[rhythmbox.git] / shell / rb-shell-player.c
blob6768c1fcb94e61ba475a8710d0cd15625825daa3
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-iradio-source.h"
58 #include "rb-library-source.h"
59 #include "eel-gconf-extensions.h"
60 #include "rb-util.h"
61 #include "rb-play-order.h"
62 #include "rb-statusbar.h"
63 #include "rb-playlist-source.h"
64 #include "rb-play-queue-source.h"
65 #include "rhythmdb.h"
66 #include "rb-podcast-manager.h"
67 #include "rb-marshal.h"
69 #ifdef HAVE_XIDLE_EXTENSION
70 #include <X11/extensions/xidle.h>
71 #endif /* HAVE_XIDLE_EXTENSION */
73 static const char* const state_to_play_order[2][2] =
74 {{"linear", "linear-loop"},
75 {"shuffle", "random-by-age-and-rating"}};
77 static void rb_shell_player_class_init (RBShellPlayerClass *klass);
78 static void rb_shell_player_init (RBShellPlayer *shell_player);
79 static GObject *rb_shell_player_constructor (GType type, guint n_construct_properties,
80 GObjectConstructParam *construct_properties);
81 static void rb_shell_player_finalize (GObject *object);
82 static void rb_shell_player_set_property (GObject *object,
83 guint prop_id,
84 const GValue *value,
85 GParamSpec *pspec);
86 static void rb_shell_player_get_property (GObject *object,
87 guint prop_id,
88 GValue *value,
89 GParamSpec *pspec);
91 static void rb_shell_player_cmd_previous (GtkAction *action,
92 RBShellPlayer *player);
93 static void rb_shell_player_cmd_play (GtkAction *action,
94 RBShellPlayer *player);
95 static void rb_shell_player_cmd_next (GtkAction *action,
96 RBShellPlayer *player);
97 static void rb_shell_player_cmd_volume_up (GtkAction *action,
98 RBShellPlayer *player);
99 static void rb_shell_player_cmd_volume_down (GtkAction *action,
100 RBShellPlayer *player);
101 static void rb_shell_player_shuffle_changed_cb (GtkAction *action,
102 RBShellPlayer *player);
103 static void rb_shell_player_repeat_changed_cb (GtkAction *action,
104 RBShellPlayer *player);
105 static void rb_shell_player_view_song_position_slider_changed_cb (GtkAction *action,
106 RBShellPlayer *player);
107 static void rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
108 RBSource *source,
109 gboolean sync_entry_view);
110 static void rb_shell_player_sync_with_source (RBShellPlayer *player);
111 static void rb_shell_player_sync_with_selected_source (RBShellPlayer *player);
112 static void rb_shell_player_entry_changed_cb (RhythmDB *db,
113 RhythmDBEntry *entry,
114 GSList *changes,
115 RBShellPlayer *player);
117 static void rb_shell_player_entry_activated_cb (RBEntryView *view,
118 RhythmDBEntry *entry,
119 RBShellPlayer *playa);
120 static void rb_shell_player_property_row_activated_cb (RBPropertyView *view,
121 const char *name,
122 RBShellPlayer *playa);
123 static void rb_shell_player_sync_volume (RBShellPlayer *player, gboolean notify);
124 static void rb_shell_player_sync_replaygain (RBShellPlayer *player,
125 RhythmDBEntry *entry);
126 static void tick_cb (RBPlayer *player, long elapsed, gpointer data);
127 static void error_cb (RBPlayer *player, const GError *err, gpointer data);
128 static void buffering_cb (RBPlayer *player, guint progress, gpointer data);
129 static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err);
131 static void info_available_cb (RBPlayer *player,
132 RBMetaDataField field,
133 GValue *value,
134 gpointer data);
135 static void rb_shell_player_set_play_order (RBShellPlayer *player,
136 const gchar *new_val);
137 static void rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
138 gboolean has_next,
139 gboolean has_previous,
140 RBShellPlayer *player);
142 static void rb_shell_player_sync_play_order (RBShellPlayer *player);
143 static void rb_shell_player_sync_control_state (RBShellPlayer *player);
144 static void rb_shell_player_sync_song_position_slider_visibility (RBShellPlayer *player);
145 static void rb_shell_player_sync_buttons (RBShellPlayer *player);
147 static void gconf_play_order_changed (GConfClient *client,guint cnxn_id,
148 GConfEntry *entry, RBShellPlayer *player);
149 static void gconf_song_position_slider_visibility_changed (GConfClient *client,guint cnxn_id,
150 GConfEntry *entry, RBShellPlayer *player);
151 static void rb_shell_player_playing_changed_cb (RBShellPlayer *player,
152 GParamSpec *arg1,
153 gpointer user_data);
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 gboolean have_url;
191 char *url;
192 long elapsed;
194 RBPlayOrder *play_order;
195 RBPlayOrder *queue_play_order;
197 GQueue *playlist_urls;
199 RBHeader *header_widget;
200 RBStatusbar *statusbar_widget;
202 guint gconf_play_order_id;
203 guint gconf_song_position_slider_visibility_id;
205 gboolean mute;
206 float volume;
208 guint do_next_idle_id;
211 #define RB_SHELL_PLAYER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_PLAYER, RBShellPlayerPrivate))
213 enum
215 PROP_0,
216 PROP_SOURCE,
217 PROP_DB,
218 PROP_UI_MANAGER,
219 PROP_ACTION_GROUP,
220 PROP_PLAY_ORDER,
221 PROP_PLAYING,
222 PROP_VOLUME,
223 PROP_STATUSBAR,
224 PROP_QUEUE_SOURCE,
225 PROP_QUEUE_ONLY,
226 PROP_STREAM_SONG,
227 PROP_PLAYING_FROM_QUEUE,
228 PROP_PLAYER,
231 enum
233 WINDOW_TITLE_CHANGED,
234 ELAPSED_CHANGED,
235 PLAYING_SOURCE_CHANGED,
236 PLAYING_CHANGED,
237 PLAYING_SONG_CHANGED,
238 PLAYING_URI_CHANGED,
239 PLAYING_SONG_PROPERTY_CHANGED,
240 LAST_SIGNAL
243 static GtkActionEntry rb_shell_player_actions [] =
245 { "ControlPrevious", GTK_STOCK_MEDIA_PREVIOUS, N_("P_revious"), "<alt>Left",
246 N_("Start playing the previous song"),
247 G_CALLBACK (rb_shell_player_cmd_previous) },
248 { "ControlNext", GTK_STOCK_MEDIA_NEXT, N_("_Next"), "<alt>Right",
249 N_("Start playing the next song"),
250 G_CALLBACK (rb_shell_player_cmd_next) },
251 { "ControlVolumeUp", NULL, N_("_Increase Volume"), "<control>Up",
252 N_("Increase playback volume"),
253 G_CALLBACK (rb_shell_player_cmd_volume_up) },
254 { "ControlVolumeDown", NULL, N_("_Decrease Volume"), "<control>Down",
255 N_("Decrease playback volume"),
256 G_CALLBACK (rb_shell_player_cmd_volume_down) },
258 static guint rb_shell_player_n_actions = G_N_ELEMENTS (rb_shell_player_actions);
260 static GtkToggleActionEntry rb_shell_player_toggle_entries [] =
262 { "ControlPlay", GTK_STOCK_MEDIA_PLAY, N_("_Play"), "<control>space",
263 N_("Start playback"),
264 G_CALLBACK (rb_shell_player_cmd_play) },
265 { "ControlShuffle", GNOME_MEDIA_SHUFFLE, N_("Sh_uffle"), "<control>U",
266 N_("Play songs in a random order"),
267 G_CALLBACK (rb_shell_player_shuffle_changed_cb) },
268 { "ControlRepeat", GNOME_MEDIA_REPEAT, N_("_Repeat"), "<control>R",
269 N_("Play first song again after all songs are played"),
270 G_CALLBACK (rb_shell_player_repeat_changed_cb) },
271 { "ViewSongPositionSlider", NULL, N_("_Song Position Slider"), NULL,
272 N_("Change the visibility of the song position slider"),
273 G_CALLBACK (rb_shell_player_view_song_position_slider_changed_cb), TRUE },
275 static guint rb_shell_player_n_toggle_entries = G_N_ELEMENTS (rb_shell_player_toggle_entries);
277 static guint rb_shell_player_signals[LAST_SIGNAL] = { 0 };
279 G_DEFINE_TYPE (RBShellPlayer, rb_shell_player, GTK_TYPE_HBOX)
281 static void
282 rb_shell_player_class_init (RBShellPlayerClass *klass)
284 GObjectClass *object_class = G_OBJECT_CLASS (klass);
286 object_class->finalize = rb_shell_player_finalize;
287 object_class->constructor = rb_shell_player_constructor;
289 object_class->set_property = rb_shell_player_set_property;
290 object_class->get_property = rb_shell_player_get_property;
292 g_object_class_install_property (object_class,
293 PROP_SOURCE,
294 g_param_spec_object ("source",
295 "RBSource",
296 "RBSource object",
297 RB_TYPE_SOURCE,
298 G_PARAM_READWRITE));
300 g_object_class_install_property (object_class,
301 PROP_UI_MANAGER,
302 g_param_spec_object ("ui-manager",
303 "GtkUIManager",
304 "GtkUIManager object",
305 GTK_TYPE_UI_MANAGER,
306 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
308 g_object_class_install_property (object_class,
309 PROP_DB,
310 g_param_spec_object ("db",
311 "RhythmDB",
312 "RhythmDB object",
313 RHYTHMDB_TYPE,
314 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
316 g_object_class_install_property (object_class,
317 PROP_ACTION_GROUP,
318 g_param_spec_object ("action-group",
319 "GtkActionGroup",
320 "GtkActionGroup object",
321 GTK_TYPE_ACTION_GROUP,
322 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
324 g_object_class_install_property (object_class,
325 PROP_QUEUE_SOURCE,
326 g_param_spec_object ("queue-source",
327 "RBPlaylistSource",
328 "RBPlaylistSource object",
329 RB_TYPE_PLAYLIST_SOURCE,
330 G_PARAM_READWRITE));
332 g_object_class_install_property (object_class,
333 PROP_QUEUE_ONLY,
334 g_param_spec_boolean ("queue-only",
335 "Queue only",
336 "Activation only adds to queue",
337 FALSE,
338 G_PARAM_READWRITE));
340 g_object_class_install_property (object_class,
341 PROP_PLAYING_FROM_QUEUE,
342 g_param_spec_boolean ("playing-from-queue",
343 "Playing from queue",
344 "Whether playing from the play queue or not",
345 FALSE,
346 G_PARAM_READABLE));
348 g_object_class_install_property (object_class,
349 PROP_PLAYER,
350 g_param_spec_object ("player",
351 "RBPlayer",
352 "RBPlayer object",
353 G_TYPE_OBJECT,
354 G_PARAM_READABLE));
356 /* If you change these, be sure to update the CORBA interface
357 * in rb-remote-bonobo.c! */
358 g_object_class_install_property (object_class,
359 PROP_PLAY_ORDER,
360 g_param_spec_string ("play-order",
361 "play-order",
362 "What play order to use",
363 "linear",
364 G_PARAM_READABLE));
365 g_object_class_install_property (object_class,
366 PROP_PLAYING,
367 g_param_spec_boolean ("playing",
368 "playing",
369 "Whether Rhythmbox is currently playing",
370 FALSE,
371 G_PARAM_READABLE));
373 g_object_class_install_property (object_class,
374 PROP_VOLUME,
375 g_param_spec_float ("volume",
376 "volume",
377 "Current playback volume",
378 0.0f, 1.0f, 1.0f,
379 G_PARAM_READWRITE));
381 g_object_class_install_property (object_class,
382 PROP_STATUSBAR,
383 g_param_spec_object ("statusbar",
384 "RBStatusbar",
385 "RBStatusbar object",
386 RB_TYPE_STATUSBAR,
387 G_PARAM_READWRITE));
389 g_object_class_install_property (object_class,
390 PROP_STREAM_SONG,
391 g_param_spec_string ("stream-song",
392 "stream-song",
393 "Current stream song title",
395 G_PARAM_READABLE));
397 rb_shell_player_signals[WINDOW_TITLE_CHANGED] =
398 g_signal_new ("window_title_changed",
399 G_OBJECT_CLASS_TYPE (object_class),
400 G_SIGNAL_RUN_LAST,
401 G_STRUCT_OFFSET (RBShellPlayerClass, window_title_changed),
402 NULL, NULL,
403 g_cclosure_marshal_VOID__STRING,
404 G_TYPE_NONE,
406 G_TYPE_STRING);
408 rb_shell_player_signals[ELAPSED_CHANGED] =
409 g_signal_new ("elapsed_changed",
410 G_OBJECT_CLASS_TYPE (object_class),
411 G_SIGNAL_RUN_LAST,
412 G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_changed),
413 NULL, NULL,
414 g_cclosure_marshal_VOID__UINT,
415 G_TYPE_NONE,
417 G_TYPE_UINT);
419 rb_shell_player_signals[PLAYING_SOURCE_CHANGED] =
420 g_signal_new ("playing-source-changed",
421 G_OBJECT_CLASS_TYPE (object_class),
422 G_SIGNAL_RUN_LAST,
423 G_STRUCT_OFFSET (RBShellPlayerClass, playing_source_changed),
424 NULL, NULL,
425 g_cclosure_marshal_VOID__OBJECT,
426 G_TYPE_NONE,
428 RB_TYPE_SOURCE);
430 rb_shell_player_signals[PLAYING_CHANGED] =
431 g_signal_new ("playing-changed",
432 G_OBJECT_CLASS_TYPE (object_class),
433 G_SIGNAL_RUN_LAST,
434 G_STRUCT_OFFSET (RBShellPlayerClass, playing_changed),
435 NULL, NULL,
436 g_cclosure_marshal_VOID__BOOLEAN,
437 G_TYPE_NONE,
439 G_TYPE_BOOLEAN);
441 rb_shell_player_signals[PLAYING_SONG_CHANGED] =
442 g_signal_new ("playing-song-changed",
443 G_OBJECT_CLASS_TYPE (object_class),
444 G_SIGNAL_RUN_LAST,
445 G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_changed),
446 NULL, NULL,
447 g_cclosure_marshal_VOID__BOXED,
448 G_TYPE_NONE,
450 RHYTHMDB_TYPE_ENTRY);
452 rb_shell_player_signals[PLAYING_URI_CHANGED] =
453 g_signal_new ("playing-uri-changed",
454 G_OBJECT_CLASS_TYPE (object_class),
455 G_SIGNAL_RUN_LAST,
456 G_STRUCT_OFFSET (RBShellPlayerClass, playing_uri_changed),
457 NULL, NULL,
458 g_cclosure_marshal_VOID__STRING,
459 G_TYPE_NONE,
461 G_TYPE_STRING);
463 rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED] =
464 g_signal_new ("playing-song-property-changed",
465 G_OBJECT_CLASS_TYPE (object_class),
466 G_SIGNAL_RUN_LAST,
467 G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_property_changed),
468 NULL, NULL,
469 rb_marshal_VOID__STRING_STRING_POINTER_POINTER,
470 G_TYPE_NONE,
472 G_TYPE_STRING, G_TYPE_STRING,
473 G_TYPE_VALUE, G_TYPE_VALUE);
475 g_type_class_add_private (klass, sizeof (RBShellPlayerPrivate));
478 static GObject *
479 rb_shell_player_constructor (GType type,
480 guint n_construct_properties,
481 GObjectConstructParam *construct_properties)
483 RBShellPlayer *player;
484 RBShellPlayerClass *klass;
485 GtkAction *action;
487 klass = RB_SHELL_PLAYER_CLASS (g_type_class_peek (RB_TYPE_SHELL_PLAYER));
489 player = RB_SHELL_PLAYER (G_OBJECT_CLASS (rb_shell_player_parent_class)->
490 constructor (type, n_construct_properties, construct_properties));
492 gtk_action_group_add_actions (player->priv->actiongroup,
493 rb_shell_player_actions,
494 rb_shell_player_n_actions,
495 player);
496 gtk_action_group_add_toggle_actions (player->priv->actiongroup,
497 rb_shell_player_toggle_entries,
498 rb_shell_player_n_toggle_entries,
499 player);
501 action = gtk_action_group_get_action (player->priv->actiongroup,
502 "ControlPlay");
503 g_object_set (action, "is-important", TRUE, NULL);
505 player->priv->syncing_state = TRUE;
506 rb_shell_player_set_playing_source (player, NULL);
507 rb_shell_player_sync_play_order (player);
508 rb_shell_player_sync_control_state (player);
509 rb_shell_player_sync_volume (player, FALSE);
510 player->priv->syncing_state = FALSE;
512 rb_shell_player_sync_song_position_slider_visibility (player);
514 g_signal_connect (G_OBJECT (player),
515 "notify::playing",
516 G_CALLBACK (rb_shell_player_playing_changed_cb),
517 NULL);
519 return G_OBJECT (player);
522 static void
523 volume_pre_unmount_cb (GnomeVFSVolumeMonitor *monitor,
524 GnomeVFSVolume *volume,
525 RBShellPlayer *player)
527 gchar *uri_mount_point;
528 gchar *volume_mount_point;
529 RhythmDBEntry *entry;
530 const char *uri;
531 gboolean playing;
533 rb_shell_player_get_playing (player, &playing, NULL);
534 if (playing) {
535 return;
538 entry = rb_shell_player_get_playing_entry (player);
539 if (entry == NULL) {
540 /* At startup for example, playing path can be NULL */
541 return;
544 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
545 uri_mount_point = rb_uri_get_mount_point (uri);
546 volume_mount_point = gnome_vfs_volume_get_activation_uri (volume);
548 if (uri_mount_point && volume_mount_point &&
549 (strcmp (uri_mount_point, volume_mount_point) == 0)) {
550 rb_shell_player_stop (player);
552 g_free (uri_mount_point);
553 g_free (volume_mount_point);
555 if (entry != NULL) {
556 rhythmdb_entry_unref (entry);
560 static void
561 reemit_playing_signal (RBShellPlayer *player,
562 GParamSpec *pspec,
563 gpointer data)
565 g_signal_emit (player, rb_shell_player_signals[PLAYING_CHANGED], 0,
566 rb_player_playing (player->priv->mmplayer));
569 static gboolean
570 notify_playing_idle (RBShellPlayer *player)
572 rb_debug ("emitting playing notification: %d", rb_player_playing (player->priv->mmplayer));
573 g_object_notify (G_OBJECT (player), "playing");
574 rb_shell_player_sync_buttons (player);
575 return FALSE;
578 static void
579 rb_shell_player_open_playlist_url (RBShellPlayer *player,
580 const char *location)
582 GError *error = NULL;
584 rb_debug ("playing stream url %s", location);
585 rb_player_open (player->priv->mmplayer, location, &error);
586 if (error == NULL)
587 rb_player_play (player->priv->mmplayer, &error);
589 if (error) {
590 GDK_THREADS_ENTER ();
591 rb_shell_player_error (player, TRUE, error);
592 g_error_free (error);
593 GDK_THREADS_LEAVE ();
595 g_idle_add ((GSourceFunc) notify_playing_idle, player);
598 static void
599 rb_shell_player_handle_eos_unlocked (RBShellPlayer *player)
601 RhythmDBEntry *entry;
602 RBSource *source;
604 source = player->priv->current_playing_source;
606 /* nothing to do */
607 if (source == NULL) {
608 return;
611 entry = rb_shell_player_get_playing_entry (player);
613 switch (rb_source_handle_eos (source)) {
614 case RB_SOURCE_EOF_ERROR:
615 rb_error_dialog (NULL, _("Stream error"),
616 _("Unexpected end of stream!"));
617 rb_shell_player_set_playing_source (player, NULL);
618 break;
619 case RB_SOURCE_EOF_STOP:
620 rb_shell_player_set_playing_source (player, NULL);
621 break;
622 case RB_SOURCE_EOF_RETRY: {
623 GTimeVal current;
624 gint diff;
626 g_get_current_time (&current);
627 diff = current.tv_sec - player->priv->last_retry.tv_sec;
628 player->priv->last_retry = current;
630 if (rb_source_try_playlist (source) &&
631 !g_queue_is_empty (player->priv->playlist_urls)) {
632 char *location = g_queue_pop_head (player->priv->playlist_urls);
633 rb_debug ("trying next radio stream url: %s", location);
635 rb_shell_player_open_playlist_url (player, location);
636 g_free (location);
637 break;
640 if (diff < 4) {
641 rb_debug ("Last retry was less than 4 seconds ago...aborting retry playback");
642 rb_shell_player_set_playing_source (player, NULL);
643 } else {
644 rb_shell_player_play_entry (player, entry, NULL);
647 break;
648 case RB_SOURCE_EOF_NEXT:
650 GError *error = NULL;
652 if (!rb_shell_player_do_next (player, &error)) {
653 if (error->domain != RB_SHELL_PLAYER_ERROR ||
654 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
655 g_warning ("Unhandled error: %s", error->message);
656 else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
657 rb_shell_player_set_playing_source (player, NULL);
660 break;
663 if (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) == NULL) {
664 rb_debug ("updating play statistics");
665 rb_source_update_play_statistics (source,
666 player->priv->db,
667 entry);
670 if (entry != NULL) {
671 rhythmdb_entry_unref (entry);
675 static void
676 rb_shell_player_handle_eos (RBShellPlayer *player)
678 rb_debug ("handling eos!");
680 GDK_THREADS_ENTER ();
682 rb_shell_player_handle_eos_unlocked (player);
684 GDK_THREADS_LEAVE ();
687 static void
688 rb_shell_player_init (RBShellPlayer *player)
690 GError *error = NULL;
692 player->priv = RB_SHELL_PLAYER_GET_PRIVATE (player);
694 player->priv->mmplayer = rb_player_new (&error);
695 if (error != NULL) {
696 GtkWidget *dialog;
697 dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
698 GTK_MESSAGE_ERROR,
699 GTK_BUTTONS_CLOSE,
700 _("Failed to create the player: %s"),
701 error->message);
702 gtk_dialog_run (GTK_DIALOG (dialog));
703 exit (1);
706 gtk_box_set_spacing (GTK_BOX (player), 12);
707 gtk_container_set_border_width (GTK_CONTAINER (player), 3);
709 g_signal_connect_object (G_OBJECT (player->priv->mmplayer),
710 "info",
711 G_CALLBACK (info_available_cb),
712 player, 0);
714 g_signal_connect_object (player->priv->mmplayer,
715 "eos",
716 G_CALLBACK (rb_shell_player_handle_eos),
717 player, G_CONNECT_SWAPPED);
719 g_signal_connect_object (G_OBJECT (player->priv->mmplayer),
720 "tick",
721 G_CALLBACK (tick_cb),
722 player, 0);
724 g_signal_connect_object (G_OBJECT (player->priv->mmplayer),
725 "error",
726 G_CALLBACK (error_cb),
727 player, 0);
729 g_signal_connect_object (G_OBJECT (player->priv->mmplayer),
730 "buffering",
731 G_CALLBACK (buffering_cb),
732 player, 0);
734 g_signal_connect (G_OBJECT (gnome_vfs_get_volume_monitor ()),
735 "volume-pre-unmount",
736 G_CALLBACK (volume_pre_unmount_cb),
737 player);
739 player->priv->gconf_play_order_id =
740 eel_gconf_notification_add (CONF_STATE_PLAY_ORDER,
741 (GConfClientNotifyFunc)gconf_play_order_changed,
742 player);
744 player->priv->header_widget = rb_header_new (player);
745 gtk_widget_show (GTK_WIDGET (player->priv->header_widget));
746 gtk_box_pack_start (GTK_BOX (player), GTK_WIDGET (player->priv->header_widget), TRUE, TRUE, 0);
748 player->priv->volume = eel_gconf_get_float (CONF_STATE_VOLUME);
750 g_signal_connect (player, "notify::playing",
751 G_CALLBACK (reemit_playing_signal), NULL);
753 player->priv->gconf_song_position_slider_visibility_id =
754 eel_gconf_notification_add (CONF_UI_SONG_POSITION_SLIDER_HIDDEN,
755 (GConfClientNotifyFunc) gconf_song_position_slider_visibility_changed,
756 player);
758 #ifdef HAVE_MMKEYS
759 /* Enable Multimedia Keys */
760 rb_shell_player_init_mmkeys (player);
761 #endif /* HAVE_MMKEYS */
764 static void
765 rb_shell_player_set_source_internal (RBShellPlayer *player,
766 RBSource *source)
768 if (player->priv->selected_source != NULL) {
769 RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
770 GList *property_views = rb_source_get_property_views (player->priv->selected_source);
772 if (songs != NULL) {
773 g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
774 G_CALLBACK (rb_shell_player_entry_activated_cb),
775 player);
778 for (; property_views; property_views = property_views->next) {
779 g_signal_handlers_disconnect_by_func (G_OBJECT (property_views->data),
780 G_CALLBACK (rb_shell_player_property_row_activated_cb),
781 player);
784 g_list_free (property_views);
787 player->priv->selected_source = source;
789 rb_debug ("selected source %p", player->priv->selected_source);
791 rb_shell_player_sync_with_selected_source (player);
792 rb_shell_player_sync_buttons (player);
794 if (player->priv->selected_source != NULL) {
795 RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
796 GList *property_views = rb_source_get_property_views (player->priv->selected_source);
798 if (songs)
799 g_signal_connect_object (G_OBJECT (songs),
800 "entry-activated",
801 G_CALLBACK (rb_shell_player_entry_activated_cb),
802 player, 0);
803 for (; property_views; property_views = property_views->next)
804 g_signal_connect_object (G_OBJECT (property_views->data),
805 "property-activated",
806 G_CALLBACK (rb_shell_player_property_row_activated_cb),
807 player, 0);
809 g_list_free (property_views);
812 /* If we're not playing, change the play order's view of the current source;
813 * if the selected source is the queue, however, set it to NULL so it'll stop
814 * once the queue is empty.
816 if (player->priv->current_playing_source == NULL) {
817 RBSource *source = player->priv->selected_source;
818 if (source == RB_SOURCE (player->priv->queue_source))
819 source = NULL;
821 rb_play_order_playing_source_changed (player->priv->play_order, source);
825 static void
826 rb_shell_player_set_db_internal (RBShellPlayer *player,
827 RhythmDB *db)
829 if (player->priv->db != NULL) {
830 g_signal_handlers_disconnect_by_func (player->priv->db,
831 G_CALLBACK (rb_shell_player_entry_changed_cb),
832 player);
835 player->priv->db = db;
837 if (player->priv->db != NULL) {
838 /* Listen for changed entries to update metadata display */
839 g_signal_connect_object (G_OBJECT (player->priv->db),
840 "entry_changed",
841 G_CALLBACK (rb_shell_player_entry_changed_cb),
842 player, 0);
846 static void
847 rb_shell_player_set_queue_source_internal (RBShellPlayer *player,
848 RBPlayQueueSource *source)
850 if (player->priv->queue_source != NULL) {
851 RBEntryView *sidebar;
853 g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
854 g_signal_handlers_disconnect_by_func (sidebar,
855 G_CALLBACK (rb_shell_player_entry_activated_cb),
856 player);
857 g_object_unref (sidebar);
859 if (player->priv->queue_play_order != NULL) {
860 g_signal_handlers_disconnect_by_func (player->priv->queue_play_order,
861 G_CALLBACK (rb_shell_player_play_order_update_cb),
862 player);
863 g_object_unref (player->priv->queue_play_order);
868 player->priv->queue_source = source;
870 if (player->priv->queue_source != NULL) {
871 RBEntryView *sidebar;
873 player->priv->queue_play_order = rb_play_order_new ("queue", player);
874 g_signal_connect_object (G_OBJECT (player->priv->queue_play_order),
875 "have_next_previous_changed",
876 G_CALLBACK (rb_shell_player_play_order_update_cb),
877 player, 0);
878 rb_shell_player_play_order_update_cb (player->priv->play_order,
879 FALSE, FALSE,
880 player);
881 rb_play_order_playing_source_changed (player->priv->queue_play_order,
882 RB_SOURCE (player->priv->queue_source));
884 g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
885 g_signal_connect_object (G_OBJECT (sidebar),
886 "entry-activated",
887 G_CALLBACK (rb_shell_player_entry_activated_cb),
888 player, 0);
889 g_object_unref (sidebar);
893 static void
894 rb_shell_player_finalize (GObject *object)
896 RBShellPlayer *player;
898 g_return_if_fail (object != NULL);
899 g_return_if_fail (RB_IS_SHELL_PLAYER (object));
901 player = RB_SHELL_PLAYER (object);
903 g_return_if_fail (player->priv != NULL);
905 eel_gconf_notification_remove (player->priv->gconf_play_order_id);
907 eel_gconf_set_float (CONF_STATE_VOLUME, player->priv->volume);
909 g_object_unref (player->priv->mmplayer);
910 g_object_unref (player->priv->play_order);
911 g_object_unref (player->priv->queue_play_order);
913 G_OBJECT_CLASS (rb_shell_player_parent_class)->finalize (object);
916 static void
917 rb_shell_player_set_property (GObject *object,
918 guint prop_id,
919 const GValue *value,
920 GParamSpec *pspec)
922 RBShellPlayer *player = RB_SHELL_PLAYER (object);
924 switch (prop_id) {
925 case PROP_SOURCE:
926 rb_shell_player_set_source_internal (player, g_value_get_object (value));
927 break;
928 case PROP_UI_MANAGER:
929 player->priv->ui_manager = g_value_get_object (value);
930 break;
931 case PROP_DB:
932 rb_shell_player_set_db_internal (player, g_value_get_object (value));
933 break;
934 case PROP_ACTION_GROUP:
935 player->priv->actiongroup = g_value_get_object (value);
936 break;
937 case PROP_PLAY_ORDER:
938 eel_gconf_set_string (CONF_STATE_PLAY_ORDER,
939 g_value_get_string (value));
940 break;
941 case PROP_VOLUME:
942 player->priv->volume = g_value_get_float (value);
943 rb_shell_player_sync_volume (player, FALSE);
944 break;
945 case PROP_STATUSBAR:
946 player->priv->statusbar_widget = g_value_get_object (value);
947 break;
948 case PROP_QUEUE_SOURCE:
949 rb_shell_player_set_queue_source_internal (player, g_value_get_object (value));
950 break;
951 case PROP_QUEUE_ONLY:
952 player->priv->queue_only = g_value_get_boolean (value);
953 break;
954 default:
955 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
956 break;
960 static void
961 rb_shell_player_get_property (GObject *object,
962 guint prop_id,
963 GValue *value,
964 GParamSpec *pspec)
966 RBShellPlayer *player = RB_SHELL_PLAYER (object);
968 switch (prop_id) {
969 case PROP_SOURCE:
970 g_value_set_object (value, player->priv->selected_source);
971 break;
972 case PROP_UI_MANAGER:
973 g_value_set_object (value, player->priv->ui_manager);
974 break;
975 case PROP_DB:
976 g_value_set_object (value, player->priv->db);
977 break;
978 case PROP_ACTION_GROUP:
979 g_value_set_object (value, player->priv->actiongroup);
980 break;
981 case PROP_PLAY_ORDER:
983 char *play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER);
984 if (!play_order)
985 play_order = g_strdup ("linear");
986 g_value_set_string_take_ownership (value, play_order);
987 break;
989 case PROP_PLAYING:
990 g_value_set_boolean (value, rb_player_playing (player->priv->mmplayer));
991 break;
992 case PROP_VOLUME:
993 g_value_set_float (value, player->priv->volume);
994 break;
995 case PROP_STATUSBAR:
996 g_value_set_object (value, player->priv->statusbar_widget);
997 break;
998 case PROP_QUEUE_SOURCE:
999 g_value_set_object (value, player->priv->queue_source);
1000 break;
1001 case PROP_QUEUE_ONLY:
1002 g_value_set_boolean (value, player->priv->queue_only);
1003 break;
1004 case PROP_STREAM_SONG:
1005 g_value_set_string (value, player->priv->song);
1006 break;
1007 case PROP_PLAYING_FROM_QUEUE:
1008 g_value_set_boolean (value, player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source));
1009 break;
1010 case PROP_PLAYER:
1011 g_value_set_object (value, player->priv->mmplayer);
1012 break;
1013 default:
1014 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1015 break;
1019 GQuark
1020 rb_shell_player_error_quark (void)
1022 static GQuark quark = 0;
1023 if (!quark)
1024 quark = g_quark_from_static_string ("rb_shell_player_error");
1026 return quark;
1029 void
1030 rb_shell_player_set_selected_source (RBShellPlayer *player,
1031 RBSource *source)
1033 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
1034 g_return_if_fail (RB_IS_SOURCE (source));
1036 g_object_set (G_OBJECT (player),
1037 "source", source,
1038 NULL);
1041 RBSource *
1042 rb_shell_player_get_playing_source (RBShellPlayer *player)
1044 return player->priv->current_playing_source;
1047 RBSource *
1048 rb_shell_player_get_active_source (RBShellPlayer *player)
1050 return player->priv->source;
1053 RBShellPlayer *
1054 rb_shell_player_new (RhythmDB *db,
1055 GtkUIManager *mgr,
1056 GtkActionGroup *actiongroup)
1058 return g_object_new (RB_TYPE_SHELL_PLAYER,
1059 "ui-manager", mgr,
1060 "action-group", actiongroup,
1061 "db", db,
1062 NULL);
1065 RhythmDBEntry *
1066 rb_shell_player_get_playing_entry (RBShellPlayer *player)
1068 RBPlayOrder *porder;
1069 RhythmDBEntry *entry;
1071 if (player->priv->current_playing_source == NULL)
1072 return NULL;
1074 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
1075 porder = player->priv->queue_play_order;
1076 else
1077 porder = player->priv->play_order;
1079 if (porder == NULL) {
1080 return NULL;
1083 entry = rb_play_order_get_playing_entry (porder);
1085 return entry;
1088 typedef struct {
1089 RBShellPlayer *player;
1090 char *location;
1091 } OpenLocationThreadData;
1093 static void
1094 playlist_entry_cb (TotemPlParser *playlist,
1095 const char *uri,
1096 const char *title,
1097 const char *genre,
1098 RBShellPlayer *player)
1100 rb_debug ("adding stream url %s", uri);
1101 g_queue_push_tail (player->priv->playlist_urls, g_strdup (uri));
1104 static gpointer
1105 open_location_thread (OpenLocationThreadData *data)
1107 TotemPlParser *playlist;
1108 TotemPlParserResult playlist_result;
1110 GDK_THREADS_ENTER ();
1111 rb_statusbar_set_progress (data->player->priv->statusbar_widget, 0.0, _("Connecting"));
1112 GDK_THREADS_LEAVE ();
1114 playlist = totem_pl_parser_new ();
1115 g_signal_connect_data (G_OBJECT (playlist), "entry",
1116 G_CALLBACK (playlist_entry_cb),
1117 data->player, NULL, 0);
1118 totem_pl_parser_add_ignored_mimetype (playlist, "x-directory/normal");
1120 playlist_result = totem_pl_parser_parse (playlist, data->location, FALSE);
1121 g_object_unref (playlist);
1123 if (playlist_result == TOTEM_PL_PARSER_RESULT_SUCCESS) {
1124 if (g_queue_is_empty (data->player->priv->playlist_urls)) {
1125 GError *error = g_error_new (RB_SHELL_PLAYER_ERROR,
1126 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
1127 _("Playlist was empty"));
1128 GDK_THREADS_ENTER ();
1129 rb_shell_player_error (data->player, TRUE, error);
1130 g_error_free (error);
1131 GDK_THREADS_LEAVE ();
1132 } else {
1133 char *location;
1135 location = g_queue_pop_head (data->player->priv->playlist_urls);
1136 rb_debug ("playing first stream url %s", data->location);
1137 rb_shell_player_open_playlist_url (data->player, location);
1138 g_free (location);
1140 } else {
1141 /* if we can't parse it as a playlist, just try playing it */
1142 rb_debug ("playlist parser failed, playing %s directly", data->location);
1143 rb_shell_player_open_playlist_url (data->player, data->location);
1146 g_free (data);
1147 return NULL;
1150 static gboolean
1151 rb_shell_player_open_location (RBShellPlayer *player,
1152 const char *location,
1153 GError **error)
1155 char *unescaped;
1156 gboolean was_playing;
1158 unescaped = gnome_vfs_unescape_string_for_display (location);
1159 rb_debug ("Opening %s...", unescaped);
1160 g_free (unescaped);
1162 was_playing = rb_player_playing (player->priv->mmplayer);
1164 g_free (player->priv->song);
1165 player->priv->song = NULL;
1166 g_object_notify (G_OBJECT (player), "stream-song");
1168 if (rb_source_try_playlist (player->priv->source)) {
1169 OpenLocationThreadData *data;
1171 data = g_new0 (OpenLocationThreadData, 1);
1172 data->player = player;
1174 /* dispose of any existing playlist urls */
1175 if (player->priv->playlist_urls) {
1176 g_queue_foreach (player->priv->playlist_urls,
1177 (GFunc) g_free,
1178 NULL);
1179 g_queue_free (player->priv->playlist_urls);
1180 player->priv->playlist_urls = NULL;
1182 player->priv->playlist_urls = g_queue_new ();
1184 /* add http:// as a prefix, if it doesn't have a URI scheme */
1185 if (strstr (location, "://"))
1186 data->location = g_strdup (location);
1187 else
1188 data->location = g_strconcat ("http://", location, NULL);
1190 g_thread_create ((GThreadFunc)open_location_thread, data, FALSE, NULL);
1191 return TRUE;
1192 } else {
1193 if (!rb_player_open (player->priv->mmplayer, location, error))
1194 return FALSE;
1196 if (!rb_player_play (player->priv->mmplayer, error))
1197 return FALSE;
1199 g_object_notify (G_OBJECT (player), "playing");
1202 return TRUE;
1205 static gboolean
1206 rb_shell_player_open_entry (RBShellPlayer *player,
1207 RhythmDBEntry *entry,
1208 GError **error)
1210 char *location;
1211 gboolean result;
1213 location = rhythmdb_entry_get_playback_uri (entry);
1214 if (location == NULL)
1215 return FALSE;
1217 result = rb_shell_player_open_location (player, location, error);
1218 g_free (location);
1220 return result;
1224 * rb_shell_player_play:
1225 * @player: a #RBShellPlayer
1226 * @error: error return
1228 * Starts playback, if it is not already playing.
1230 * @return: whether playback is now occurring (TRUE when successfully started
1231 * or already playing).
1233 gboolean
1234 rb_shell_player_play (RBShellPlayer *player,
1235 GError **error)
1237 RBEntryView *songs;
1239 if (rb_player_playing (player->priv->mmplayer))
1240 return TRUE;
1242 if (!rb_player_play (player->priv->mmplayer, error))
1243 return FALSE;
1245 songs = rb_source_get_entry_view (player->priv->current_playing_source);
1246 if (songs)
1247 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
1249 rb_shell_player_sync_with_source (player);
1250 rb_shell_player_sync_buttons (player);
1252 return TRUE;
1255 static void
1256 rb_shell_player_set_entry_playback_error (RBShellPlayer *player,
1257 RhythmDBEntry *entry,
1258 char *message)
1260 GValue value = { 0, };
1262 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
1264 g_value_init (&value, G_TYPE_STRING);
1265 g_value_set_string (&value, message);
1266 rhythmdb_entry_set (player->priv->db,
1267 entry,
1268 RHYTHMDB_PROP_PLAYBACK_ERROR,
1269 &value);
1270 g_value_unset (&value);
1271 rhythmdb_commit (player->priv->db);
1274 static gboolean
1275 do_next_idle (RBShellPlayer *player)
1277 /* use the EOS callback, so that EOF_SOURCE_ conditions are handled properly */
1278 rb_shell_player_handle_eos (player);
1279 player->priv->do_next_idle_id = 0;
1281 return FALSE;
1284 static gboolean
1285 rb_shell_player_set_playing_entry (RBShellPlayer *player,
1286 RhythmDBEntry *entry,
1287 gboolean out_of_order,
1288 GError **error)
1290 GError *tmp_error = NULL;
1291 const char *location;
1292 GValue val = {0,};
1294 g_return_val_if_fail (player->priv->current_playing_source != NULL, TRUE);
1295 g_return_val_if_fail (entry != NULL, TRUE);
1297 if (out_of_order) {
1298 RBPlayOrder *porder;
1299 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
1300 porder = player->priv->queue_play_order;
1301 else
1302 porder = player->priv->play_order;
1303 rb_play_order_set_playing_entry (porder, entry);
1306 if (!rb_shell_player_open_entry (player, entry, &tmp_error))
1307 goto lose;
1308 rb_shell_player_sync_replaygain (player, entry);
1310 rb_debug ("Success!");
1311 /* clear error on successful playback */
1312 g_value_init (&val, G_TYPE_STRING);
1313 g_value_set_string (&val, NULL);
1314 rhythmdb_entry_set (player->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
1315 g_value_unset (&val);
1317 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1318 g_signal_emit (G_OBJECT (player),
1319 rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
1320 entry);
1321 g_signal_emit (G_OBJECT (player),
1322 rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
1323 location);
1325 rb_shell_player_sync_with_source (player);
1326 rb_shell_player_sync_buttons (player);
1328 return TRUE;
1329 lose:
1330 /* Ignore errors, shutdown the player */
1331 rb_player_close (player->priv->mmplayer, NULL);
1332 if (tmp_error == NULL)
1333 tmp_error = g_error_new (RB_SHELL_PLAYER_ERROR,
1334 RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
1335 "Problem occurred without error being set. "
1336 "This is a bug in Rhythmbox or GStreamer.");
1337 /* Mark this song as failed */
1338 rb_shell_player_set_entry_playback_error (player, entry, tmp_error->message);
1339 g_propagate_error (error, tmp_error);
1341 rb_shell_player_sync_with_source (player);
1342 rb_shell_player_sync_buttons (player);
1343 g_object_notify (G_OBJECT (player), "playing");
1345 return FALSE;
1348 static void
1349 gconf_play_order_changed (GConfClient *client,
1350 guint cnxn_id,
1351 GConfEntry *entry,
1352 RBShellPlayer *player)
1354 rb_debug ("gconf play order changed");
1355 player->priv->syncing_state = TRUE;
1356 rb_shell_player_sync_play_order (player);
1357 rb_shell_player_sync_buttons (player);
1358 rb_shell_player_sync_control_state (player);
1359 g_object_notify (G_OBJECT (player), "play-order");
1360 player->priv->syncing_state = FALSE;
1363 gboolean
1364 rb_shell_player_get_playback_state (RBShellPlayer *player,
1365 gboolean *shuffle,
1366 gboolean *repeat)
1368 int i, j;
1369 char *play_order;
1371 play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER);
1372 if (!play_order) {
1373 g_warning (CONF_STATE_PLAY_ORDER " gconf key not found!");
1374 return FALSE;
1377 for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
1378 for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
1379 if (!strcmp (play_order, state_to_play_order[i][j]))
1380 goto found;
1382 g_free (play_order);
1383 return FALSE;
1385 found:
1386 *shuffle = i > 0;
1387 *repeat = j > 0;
1388 g_free (play_order);
1389 return TRUE;
1392 static void
1393 rb_shell_player_set_play_order (RBShellPlayer *player,
1394 const gchar *new_val)
1396 char *old_val;
1398 g_object_get (player, "play-order", &old_val, NULL);
1399 if (strcmp (old_val, new_val) != 0) {
1400 /* The notify signal will be emitted by the gconf notifier */
1401 eel_gconf_set_string (CONF_STATE_PLAY_ORDER, new_val);
1403 g_free (old_val);
1406 void
1407 rb_shell_player_set_playback_state (RBShellPlayer *player,
1408 gboolean shuffle,
1409 gboolean repeat)
1411 const char *neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
1412 rb_shell_player_set_play_order (player, neworder);
1415 static void
1416 rb_shell_player_sync_play_order (RBShellPlayer *player)
1418 char *new_play_order;
1419 RhythmDBEntry *playing_entry = NULL;
1420 RBSource *source;
1422 new_play_order = eel_gconf_get_string (CONF_STATE_PLAY_ORDER);
1423 if (new_play_order == NULL) {
1424 g_warning (CONF_STATE_PLAY_ORDER " gconf key not found!");
1425 new_play_order = g_strdup ("linear");
1428 if (player->priv->play_order != NULL) {
1429 playing_entry = rb_play_order_get_playing_entry (player->priv->play_order);
1430 g_signal_handlers_disconnect_by_func (player->priv->play_order,
1431 G_CALLBACK (rb_shell_player_play_order_update_cb),
1432 player);
1433 g_object_unref (player->priv->play_order);
1436 player->priv->play_order = rb_play_order_new (new_play_order, player);
1437 g_signal_connect_object (player->priv->play_order,
1438 "have_next_previous_changed",
1439 G_CALLBACK (rb_shell_player_play_order_update_cb),
1440 player, 0);
1441 rb_shell_player_play_order_update_cb (player->priv->play_order,
1442 FALSE, FALSE,
1443 player);
1445 source = player->priv->current_playing_source;
1446 if (source == NULL) {
1447 source = player->priv->selected_source;
1449 rb_play_order_playing_source_changed (player->priv->play_order, source);
1451 if (playing_entry != NULL) {
1452 rb_play_order_set_playing_entry (player->priv->play_order, playing_entry);
1453 rhythmdb_entry_unref (playing_entry);
1456 g_free (new_play_order);
1459 static void
1460 rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
1461 gboolean has_next,
1462 gboolean has_previous,
1463 RBShellPlayer *player)
1465 /* we cannot depend on the values of has_next, has_previous or porder
1466 * since this can be called for the main porder, queue porder, etc
1468 gboolean have_next = FALSE;
1469 gboolean have_previous = FALSE;
1470 GtkAction *action;
1471 RhythmDBEntry *entry;
1473 entry = rb_shell_player_get_playing_entry (player);
1474 if (entry != NULL) {
1475 have_next = TRUE;
1476 have_previous = TRUE;
1477 rhythmdb_entry_unref (entry);
1478 } else {
1479 if (player->priv->current_playing_source &&
1480 (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_NEXT)) {
1481 have_next = rb_play_order_has_next (player->priv->play_order);
1482 have_previous = rb_play_order_has_previous (player->priv->play_order);
1484 if (player->priv->queue_play_order) {
1485 have_next |= rb_play_order_has_next (player->priv->queue_play_order);
1486 have_previous |= rb_play_order_has_previous (player->priv->queue_play_order);
1490 action = gtk_action_group_get_action (player->priv->actiongroup,
1491 "ControlPrevious");
1492 g_object_set (action, "sensitive", have_previous, NULL);
1493 action = gtk_action_group_get_action (player->priv->actiongroup,
1494 "ControlNext");
1495 g_object_set (action, "sensitive", have_next, NULL);
1498 static gboolean
1499 rb_shell_player_jump_to_current_idle (RBShellPlayer *player)
1501 rb_shell_player_jump_to_current (player);
1502 return FALSE;
1505 static void
1506 rb_shell_player_sync_song_position_slider_visibility (RBShellPlayer *player)
1508 gboolean visible;
1509 GtkAction *action;
1511 visible = !eel_gconf_get_boolean (CONF_UI_SONG_POSITION_SLIDER_HIDDEN);
1513 rb_header_set_show_position_slider (player->priv->header_widget,
1514 visible);
1516 action = gtk_action_group_get_action (player->priv->actiongroup,
1517 "ViewSongPositionSlider");
1518 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
1519 visible);
1522 void
1523 rb_shell_player_jump_to_current (RBShellPlayer *player)
1525 RBSource *source;
1526 RhythmDBEntry *entry;
1527 RBEntryView *songs;
1529 source = player->priv->current_playing_source ? player->priv->current_playing_source :
1530 player->priv->selected_source;
1532 songs = rb_source_get_entry_view (source);
1533 entry = rb_shell_player_get_playing_entry (player);
1534 if (songs != NULL) {
1535 if (entry != NULL) {
1536 rb_entry_view_scroll_to_entry (songs, entry);
1537 rb_entry_view_select_entry (songs, entry);
1538 } else {
1539 rb_entry_view_select_none (songs);
1543 if (entry != NULL) {
1544 rhythmdb_entry_unref (entry);
1548 static void
1549 swap_playing_source (RBShellPlayer *player,
1550 RBSource *new_source)
1552 if (player->priv->current_playing_source != NULL) {
1553 RBEntryView *old_songs = rb_source_get_entry_view (player->priv->current_playing_source);
1554 if (old_songs)
1555 rb_entry_view_set_state (old_songs, RB_ENTRY_VIEW_NOT_PLAYING);
1557 if (new_source != NULL) {
1558 RBEntryView *new_songs = rb_source_get_entry_view (new_source);
1560 if (new_songs) {
1561 rb_entry_view_set_state (new_songs, RB_ENTRY_VIEW_PLAYING);
1562 rb_shell_player_set_playing_source (player, new_source);
1567 gboolean
1568 rb_shell_player_do_previous (RBShellPlayer *player,
1569 GError **error)
1571 RhythmDBEntry *entry = NULL;
1572 RBSource *new_source;
1574 if (player->priv->current_playing_source == NULL) {
1575 g_set_error (error,
1576 RB_SHELL_PLAYER_ERROR,
1577 RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
1578 _("Not currently playing"));
1579 return FALSE;
1582 rb_debug ("going to previous");
1584 if (player->priv->queue_play_order) {
1585 entry = rb_play_order_get_previous (player->priv->queue_play_order);
1586 if (entry != NULL) {
1587 new_source = RB_SOURCE (player->priv->queue_source);
1588 rb_play_order_go_previous (player->priv->queue_play_order);
1592 if (entry == NULL) {
1593 new_source = player->priv->source;
1594 entry = rb_play_order_get_previous (player->priv->play_order);
1595 if (entry)
1596 rb_play_order_go_previous (player->priv->play_order);
1599 if (entry != NULL) {
1600 rb_debug ("previous song found, doing previous");
1601 if (new_source != player->priv->current_playing_source)
1602 swap_playing_source (player, new_source);
1604 if (!rb_shell_player_set_playing_entry (player, entry, FALSE, error)) {
1605 rhythmdb_entry_unref (entry);
1606 return FALSE;
1609 rb_shell_player_jump_to_current (player);
1610 rhythmdb_entry_unref (entry);
1611 } else {
1612 rb_debug ("no previous song found, signaling error");
1613 g_set_error (error,
1614 RB_SHELL_PLAYER_ERROR,
1615 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
1616 _("No previous song"));
1617 rb_shell_player_set_playing_source (player, NULL);
1618 return FALSE;
1621 return TRUE;
1624 gboolean
1625 rb_shell_player_do_next (RBShellPlayer *player,
1626 GError **error)
1628 RBSource *new_source = NULL;
1629 RhythmDBEntry *entry = NULL;
1630 gboolean rv = TRUE;
1632 if (player->priv->source == NULL)
1633 return TRUE;
1635 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source)) {
1636 /* Look for another entry in the queue, and fall back to the playing
1637 * source if there isn't one. Always call _get_next on the queue
1638 * so the current entry is removed.
1640 entry = rb_play_order_get_next (player->priv->queue_play_order);
1641 rb_play_order_go_next (player->priv->queue_play_order);
1643 if (entry != NULL) {
1644 new_source = RB_SOURCE (player->priv->queue_source);
1645 } else {
1646 /* If we haven't played anything from the playing source yet,
1647 * _get_playing_entry will return NULL, so we'll have to advance
1648 * the play order to get an entry to play.
1650 entry = rb_play_order_get_playing_entry (player->priv->play_order);
1651 if (entry == NULL) {
1652 entry = rb_play_order_get_next (player->priv->play_order);
1653 rb_play_order_go_next (player->priv->play_order);
1656 new_source = player->priv->source;
1658 } else {
1659 /* Advance the play order, and then let the queue override it. */
1660 entry = rb_play_order_get_next (player->priv->play_order);
1661 if (entry != NULL) {
1662 new_source = player->priv->source;
1663 rb_play_order_go_next (player->priv->play_order);
1666 if (player->priv->queue_play_order) {
1667 RhythmDBEntry *queue_entry;
1669 queue_entry = rb_play_order_get_next (player->priv->queue_play_order);
1670 if (queue_entry != NULL) {
1671 if (entry != NULL) {
1672 rhythmdb_entry_unref (entry);
1674 entry = queue_entry;
1675 new_source = RB_SOURCE (player->priv->queue_source);
1676 rb_play_order_go_next (player->priv->queue_play_order);
1681 /* play the new entry */
1682 if (entry != NULL) {
1683 /* if the entry view containing the playing entry changed, update it */
1684 if (new_source != player->priv->current_playing_source)
1685 swap_playing_source (player, new_source);
1687 if (!rb_shell_player_set_playing_entry (player, entry, FALSE, error))
1688 rv = FALSE;
1689 } else {
1690 g_set_error (error,
1691 RB_SHELL_PLAYER_ERROR,
1692 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
1693 _("No next song"));
1694 rb_debug ("No next entry, stopping playback");
1695 rb_shell_player_set_playing_source (player, NULL);
1696 rb_play_order_set_playing_entry (player->priv->play_order, NULL);
1697 g_object_notify (G_OBJECT (player), "playing");
1698 rv = FALSE;
1701 g_idle_add ((GSourceFunc)rb_shell_player_jump_to_current_idle, player);
1703 if (entry != NULL) {
1704 rhythmdb_entry_unref (entry);
1707 return rv;
1710 static gboolean
1711 rb_shell_player_do_previous_or_seek (RBShellPlayer *player,
1712 GError **error)
1714 rb_debug ("previous");
1715 /* If we're in the first 3 seconds go to the previous song,
1716 * else restart the current one.
1718 if (player->priv->current_playing_source != NULL
1719 && rb_source_can_pause (player->priv->source)
1720 && rb_player_get_time (player->priv->mmplayer) > 3) {
1722 /* see if there's anything to go back to */
1723 gboolean have_previous;
1724 have_previous = rb_play_order_has_previous (player->priv->play_order);
1725 if (player->priv->queue_play_order)
1726 have_previous |= rb_play_order_has_previous (player->priv->queue_play_order);
1728 if (have_previous) {
1729 rb_debug ("after 3 second previous, restarting song");
1730 rb_player_set_time (player->priv->mmplayer, 0);
1731 rb_header_sync_time (player->priv->header_widget);
1732 return TRUE;
1736 return rb_shell_player_do_previous (player, error);
1739 static void
1740 rb_shell_player_cmd_previous (GtkAction *action,
1741 RBShellPlayer *player)
1743 GError *error = NULL;
1745 if (!rb_shell_player_do_previous_or_seek (player, &error)) {
1746 if (error->domain != RB_SHELL_PLAYER_ERROR ||
1747 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
1748 g_warning ("cmd_previous: Unhandled error: %s", error->message);
1749 else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
1750 rb_shell_player_set_playing_source (player, NULL);
1754 static void
1755 rb_shell_player_cmd_next (GtkAction *action,
1756 RBShellPlayer *player)
1758 GError *error = NULL;
1760 if (!rb_shell_player_do_next (player, &error)) {
1761 if (error->domain != RB_SHELL_PLAYER_ERROR ||
1762 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
1763 g_warning ("cmd_next: Unhandled error: %s", error->message);
1764 else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
1765 rb_shell_player_set_playing_source (player, NULL);
1769 void
1770 rb_shell_player_play_entry (RBShellPlayer *player,
1771 RhythmDBEntry *entry,
1772 RBSource *source)
1774 GError *error = NULL;
1776 if (source == NULL)
1777 source = player->priv->selected_source;
1778 rb_shell_player_set_playing_source (player, source);
1780 if (!rb_shell_player_set_playing_entry (player, entry, TRUE, &error)) {
1781 rb_shell_player_error (player, FALSE, error);
1782 g_clear_error (&error);
1786 static void
1787 rb_shell_player_cmd_volume_up (GtkAction *action,
1788 RBShellPlayer *player)
1790 rb_shell_player_set_volume_relative (player, 0.1, NULL);
1793 static void
1794 rb_shell_player_cmd_volume_down (GtkAction *action,
1795 RBShellPlayer *player)
1797 rb_shell_player_set_volume_relative (player, -0.1, NULL);
1800 static void
1801 rb_shell_player_cmd_play (GtkAction *action,
1802 RBShellPlayer *player)
1804 GError *error = NULL;
1805 rb_debug ("play!");
1806 if (!rb_shell_player_playpause (player, FALSE, &error))
1807 rb_error_dialog (NULL,
1808 _("Couldn't start playback"),
1809 "%s", (error) ? error->message : "(null)");
1810 g_clear_error (&error);
1813 /* unused parameter can't be removed without breaking dbus interface compatibility */
1814 gboolean
1815 rb_shell_player_playpause (RBShellPlayer *player,
1816 gboolean unused,
1817 GError **error)
1819 gboolean ret;
1820 RBEntryView *songs;
1822 rb_debug ("doing playpause");
1824 g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), TRUE);
1826 ret = TRUE;
1828 if (rb_player_playing (player->priv->mmplayer)) {
1829 if (player->priv->source == NULL) {
1830 rb_debug ("playing source is already NULL");
1831 } else if (rb_source_can_pause (player->priv->source)) {
1832 rb_debug ("pausing mm player");
1833 rb_player_pause (player->priv->mmplayer);
1834 songs = rb_source_get_entry_view (player->priv->current_playing_source);
1835 if (songs)
1836 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PAUSED);
1837 } else {
1838 rb_debug ("setting playing source to NULL");
1839 rb_shell_player_set_playing_source (player, NULL);
1841 } else {
1842 RhythmDBEntry *entry;
1843 RBSource *new_source;
1844 gboolean out_of_order = FALSE;
1846 if (player->priv->source == NULL) {
1847 /* no current stream, pull one in from the currently
1848 * selected source */
1849 rb_debug ("no playing source, using selected source");
1850 rb_shell_player_set_playing_source (player, player->priv->selected_source);
1852 new_source = player->priv->current_playing_source;
1854 entry = rb_shell_player_get_playing_entry (player);
1855 if (entry == NULL) {
1856 /* queue takes precedence over selection */
1857 if (player->priv->queue_play_order) {
1858 entry = rb_play_order_get_next (player->priv->queue_play_order);
1859 if (entry != NULL) {
1860 new_source = RB_SOURCE (player->priv->queue_source);
1861 rb_play_order_go_next (player->priv->queue_play_order);
1865 /* selection takes precedence over first item in play order */
1866 if (entry == NULL) {
1867 GList *selection = NULL;
1869 songs = rb_source_get_entry_view (player->priv->source);
1870 if (songs)
1871 selection = rb_entry_view_get_selected_entries (songs);
1873 if (selection != NULL) {
1874 rb_debug ("choosing first selected entry");
1875 entry = (RhythmDBEntry*) selection->data;
1876 if (entry)
1877 out_of_order = TRUE;
1879 g_list_free (selection);
1883 /* play order is last */
1884 if (entry == NULL) {
1885 rb_debug ("getting entry from play order");
1886 entry = rb_play_order_get_next (player->priv->play_order);
1887 if (entry != NULL)
1888 rb_play_order_go_next (player->priv->play_order);
1891 if (entry != NULL) {
1892 /* if the entry view containing the playing entry changed, update it */
1893 if (new_source != player->priv->current_playing_source)
1894 swap_playing_source (player, new_source);
1896 if (!rb_shell_player_set_playing_entry (player, entry, out_of_order, error))
1897 ret = FALSE;
1898 rb_shell_player_jump_to_current (player);
1900 } else {
1901 if (!rb_shell_player_play (player, error)) {
1902 rb_shell_player_set_playing_source (player, NULL);
1903 ret = FALSE;
1907 if (entry != NULL) {
1908 rhythmdb_entry_unref (entry);
1912 rb_shell_player_sync_with_source (player);
1913 rb_shell_player_sync_buttons (player);
1914 g_object_notify (G_OBJECT (player), "playing");
1916 return ret;
1919 static void
1920 rb_shell_player_sync_control_state (RBShellPlayer *player)
1922 gboolean shuffle, repeat;
1923 GtkAction *action;
1924 rb_debug ("syncing control state");
1926 if (!rb_shell_player_get_playback_state (player, &shuffle,
1927 &repeat))
1928 return;
1930 action = gtk_action_group_get_action (player->priv->actiongroup,
1931 "ControlShuffle");
1932 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), shuffle);
1933 action = gtk_action_group_get_action (player->priv->actiongroup,
1934 "ControlRepeat");
1935 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), repeat);
1938 static void
1939 rb_shell_player_sync_volume (RBShellPlayer *player,
1940 gboolean notify)
1942 GtkAction *action;
1943 RhythmDBEntry *entry;
1945 if (player->priv->volume <= 0.0){
1946 player->priv->volume = 0.0;
1947 } else if (player->priv->volume >= 1.0){
1948 player->priv->volume = 1.0;
1951 action = gtk_action_group_get_action (player->priv->actiongroup,
1952 "ControlVolumeUp");
1953 g_object_set (G_OBJECT (action), "sensitive", player->priv->volume < 0.9999, NULL);
1955 action = gtk_action_group_get_action (player->priv->actiongroup,
1956 "ControlVolumeDown");
1957 g_object_set (G_OBJECT (action), "sensitive", player->priv->volume > 0.0001, NULL);
1959 rb_player_set_volume (player->priv->mmplayer,
1960 player->priv->mute ? 0.0 : player->priv->volume);
1962 eel_gconf_set_float (CONF_STATE_VOLUME, player->priv->volume);
1965 entry = rb_shell_player_get_playing_entry (player);
1966 rb_shell_player_sync_replaygain (player, entry);
1967 if (entry != NULL) {
1968 rhythmdb_entry_unref (entry);
1971 if (notify)
1972 g_object_notify (G_OBJECT (player), "volume");
1975 void
1976 rb_shell_player_toggle_mute (RBShellPlayer *player)
1978 player->priv->mute = !player->priv->mute;
1979 rb_shell_player_sync_volume (player, FALSE);
1982 static void
1983 rb_shell_player_sync_replaygain (RBShellPlayer *player,
1984 RhythmDBEntry *entry)
1986 double entry_track_gain = 0;
1987 double entry_track_peak = 0;
1988 double entry_album_gain = 0;
1989 double entry_album_peak = 0;
1991 if (entry != NULL) {
1992 entry_track_gain = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_TRACK_GAIN);
1993 entry_track_peak = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_TRACK_PEAK);
1994 entry_album_gain = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_ALBUM_GAIN);
1995 entry_album_peak = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_ALBUM_PEAK);
1998 rb_player_set_replaygain (player->priv->mmplayer, entry_track_gain,
1999 entry_track_peak, entry_album_gain, entry_album_peak);
2002 gboolean
2003 rb_shell_player_set_volume (RBShellPlayer *player,
2004 gdouble volume,
2005 GError **error)
2007 player->priv->volume = volume;
2008 rb_shell_player_sync_volume (player, TRUE);
2009 return TRUE;
2012 gboolean
2013 rb_shell_player_set_volume_relative (RBShellPlayer *player,
2014 gdouble delta,
2015 GError **error)
2017 /* rb_shell_player_sync_volume does clipping */
2018 player->priv->volume += delta;
2019 rb_shell_player_sync_volume (player, TRUE);
2020 return TRUE;
2023 gboolean
2024 rb_shell_player_get_volume (RBShellPlayer *player,
2025 gdouble *volume,
2026 GError **error)
2028 *volume = player->priv->volume;
2029 return TRUE;
2032 gboolean
2033 rb_shell_player_set_mute (RBShellPlayer *player,
2034 gboolean mute,
2035 GError **error)
2037 player->priv->mute = mute;
2038 rb_shell_player_sync_volume (player, FALSE);
2039 return TRUE;
2042 gboolean
2043 rb_shell_player_get_mute (RBShellPlayer *player,
2044 gboolean *mute,
2045 GError **error)
2047 *mute = player->priv->mute;
2048 return TRUE;
2051 static void
2052 gconf_song_position_slider_visibility_changed (GConfClient *client,
2053 guint cnxn_id,
2054 GConfEntry *entry,
2055 RBShellPlayer *player)
2057 rb_debug ("song position slider visibility visibility changed");
2058 rb_shell_player_sync_song_position_slider_visibility (player);
2061 static void
2062 rb_shell_player_shuffle_changed_cb (GtkAction *action,
2063 RBShellPlayer *player)
2065 const char *neworder;
2066 gboolean shuffle = FALSE;
2067 gboolean repeat = FALSE;
2069 if (player->priv->syncing_state)
2070 return;
2072 rb_debug ("shuffle changed");
2074 rb_shell_player_get_playback_state (player, &shuffle, &repeat);
2076 shuffle = !shuffle;
2077 neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
2078 rb_shell_player_set_play_order (player, neworder);
2081 static void
2082 rb_shell_player_repeat_changed_cb (GtkAction *action,
2083 RBShellPlayer *player)
2085 const char *neworder;
2086 gboolean shuffle = FALSE;
2087 gboolean repeat = FALSE;
2088 rb_debug ("repeat changed");
2090 if (player->priv->syncing_state)
2091 return;
2093 rb_shell_player_get_playback_state (player, &shuffle, &repeat);
2095 repeat = !repeat;
2096 neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
2097 rb_shell_player_set_play_order (player, neworder);
2100 static void
2101 rb_shell_player_view_song_position_slider_changed_cb (GtkAction *action,
2102 RBShellPlayer *player)
2104 eel_gconf_set_boolean (CONF_UI_SONG_POSITION_SLIDER_HIDDEN,
2105 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
2108 static void
2109 rb_shell_player_entry_activated_cb (RBEntryView *view,
2110 RhythmDBEntry *entry,
2111 RBShellPlayer *playa)
2113 gboolean was_from_queue = FALSE;
2114 RhythmDBEntry *prev_entry = NULL;
2115 GError *error = NULL;
2116 gboolean source_set = FALSE;
2117 char *playback_uri;
2119 g_return_if_fail (entry != NULL);
2121 rb_debug ("got entry %p activated", entry);
2123 /* don't play hidden entries */
2124 if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
2125 return;
2127 /* skip entries with no playback uri */
2128 playback_uri = rhythmdb_entry_get_playback_uri (entry);
2129 if (playback_uri == NULL) {
2130 return;
2132 g_free (playback_uri);
2134 /* figure out where the previous entry came from */
2135 if ((playa->priv->queue_source != NULL) &&
2136 (playa->priv->current_playing_source == RB_SOURCE (playa->priv->queue_source))) {
2137 prev_entry = rb_shell_player_get_playing_entry (playa);
2138 was_from_queue = TRUE;
2141 if (playa->priv->queue_source) {
2142 RBEntryView *queue_sidebar;
2144 g_object_get (playa->priv->queue_source, "sidebar", &queue_sidebar, NULL);
2146 if (view == queue_sidebar || view == rb_source_get_entry_view (RB_SOURCE (playa->priv->queue_source))) {
2148 /* fall back to the current selected source once the queue is empty */
2149 if (view == queue_sidebar && playa->priv->source == NULL) {
2150 rb_play_order_playing_source_changed (playa->priv->play_order,
2151 playa->priv->selected_source);
2152 playa->priv->source = playa->priv->selected_source;
2155 /* queue entry activated: move it to the start of the queue */
2156 rb_static_playlist_source_move_entry (RB_STATIC_PLAYLIST_SOURCE (playa->priv->queue_source), entry, 0);
2157 rb_shell_player_set_playing_source (playa, RB_SOURCE (playa->priv->queue_source));
2159 /* since we just moved the entry, we should give it focus.
2160 * just calling rb_shell_player_jump_to_current here
2161 * looks terribly ugly, though. */
2162 g_idle_add ((GSourceFunc)rb_shell_player_jump_to_current_idle, playa);
2163 was_from_queue = FALSE;
2164 source_set = TRUE;
2165 } else {
2166 if (playa->priv->queue_only) {
2167 rb_source_add_to_queue (playa->priv->selected_source,
2168 RB_SOURCE (playa->priv->queue_source));
2169 rb_shell_player_set_playing_source (playa, RB_SOURCE (playa->priv->queue_source));
2170 source_set = TRUE;
2174 g_object_unref (queue_sidebar);
2177 /* bail out if queue only */
2178 if (playa->priv->queue_only) {
2179 return;
2182 if (!source_set) {
2183 rb_shell_player_set_playing_source (playa, playa->priv->selected_source);
2184 source_set = TRUE;
2187 if (!rb_shell_player_set_playing_entry (playa, entry, TRUE, &error)) {
2188 rb_shell_player_error (playa, FALSE, error);
2189 g_clear_error (&error);
2192 /* if we were previously playing from the queue, clear its playing entry,
2193 * so we'll start again from the start.
2195 if (was_from_queue && prev_entry != NULL) {
2196 rb_play_order_set_playing_entry (playa->priv->queue_play_order, NULL);
2199 if (prev_entry != NULL) {
2200 rhythmdb_entry_unref (prev_entry);
2204 static void
2205 rb_shell_player_property_row_activated_cb (RBPropertyView *view,
2206 const char *name,
2207 RBShellPlayer *playa)
2209 RhythmDBEntry *entry = NULL;
2210 RhythmDBQueryModel *model;
2211 GtkTreeIter iter;
2212 GError *error = NULL;
2214 rb_debug ("got property activated");
2216 rb_shell_player_set_playing_source (playa, playa->priv->selected_source);
2218 /* RHYTHMDBFIXME - do we need to wait here until the query is finished?
2219 * in theory, yes, but in practice the query is started when the row is
2220 * selected (on the first click when doubleclicking, or when using the
2221 * keyboard to select then activate) and is pretty much always done by
2222 * the time we get in here.
2224 g_object_get (playa->priv->selected_source, "query-model", &model, NULL);
2226 if (!model)
2227 return;
2229 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter)) {
2230 goto done;
2233 entry = rhythmdb_query_model_iter_to_entry (model, &iter);
2234 if (!rb_shell_player_set_playing_entry (playa, entry, TRUE, &error)) {
2235 rb_shell_player_error (playa, FALSE, error);
2236 g_clear_error (&error);
2239 if (entry != NULL) {
2240 rhythmdb_entry_unref (entry);
2243 done:
2244 g_object_unref (model);
2247 static void
2248 rb_shell_player_entry_changed_cb (RhythmDB *db,
2249 RhythmDBEntry *entry,
2250 GSList *changes,
2251 RBShellPlayer *player)
2253 GSList *t;
2254 gboolean synced = FALSE;
2255 const char *location;
2256 RhythmDBEntry *playing_entry;
2258 playing_entry = rb_shell_player_get_playing_entry (player);
2260 /* We try to update only if the changed entry is currently playing */
2261 if (entry != playing_entry) {
2262 if (playing_entry != NULL) {
2263 rhythmdb_entry_unref (playing_entry);
2265 return;
2268 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
2269 for (t = changes; t; t = t->next) {
2270 RhythmDBEntryChange *change = t->data;
2272 /* update UI if the artist, title or album has changed */
2273 switch (change->prop) {
2274 case RHYTHMDB_PROP_TITLE:
2275 case RHYTHMDB_PROP_ARTIST:
2276 case RHYTHMDB_PROP_ALBUM:
2277 if (!synced) {
2278 rb_shell_player_sync_with_source (player);
2279 synced = TRUE;
2281 break;
2282 default:
2283 break;
2286 /* emit dbus signals for changes with easily marshallable types */
2287 switch (rhythmdb_get_property_type (db, change->prop)) {
2288 case G_TYPE_STRING:
2289 case G_TYPE_BOOLEAN:
2290 case G_TYPE_ULONG:
2291 case G_TYPE_UINT64:
2292 case G_TYPE_DOUBLE:
2293 g_signal_emit (G_OBJECT (player),
2294 rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
2295 location,
2296 rhythmdb_nice_elt_name_from_propid (db, change->prop),
2297 &change->old,
2298 &change->new);
2299 break;
2300 default:
2301 break;
2305 if (playing_entry != NULL) {
2306 rhythmdb_entry_unref (playing_entry);
2310 static void
2311 rb_shell_player_sync_with_source (RBShellPlayer *player)
2313 const char *entry_title = NULL;
2314 const char *artist = NULL;
2315 char *title;
2316 RhythmDBEntry *entry;
2318 entry = rb_shell_player_get_playing_entry (player);
2319 rb_debug ("playing source: %p, active entry: %p", player->priv->current_playing_source, entry);
2321 if (entry != NULL) {
2322 entry_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
2323 artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
2326 if (player->priv->song && entry_title)
2327 title = g_strdup_printf ("%s (%s)", player->priv->song,
2328 entry_title);
2329 else if (entry_title && artist && artist[0] != '\0')
2330 title = g_strdup_printf ("%s - %s", artist, entry_title);
2331 else if (entry_title)
2332 title = g_strdup (entry_title);
2333 else
2334 title = NULL;
2336 player->priv->elapsed = rb_player_get_time (player->priv->mmplayer);
2338 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[WINDOW_TITLE_CHANGED], 0,
2339 title);
2340 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED], 0,
2341 (guint) player->priv->elapsed);
2343 /* Sync the header */
2344 if (player->priv->song)
2345 rb_header_set_title (player->priv->header_widget, title);
2346 else
2347 rb_header_set_title (player->priv->header_widget, entry_title);
2348 g_free (title);
2350 rb_header_set_playing_entry (player->priv->header_widget, entry);
2351 rb_header_sync (player->priv->header_widget);
2353 if (entry != NULL) {
2354 rhythmdb_entry_unref (entry);
2358 static void
2359 rb_shell_player_sync_buttons (RBShellPlayer *player)
2361 GtkAction *action;
2362 RBSource *source;
2363 gboolean not_small;
2364 gboolean playing_from_queue;
2365 RBEntryView *view;
2366 int entry_view_state;
2367 RhythmDBEntry *entry;
2369 entry = rb_shell_player_get_playing_entry (player);
2370 if (entry != NULL) {
2371 source = player->priv->current_playing_source;
2372 entry_view_state = rb_player_playing (player->priv->mmplayer) ?
2373 RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_PAUSED;
2374 } else {
2375 source = player->priv->selected_source;
2376 entry_view_state = RB_ENTRY_VIEW_NOT_PLAYING;
2379 source = (entry == NULL) ? player->priv->selected_source : player->priv->current_playing_source;
2381 playing_from_queue = (source == RB_SOURCE (player->priv->queue_source));
2383 rb_debug ("syncing with source %p", source);
2385 not_small = !eel_gconf_get_boolean (CONF_UI_SMALL_DISPLAY);
2386 action = gtk_action_group_get_action (player->priv->actiongroup,
2387 "ViewJumpToPlaying");
2388 g_object_set (action,
2389 "sensitive", entry != NULL && not_small, NULL);
2391 if (source != NULL) {
2392 view = rb_source_get_entry_view (source);
2393 if (view)
2394 rb_entry_view_set_state (view, entry_view_state);
2397 if (entry != NULL) {
2398 rhythmdb_entry_unref (entry);
2402 void
2403 rb_shell_player_set_playing_source (RBShellPlayer *player,
2404 RBSource *source)
2406 rb_shell_player_set_playing_source_internal (player, source, TRUE);
2409 static void
2410 actually_set_playing_source (RBShellPlayer *player,
2411 RBSource *source,
2412 gboolean sync_entry_view)
2414 player->priv->source = source;
2415 player->priv->current_playing_source = source;
2417 if (source != NULL) {
2418 RBEntryView *songs = rb_source_get_entry_view (player->priv->source);
2419 if (sync_entry_view && songs) {
2420 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
2424 if (player->priv->play_order && source != RB_SOURCE (player->priv->queue_source)) {
2425 if (source == NULL)
2426 source = player->priv->selected_source;
2427 rb_play_order_playing_source_changed (player->priv->play_order, source);
2430 rb_shell_player_play_order_update_cb (player->priv->play_order,
2431 FALSE, FALSE,
2432 player);
2435 static void
2436 rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
2437 RBSource *source,
2438 gboolean sync_entry_view)
2441 gboolean emit_source_changed = TRUE;
2442 gboolean emit_playing_from_queue_changed = FALSE;
2444 if (player->priv->source == source &&
2445 player->priv->current_playing_source == source &&
2446 source != NULL)
2447 return;
2449 rb_debug ("setting playing source to %p", source);
2451 if (RB_SOURCE (player->priv->queue_source) == source) {
2453 if (player->priv->current_playing_source != source)
2454 emit_playing_from_queue_changed = TRUE;
2456 if (player->priv->source == NULL) {
2457 actually_set_playing_source (player, source, sync_entry_view);
2458 } else {
2459 emit_source_changed = FALSE;
2460 player->priv->current_playing_source = source;
2463 } else {
2464 if (player->priv->current_playing_source != source) {
2465 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
2466 emit_playing_from_queue_changed = TRUE;
2468 /* stop the old source */
2469 if (player->priv->current_playing_source != NULL) {
2470 if (sync_entry_view) {
2471 RBEntryView *songs = rb_source_get_entry_view (player->priv->current_playing_source);
2472 rb_debug ("source is already playing, stopping it");
2474 /* clear the playing entry if we're switching between non-queue sources */
2475 if (player->priv->current_playing_source != RB_SOURCE (player->priv->queue_source))
2476 rb_play_order_set_playing_entry (player->priv->play_order, NULL);
2478 if (songs)
2479 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_NOT_PLAYING);
2483 actually_set_playing_source (player, source, sync_entry_view);
2486 g_free (player->priv->url);
2487 g_free (player->priv->song);
2488 player->priv->song = NULL;
2489 player->priv->url = NULL;
2490 player->priv->have_url = FALSE;
2492 if (player->priv->current_playing_source == NULL)
2493 rb_shell_player_stop (player);
2495 rb_shell_player_sync_with_source (player);
2496 g_object_notify (G_OBJECT (player), "playing");
2497 if (player->priv->selected_source)
2498 rb_shell_player_sync_buttons (player);
2500 if (emit_source_changed) {
2501 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SOURCE_CHANGED],
2502 0, player->priv->source);
2504 if (emit_playing_from_queue_changed) {
2505 g_object_notify (G_OBJECT (player), "playing-from-queue");
2510 * rb_shell_player_stop:
2511 * @player: a #RBShellPlayer.
2513 * Completely stops playback, freeing resources and unloading the file.
2515 * In general rb_shell_player_pause() should be used instead, as it stops the
2516 * audio, but does not completely free resources.
2519 void
2520 rb_shell_player_stop (RBShellPlayer *player)
2522 GError *error = NULL;
2523 rb_debug ("stopping");
2525 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
2527 if (error == NULL)
2528 rb_player_close (player->priv->mmplayer, &error);
2529 if (error) {
2530 rb_error_dialog (NULL,
2531 _("Couldn't stop playback"),
2532 "%s", error->message);
2533 g_error_free (error);
2536 rb_shell_player_sync_with_source (player);
2537 g_object_notify (G_OBJECT (player), "playing");
2538 rb_shell_player_sync_buttons (player);
2542 * rb_shell_player_pause:
2543 * @player: a #RBShellPlayer
2544 * @error: error return
2546 * Pauses playback if possible, completely stopping if not.
2548 * @return: whether playback is not occurring (TRUE when successfully
2549 * paused/stopped or playback was not occurring).
2552 gboolean
2553 rb_shell_player_pause (RBShellPlayer *player,
2554 GError **error)
2556 if (rb_player_playing (player->priv->mmplayer))
2557 return rb_shell_player_playpause (player, FALSE, error);
2558 else
2559 return TRUE;
2563 * rb_shell_player_get_playing:
2564 * @player: a #RBShellPlayer
2565 * @playing: playback state return
2566 * @error: error return
2568 * Reports whether playback is occuring by setting playing.
2570 * @return: whether the playback state could be reported successfully.
2572 gboolean
2573 rb_shell_player_get_playing (RBShellPlayer *player,
2574 gboolean *playing,
2575 GError **error)
2577 if (playing != NULL)
2578 *playing = rb_player_playing (player->priv->mmplayer);
2580 return TRUE;
2583 char *
2584 rb_shell_player_get_playing_time_string (RBShellPlayer *player)
2586 return rb_make_elapsed_time_string (player->priv->elapsed,
2587 rb_shell_player_get_playing_song_duration (player),
2588 !eel_gconf_get_boolean (CONF_UI_TIME_DISPLAY));
2591 gboolean
2592 rb_shell_player_get_playing_time (RBShellPlayer *player,
2593 guint *time,
2594 GError **error)
2596 if (time != NULL)
2597 *time = (guint) rb_player_get_time (player->priv->mmplayer);
2599 return TRUE;
2602 gboolean
2603 rb_shell_player_set_playing_time (RBShellPlayer *player,
2604 guint time,
2605 GError **error)
2607 if (rb_player_seekable (player->priv->mmplayer)) {
2608 rb_player_set_time (player->priv->mmplayer, (long) time);
2609 return TRUE;
2610 } else {
2611 g_set_error (error,
2612 RB_SHELL_PLAYER_ERROR,
2613 RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
2614 _("Current song is not seekable"));
2615 return FALSE;
2619 void
2620 rb_shell_player_seek (RBShellPlayer *player, long offset)
2622 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
2624 if (rb_player_seekable (player->priv->mmplayer)) {
2625 long t = rb_player_get_time (player->priv->mmplayer);
2626 rb_player_set_time (player->priv->mmplayer, t + offset);
2630 long
2631 rb_shell_player_get_playing_song_duration (RBShellPlayer *player)
2633 RhythmDBEntry *current_entry;
2634 long val;
2636 g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), -1);
2638 current_entry = rb_shell_player_get_playing_entry (player);
2640 if (current_entry == NULL) {
2641 rb_debug ("Did not get playing entry : return -1 as length");
2642 return -1;
2645 val = rhythmdb_entry_get_ulong (current_entry, RHYTHMDB_PROP_DURATION);
2647 rhythmdb_entry_unref (current_entry);
2649 return val;
2652 static void
2653 rb_shell_player_sync_with_selected_source (RBShellPlayer *player)
2655 rb_debug ("syncing with selected source: %p", player->priv->selected_source);
2656 if (player->priv->source == NULL)
2658 rb_debug ("no playing source, new source is %p", player->priv->selected_source);
2660 player->priv->have_url = rb_source_have_url (player->priv->selected_source);
2662 rb_shell_player_sync_with_source (player);
2666 static void
2667 rb_shell_player_error (RBShellPlayer *player,
2668 gboolean async,
2669 const GError *err)
2671 RhythmDBEntry *entry;
2673 g_return_if_fail (player->priv->handling_error == FALSE);
2675 player->priv->handling_error = TRUE;
2677 entry = rb_shell_player_get_playing_entry (player);
2679 rb_debug ("playback error while playing: %s", err->message);
2680 /* For synchronous errors the entry playback error has already been set */
2681 if (entry && async)
2682 rb_shell_player_set_entry_playback_error (player, entry, err->message);
2684 if (err->code == RB_PLAYER_ERROR_NO_AUDIO) {
2685 /* stream has completely ended */
2686 rb_shell_player_set_playing_source (player, NULL);
2687 } else if ((player->priv->current_playing_source != NULL) &&
2688 (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_RETRY)) {
2689 /* receiving an error means a broken stream or non-audio stream, so abort */
2690 rb_error_dialog (NULL,
2691 _("Couldn't start playback"),
2692 "%s", (err) ? err->message : "(null)");
2693 rb_shell_player_set_playing_source (player, NULL);
2694 } else if (player->priv->do_next_idle_id == 0) {
2695 player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_idle, player);
2698 player->priv->handling_error = FALSE;
2700 if (entry != NULL) {
2701 rhythmdb_entry_unref (entry);
2705 static void
2706 error_cb (RBPlayer *mmplayer,
2707 const GError *err,
2708 gpointer data)
2710 RBShellPlayer *player = RB_SHELL_PLAYER (data);
2712 if (player->priv->handling_error)
2713 return;
2715 if (player->priv->source == NULL) {
2716 rb_debug ("ignoring error (no source): %s", err->message);
2717 return;
2720 GDK_THREADS_ENTER ();
2722 rb_shell_player_error (player, TRUE, err);
2724 rb_debug ("exiting error hander");
2725 GDK_THREADS_LEAVE ();
2728 static void
2729 tick_cb (RBPlayer *mmplayer,
2730 long elapsed,
2731 gpointer data)
2733 RBShellPlayer *player = RB_SHELL_PLAYER (data);
2735 GDK_THREADS_ENTER ();
2737 if (rb_player_playing (mmplayer)) {
2738 if (player->priv->elapsed != elapsed) {
2739 player->priv->elapsed = elapsed;
2740 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED],
2741 0, (guint) elapsed);
2745 GDK_THREADS_LEAVE ();
2748 static void
2749 info_available_cb (RBPlayer *mmplayer,
2750 RBMetaDataField field,
2751 GValue *value,
2752 gpointer data)
2754 RBShellPlayer *player = RB_SHELL_PLAYER (data);
2755 RhythmDBEntry *entry;
2756 RhythmDBPropType entry_field = 0;
2757 gboolean changed = FALSE;
2758 gboolean set_field = FALSE;
2759 RhythmDBEntryType type;
2761 rb_debug ("info: %d", field);
2763 /* Sanity check, this signal may come in after we stopped the
2764 * player */
2765 if (player->priv->source == NULL
2766 || !rb_player_opened (player->priv->mmplayer)) {
2767 rb_debug ("Got info_available but no playing source!");
2768 return;
2771 GDK_THREADS_ENTER ();
2773 entry = rb_shell_player_get_playing_entry (player);
2774 if (entry == NULL) {
2775 rb_debug ("Got info_available but no playing entry!");
2776 goto out_unlock;
2779 type = rhythmdb_entry_get_entry_type (entry);
2780 if (type != RHYTHMDB_ENTRY_TYPE_IRADIO_STATION) {
2781 rb_debug ("Got info_available but entry isn't an iradio station");
2782 goto out_unlock;
2785 switch (field) {
2786 case RB_METADATA_FIELD_TITLE:
2788 char *song = g_value_dup_string (value);
2789 if (!g_utf8_validate (song, -1, NULL)) {
2790 g_warning ("Invalid UTF-8 from internet radio: %s", song);
2791 goto out_unlock;
2794 if ((!song && player->priv->song)
2795 || !player->priv->song
2796 || strcmp (song, player->priv->song)) {
2797 changed = TRUE;
2798 g_free (player->priv->song);
2799 player->priv->song = song;
2800 g_object_notify (G_OBJECT (player), "stream-song");
2802 else
2803 g_free (song);
2804 break;
2806 case RB_METADATA_FIELD_GENRE:
2808 const char *genre = g_value_get_string (value);
2809 const char *existing;
2810 if (!g_utf8_validate (genre, -1, NULL)) {
2811 g_warning ("Invalid UTF-8 from internet radio: %s", genre);
2812 goto out_unlock;
2815 /* check if the db entry already has a genre; if so, don't change it */
2816 existing = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
2817 if ((existing == NULL) ||
2818 (strcmp (existing, "") == 0) ||
2819 (strcmp (existing, _("Unknown")) == 0)) {
2820 entry_field = RHYTHMDB_PROP_GENRE;
2821 rb_debug ("setting genre of iradio station to %s", genre);
2822 set_field = TRUE;
2823 } else {
2824 rb_debug ("iradio station already has genre: %s; ignoring %s", existing, genre);
2826 break;
2828 case RB_METADATA_FIELD_COMMENT:
2830 const char *name = g_value_get_string (value);
2831 const char *existing;
2832 const char *location;
2834 if (!g_utf8_validate (name, -1, NULL)) {
2835 g_warning ("Invalid UTF-8 from internet radio: %s", name);
2836 goto out_unlock;
2839 /* check if the db entry already has a title; if so, don't change it.
2840 * consider title==URI to be the same as no title, since that's what
2841 * happens for stations imported by DnD or commandline args.
2842 * if the station title really is the same as the URI, then surely
2843 * the station title in the stream metadata will say that too..
2845 existing = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
2846 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
2847 if ((existing == NULL) ||
2848 (strcmp (existing, "") == 0) ||
2849 (strcmp (existing, location) == 0)) {
2850 entry_field = RHYTHMDB_PROP_TITLE;
2851 rb_debug ("setting title of iradio station to %s", name);
2852 set_field = TRUE;
2853 } else {
2854 rb_debug ("iradio station already has title: %s; ignoring %s", existing, name);
2856 break;
2858 case RB_METADATA_FIELD_BITRATE:
2859 if (!rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE)) {
2860 gulong bitrate;
2862 /* GStreamer sends us bitrate in bps, but we need it in kbps*/
2863 bitrate = g_value_get_ulong (value);
2864 g_value_set_ulong (value, bitrate/1000);
2866 rb_debug ("setting bitrate of iradio station to %lu",
2867 g_value_get_ulong (value));
2868 entry_field = RHYTHMDB_PROP_BITRATE;
2869 set_field = TRUE;
2871 break;
2872 default:
2873 break;
2876 if (changed)
2877 rb_shell_player_sync_with_source (player);
2879 if (set_field && entry_field != 0) {
2880 rhythmdb_entry_set (player->priv->db, entry, entry_field, value);
2881 rhythmdb_commit (player->priv->db);
2884 out_unlock:
2885 if (entry != NULL) {
2886 rhythmdb_entry_unref (entry);
2889 GDK_THREADS_LEAVE ();
2892 static void
2893 buffering_cb (RBPlayer *mmplayer,
2894 guint progress,
2895 gpointer data)
2897 RBShellPlayer *player = RB_SHELL_PLAYER (data);
2899 GDK_THREADS_ENTER ();
2900 rb_statusbar_set_progress (player->priv->statusbar_widget, ((double)progress)/100, _("Buffering"));
2901 GDK_THREADS_LEAVE ();
2904 gboolean
2905 rb_shell_player_get_playing_path (RBShellPlayer *shell_player,
2906 const gchar **path,
2907 GError **error)
2909 RhythmDBEntry *entry;
2911 entry = rb_shell_player_get_playing_entry (shell_player);
2912 if (entry != NULL) {
2913 *path = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
2914 } else {
2915 *path = NULL;
2918 if (entry != NULL) {
2919 rhythmdb_entry_unref (entry);
2922 return TRUE;
2925 #ifdef HAVE_MMKEYS
2926 static void
2927 grab_mmkey (int key_code,
2928 GdkWindow *root)
2930 gdk_error_trap_push ();
2932 XGrabKey (GDK_DISPLAY (), key_code,
2934 GDK_WINDOW_XID (root), True,
2935 GrabModeAsync, GrabModeAsync);
2936 XGrabKey (GDK_DISPLAY (), key_code,
2937 Mod2Mask,
2938 GDK_WINDOW_XID (root), True,
2939 GrabModeAsync, GrabModeAsync);
2940 XGrabKey (GDK_DISPLAY (), key_code,
2941 Mod5Mask,
2942 GDK_WINDOW_XID (root), True,
2943 GrabModeAsync, GrabModeAsync);
2944 XGrabKey (GDK_DISPLAY (), key_code,
2945 LockMask,
2946 GDK_WINDOW_XID (root), True,
2947 GrabModeAsync, GrabModeAsync);
2948 XGrabKey (GDK_DISPLAY (), key_code,
2949 Mod2Mask | Mod5Mask,
2950 GDK_WINDOW_XID (root), True,
2951 GrabModeAsync, GrabModeAsync);
2952 XGrabKey (GDK_DISPLAY (), key_code,
2953 Mod2Mask | LockMask,
2954 GDK_WINDOW_XID (root), True,
2955 GrabModeAsync, GrabModeAsync);
2956 XGrabKey (GDK_DISPLAY (), key_code,
2957 Mod5Mask | LockMask,
2958 GDK_WINDOW_XID (root), True,
2959 GrabModeAsync, GrabModeAsync);
2960 XGrabKey (GDK_DISPLAY (), key_code,
2961 Mod2Mask | Mod5Mask | LockMask,
2962 GDK_WINDOW_XID (root), True,
2963 GrabModeAsync, GrabModeAsync);
2965 gdk_flush ();
2966 if (gdk_error_trap_pop ()) {
2967 rb_debug ("Error grabbing key");
2971 static GdkFilterReturn
2972 filter_mmkeys (GdkXEvent *xevent,
2973 GdkEvent *event,
2974 gpointer data)
2976 XEvent *xev;
2977 XKeyEvent *key;
2978 RBShellPlayer *player;
2979 xev = (XEvent *) xevent;
2980 if (xev->type != KeyPress) {
2981 return GDK_FILTER_CONTINUE;
2984 key = (XKeyEvent *) xevent;
2986 player = (RBShellPlayer *)data;
2988 if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPlay) == key->keycode) {
2989 rb_shell_player_playpause (player, FALSE, NULL);
2990 return GDK_FILTER_REMOVE;
2991 } else if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPause) == key->keycode) {
2992 rb_shell_player_pause (player, NULL);
2993 return GDK_FILTER_REMOVE;
2994 } else if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioStop) == key->keycode) {
2995 rb_shell_player_stop (player);
2996 return GDK_FILTER_REMOVE;
2997 } else if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPrev) == key->keycode) {
2998 rb_shell_player_cmd_previous (NULL, player);
2999 return GDK_FILTER_REMOVE;
3000 } else if (XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioNext) == key->keycode) {
3001 rb_shell_player_cmd_next (NULL, player);
3002 return GDK_FILTER_REMOVE;
3003 } else {
3004 return GDK_FILTER_CONTINUE;
3008 static void
3009 rb_shell_player_init_mmkeys (RBShellPlayer *shell_player)
3011 gint keycodes[] = {0, 0, 0, 0, 0};
3012 GdkDisplay *display;
3013 GdkScreen *screen;
3014 GdkWindow *root;
3015 guint i, j;
3017 keycodes[0] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPlay);
3018 keycodes[1] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioStop);
3019 keycodes[2] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPrev);
3020 keycodes[3] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioNext);
3021 keycodes[4] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPause);
3023 display = gdk_display_get_default ();
3025 for (i = 0; i < gdk_display_get_n_screens (display); i++) {
3026 screen = gdk_display_get_screen (display, i);
3028 if (screen != NULL) {
3029 root = gdk_screen_get_root_window (screen);
3031 for (j = 0; j < G_N_ELEMENTS (keycodes) ; j++) {
3032 if (keycodes[j] != 0)
3033 grab_mmkey (keycodes[j], root);
3036 gdk_window_add_filter (root, filter_mmkeys,
3037 (gpointer) shell_player);
3041 #endif /* HAVE_MMKEYS */
3043 static gboolean
3044 _idle_unblock_signal_cb (gpointer data)
3046 RBShellPlayer *player = (RBShellPlayer *)data;
3047 GtkAction *action;
3048 gboolean playing;
3050 action = gtk_action_group_get_action (player->priv->actiongroup,
3051 "ControlPlay");
3053 /* sync the active state of the action again */
3054 g_object_get (player, "playing", &playing, NULL);
3055 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing);
3057 g_signal_handlers_unblock_by_func (action, rb_shell_player_cmd_play, player);
3058 return FALSE;
3061 static void
3062 rb_shell_player_playing_changed_cb (RBShellPlayer *player,
3063 GParamSpec *arg1,
3064 gpointer user_data)
3066 GtkAction *action;
3067 gboolean playing;
3068 char *tooltip;
3070 g_object_get (player, "playing", &playing, NULL);
3071 action = gtk_action_group_get_action (player->priv->actiongroup,
3072 "ControlPlay");
3073 if (playing) {
3074 tooltip = g_strdup (_("Stop playback"));
3075 } else {
3076 tooltip = g_strdup (_("Start playback"));
3078 g_object_set (action, "tooltip", tooltip, NULL);
3079 g_free (tooltip);
3081 /* block the signal, so that it doesn't get stuck by triggering recursively,
3082 * and don't unblock it until whatever else is happening has finished.
3084 g_signal_handlers_block_by_func (action, rb_shell_player_cmd_play, player);
3085 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing);
3086 g_idle_add (_idle_unblock_signal_cb, player);
3089 /* This should really be standard. */
3090 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3092 GType
3093 rb_shell_player_error_get_type (void)
3095 static GType etype = 0;
3097 if (etype == 0) {
3098 static const GEnumValue values[] = {
3099 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_PLAYLIST_PARSE_ERROR, "Playing parsing error"),
3100 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST, "End of playlist reached"),
3101 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_PLAYING, "Not playing"),
3102 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE, "Not seekable"),
3103 { 0, 0, 0 }
3106 etype = g_enum_register_static ("RBShellPlayerError", values);
3109 return etype;