Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
rb-shell-player.c:2765:3 | clang-analyzer | Value stored to 'source_set' is never read | ||
rb-shell-player.c:3028:3 | clang-analyzer | Value stored to 'source' is never read | ||
rb-shell-player.c:3032:3 | clang-analyzer | Value stored to 'source' is never read |
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2002, 2003 Jorn Baayen <jorn@nl.linux.org>
4 * Copyright (C) 2002,2003 Colin Walters <walters@debian.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * The Rhythmbox authors hereby grant permission for non-GPL compatible
12 * GStreamer plugins to be used and distributed together with GStreamer
13 * and Rhythmbox. This permission is above and beyond the permissions granted
14 * by the GPL license by which Rhythmbox is covered. If you modify this code
15 * you may extend this exception to your version of the code, but you are not
16 * obligated to do so. If you do not wish to do so, delete this exception
17 * statement from your version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
27 *
28 */
29
30 /**
31 * SECTION:rb-shell-player
32 * @short_description: playback state management
33 *
34 * The shell player (or player shell, depending on who you're talking to)
35 * manages the #RBPlayer instance, tracks the current playing #RhythmDBEntry,
36 * and manages the various #RBPlayOrder instances. It provides simple operations
37 * such as next, previous, play/pause, and seek.
38 *
39 * When playing internet radio streams, it first attempts to read the stream URL
40 * as a playlist. If this succeeds, the URLs from the playlist are stored in a
41 * list and tried in turn in case of errors. If the playlist parsing fails, the
42 * stream URL is played directly.
43 *
44 * The mapping from the separate shuffle and repeat settings to an #RBPlayOrder
45 * instance occurs in here. The play order logic can also support a number of
46 * additional play orders not accessible via the shuffle and repeat buttons.
47 *
48 * If the player backend supports multiple streams, the shell player crossfades
49 * between streams by watching the elapsed time of the current stream and simulating
50 * an end-of-stream event when it gets within the crossfade duration of the actual
51 * end.
52 */
53
54 #include "config.h"
55
56 #include <unistd.h>
57 #include <stdlib.h>
58 #include <time.h>
59 #include <string.h>
60
61 #include <glib.h>
62 #include <glib/gi18n.h>
63 #include <gtk/gtk.h>
64
65 #include "rb-property-view.h"
66 #include "rb-shell-player.h"
67 #include "rb-stock-icons.h"
68 #include "rb-builder-helpers.h"
69 #include "rb-file-helpers.h"
70 #include "rb-cut-and-paste-code.h"
71 #include "rb-dialog.h"
72 #include "rb-debug.h"
73 #include "rb-player.h"
74 #include "rb-header.h"
75 #include "totem-pl-parser.h"
76 #include "rb-metadata.h"
77 #include "rb-library-source.h"
78 #include "rb-util.h"
79 #include "rb-play-order.h"
80 #include "rb-playlist-source.h"
81 #include "rb-play-queue-source.h"
82 #include "rhythmdb.h"
83 #include "rb-podcast-manager.h"
84 #include "rb-marshal.h"
85 #include "rb-missing-plugins.h"
86 #include "rb-ext-db.h"
87
88 /* Play Orders */
89 #include "rb-play-order-linear.h"
90 #include "rb-play-order-linear-loop.h"
91 #include "rb-play-order-shuffle.h"
92 #include "rb-play-order-random-equal-weights.h"
93 #include "rb-play-order-random-by-age.h"
94 #include "rb-play-order-random-by-rating.h"
95 #include "rb-play-order-random-by-age-and-rating.h"
96 #include "rb-play-order-queue.h"
97
98 static const char* const state_to_play_order[2][2] =
99 {{"linear", "linear-loop"},
100 {"shuffle", "random-by-age-and-rating"}};
101
102 static void rb_shell_player_class_init (RBShellPlayerClass *klass);
103 static void rb_shell_player_init (RBShellPlayer *shell_player);
104 static void rb_shell_player_constructed (GObject *object);
105 static void rb_shell_player_dispose (GObject *object);
106 static void rb_shell_player_finalize (GObject *object);
107 static void rb_shell_player_set_property (GObject *object,
108 guint prop_id,
109 const GValue *value,
110 GParamSpec *pspec);
111 static void rb_shell_player_get_property (GObject *object,
112 guint prop_id,
113 GValue *value,
114 GParamSpec *pspec);
115
116 static void rb_shell_player_cmd_previous (GtkAction *action,
117 RBShellPlayer *player);
118 static void rb_shell_player_cmd_play (GtkAction *action,
119 RBShellPlayer *player);
120 static void rb_shell_player_cmd_next (GtkAction *action,
121 RBShellPlayer *player);
122 static void rb_shell_player_cmd_volume_up (GtkAction *action,
123 RBShellPlayer *player);
124 static void rb_shell_player_cmd_volume_down (GtkAction *action,
125 RBShellPlayer *player);
126 static void rb_shell_player_shuffle_changed_cb (GtkAction *action,
127 RBShellPlayer *player);
128 static void rb_shell_player_repeat_changed_cb (GtkAction *action,
129 RBShellPlayer *player);
130 static void rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
131 RBSource *source,
132 gboolean sync_entry_view);
133 static void rb_shell_player_sync_with_source (RBShellPlayer *player);
134 static void rb_shell_player_sync_with_selected_source (RBShellPlayer *player);
135 static void rb_shell_player_entry_changed_cb (RhythmDB *db,
136 RhythmDBEntry *entry,
137 GArray *changes,
138 RBShellPlayer *player);
139
140 static void rb_shell_player_entry_activated_cb (RBEntryView *view,
141 RhythmDBEntry *entry,
142 RBShellPlayer *player);
143 static void rb_shell_player_property_row_activated_cb (RBPropertyView *view,
144 const char *name,
145 RBShellPlayer *player);
146 static void rb_shell_player_sync_volume (RBShellPlayer *player, gboolean notify, gboolean set_volume);
147 static void tick_cb (RBPlayer *player, RhythmDBEntry *entry, gint64 elapsed, gint64 duration, gpointer data);
148 static void error_cb (RBPlayer *player, RhythmDBEntry *entry, const GError *err, gpointer data);
149 static void missing_plugins_cb (RBPlayer *player, RhythmDBEntry *entry, const char **details, const char **descriptions, RBShellPlayer *sp);
150 static void playing_stream_cb (RBPlayer *player, RhythmDBEntry *entry, RBShellPlayer *shell_player);
151 static void player_image_cb (RBPlayer *player, RhythmDBEntry *entry, GdkPixbuf *image, RBShellPlayer *shell_player);
152 static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err);
153
154 static void rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
155 gboolean has_next,
156 gboolean has_previous,
157 RBShellPlayer *player);
158
159 static void rb_shell_player_sync_play_order (RBShellPlayer *player);
160 static void rb_shell_player_sync_control_state (RBShellPlayer *player);
161 static void rb_shell_player_sync_buttons (RBShellPlayer *player);
162
163 static void player_settings_changed_cb (GSettings *settings,
164 const char *key,
165 RBShellPlayer *player);
166 static void rb_shell_player_playing_changed_cb (RBShellPlayer *player,
167 GParamSpec *arg1,
168 gpointer user_data);
169 static void rb_shell_player_extra_metadata_cb (RhythmDB *db,
170 RhythmDBEntry *entry,
171 const char *field,
172 GValue *metadata,
173 RBShellPlayer *player);
174
175 static gboolean rb_shell_player_open_location (RBShellPlayer *player,
176 RhythmDBEntry *entry,
177 RBPlayerPlayType play_type,
178 GError **error);
179 static gboolean rb_shell_player_do_next_internal (RBShellPlayer *player,
180 gboolean from_eos,
181 gboolean allow_stop,
182 GError **error);
183 static void rb_shell_player_slider_dragging_cb (GObject *header,
184 GParamSpec *pspec,
185 RBShellPlayer *player);
186 static void rb_shell_player_volume_changed_cb (RBPlayer *player,
187 float volume,
188 RBShellPlayer *shell_player);
189
190
191
192 typedef struct {
193 /** Value of the state/play-order setting */
194 char *name;
195 /** Contents of the play order dropdown; should be gettext()ed before use. */
196 char *description;
197 /** the play order's gtype id */
198 GType order_type;
199 /** TRUE if the play order should appear in the dropdown */
200 gboolean is_in_dropdown;
201 } RBPlayOrderDescription;
202
203 static void _play_order_description_free (RBPlayOrderDescription *order);
204
205 static RBPlayOrder* rb_play_order_new (RBShellPlayer *player, const char* porder_name);
206
207 /* number of nanoseconds before the end of a track to start prerolling the next */
208 #define PREROLL_TIME RB_PLAYER_SECOND
209
210 struct RBShellPlayerPrivate
211 {
212 RhythmDB *db;
213
214 gboolean syncing_state;
215 gboolean queue_only;
216
217 RBSource *selected_source;
218 RBSource *source;
219 RBPlayQueueSource *queue_source;
220 RBSource *current_playing_source;
221
222 GHashTable *play_orders; /* char* -> RBPlayOrderDescription* map */
223
224 gboolean did_retry;
225 GTimeVal last_retry;
226
227 GtkUIManager *ui_manager;
228 GtkActionGroup *actiongroup;
229
230 gboolean handling_error;
231
232 RBPlayer *mmplayer;
233
234 guint elapsed;
235 gint64 track_transition_time;
236 RhythmDBEntry *playing_entry;
237 gboolean playing_entry_eos;
238 gboolean jump_to_playing_entry;
239
240 RBPlayOrder *play_order;
241 RBPlayOrder *queue_play_order;
242
243 GQueue *playlist_urls;
244 GCancellable *parser_cancellable;
245
246 RBHeader *header_widget;
247
248 GSettings *settings;
249 GSettings *ui_settings;
250
251 gboolean has_prev;
252 gboolean has_next;
253 gboolean mute;
254 float volume;
255
256 guint do_next_idle_id;
257 guint unblock_play_id;
258 };
259
260 #define RB_SHELL_PLAYER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_PLAYER, RBShellPlayerPrivate))
261
262 enum
263 {
264 PROP_0,
265 PROP_SOURCE,
266 PROP_DB,
267 PROP_UI_MANAGER,
268 PROP_ACTION_GROUP,
269 PROP_PLAY_ORDER,
270 PROP_PLAYING,
271 PROP_VOLUME,
272 PROP_HEADER,
273 PROP_QUEUE_SOURCE,
274 PROP_QUEUE_ONLY,
275 PROP_PLAYING_FROM_QUEUE,
276 PROP_PLAYER,
277 PROP_MUTE,
278 PROP_HAS_NEXT,
279 PROP_HAS_PREV
280 };
281
282 enum
283 {
284 WINDOW_TITLE_CHANGED,
285 ELAPSED_CHANGED,
286 PLAYING_SOURCE_CHANGED,
287 PLAYING_CHANGED,
288 PLAYING_SONG_CHANGED,
289 PLAYING_URI_CHANGED,
290 PLAYING_SONG_PROPERTY_CHANGED,
291 ELAPSED_NANO_CHANGED,
292 LAST_SIGNAL
293 };
294
295 static GtkActionEntry rb_shell_player_actions [] =
296 {
297 { "ControlPrevious", GTK_STOCK_MEDIA_PREVIOUS, N_("Pre_vious"), "<alt>Left",
298 N_("Start playing the previous song"),
299 G_CALLBACK (rb_shell_player_cmd_previous) },
300 { "ControlNext", GTK_STOCK_MEDIA_NEXT, N_("_Next"), "<alt>Right",
301 N_("Start playing the next song"),
302 G_CALLBACK (rb_shell_player_cmd_next) },
303 { "ControlVolumeUp", NULL, N_("_Increase Volume"), "<control>Up",
304 N_("Increase playback volume"),
305 G_CALLBACK (rb_shell_player_cmd_volume_up) },
306 { "ControlVolumeDown", NULL, N_("_Decrease Volume"), "<control>Down",
307 N_("Decrease playback volume"),
308 G_CALLBACK (rb_shell_player_cmd_volume_down) },
309 };
310 static guint rb_shell_player_n_actions = G_N_ELEMENTS (rb_shell_player_actions);
311
312 static GtkToggleActionEntry rb_shell_player_toggle_entries [] =
313 {
314 { "ControlPlay", GTK_STOCK_MEDIA_PLAY, N_("_Play"), "<control>space",
315 N_("Start playback"),
316 G_CALLBACK (rb_shell_player_cmd_play) },
317 { "ControlShuffle", GNOME_MEDIA_SHUFFLE, N_("Sh_uffle"), "<control>U",
318 N_("Play songs in a random order"),
319 G_CALLBACK (rb_shell_player_shuffle_changed_cb) },
320 { "ControlRepeat", GNOME_MEDIA_REPEAT, N_("_Repeat"), "<control>R",
321 N_("Play first song again after all songs are played"),
322 G_CALLBACK (rb_shell_player_repeat_changed_cb) },
323 };
324 static guint rb_shell_player_n_toggle_entries = G_N_ELEMENTS (rb_shell_player_toggle_entries);
325
326 static guint rb_shell_player_signals[LAST_SIGNAL] = { 0 };
327
328 G_DEFINE_TYPE (RBShellPlayer, rb_shell_player, G_TYPE_OBJECT)
329
330 static void
331 rb_shell_player_class_init (RBShellPlayerClass *klass)
332 {
333 GObjectClass *object_class = G_OBJECT_CLASS (klass);
334
335 object_class->dispose = rb_shell_player_dispose;
336 object_class->finalize = rb_shell_player_finalize;
337 object_class->constructed = rb_shell_player_constructed;
338
339 object_class->set_property = rb_shell_player_set_property;
340 object_class->get_property = rb_shell_player_get_property;
341
342 /**
343 * RBShellPlayer:source:
344 *
345 * The current source that is selected for playback.
346 */
347 g_object_class_install_property (object_class,
348 PROP_SOURCE,
349 g_param_spec_object ("source",
350 "RBSource",
351 "RBSource object",
352 RB_TYPE_SOURCE,
353 G_PARAM_READWRITE));
354
355 /**
356 * RBShellPlayer:ui-manager:
357 *
358 * The GtkUIManager
359 */
360 g_object_class_install_property (object_class,
361 PROP_UI_MANAGER,
362 g_param_spec_object ("ui-manager",
363 "GtkUIManager",
364 "GtkUIManager object",
365 GTK_TYPE_UI_MANAGER,
366 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
367
368 /**
369 * RBShellPlayer:db:
370 *
371 * The #RhythmDB
372 */
373 g_object_class_install_property (object_class,
374 PROP_DB,
375 g_param_spec_object ("db",
376 "RhythmDB",
377 "RhythmDB object",
378 RHYTHMDB_TYPE,
379 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
380
381 /**
382 * RBShellPlayer:action-group:
383 *
384 * The #GtkActionGroup to use for player actions
385 */
386 g_object_class_install_property (object_class,
387 PROP_ACTION_GROUP,
388 g_param_spec_object ("action-group",
389 "GtkActionGroup",
390 "GtkActionGroup object",
391 GTK_TYPE_ACTION_GROUP,
392 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
393
394 /**
395 * RBShellPlayer:queue-source:
396 *
397 * The play queue source
398 */
399 g_object_class_install_property (object_class,
400 PROP_QUEUE_SOURCE,
401 g_param_spec_object ("queue-source",
402 "RBPlayQueueSource",
403 "RBPlayQueueSource object",
404 RB_TYPE_PLAYLIST_SOURCE,
405 G_PARAM_READWRITE));
406
407 /**
408 * RBShellPlayer:queue-only:
409 *
410 * If %TRUE, activating an entry should only add it to the play queue.
411 */
412 g_object_class_install_property (object_class,
413 PROP_QUEUE_ONLY,
414 g_param_spec_boolean ("queue-only",
415 "Queue only",
416 "Activation only adds to queue",
417 FALSE,
418 G_PARAM_READWRITE));
419
420 /**
421 * RBShellPlayer:playing-from-queue:
422 *
423 * If %TRUE, the current playing entry came from the play queue.
424 */
425 g_object_class_install_property (object_class,
426 PROP_PLAYING_FROM_QUEUE,
427 g_param_spec_boolean ("playing-from-queue",
428 "Playing from queue",
429 "Whether playing from the play queue or not",
430 FALSE,
431 G_PARAM_READABLE));
432
433 /**
434 * RBShellPlayer:player:
435 *
436 * The player backend object (an object implementing the #RBPlayer interface).
437 */
438 g_object_class_install_property (object_class,
439 PROP_PLAYER,
440 g_param_spec_object ("player",
441 "RBPlayer",
442 "RBPlayer object",
443 G_TYPE_OBJECT,
444 G_PARAM_READABLE));
445
446 /**
447 * RBShellPlayer:play-order:
448 *
449 * The current play order object.
450 */
451 g_object_class_install_property (object_class,
452 PROP_PLAY_ORDER,
453 g_param_spec_string ("play-order",
454 "play-order",
455 "What play order to use",
456 "linear",
457 G_PARAM_READABLE));
458 /**
459 * RBShellPlayer:playing:
460 *
461 * Whether Rhythmbox is currently playing something
462 */
463 g_object_class_install_property (object_class,
464 PROP_PLAYING,
465 g_param_spec_boolean ("playing",
466 "playing",
467 "Whether Rhythmbox is currently playing",
468 FALSE,
469 G_PARAM_READABLE));
470 /**
471 * RBShellPlayer:volume:
472 *
473 * The current playback volume (between 0.0 and 1.0)
474 */
475 g_object_class_install_property (object_class,
476 PROP_VOLUME,
477 g_param_spec_float ("volume",
478 "volume",
479 "Current playback volume",
480 0.0f, 1.0f, 1.0f,
481 G_PARAM_READWRITE));
482
483 /**
484 * RBShellPlayer:header:
485 *
486 * The #RBHeader object
487 */
488 g_object_class_install_property (object_class,
489 PROP_HEADER,
490 g_param_spec_object ("header",
491 "RBHeader",
492 "RBHeader object",
493 RB_TYPE_HEADER,
494 G_PARAM_READWRITE));
495 /**
496 * RBShellPlayer:mute:
497 *
498 * Whether playback is currently muted.
499 */
500 g_object_class_install_property (object_class,
501 PROP_MUTE,
502 g_param_spec_boolean ("mute",
503 "mute",
504 "Whether playback is muted",
505 FALSE,
506 G_PARAM_READWRITE));
507 /**
508 * RBShellPlayer:has-next:
509 *
510 * Whether there is a track to play after the current track.
511 */
512 g_object_class_install_property (object_class,
513 PROP_HAS_NEXT,
514 g_param_spec_boolean ("has-next",
515 "has-next",
516 "Whether there is a next track",
517 FALSE,
518 G_PARAM_READABLE));
519 /**
520 * RBShellPlayer:has-prev:
521 *
522 * Whether there was a previous track before the current track.
523 */
524 g_object_class_install_property (object_class,
525 PROP_HAS_PREV,
526 g_param_spec_boolean ("has-prev",
527 "has-prev",
528 "Whether there is a previous track",
529 FALSE,
530 G_PARAM_READABLE));
531
532 /**
533 * RBShellPlayer::window-title-changed:
534 * @player: the #RBShellPlayer
535 * @title: the new window title
536 *
537 * Emitted when the main window title text should be changed
538 */
539 rb_shell_player_signals[WINDOW_TITLE_CHANGED] =
540 g_signal_new ("window_title_changed",
541 G_OBJECT_CLASS_TYPE (object_class),
542 G_SIGNAL_RUN_LAST,
543 G_STRUCT_OFFSET (RBShellPlayerClass, window_title_changed),
544 NULL, NULL,
545 g_cclosure_marshal_VOID__STRING,
546 G_TYPE_NONE,
547 1,
548 G_TYPE_STRING);
549
550 /**
551 * RBShellPlayer::elapsed-changed:
552 * @player: the #RBShellPlayer
553 * @elapsed: the new playback position in seconds
554 *
555 * Emitted when the playback position changes.
556 */
557 rb_shell_player_signals[ELAPSED_CHANGED] =
558 g_signal_new ("elapsed_changed",
559 G_OBJECT_CLASS_TYPE (object_class),
560 G_SIGNAL_RUN_LAST,
561 G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_changed),
562 NULL, NULL,
563 g_cclosure_marshal_VOID__UINT,
564 G_TYPE_NONE,
565 1,
566 G_TYPE_UINT);
567
568 /**
569 * RBShellPlayer::playing-source-changed:
570 * @player: the #RBShellPlayer
571 * @source: the #RBSource that is now playing
572 *
573 * Emitted when a new #RBSource instance starts playing
574 */
575 rb_shell_player_signals[PLAYING_SOURCE_CHANGED] =
576 g_signal_new ("playing-source-changed",
577 G_OBJECT_CLASS_TYPE (object_class),
578 G_SIGNAL_RUN_LAST,
579 G_STRUCT_OFFSET (RBShellPlayerClass, playing_source_changed),
580 NULL, NULL,
581 g_cclosure_marshal_VOID__OBJECT,
582 G_TYPE_NONE,
583 1,
584 RB_TYPE_SOURCE);
585
586 /**
587 * RBShellPlayer::playing-changed:
588 * @player: the #RBShellPlayer
589 * @playing: flag indicating playback state
590 *
591 * Emitted when playback either stops or starts.
592 */
593 rb_shell_player_signals[PLAYING_CHANGED] =
594 g_signal_new ("playing-changed",
595 G_OBJECT_CLASS_TYPE (object_class),
596 G_SIGNAL_RUN_LAST,
597 G_STRUCT_OFFSET (RBShellPlayerClass, playing_changed),
598 NULL, NULL,
599 g_cclosure_marshal_VOID__BOOLEAN,
600 G_TYPE_NONE,
601 1,
602 G_TYPE_BOOLEAN);
603
604 /**
605 * RBShellPlayer::playing-song-changed:
606 * @player: the #RBShellPlayer
607 * @entry: the new playing #RhythmDBEntry
608 *
609 * Emitted when the playing database entry changes
610 */
611 rb_shell_player_signals[PLAYING_SONG_CHANGED] =
612 g_signal_new ("playing-song-changed",
613 G_OBJECT_CLASS_TYPE (object_class),
614 G_SIGNAL_RUN_LAST,
615 G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_changed),
616 NULL, NULL,
617 g_cclosure_marshal_VOID__BOXED,
618 G_TYPE_NONE,
619 1,
620 RHYTHMDB_TYPE_ENTRY);
621
622 /**
623 * RBShellPlayer::playing-uri-changed:
624 * @player: the #RBShellPlayer
625 * @uri: the URI of the new playing entry
626 *
627 * Emitted when the playing database entry changes, providing the
628 * URI of the entry.
629 */
630 rb_shell_player_signals[PLAYING_URI_CHANGED] =
631 g_signal_new ("playing-uri-changed",
632 G_OBJECT_CLASS_TYPE (object_class),
633 G_SIGNAL_RUN_LAST,
634 G_STRUCT_OFFSET (RBShellPlayerClass, playing_uri_changed),
635 NULL, NULL,
636 g_cclosure_marshal_VOID__STRING,
637 G_TYPE_NONE,
638 1,
639 G_TYPE_STRING);
640
641 /**
642 * RBShellPlayer::playing-song-property-changed:
643 * @player: the #RBShellPlayer
644 * @uri: the URI of the playing entry
645 * @property: the name of the property that changed
646 * @old: the previous value for the property
647 * @newvalue: the new value of the property
648 *
649 * Emitted when a property of the playing database entry changes.
650 */
651 rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED] =
652 g_signal_new ("playing-song-property-changed",
653 G_OBJECT_CLASS_TYPE (object_class),
654 G_SIGNAL_RUN_LAST,
655 G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_property_changed),
656 NULL, NULL,
657 rb_marshal_VOID__STRING_STRING_POINTER_POINTER,
658 G_TYPE_NONE,
659 4,
660 G_TYPE_STRING, G_TYPE_STRING,
661 G_TYPE_VALUE, G_TYPE_VALUE);
662
663 /**
664 * RBShellPlayer::elapsed-nano-changed:
665 * @player: the #RBShellPlayer
666 * @elapsed: the new playback position in nanoseconds
667 *
668 * Emitted when the playback position changes. Only use this (as opposed to
669 * elapsed-changed) when you require subsecond precision. This signal will be
670 * emitted multiple times per second.
671 */
672 rb_shell_player_signals[ELAPSED_NANO_CHANGED] =
673 g_signal_new ("elapsed-nano-changed",
674 G_OBJECT_CLASS_TYPE (object_class),
675 G_SIGNAL_RUN_LAST,
676 G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_nano_changed),
677 NULL, NULL,
678 rb_marshal_VOID__INT64,
679 G_TYPE_NONE,
680 1,
681 G_TYPE_INT64);
682
683 g_type_class_add_private (klass, sizeof (RBShellPlayerPrivate));
684 }
685
686 static void
687 rb_shell_player_constructed (GObject *object)
688 {
689 RBShellPlayer *player;
690 GtkAction *action;
691
692 RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);
693
694 player = RB_SHELL_PLAYER (object);
695
696 gtk_action_group_add_actions (player->priv->actiongroup,
697 rb_shell_player_actions,
698 rb_shell_player_n_actions,
699 player);
700 gtk_action_group_add_toggle_actions (player->priv->actiongroup,
701 rb_shell_player_toggle_entries,
702 rb_shell_player_n_toggle_entries,
703 player);
704
705 player_settings_changed_cb (player->priv->settings, "transition-time", player);
706 player_settings_changed_cb (player->priv->settings, "play-order", player);
707
708 action = gtk_action_group_get_action (player->priv->actiongroup,
709 "ControlPlay");
710 g_object_set (action, "is-important", TRUE, NULL);
711
712 action = gtk_action_group_get_action (player->priv->actiongroup, "ControlPrevious");
713 g_object_bind_property (player, "has-prev", action, "sensitive", G_BINDING_DEFAULT);
714 action = gtk_action_group_get_action (player->priv->actiongroup, "ControlNext");
715 g_object_bind_property (player, "has-next", action, "sensitive", G_BINDING_DEFAULT);
716
717 player->priv->syncing_state = TRUE;
718 rb_shell_player_set_playing_source (player, NULL);
719 rb_shell_player_sync_play_order (player);
720 rb_shell_player_sync_control_state (player);
721 rb_shell_player_sync_volume (player, FALSE, TRUE);
722 player->priv->syncing_state = FALSE;
723
724 g_signal_connect (player,
725 "notify::playing",
726 G_CALLBACK (rb_shell_player_playing_changed_cb),
727 NULL);
728 }
729
730 static void
731 volume_pre_unmount_cb (GVolumeMonitor *monitor,
732 GMount *mount,
733 RBShellPlayer *player)
734 {
735 const char *entry_mount_point;
736 GFile *mount_root;
737 RhythmDBEntry *entry;
738
739 entry = rb_shell_player_get_playing_entry (player);
740 if (entry == NULL) {
741 return;
742 }
743
744 entry_mount_point = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
745 if (entry_mount_point == NULL) {
746 return;
747 }
748
749 mount_root = g_mount_get_root (mount);
750 if (mount_root != NULL) {
751 char *mount_point;
752
753 mount_point = g_file_get_uri (mount_root);
754 if (mount_point && entry_mount_point &&
755 strcmp (entry_mount_point, mount_point) == 0) {
756 rb_shell_player_stop (player);
757 }
758
759 g_free (mount_point);
760 g_object_unref (mount_root);
761 }
762
763 rhythmdb_entry_unref (entry);
764 }
765
766 static void
767 reemit_playing_signal (RBShellPlayer *player,
768 GParamSpec *pspec,
769 gpointer data)
770 {
771 g_signal_emit (player, rb_shell_player_signals[PLAYING_CHANGED], 0,
772 rb_player_playing (player->priv->mmplayer));
773 }
774
775 static void
776 rb_shell_player_open_playlist_url (RBShellPlayer *player,
777 const char *location,
778 RhythmDBEntry *entry,
779 RBPlayerPlayType play_type)
780 {
781 GError *error = NULL;
782
783 rb_debug ("playing stream url %s", location);
784 rb_player_open (player->priv->mmplayer,
785 location,
786 rhythmdb_entry_ref (entry),
787 (GDestroyNotify) rhythmdb_entry_unref,
788 &error);
789 if (error == NULL)
790 rb_player_play (player->priv->mmplayer, play_type, player->priv->track_transition_time, &error);
791
792 if (error) {
793 GDK_THREADS_ENTER ();
794 rb_shell_player_error (player, TRUE, error);
795 g_error_free (error);
796 GDK_THREADS_LEAVE ();
797 }
798 }
799
800 static void
801 rb_shell_player_handle_eos_unlocked (RBShellPlayer *player, RhythmDBEntry *entry, gboolean allow_stop)
802 {
803 RBSource *source;
804 gboolean update_stats;
805 gboolean dragging;
806
807 source = player->priv->current_playing_source;
808
809 /* nothing to do */
810 if (source == NULL) {
811 return;
812 }
813
814 if (player->priv->playing_entry_eos) {
815 rb_debug ("playing entry has already EOS'd");
816 return;
817 }
818
819 if (entry != NULL) {
820 if (player->priv->playing_entry != entry) {
821 rb_debug ("EOS'd entry is not the current playing entry; ignoring");
822 return;
823 }
824
825 rhythmdb_entry_ref (entry);
826 }
827
828 /* defer EOS handling while the position slider is being dragged */
829 g_object_get (player->priv->header_widget, "slider-dragging", &dragging, NULL);
830 if (dragging) {
831 rb_debug ("slider is dragging, will handle EOS (if applicable) on release");
832 player->priv->playing_entry_eos = TRUE;
833 if (entry != NULL)
834 rhythmdb_entry_unref (entry);
835 return;
836 }
837
838 update_stats = FALSE;
839 switch (rb_source_handle_eos (source)) {
840 case RB_SOURCE_EOF_ERROR:
841 if (allow_stop) {
842 rb_error_dialog (NULL, _("Stream error"),
843 _("Unexpected end of stream!"));
844 rb_shell_player_stop (player);
845 player->priv->playing_entry_eos = TRUE;
846 update_stats = TRUE;
847 }
848 break;
849 case RB_SOURCE_EOF_STOP:
850 if (allow_stop) {
851 rb_shell_player_stop (player);
852 player->priv->playing_entry_eos = TRUE;
853 update_stats = TRUE;
854 }
855 break;
856 case RB_SOURCE_EOF_RETRY: {
857 GTimeVal current;
858 gint diff;
859
860 g_get_current_time (¤t);
861 diff = current.tv_sec - player->priv->last_retry.tv_sec;
862 player->priv->last_retry = current;
863
864 if (rb_source_try_playlist (source) &&
865 !g_queue_is_empty (player->priv->playlist_urls)) {
866 char *location = g_queue_pop_head (player->priv->playlist_urls);
867 rb_debug ("trying next radio stream url: %s", location);
868
869 /* we're handling an unexpected EOS here, so crossfading isn't
870 * really possible anyway -> specify FALSE.
871 */
872 rb_shell_player_open_playlist_url (player, location, entry, FALSE);
873 g_free (location);
874 break;
875 }
876
877 if (allow_stop) {
878 if (diff < 4) {
879 rb_debug ("Last retry was less than 4 seconds ago...aborting retry playback");
880 rb_shell_player_stop (player);
881 } else {
882 rb_shell_player_play_entry (player, entry, NULL);
883 }
884 player->priv->playing_entry_eos = TRUE;
885 update_stats = TRUE;
886 }
887 }
888 break;
889 case RB_SOURCE_EOF_NEXT:
890 {
891 GError *error = NULL;
892
893 player->priv->playing_entry_eos = TRUE;
894 update_stats = TRUE;
895 if (!rb_shell_player_do_next_internal (player, TRUE, allow_stop, &error)) {
896 if (error->domain != RB_SHELL_PLAYER_ERROR ||
897 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
898 g_warning ("Unhandled error: %s", error->message);
899 } else if (allow_stop == FALSE) {
900 /* handle the real EOS when it happens */
901 player->priv->playing_entry_eos = FALSE;
902 update_stats = FALSE;
903 }
904 }
905 }
906 break;
907 }
908
909 if (update_stats &&
910 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) == NULL) {
911 rb_debug ("updating play statistics");
912 rb_source_update_play_statistics (source,
913 player->priv->db,
914 entry);
915 }
916
917 if (entry != NULL)
918 rhythmdb_entry_unref (entry);
919 }
920
921 static void
922 rb_shell_player_slider_dragging_cb (GObject *header, GParamSpec *pspec, RBShellPlayer *player)
923 {
924 gboolean drag;
925
926 g_object_get (player->priv->header_widget, "slider-dragging", &drag, NULL);
927 rb_debug ("slider dragging? %d", drag);
928
929 /* if an EOS occurred while dragging, process it now */
930 if (drag == FALSE && player->priv->playing_entry_eos) {
931 rb_debug ("processing EOS delayed due to slider dragging");
932 player->priv->playing_entry_eos = FALSE;
933 rb_shell_player_handle_eos_unlocked (player, rb_shell_player_get_playing_entry (player), FALSE);
934 }
935 }
936
937 static void
938 rb_shell_player_handle_eos (RBPlayer *player,
939 RhythmDBEntry *entry,
940 gboolean early,
941 RBShellPlayer *shell_player)
942 {
943 const char *location;
944 if (entry == NULL) {
945 /* special case: this is called with entry == NULL to simulate an EOS
946 * from the current playing entry.
947 */
948 entry = shell_player->priv->playing_entry;
949 if (entry == NULL) {
950 rb_debug ("called to simulate EOS for playing entry, but nothing is playing");
951 return;
952 }
953 }
954
955 GDK_THREADS_ENTER ();
956
957 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
958 if (entry != shell_player->priv->playing_entry) {
959 rb_debug ("got unexpected eos for %s", location);
960 } else {
961 rb_debug ("handling eos for %s", location);
962 /* don't allow playback to be stopped on early EOS notifications */
963 rb_shell_player_handle_eos_unlocked (shell_player, entry, (early == FALSE));
964 }
965
966 GDK_THREADS_LEAVE ();
967 }
968
969
970 static void
971 rb_shell_player_handle_redirect (RBPlayer *player,
972 RhythmDBEntry *entry,
973 const gchar *uri,
974 RBShellPlayer *shell_player)
975 {
976 GValue val = { 0 };
977
978 rb_debug ("redirect to %s", uri);
979
980 /* Stop existing stream */
981 rb_player_close (shell_player->priv->mmplayer, NULL, NULL);
982
983 /* Update entry */
984 g_value_init (&val, G_TYPE_STRING);
985 g_value_set_string (&val, uri);
986 rhythmdb_entry_set (shell_player->priv->db, entry, RHYTHMDB_PROP_LOCATION, &val);
987 g_value_unset (&val);
988 rhythmdb_commit (shell_player->priv->db);
989
990 /* Play new URI */
991 rb_shell_player_open_location (shell_player, entry, RB_PLAYER_PLAY_REPLACE, NULL);
992 }
993
994 static void
995 rb_shell_player_init (RBShellPlayer *player)
996 {
997 GError *error = NULL;
998
999 player->priv = RB_SHELL_PLAYER_GET_PRIVATE (player);
1000
1001 player->priv->settings = g_settings_new ("org.gnome.rhythmbox.player");
1002 player->priv->ui_settings = g_settings_new ("org.gnome.rhythmbox");
1003 g_signal_connect_object (player->priv->settings,
1004 "changed",
1005 G_CALLBACK (player_settings_changed_cb),
1006 player, 0);
1007
1008 player->priv->play_orders = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)_play_order_description_free);
1009
1010 rb_shell_player_add_play_order (player, "linear", N_("Linear"),
1011 RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
1012 rb_shell_player_add_play_order (player, "linear-loop", N_("Linear looping"),
1013 RB_TYPE_LINEAR_PLAY_ORDER_LOOP, FALSE);
1014 rb_shell_player_add_play_order (player, "shuffle", N_("Shuffle"),
1015 RB_TYPE_SHUFFLE_PLAY_ORDER, FALSE);
1016 rb_shell_player_add_play_order (player, "random-equal-weights", N_("Random with equal weights"),
1017 RB_TYPE_RANDOM_PLAY_ORDER_EQUAL_WEIGHTS, FALSE);
1018 rb_shell_player_add_play_order (player, "random-by-age", N_("Random by time since last play"),
1019 RB_TYPE_RANDOM_PLAY_ORDER_BY_AGE, FALSE);
1020 rb_shell_player_add_play_order (player, "random-by-rating", N_("Random by rating"),
1021 RB_TYPE_RANDOM_PLAY_ORDER_BY_RATING, FALSE);
1022 rb_shell_player_add_play_order (player, "random-by-age-and-rating", N_("Random by time since last play and rating"),
1023 RB_TYPE_RANDOM_PLAY_ORDER_BY_AGE_AND_RATING, FALSE);
1024 rb_shell_player_add_play_order (player, "queue", N_("Linear, removing entries once played"),
1025 RB_TYPE_QUEUE_PLAY_ORDER, TRUE);
1026
1027 player->priv->mmplayer = rb_player_new (g_settings_get_boolean (player->priv->settings, "use-xfade-backend"),
1028 &error);
1029 if (error != NULL) {
1030 GtkWidget *dialog;
1031 dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
1032 GTK_MESSAGE_ERROR,
1033 GTK_BUTTONS_CLOSE,
1034 _("Failed to create the player: %s"),
1035 error->message);
1036 gtk_dialog_run (GTK_DIALOG (dialog));
1037 exit (1);
1038 }
1039
1040 g_signal_connect_object (player->priv->mmplayer,
1041 "eos",
1042 G_CALLBACK (rb_shell_player_handle_eos),
1043 player, 0);
1044
1045 g_signal_connect_object (player->priv->mmplayer,
1046 "redirect",
1047 G_CALLBACK (rb_shell_player_handle_redirect),
1048 player, 0);
1049
1050 g_signal_connect_object (player->priv->mmplayer,
1051 "tick",
1052 G_CALLBACK (tick_cb),
1053 player, 0);
1054
1055 g_signal_connect_object (player->priv->mmplayer,
1056 "error",
1057 G_CALLBACK (error_cb),
1058 player, 0);
1059
1060 g_signal_connect_object (player->priv->mmplayer,
1061 "playing-stream",
1062 G_CALLBACK (playing_stream_cb),
1063 player, 0);
1064
1065 g_signal_connect_object (player->priv->mmplayer,
1066 "missing-plugins",
1067 G_CALLBACK (missing_plugins_cb),
1068 player, 0);
1069 g_signal_connect_object (player->priv->mmplayer,
1070 "volume-changed",
1071 G_CALLBACK (rb_shell_player_volume_changed_cb),
1072 player, 0);
1073
1074 g_signal_connect_object (player->priv->mmplayer,
1075 "image",
1076 G_CALLBACK (player_image_cb),
1077 player, 0);
1078
1079 {
1080 GVolumeMonitor *monitor = g_volume_monitor_get ();
1081 g_signal_connect (G_OBJECT (monitor),
1082 "mount-pre-unmount",
1083 G_CALLBACK (volume_pre_unmount_cb),
1084 player);
1085 g_object_unref (monitor); /* hmm */
1086 }
1087
1088 player->priv->volume = g_settings_get_double (player->priv->settings, "volume");
1089
1090 g_signal_connect (player, "notify::playing",
1091 G_CALLBACK (reemit_playing_signal), NULL);
1092 }
1093
1094 static void
1095 rb_shell_player_set_source_internal (RBShellPlayer *player,
1096 RBSource *source)
1097 {
1098 if (player->priv->selected_source != NULL) {
1099 RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
1100 GList *property_views = rb_source_get_property_views (player->priv->selected_source);
1101 GList *l;
1102
1103 if (songs != NULL) {
1104 g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
1105 G_CALLBACK (rb_shell_player_entry_activated_cb),
1106 player);
1107 }
1108
1109 for (l = property_views; l != NULL; l = g_list_next (l)) {
1110 g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
1111 G_CALLBACK (rb_shell_player_property_row_activated_cb),
1112 player);
1113 }
1114
1115 g_list_free (property_views);
1116 }
1117
1118 player->priv->selected_source = source;
1119
1120 rb_debug ("selected source %p", player->priv->selected_source);
1121
1122 rb_shell_player_sync_with_selected_source (player);
1123 rb_shell_player_sync_buttons (player);
1124
1125 if (player->priv->selected_source != NULL) {
1126 RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
1127 GList *property_views = rb_source_get_property_views (player->priv->selected_source);
1128 GList *l;
1129
1130 if (songs)
1131 g_signal_connect_object (G_OBJECT (songs),
1132 "entry-activated",
1133 G_CALLBACK (rb_shell_player_entry_activated_cb),
1134 player, 0);
1135 for (l = property_views; l != NULL; l = g_list_next (l)) {
1136 g_signal_connect_object (G_OBJECT (l->data),
1137 "property-activated",
1138 G_CALLBACK (rb_shell_player_property_row_activated_cb),
1139 player, 0);
1140 }
1141
1142 g_list_free (property_views);
1143 }
1144
1145 /* If we're not playing, change the play order's view of the current source;
1146 * if the selected source is the queue, however, set it to NULL so it'll stop
1147 * once the queue is empty.
1148 */
1149 if (player->priv->current_playing_source == NULL) {
1150 RBPlayOrder *porder = NULL;
1151 RBSource *source = player->priv->selected_source;
1152 if (source == RB_SOURCE (player->priv->queue_source)) {
1153 source = NULL;
1154 } else if (source != NULL) {
1155 g_object_get (source, "play-order", &porder, NULL);
1156 }
1157
1158 if (porder == NULL)
1159 porder = g_object_ref (player->priv->play_order);
1160
1161 rb_play_order_playing_source_changed (porder, source);
1162 g_object_unref (porder);
1163 }
1164 }
1165
1166 static void
1167 rb_shell_player_set_db_internal (RBShellPlayer *player,
1168 RhythmDB *db)
1169 {
1170 if (player->priv->db != NULL) {
1171 g_signal_handlers_disconnect_by_func (player->priv->db,
1172 G_CALLBACK (rb_shell_player_entry_changed_cb),
1173 player);
1174 g_signal_handlers_disconnect_by_func (player->priv->db,
1175 G_CALLBACK (rb_shell_player_extra_metadata_cb),
1176 player);
1177 }
1178
1179 player->priv->db = db;
1180
1181 if (player->priv->db != NULL) {
1182 /* Listen for changed entries to update metadata display */
1183 g_signal_connect_object (G_OBJECT (player->priv->db),
1184 "entry_changed",
1185 G_CALLBACK (rb_shell_player_entry_changed_cb),
1186 player, 0);
1187 g_signal_connect_object (G_OBJECT (player->priv->db),
1188 "entry_extra_metadata_notify",
1189 G_CALLBACK (rb_shell_player_extra_metadata_cb),
1190 player, 0);
1191 }
1192 }
1193
1194 static void
1195 rb_shell_player_set_queue_source_internal (RBShellPlayer *player,
1196 RBPlayQueueSource *source)
1197 {
1198 if (player->priv->queue_source != NULL) {
1199 RBEntryView *sidebar;
1200
1201 g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
1202 g_signal_handlers_disconnect_by_func (sidebar,
1203 G_CALLBACK (rb_shell_player_entry_activated_cb),
1204 player);
1205 g_object_unref (sidebar);
1206
1207 if (player->priv->queue_play_order != NULL) {
1208 g_signal_handlers_disconnect_by_func (player->priv->queue_play_order,
1209 G_CALLBACK (rb_shell_player_play_order_update_cb),
1210 player);
1211 g_object_unref (player->priv->queue_play_order);
1212 }
1213
1214 }
1215
1216 player->priv->queue_source = source;
1217
1218 if (player->priv->queue_source != NULL) {
1219 RBEntryView *sidebar;
1220
1221 g_object_get (player->priv->queue_source, "play-order", &player->priv->queue_play_order, NULL);
1222
1223 g_signal_connect_object (G_OBJECT (player->priv->queue_play_order),
1224 "have_next_previous_changed",
1225 G_CALLBACK (rb_shell_player_play_order_update_cb),
1226 player, 0);
1227 rb_shell_player_play_order_update_cb (player->priv->play_order,
1228 FALSE, FALSE,
1229 player);
1230 rb_play_order_playing_source_changed (player->priv->queue_play_order,
1231 RB_SOURCE (player->priv->queue_source));
1232
1233 g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
1234 g_signal_connect_object (G_OBJECT (sidebar),
1235 "entry-activated",
1236 G_CALLBACK (rb_shell_player_entry_activated_cb),
1237 player, 0);
1238 g_object_unref (sidebar);
1239 }
1240 }
1241
1242 static void
1243 rb_shell_player_dispose (GObject *object)
1244 {
1245 RBShellPlayer *player;
1246
1247 g_return_if_fail (object != NULL);
1248 g_return_if_fail (RB_IS_SHELL_PLAYER (object));
1249
1250 player = RB_SHELL_PLAYER (object);
1251
1252 g_return_if_fail (player->priv != NULL);
1253
1254 if (player->priv->ui_settings != NULL) {
1255 g_object_unref (player->priv->ui_settings);
1256 player->priv->ui_settings = NULL;
1257 }
1258
1259 if (player->priv->settings != NULL) {
1260 /* hm, is this really the place to do this? */
1261 g_settings_set_double (player->priv->settings,
1262 "volume",
1263 player->priv->volume);
1264
1265 g_object_unref (player->priv->settings);
1266 player->priv->settings = NULL;
1267 }
1268
1269 if (player->priv->mmplayer != NULL) {
1270 g_object_unref (player->priv->mmplayer);
1271 player->priv->mmplayer = NULL;
1272 }
1273
1274 if (player->priv->play_order != NULL) {
1275 g_object_unref (player->priv->play_order);
1276 player->priv->play_order = NULL;
1277 }
1278
1279 if (player->priv->queue_play_order != NULL) {
1280 g_object_unref (player->priv->queue_play_order);
1281 player->priv->queue_play_order = NULL;
1282 }
1283
1284 if (player->priv->do_next_idle_id != 0) {
1285 g_source_remove (player->priv->do_next_idle_id);
1286 player->priv->do_next_idle_id = 0;
1287 }
1288
1289 if (player->priv->unblock_play_id != 0) {
1290 g_source_remove (player->priv->unblock_play_id);
1291 player->priv->unblock_play_id = 0;
1292 }
1293
1294 G_OBJECT_CLASS (rb_shell_player_parent_class)->dispose (object);
1295 }
1296
1297 static void
1298 rb_shell_player_finalize (GObject *object)
1299 {
1300 RBShellPlayer *player;
1301
1302 g_return_if_fail (object != NULL);
1303 g_return_if_fail (RB_IS_SHELL_PLAYER (object));
1304
1305 player = RB_SHELL_PLAYER (object);
1306
1307 g_return_if_fail (player->priv != NULL);
1308
1309 g_hash_table_destroy (player->priv->play_orders);
1310
1311 G_OBJECT_CLASS (rb_shell_player_parent_class)->finalize (object);
1312 }
1313
1314 static void
1315 rb_shell_player_set_property (GObject *object,
1316 guint prop_id,
1317 const GValue *value,
1318 GParamSpec *pspec)
1319 {
1320 RBShellPlayer *player = RB_SHELL_PLAYER (object);
1321
1322 switch (prop_id) {
1323 case PROP_SOURCE:
1324 rb_shell_player_set_source_internal (player, g_value_get_object (value));
1325 break;
1326 case PROP_UI_MANAGER:
1327 player->priv->ui_manager = g_value_get_object (value);
1328 break;
1329 case PROP_DB:
1330 rb_shell_player_set_db_internal (player, g_value_get_object (value));
1331 break;
1332 case PROP_ACTION_GROUP:
1333 player->priv->actiongroup = g_value_get_object (value);
1334 break;
1335 case PROP_PLAY_ORDER:
1336 g_settings_set_string (player->priv->settings,
1337 "play-order",
1338 g_value_get_string (value));
1339 break;
1340 case PROP_VOLUME:
1341 player->priv->volume = g_value_get_float (value);
1342 rb_shell_player_sync_volume (player, FALSE, TRUE);
1343 break;
1344 case PROP_HEADER:
1345 player->priv->header_widget = g_value_get_object (value);
1346 g_signal_connect_object (player->priv->header_widget,
1347 "notify::slider-dragging",
1348 G_CALLBACK (rb_shell_player_slider_dragging_cb),
1349 player, 0);
1350 break;
1351 case PROP_QUEUE_SOURCE:
1352 rb_shell_player_set_queue_source_internal (player, g_value_get_object (value));
1353 break;
1354 case PROP_QUEUE_ONLY:
1355 player->priv->queue_only = g_value_get_boolean (value);
1356 break;
1357 case PROP_MUTE:
1358 player->priv->mute = g_value_get_boolean (value);
1359 rb_shell_player_sync_volume (player, FALSE, TRUE);
1360 default:
1361 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1362 break;
1363 }
1364 }
1365
1366 static void
1367 rb_shell_player_get_property (GObject *object,
1368 guint prop_id,
1369 GValue *value,
1370 GParamSpec *pspec)
1371 {
1372 RBShellPlayer *player = RB_SHELL_PLAYER (object);
1373
1374 switch (prop_id) {
1375 case PROP_SOURCE:
1376 g_value_set_object (value, player->priv->selected_source);
1377 break;
1378 case PROP_UI_MANAGER:
1379 g_value_set_object (value, player->priv->ui_manager);
1380 break;
1381 case PROP_DB:
1382 g_value_set_object (value, player->priv->db);
1383 break;
1384 case PROP_ACTION_GROUP:
1385 g_value_set_object (value, player->priv->actiongroup);
1386 break;
1387 case PROP_PLAY_ORDER:
1388 {
1389 char *play_order = g_settings_get_string (player->priv->settings,
1390 "play-order");
1391 if (play_order == NULL)
1392 play_order = g_strdup ("linear");
1393 g_value_take_string (value, play_order);
1394 break;
1395 }
1396 case PROP_PLAYING:
1397 if (player->priv->mmplayer != NULL)
1398 g_value_set_boolean (value, rb_player_playing (player->priv->mmplayer));
1399 else
1400 g_value_set_boolean (value, FALSE);
1401 break;
1402 case PROP_VOLUME:
1403 g_value_set_float (value, player->priv->volume);
1404 break;
1405 case PROP_HEADER:
1406 g_value_set_object (value, player->priv->header_widget);
1407 break;
1408 case PROP_QUEUE_SOURCE:
1409 g_value_set_object (value, player->priv->queue_source);
1410 break;
1411 case PROP_QUEUE_ONLY:
1412 g_value_set_boolean (value, player->priv->queue_only);
1413 break;
1414 case PROP_PLAYING_FROM_QUEUE:
1415 g_value_set_boolean (value, player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source));
1416 break;
1417 case PROP_PLAYER:
1418 g_value_set_object (value, player->priv->mmplayer);
1419 break;
1420 case PROP_MUTE:
1421 g_value_set_boolean (value, player->priv->mute);
1422 break;
1423 case PROP_HAS_NEXT:
1424 g_value_set_boolean (value, player->priv->has_next);
1425 break;
1426 case PROP_HAS_PREV:
1427 g_value_set_boolean (value, player->priv->has_prev);
1428 break;
1429 default:
1430 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1431 break;
1432 }
1433 }
1434
1435 GQuark
1436 rb_shell_player_error_quark (void)
1437 {
1438 static GQuark quark = 0;
1439 if (!quark)
1440 quark = g_quark_from_static_string ("rb_shell_player_error");
1441
1442 return quark;
1443 }
1444
1445 /**
1446 * rb_shell_player_set_selected_source:
1447 * @player: the #RBShellPlayer
1448 * @source: the #RBSource to select
1449 *
1450 * Updates the player to reflect a new source being selected.
1451 */
1452 void
1453 rb_shell_player_set_selected_source (RBShellPlayer *player,
1454 RBSource *source)
1455 {
1456 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
1457 g_return_if_fail (source == NULL || RB_IS_SOURCE (source));
1458
1459 g_object_set (player, "source", source, NULL);
1460 }
1461
1462 /**
1463 * rb_shell_player_get_playing_source:
1464 * @player: the #RBShellPlayer
1465 *
1466 * Retrieves the current playing source. That is, the source from
1467 * which the current song was drawn. This differs from
1468 * #rb_shell_player_get_active_source when the current song came
1469 * from the play queue.
1470 *
1471 * Return value: (transfer none): the current playing #RBSource
1472 */
1473 RBSource *
1474 rb_shell_player_get_playing_source (RBShellPlayer *player)
1475 {
1476 return player->priv->current_playing_source;
1477 }
1478
1479 /**
1480 * rb_shell_player_get_active_source:
1481 * @player: the #RBShellPlayer
1482 *
1483 * Retrieves the active source. This is the source that the user
1484 * selected for playback.
1485 *
1486 * Return value: (transfer none): the active #RBSource
1487 */
1488 RBSource *
1489 rb_shell_player_get_active_source (RBShellPlayer *player)
1490 {
1491 return player->priv->source;
1492 }
1493
1494 /**
1495 * rb_shell_player_new:
1496 * @db: the #RhythmDB
1497 * @mgr: the #GtkUIManager
1498 * @actiongroup: the #GtkActionGroup to use
1499 *
1500 * Creates the #RBShellPlayer
1501 *
1502 * Return value: the #RBShellPlayer instance
1503 */
1504 RBShellPlayer *
1505 rb_shell_player_new (RhythmDB *db,
1506 GtkUIManager *mgr,
1507 GtkActionGroup *actiongroup)
1508 {
1509 return g_object_new (RB_TYPE_SHELL_PLAYER,
1510 "ui-manager", mgr,
1511 "action-group", actiongroup,
1512 "db", db,
1513 NULL);
1514 }
1515
1516 /**
1517 * rb_shell_player_get_playing_entry:
1518 * @player: the #RBShellPlayer
1519 *
1520 * Retrieves the currently playing #RhythmDBEntry, or NULL if
1521 * nothing is playing. The caller must unref the entry
1522 * (using #rhythmdb_entry_unref) when it is no longer needed.
1523 *
1524 * Return value: (transfer full) (allow-none): the currently playing #RhythmDBEntry, or NULL
1525 */
1526 RhythmDBEntry *
1527 rb_shell_player_get_playing_entry (RBShellPlayer *player)
1528 {
1529 RBPlayOrder *porder;
1530 RhythmDBEntry *entry;
1531
1532 if (player->priv->current_playing_source == NULL) {
1533 return NULL;
1534 }
1535
1536 g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
1537 if (porder == NULL)
1538 porder = g_object_ref (player->priv->play_order);
1539
1540 entry = rb_play_order_get_playing_entry (porder);
1541 g_object_unref (porder);
1542
1543 return entry;
1544 }
1545
1546 typedef struct {
1547 RBShellPlayer *player;
1548 char *location;
1549 RhythmDBEntry *entry;
1550 RBPlayerPlayType play_type;
1551 GCancellable *cancellable;
1552 } OpenLocationThreadData;
1553
1554 static void
1555 playlist_entry_cb (TotemPlParser *playlist,
1556 const char *uri,
1557 GHashTable *metadata,
1558 OpenLocationThreadData *data)
1559 {
1560 if (g_cancellable_is_cancelled (data->cancellable)) {
1561 rb_debug ("playlist parser cancelled");
1562 } else {
1563 rb_debug ("adding stream url %s (%p)", uri, playlist);
1564 g_queue_push_tail (data->player->priv->playlist_urls, g_strdup (uri));
1565 }
1566 }
1567
1568 static gpointer
1569 open_location_thread (OpenLocationThreadData *data)
1570 {
1571 TotemPlParser *playlist;
1572 TotemPlParserResult playlist_result;
1573
1574 playlist = totem_pl_parser_new ();
1575
1576 g_signal_connect_data (playlist, "entry-parsed",
1577 G_CALLBACK (playlist_entry_cb),
1578 data, NULL, 0);
1579
1580 totem_pl_parser_add_ignored_mimetype (playlist, "x-directory/normal");
1581 totem_pl_parser_add_ignored_mimetype (playlist, "inode/directory");
1582
1583 playlist_result = totem_pl_parser_parse (playlist, data->location, FALSE);
1584 g_object_unref (playlist);
1585
1586 if (g_cancellable_is_cancelled (data->cancellable)) {
1587 playlist_result = TOTEM_PL_PARSER_RESULT_CANCELLED;
1588 }
1589
1590 switch (playlist_result) {
1591 case TOTEM_PL_PARSER_RESULT_SUCCESS:
1592 if (g_queue_is_empty (data->player->priv->playlist_urls)) {
1593 GError *error = g_error_new (RB_SHELL_PLAYER_ERROR,
1594 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
1595 _("Playlist was empty"));
1596 GDK_THREADS_ENTER ();
1597 rb_shell_player_error (data->player, TRUE, error);
1598 g_error_free (error);
1599 GDK_THREADS_LEAVE ();
1600 } else {
1601 char *location;
1602
1603 location = g_queue_pop_head (data->player->priv->playlist_urls);
1604 rb_debug ("playing first stream url %s", location);
1605 rb_shell_player_open_playlist_url (data->player, location, data->entry, data->play_type);
1606 g_free (location);
1607 }
1608 break;
1609
1610 case TOTEM_PL_PARSER_RESULT_CANCELLED:
1611 rb_debug ("playlist parser was cancelled");
1612 break;
1613
1614 default:
1615 /* if we can't parse it as a playlist, just try playing it */
1616 rb_debug ("playlist parser failed, playing %s directly", data->location);
1617 rb_shell_player_open_playlist_url (data->player, data->location, data->entry, data->play_type);
1618 break;
1619 }
1620
1621 g_object_unref (data->cancellable);
1622 g_free (data);
1623 return NULL;
1624 }
1625
1626 static gboolean
1627 rb_shell_player_open_location (RBShellPlayer *player,
1628 RhythmDBEntry *entry,
1629 RBPlayerPlayType play_type,
1630 GError **error)
1631 {
1632 char *location;
1633 gboolean ret = TRUE;
1634
1635 /* dispose of any existing playlist urls */
1636 if (player->priv->playlist_urls) {
1637 g_queue_foreach (player->priv->playlist_urls,
1638 (GFunc) g_free,
1639 NULL);
1640 g_queue_free (player->priv->playlist_urls);
1641 player->priv->playlist_urls = NULL;
1642 }
1643 if (rb_source_try_playlist (player->priv->source)) {
1644 player->priv->playlist_urls = g_queue_new ();
1645 }
1646
1647 location = rhythmdb_entry_get_playback_uri (entry);
1648 if (location == NULL) {
1649 return FALSE;
1650 }
1651
1652 if (rb_source_try_playlist (player->priv->source)) {
1653 OpenLocationThreadData *data;
1654
1655 data = g_new0 (OpenLocationThreadData, 1);
1656 data->player = player;
1657 data->play_type = play_type;
1658 data->entry = entry;
1659
1660 /* add http:// as a prefix, if it doesn't have a URI scheme */
1661 if (strstr (location, "://"))
1662 data->location = g_strdup (location);
1663 else
1664 data->location = g_strconcat ("http://", location, NULL);
1665
1666 if (player->priv->parser_cancellable == NULL) {
1667 player->priv->parser_cancellable = g_cancellable_new ();
1668 }
1669 data->cancellable = g_object_ref (player->priv->parser_cancellable);
1670
1671 g_thread_new ("open-location", (GThreadFunc)open_location_thread, data);
1672 } else {
1673 if (player->priv->parser_cancellable != NULL) {
1674 g_object_unref (player->priv->parser_cancellable);
1675 player->priv->parser_cancellable = NULL;
1676 }
1677
1678 rhythmdb_entry_ref (entry);
1679 ret = ret && rb_player_open (player->priv->mmplayer, location, entry, (GDestroyNotify) rhythmdb_entry_unref, error);
1680
1681 ret = ret && rb_player_play (player->priv->mmplayer, play_type, player->priv->track_transition_time, error);
1682 }
1683
1684 g_free (location);
1685 return ret;
1686 }
1687
1688 /**
1689 * rb_shell_player_play:
1690 * @player: a #RBShellPlayer
1691 * @error: error return
1692 *
1693 * Starts playback, if it is not already playing.
1694 *
1695 * Return value: whether playback is now occurring (TRUE when successfully started
1696 * or already playing).
1697 **/
1698 gboolean
1699 rb_shell_player_play (RBShellPlayer *player,
1700 GError **error)
1701 {
1702 RBEntryView *songs;
1703
1704 if (player->priv->current_playing_source == NULL) {
1705 rb_debug ("current playing source is NULL");
1706 g_set_error (error,
1707 RB_SHELL_PLAYER_ERROR,
1708 RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
1709 "Current playing source is NULL");
1710 return FALSE;
1711 }
1712
1713 if (rb_player_playing (player->priv->mmplayer))
1714 return TRUE;
1715
1716 if (player->priv->parser_cancellable != NULL) {
1717 rb_debug ("currently parsing a playlist");
1718 return TRUE;
1719 }
1720
1721 /* we're obviously not playing anything, so crossfading is irrelevant */
1722 if (!rb_player_play (player->priv->mmplayer, RB_PLAYER_PLAY_REPLACE, 0.0f, error)) {
1723 rb_debug ("player doesn't want to");
1724 return FALSE;
1725 }
1726
1727 songs = rb_source_get_entry_view (player->priv->current_playing_source);
1728 if (songs)
1729 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
1730
1731 return TRUE;
1732 }
1733
1734 static void
1735 rb_shell_player_set_entry_playback_error (RBShellPlayer *player,
1736 RhythmDBEntry *entry,
1737 char *message)
1738 {
1739 GValue value = { 0, };
1740
1741 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
1742
1743 g_value_init (&value, G_TYPE_STRING);
1744 g_value_set_string (&value, message);
1745 rhythmdb_entry_set (player->priv->db,
1746 entry,
1747 RHYTHMDB_PROP_PLAYBACK_ERROR,
1748 &value);
1749 g_value_unset (&value);
1750 rhythmdb_commit (player->priv->db);
1751 }
1752
1753 static gboolean
1754 rb_shell_player_set_playing_entry (RBShellPlayer *player,
1755 RhythmDBEntry *entry,
1756 gboolean out_of_order,
1757 gboolean wait_for_eos,
1758 GError **error)
1759 {
1760 GError *tmp_error = NULL;
1761 GValue val = {0,};
1762 RBPlayerPlayType play_type;
1763
1764 g_return_val_if_fail (player->priv->current_playing_source != NULL, TRUE);
1765 g_return_val_if_fail (entry != NULL, TRUE);
1766
1767 play_type = wait_for_eos ? RB_PLAYER_PLAY_AFTER_EOS : RB_PLAYER_PLAY_REPLACE;
1768
1769 if (out_of_order) {
1770 RBPlayOrder *porder;
1771
1772 g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
1773 if (porder == NULL)
1774 porder = g_object_ref (player->priv->play_order);
1775 rb_play_order_set_playing_entry (porder, entry);
1776 g_object_unref (porder);
1777 }
1778
1779 if (player->priv->playing_entry != NULL &&
1780 player->priv->track_transition_time > 0) {
1781 const char *previous_album;
1782 const char *album;
1783
1784 previous_album = rhythmdb_entry_get_string (player->priv->playing_entry, RHYTHMDB_PROP_ALBUM);
1785 album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
1786 /* only crossfade if we're not going from the end of one song on an
1787 * album to the start of another. "Unknown" doesn't count as an album.
1788 */
1789 if (wait_for_eos == FALSE ||
1790 strcmp (album, _("Unknown")) == 0 ||
1791 strcmp (album, previous_album) != 0) {
1792 play_type = RB_PLAYER_PLAY_CROSSFADE;
1793 }
1794 }
1795
1796 if (rb_shell_player_open_location (player, entry, play_type, &tmp_error) == FALSE) {
1797 goto lose;
1798 }
1799
1800 rb_debug ("Success!");
1801 /* clear error on successful playback */
1802 g_value_init (&val, G_TYPE_STRING);
1803 g_value_set_string (&val, NULL);
1804 rhythmdb_entry_set (player->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
1805 rhythmdb_commit (player->priv->db);
1806 g_value_unset (&val);
1807
1808 return TRUE;
1809 lose:
1810 /* Ignore errors, shutdown the player */
1811 rb_player_close (player->priv->mmplayer, NULL /* XXX specify uri? */, NULL);
1812
1813 if (tmp_error == NULL) {
1814 tmp_error = g_error_new (RB_SHELL_PLAYER_ERROR,
1815 RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
1816 "Problem occurred without error being set. "
1817 "This is a bug in Rhythmbox or GStreamer.");
1818 }
1819 /* Mark this song as failed */
1820 rb_shell_player_set_entry_playback_error (player, entry, tmp_error->message);
1821 g_propagate_error (error, tmp_error);
1822
1823 rb_shell_player_sync_with_source (player);
1824 rb_shell_player_sync_buttons (player);
1825 g_object_notify (G_OBJECT (player), "playing");
1826
1827 return FALSE;
1828 }
1829
1830 static void
1831 player_settings_changed_cb (GSettings *settings, const char *key, RBShellPlayer *player)
1832 {
1833 if (g_strcmp0 (key, "play-order") == 0) {
1834 rb_debug ("play order setting changed");
1835 player->priv->syncing_state = TRUE;
1836 rb_shell_player_sync_play_order (player);
1837 rb_shell_player_sync_buttons (player);
1838 rb_shell_player_sync_control_state (player);
1839 g_object_notify (G_OBJECT (player), "play-order");
1840 player->priv->syncing_state = FALSE;
1841 } else if (g_strcmp0 (key, "transition-time") == 0) {
1842 double newtime;
1843 rb_debug ("track transition time changed");
1844 newtime = g_settings_get_double (player->priv->settings, "transition-time");
1845 player->priv->track_transition_time = newtime * RB_PLAYER_SECOND;
1846 }
1847 }
1848
1849 /**
1850 * rb_shell_player_get_playback_state:
1851 * @player: the #RBShellPlayer
1852 * @shuffle: (out): returns the current shuffle setting
1853 * @repeat: (out): returns the current repeat setting
1854 *
1855 * Retrieves the current state of the shuffle and repeat settings.
1856 *
1857 * Return value: %TRUE if successful.
1858 */
1859 gboolean
1860 rb_shell_player_get_playback_state (RBShellPlayer *player,
1861 gboolean *shuffle,
1862 gboolean *repeat)
1863 {
1864 int i, j;
1865 char *play_order;
1866
1867 play_order = g_settings_get_string (player->priv->settings, "play-order");
1868 for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
1869 for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
1870 if (!strcmp (play_order, state_to_play_order[i][j]))
1871 goto found;
1872
1873 g_free (play_order);
1874 return FALSE;
1875
1876 found:
1877 if (shuffle != NULL) {
1878 *shuffle = i > 0;
1879 }
1880 if (repeat != NULL) {
1881 *repeat = j > 0;
1882 }
1883 g_free (play_order);
1884 return TRUE;
1885 }
1886
1887 /**
1888 * rb_shell_player_set_playback_state:
1889 * @player: the #RBShellPlayer
1890 * @shuffle: whether to enable the shuffle setting
1891 * @repeat: whether to enable the repeat setting
1892 *
1893 * Sets the state of the shuffle and repeat settings.
1894 */
1895 void
1896 rb_shell_player_set_playback_state (RBShellPlayer *player,
1897 gboolean shuffle,
1898 gboolean repeat)
1899 {
1900 const char *neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
1901 g_settings_set_string (player->priv->settings, "play-order", neworder);
1902 }
1903
1904 static void
1905 rb_shell_player_sync_play_order (RBShellPlayer *player)
1906 {
1907 char *new_play_order;
1908 RhythmDBEntry *playing_entry = NULL;
1909 RBSource *source;
1910
1911 new_play_order = g_settings_get_string (player->priv->settings, "play-order");
1912 if (player->priv->play_order != NULL) {
1913 playing_entry = rb_play_order_get_playing_entry (player->priv->play_order);
1914 g_signal_handlers_disconnect_by_func (player->priv->play_order,
1915 G_CALLBACK (rb_shell_player_play_order_update_cb),
1916 player);
1917 g_object_unref (player->priv->play_order);
1918 }
1919
1920 player->priv->play_order = rb_play_order_new (player, new_play_order);
1921
1922 g_signal_connect_object (player->priv->play_order,
1923 "have_next_previous_changed",
1924 G_CALLBACK (rb_shell_player_play_order_update_cb),
1925 player, 0);
1926 rb_shell_player_play_order_update_cb (player->priv->play_order,
1927 FALSE, FALSE,
1928 player);
1929
1930 source = player->priv->current_playing_source;
1931 if (source == NULL) {
1932 source = player->priv->selected_source;
1933 }
1934 rb_play_order_playing_source_changed (player->priv->play_order, source);
1935
1936 if (playing_entry != NULL) {
1937 rb_play_order_set_playing_entry (player->priv->play_order, playing_entry);
1938 rhythmdb_entry_unref (playing_entry);
1939 }
1940
1941 g_free (new_play_order);
1942 }
1943
1944 static void
1945 rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
1946 gboolean _has_next,
1947 gboolean _has_previous,
1948 RBShellPlayer *player)
1949 {
1950 /* we cannot depend on the values of has_next, has_previous or porder
1951 * since this can be called for the main porder, queue porder, etc
1952 */
1953 gboolean has_next = FALSE;
1954 gboolean has_prev = FALSE;
1955 RhythmDBEntry *entry;
1956
1957 entry = rb_shell_player_get_playing_entry (player);
1958 if (entry != NULL) {
1959 has_next = TRUE;
1960 has_prev = TRUE;
1961 rhythmdb_entry_unref (entry);
1962 } else {
1963 if (player->priv->current_playing_source &&
1964 (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_NEXT)) {
1965 RBPlayOrder *porder;
1966 g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
1967 if (porder == NULL)
1968 porder = g_object_ref (player->priv->play_order);
1969 has_next = rb_play_order_has_next (porder);
1970 g_object_unref (porder);
1971 }
1972 if (player->priv->queue_play_order) {
1973 has_next |= rb_play_order_has_next (player->priv->queue_play_order);
1974 }
1975 has_prev = (player->priv->current_playing_source != NULL);
1976 }
1977
1978 if (has_prev != player->priv->has_prev) {
1979 player->priv->has_prev = has_prev;
1980 g_object_notify (G_OBJECT (player), "has-prev");
1981 }
1982 if (has_next != player->priv->has_next) {
1983 player->priv->has_next = has_next;
1984 g_object_notify (G_OBJECT (player), "has-next");
1985 }
1986 }
1987
1988 /**
1989 * rb_shell_player_jump_to_current:
1990 * @player: the #RBShellPlayer
1991 *
1992 * Scrolls the #RBEntryView for the current playing source so that
1993 * the current playing entry is visible and selects the row for the
1994 * entry. If there is no current playing entry, the selection is
1995 * cleared instead.
1996 */
1997 void
1998 rb_shell_player_jump_to_current (RBShellPlayer *player)
1999 {
2000 RBSource *source;
2001 RhythmDBEntry *entry;
2002 RBEntryView *songs;
2003
2004 source = player->priv->current_playing_source ? player->priv->current_playing_source :
2005 player->priv->selected_source;
2006
2007 songs = rb_source_get_entry_view (source);
2008 entry = rb_shell_player_get_playing_entry (player);
2009 if (songs != NULL) {
2010 if (entry != NULL) {
2011 rb_entry_view_scroll_to_entry (songs, entry);
2012 rb_entry_view_select_entry (songs, entry);
2013 } else {
2014 rb_entry_view_select_none (songs);
2015 }
2016 }
2017
2018 if (entry != NULL) {
2019 rhythmdb_entry_unref (entry);
2020 }
2021 }
2022
2023 static void
2024 swap_playing_source (RBShellPlayer *player,
2025 RBSource *new_source)
2026 {
2027 if (player->priv->current_playing_source != NULL) {
2028 RBEntryView *old_songs = rb_source_get_entry_view (player->priv->current_playing_source);
2029 if (old_songs)
2030 rb_entry_view_set_state (old_songs, RB_ENTRY_VIEW_NOT_PLAYING);
2031 }
2032 if (new_source != NULL) {
2033 RBEntryView *new_songs = rb_source_get_entry_view (new_source);
2034
2035 if (new_songs) {
2036 rb_entry_view_set_state (new_songs, RB_ENTRY_VIEW_PLAYING);
2037 rb_shell_player_set_playing_source (player, new_source);
2038 }
2039 }
2040 }
2041
2042 /**
2043 * rb_shell_player_do_previous:
2044 * @player: the #RBShellPlayer
2045 * @error: returns any error information
2046 *
2047 * If the current song has been playing for more than 3 seconds,
2048 * restarts it, otherwise, goes back to the previous song.
2049 * Fails if there is no current song, or if inside the first
2050 * 3 seconds of the first song in the play order.
2051 *
2052 * Return value: %TRUE if successful
2053 */
2054 gboolean
2055 rb_shell_player_do_previous (RBShellPlayer *player,
2056 GError **error)
2057 {
2058 RhythmDBEntry *entry = NULL;
2059 RBSource *new_source;
2060
2061 if (player->priv->current_playing_source == NULL) {
2062 g_set_error (error,
2063 RB_SHELL_PLAYER_ERROR,
2064 RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
2065 _("Not currently playing"));
2066 return FALSE;
2067 }
2068
2069 /* If we're in the first 3 seconds go to the previous song,
2070 * else restart the current one.
2071 */
2072 if (player->priv->current_playing_source != NULL
2073 && rb_source_can_pause (player->priv->source)
2074 && rb_player_get_time (player->priv->mmplayer) > (G_GINT64_CONSTANT (3) * RB_PLAYER_SECOND)) {
2075 rb_debug ("after 3 second previous, restarting song");
2076 rb_player_set_time (player->priv->mmplayer, 0);
2077 rb_shell_player_sync_with_source (player);
2078 return TRUE;
2079 }
2080
2081 rb_debug ("going to previous");
2082
2083 /* hrm, does this actually do anything at all? */
2084 if (player->priv->queue_play_order) {
2085 entry = rb_play_order_get_previous (player->priv->queue_play_order);
2086 if (entry != NULL) {
2087 new_source = RB_SOURCE (player->priv->queue_source);
2088 rb_play_order_go_previous (player->priv->queue_play_order);
2089 }
2090 }
2091
2092 if (entry == NULL) {
2093 RBPlayOrder *porder;
2094
2095 new_source = player->priv->source;
2096 g_object_get (new_source, "play-order", &porder, NULL);
2097 if (porder == NULL)
2098 porder = g_object_ref (player->priv->play_order);
2099
2100 entry = rb_play_order_get_previous (porder);
2101 if (entry)
2102 rb_play_order_go_previous (porder);
2103 g_object_unref (porder);
2104 }
2105
2106 if (entry != NULL) {
2107 rb_debug ("previous song found, doing previous");
2108 if (new_source != player->priv->current_playing_source)
2109 swap_playing_source (player, new_source);
2110
2111 player->priv->jump_to_playing_entry = TRUE;
2112 if (!rb_shell_player_set_playing_entry (player, entry, FALSE, FALSE, error)) {
2113 rhythmdb_entry_unref (entry);
2114 return FALSE;
2115 }
2116
2117 rhythmdb_entry_unref (entry);
2118 } else {
2119 rb_debug ("no previous song found, signalling error");
2120 g_set_error (error,
2121 RB_SHELL_PLAYER_ERROR,
2122 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
2123 _("No previous song"));
2124 rb_shell_player_stop (player);
2125 return FALSE;
2126 }
2127
2128 return TRUE;
2129 }
2130
2131 static gboolean
2132 rb_shell_player_do_next_internal (RBShellPlayer *player, gboolean from_eos, gboolean allow_stop, GError **error)
2133 {
2134 RBSource *new_source = NULL;
2135 RhythmDBEntry *entry = NULL;
2136 gboolean rv = TRUE;
2137
2138 if (player->priv->source == NULL)
2139 return TRUE;
2140
2141
2142 /* try the current playing source's play order, if it has one */
2143 if (player->priv->current_playing_source != NULL) {
2144 RBPlayOrder *porder;
2145 g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
2146 if (porder != NULL) {
2147 entry = rb_play_order_get_next (porder);
2148 if (entry != NULL) {
2149 rb_play_order_go_next (porder);
2150 new_source = player->priv->current_playing_source;
2151 }
2152 g_object_unref (porder);
2153 }
2154 }
2155
2156 /* if that's different to the playing source that the user selected
2157 * (ie we're playing from the queue), try that too
2158 */
2159 if (entry == NULL) {
2160 RBPlayOrder *porder;
2161 g_object_get (player->priv->source, "play-order", &porder, NULL);
2162 if (porder == NULL)
2163 porder = g_object_ref (player->priv->play_order);
2164
2165 /*
2166 * If we interrupted this source to play from something else,
2167 * we should go back to whatever it wanted to play before.
2168 */
2169 if (player->priv->source != player->priv->current_playing_source)
2170 entry = rb_play_order_get_playing_entry (porder);
2171
2172 /* if that didn't help, advance the play order */
2173 if (entry == NULL) {
2174 entry = rb_play_order_get_next (porder);
2175 if (entry != NULL) {
2176 rb_debug ("got new entry %s from play order",
2177 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
2178 rb_play_order_go_next (porder);
2179 }
2180 }
2181
2182 if (entry != NULL)
2183 new_source = player->priv->source;
2184
2185 g_object_unref (porder);
2186 }
2187
2188 /* if the new entry isn't from the play queue anyway, let the play queue
2189 * override the regular play order.
2190 */
2191 if (player->priv->queue_play_order &&
2192 new_source != RB_SOURCE (player->priv->queue_source)) {
2193 RhythmDBEntry *queue_entry;
2194
2195 queue_entry = rb_play_order_get_next (player->priv->queue_play_order);
2196 rb_play_order_go_next (player->priv->queue_play_order);
2197 if (queue_entry != NULL) {
2198 rb_debug ("got new entry %s from queue play order",
2199 rhythmdb_entry_get_string (queue_entry, RHYTHMDB_PROP_LOCATION));
2200 if (entry != NULL) {
2201 rhythmdb_entry_unref (entry);
2202 }
2203 entry = queue_entry;
2204 new_source = RB_SOURCE (player->priv->queue_source);
2205 } else {
2206 rb_debug ("didn't get a new entry from queue play order");
2207 }
2208 }
2209
2210 /* play the new entry */
2211 if (entry != NULL) {
2212 /* if the entry view containing the playing entry changed, update it */
2213 if (new_source != player->priv->current_playing_source)
2214 swap_playing_source (player, new_source);
2215
2216 player->priv->jump_to_playing_entry = TRUE;
2217 if (!rb_shell_player_set_playing_entry (player, entry, FALSE, from_eos, error))
2218 rv = FALSE;
2219 } else {
2220 g_set_error (error,
2221 RB_SHELL_PLAYER_ERROR,
2222 RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
2223 _("No next song"));
2224 rv = FALSE;
2225
2226 if (allow_stop) {
2227 rb_debug ("No next entry, stopping playback");
2228
2229 /* hmm, need to set playing entry on the playing source's
2230 * play order if it has one?
2231 */
2232
2233 rb_shell_player_stop (player);
2234 rb_play_order_set_playing_entry (player->priv->play_order, NULL);
2235 }
2236 }
2237
2238 if (entry != NULL) {
2239 rhythmdb_entry_unref (entry);
2240 }
2241
2242 return rv;
2243 }
2244
2245 /**
2246 * rb_shell_player_do_next:
2247 * @player: the #RBShellPlayer
2248 * @error: returns error information
2249 *
2250 * Skips to the next song. Consults the play queue and handles
2251 * transitions between the play queue and the active source.
2252 * Fails if there is no entry to play after the current one.
2253 *
2254 * Return value: %TRUE if successful
2255 */
2256 gboolean
2257 rb_shell_player_do_next (RBShellPlayer *player,
2258 GError **error)
2259 {
2260 return rb_shell_player_do_next_internal (player, FALSE, TRUE, error);
2261 }
2262
2263 static void
2264 rb_shell_player_cmd_previous (GtkAction *action,
2265 RBShellPlayer *player)
2266 {
2267 GError *error = NULL;
2268
2269 if (!rb_shell_player_do_previous (player, &error)) {
2270 if (error->domain != RB_SHELL_PLAYER_ERROR ||
2271 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
2272 g_warning ("cmd_previous: Unhandled error: %s", error->message);
2273 } else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
2274 rb_shell_player_stop (player);
2275 }
2276 }
2277 }
2278
2279 static void
2280 rb_shell_player_cmd_next (GtkAction *action,
2281 RBShellPlayer *player)
2282 {
2283 GError *error = NULL;
2284
2285 if (!rb_shell_player_do_next (player, &error)) {
2286 if (error->domain != RB_SHELL_PLAYER_ERROR ||
2287 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
2288 g_warning ("cmd_next: Unhandled error: %s", error->message);
2289 } else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
2290 rb_shell_player_stop (player);
2291 }
2292 }
2293 }
2294
2295 /**
2296 * rb_shell_player_play_entry:
2297 * @player: the #RBShellPlayer
2298 * @entry: the #RhythmDBEntry to play
2299 * @source: the new #RBSource to set as playing (or NULL to use the
2300 * selected source)
2301 *
2302 * Plays a specified entry.
2303 */
2304 void
2305 rb_shell_player_play_entry (RBShellPlayer *player,
2306 RhythmDBEntry *entry,
2307 RBSource *source)
2308 {
2309 GError *error = NULL;
2310
2311 if (source == NULL)
2312 source = player->priv->selected_source;
2313 rb_shell_player_set_playing_source (player, source);
2314
2315 player->priv->jump_to_playing_entry = FALSE;
2316 if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
2317 rb_shell_player_error (player, FALSE, error);
2318 g_clear_error (&error);
2319 }
2320 }
2321
2322 static void
2323 rb_shell_player_cmd_volume_up (GtkAction *action,
2324 RBShellPlayer *player)
2325 {
2326 rb_shell_player_set_volume_relative (player, 0.1, NULL);
2327 }
2328
2329 static void
2330 rb_shell_player_cmd_volume_down (GtkAction *action,
2331 RBShellPlayer *player)
2332 {
2333 rb_shell_player_set_volume_relative (player, -0.1, NULL);
2334 }
2335
2336 static void
2337 rb_shell_player_cmd_play (GtkAction *action,
2338 RBShellPlayer *player)
2339 {
2340 GError *error = NULL;
2341 rb_debug ("play!");
2342 if (!rb_shell_player_playpause (player, FALSE, &error))
2343 rb_error_dialog (NULL,
2344 _("Couldn't start playback"),
2345 "%s", (error) ? error->message : "(null)");
2346 g_clear_error (&error);
2347 }
2348
2349 /* unused parameter can't be removed without breaking dbus interface compatibility */
2350 /**
2351 * rb_shell_player_playpause:
2352 * @player: the #RBShellPlayer
2353 * @unused: nothing
2354 * @error: returns error information
2355 *
2356 * Toggles between playing and paused state. If there is no playing
2357 * entry, chooses an entry from (in order of preference) the play queue,
2358 * the selection in the current source, or the play order.
2359 *
2360 * Return value: %TRUE if successful
2361 */
2362 gboolean
2363 rb_shell_player_playpause (RBShellPlayer *player,
2364 gboolean unused,
2365 GError **error)
2366 {
2367 gboolean ret;
2368 RBEntryView *songs;
2369
2370 rb_debug ("doing playpause");
2371
2372 g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), TRUE);
2373
2374 ret = TRUE;
2375
2376 if (rb_player_playing (player->priv->mmplayer)) {
2377 if (player->priv->source == NULL) {
2378 rb_debug ("playing source is already NULL");
2379 } else if (rb_source_can_pause (player->priv->source)) {
2380 rb_debug ("pausing mm player");
2381 rb_player_pause (player->priv->mmplayer);
2382 songs = rb_source_get_entry_view (player->priv->current_playing_source);
2383 if (songs)
2384 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PAUSED);
2385
2386 /* might need a signal for when the player has actually paused here? */
2387 g_object_notify (G_OBJECT (player), "playing");
2388 /* mostly for that */
2389 } else {
2390 rb_debug ("stopping playback");
2391 rb_shell_player_stop (player);
2392 }
2393 } else {
2394 RhythmDBEntry *entry;
2395 RBSource *new_source;
2396 gboolean out_of_order = FALSE;
2397
2398 if (player->priv->source == NULL) {
2399 /* no current stream, pull one in from the currently
2400 * selected source */
2401 rb_debug ("no playing source, using selected source");
2402 rb_shell_player_set_playing_source (player, player->priv->selected_source);
2403 }
2404 new_source = player->priv->current_playing_source;
2405
2406 entry = rb_shell_player_get_playing_entry (player);
2407 if (entry == NULL) {
2408 /* queue takes precedence over selection */
2409 if (player->priv->queue_play_order) {
2410 entry = rb_play_order_get_next (player->priv->queue_play_order);
2411 if (entry != NULL) {
2412 new_source = RB_SOURCE (player->priv->queue_source);
2413 rb_play_order_go_next (player->priv->queue_play_order);
2414 }
2415 }
2416
2417 /* selection takes precedence over first item in play order */
2418 if (entry == NULL) {
2419 GList *selection = NULL;
2420
2421 songs = rb_source_get_entry_view (player->priv->source);
2422 if (songs)
2423 selection = rb_entry_view_get_selected_entries (songs);
2424
2425 if (selection != NULL) {
2426 rb_debug ("choosing first selected entry");
2427 entry = (RhythmDBEntry*) selection->data;
2428 if (entry)
2429 out_of_order = TRUE;
2430
2431 g_list_free (selection);
2432 }
2433 }
2434
2435 /* play order is last */
2436 if (entry == NULL) {
2437 RBPlayOrder *porder;
2438
2439 rb_debug ("getting entry from play order");
2440 g_object_get (player->priv->source, "play-order", &porder, NULL);
2441 if (porder == NULL)
2442 porder = g_object_ref (player->priv->play_order);
2443
2444 entry = rb_play_order_get_next (porder);
2445 if (entry != NULL)
2446 rb_play_order_go_next (porder);
2447 g_object_unref (porder);
2448 }
2449
2450 if (entry != NULL) {
2451 /* if the entry view containing the playing entry changed, update it */
2452 if (new_source != player->priv->current_playing_source)
2453 swap_playing_source (player, new_source);
2454
2455 player->priv->jump_to_playing_entry = TRUE;
2456 if (!rb_shell_player_set_playing_entry (player, entry, out_of_order, FALSE, error))
2457 ret = FALSE;
2458 }
2459 } else {
2460 if (!rb_shell_player_play (player, error)) {
2461 rb_shell_player_stop (player);
2462 ret = FALSE;
2463 }
2464 }
2465
2466 if (entry != NULL) {
2467 rhythmdb_entry_unref (entry);
2468 }
2469 }
2470
2471 rb_shell_player_sync_with_source (player);
2472 rb_shell_player_sync_buttons (player);
2473
2474 return ret;
2475 }
2476
2477 static void
2478 rb_shell_player_sync_control_state (RBShellPlayer *player)
2479 {
2480 gboolean shuffle, repeat;
2481 GtkAction *action;
2482 rb_debug ("syncing control state");
2483
2484 if (!rb_shell_player_get_playback_state (player, &shuffle,
2485 &repeat))
2486 return;
2487
2488 action = gtk_action_group_get_action (player->priv->actiongroup,
2489 "ControlShuffle");
2490 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), shuffle);
2491 action = gtk_action_group_get_action (player->priv->actiongroup,
2492 "ControlRepeat");
2493 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), repeat);
2494 }
2495
2496 static void
2497 sync_volume_cb (GSettings *settings, RBShellPlayer *player)
2498 {
2499 g_settings_set_double (player->priv->settings, "volume", player->priv->volume);
2500 }
2501
2502 static void
2503 rb_shell_player_sync_volume (RBShellPlayer *player,
2504 gboolean notify,
2505 gboolean set_volume)
2506 {
2507 GtkAction *action;
2508 RhythmDBEntry *entry;
2509
2510 if (player->priv->volume <= 0.0){
2511 player->priv->volume = 0.0;
2512 } else if (player->priv->volume >= 1.0){
2513 player->priv->volume = 1.0;
2514 }
2515
2516 action = gtk_action_group_get_action (player->priv->actiongroup,
2517 "ControlVolumeUp");
2518 g_object_set (G_OBJECT (action), "sensitive", player->priv->volume < 0.9999, NULL);
2519
2520 action = gtk_action_group_get_action (player->priv->actiongroup,
2521 "ControlVolumeDown");
2522 g_object_set (G_OBJECT (action), "sensitive", player->priv->volume > 0.0001, NULL);
2523
2524 if (set_volume) {
2525 rb_player_set_volume (player->priv->mmplayer,
2526 player->priv->mute ? 0.0 : player->priv->volume);
2527 }
2528
2529 if (player->priv->syncing_state == FALSE) {
2530 rb_settings_delayed_sync (player->priv->settings,
2531 (RBDelayedSyncFunc) sync_volume_cb,
2532 g_object_ref (player),
2533 g_object_unref);
2534 }
2535
2536 entry = rb_shell_player_get_playing_entry (player);
2537 if (entry != NULL) {
2538 rhythmdb_entry_unref (entry);
2539 }
2540
2541 if (notify)
2542 g_object_notify (G_OBJECT (player), "volume");
2543 }
2544
2545 /**
2546 * rb_shell_player_set_volume:
2547 * @player: the #RBShellPlayer
2548 * @volume: the volume level (between 0 and 1)
2549 * @error: returns the error information
2550 *
2551 * Sets the playback volume level.
2552 *
2553 * Return value: %TRUE on success
2554 */
2555 gboolean
2556 rb_shell_player_set_volume (RBShellPlayer *player,
2557 gdouble volume,
2558 GError **error)
2559 {
2560 player->priv->volume = volume;
2561 rb_shell_player_sync_volume (player, TRUE, TRUE);
2562 return TRUE;
2563 }
2564
2565 /**
2566 * rb_shell_player_set_volume_relative:
2567 * @player: the #RBShellPlayer
2568 * @delta: difference to apply to the volume level (between -1 and 1)
2569 * @error: returns error information
2570 *
2571 * Adds the specified value to the current volume level.
2572 *
2573 * Return value: %TRUE on success
2574 */
2575 gboolean
2576 rb_shell_player_set_volume_relative (RBShellPlayer *player,
2577 gdouble delta,
2578 GError **error)
2579 {
2580 /* rb_shell_player_sync_volume does clipping */
2581 player->priv->volume += delta;
2582 rb_shell_player_sync_volume (player, TRUE, TRUE);
2583 return TRUE;
2584 }
2585
2586 /**
2587 * rb_shell_player_get_volume:
2588 * @player: the #RBShellPlayer
2589 * @volume: (out): returns the volume level
2590 * @error: returns error information
2591 *
2592 * Returns the current volume level
2593 *
2594 * Return value: the current volume level.
2595 */
2596 gboolean
2597 rb_shell_player_get_volume (RBShellPlayer *player,
2598 gdouble *volume,
2599 GError **error)
2600 {
2601 *volume = player->priv->volume;
2602 return TRUE;
2603 }
2604
2605 static void
2606 rb_shell_player_volume_changed_cb (RBPlayer *player,
2607 float volume,
2608 RBShellPlayer *shell_player)
2609 {
2610 shell_player->priv->volume = volume;
2611 rb_shell_player_sync_volume (shell_player, TRUE, FALSE);
2612 }
2613
2614 /**
2615 * rb_shell_player_set_mute:
2616 * @player: the #RBShellPlayer
2617 * @mute: %TRUE to mute playback
2618 * @error: returns error information
2619 *
2620 * Updates the mute setting on the player.
2621 *
2622 * Return value: %TRUE if successful
2623 */
2624 gboolean
2625 rb_shell_player_set_mute (RBShellPlayer *player,
2626 gboolean mute,
2627 GError **error)
2628 {
2629 player->priv->mute = mute;
2630 rb_shell_player_sync_volume (player, FALSE, TRUE);
2631 return TRUE;
2632 }
2633
2634 /**
2635 * rb_shell_player_get_mute:
2636 * @player: the #RBShellPlayer
2637 * @mute: (out): returns the current mute setting
2638 * @error: returns error information
2639 *
2640 * Returns %TRUE if currently muted
2641 *
2642 * Return value: %TRUE if currently muted
2643 */
2644 gboolean
2645 rb_shell_player_get_mute (RBShellPlayer *player,
2646 gboolean *mute,
2647 GError **error)
2648 {
2649 *mute = player->priv->mute;
2650 return TRUE;
2651 }
2652
2653 static void
2654 rb_shell_player_shuffle_changed_cb (GtkAction *action,
2655 RBShellPlayer *player)
2656 {
2657 const char *neworder;
2658 gboolean shuffle = FALSE;
2659 gboolean repeat = FALSE;
2660
2661 if (player->priv->syncing_state)
2662 return;
2663
2664 rb_debug ("shuffle changed");
2665
2666 rb_shell_player_get_playback_state (player, &shuffle, &repeat);
2667
2668 shuffle = !shuffle;
2669 neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
2670 g_settings_set_string (player->priv->settings, "play-order", neworder);
2671 }
2672
2673 static void
2674 rb_shell_player_repeat_changed_cb (GtkAction *action,
2675 RBShellPlayer *player)
2676 {
2677 const char *neworder;
2678 gboolean shuffle = FALSE;
2679 gboolean repeat = FALSE;
2680 rb_debug ("repeat changed");
2681
2682 if (player->priv->syncing_state)
2683 return;
2684
2685 rb_shell_player_get_playback_state (player, &shuffle, &repeat);
2686
2687 repeat = !repeat;
2688 neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
2689 g_settings_set_string (player->priv->settings, "play-order", neworder);
2690 }
2691
2692 static void
2693 rb_shell_player_entry_activated_cb (RBEntryView *view,
2694 RhythmDBEntry *entry,
2695 RBShellPlayer *player)
2696 {
2697 gboolean was_from_queue = FALSE;
2698 RhythmDBEntry *prev_entry = NULL;
2699 GError *error = NULL;
2700 gboolean source_set = FALSE;
2701 gboolean jump_to_entry = FALSE;
2702 char *playback_uri;
2703
2704 g_return_if_fail (entry != NULL);
2705
2706 rb_debug ("got entry %p activated", entry);
2707
2708 /* don't play hidden entries */
2709 if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
2710 return;
2711
2712 /* skip entries with no playback uri */
2713 playback_uri = rhythmdb_entry_get_playback_uri (entry);
2714 if (playback_uri == NULL)
2715 return;
2716
2717 g_free (playback_uri);
2718
2719 /* figure out where the previous entry came from */
2720 if ((player->priv->queue_source != NULL) &&
2721 (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))) {
2722 prev_entry = rb_shell_player_get_playing_entry (player);
2723 was_from_queue = TRUE;
2724 }
2725
2726 if (player->priv->queue_source) {
2727 RBEntryView *queue_sidebar;
2728
2729 g_object_get (player->priv->queue_source, "sidebar", &queue_sidebar, NULL);
2730
2731 if (view == queue_sidebar || view == rb_source_get_entry_view (RB_SOURCE (player->priv->queue_source))) {
2732
2733 /* fall back to the current selected source once the queue is empty */
2734 if (view == queue_sidebar && player->priv->source == NULL) {
2735 /* XXX only do this if the selected source doesn't have its own play order? */
2736 rb_play_order_playing_source_changed (player->priv->play_order,
2737 player->priv->selected_source);
2738 player->priv->source = player->priv->selected_source;
2739 }
2740
2741 rb_shell_player_set_playing_source (player, RB_SOURCE (player->priv->queue_source));
2742
2743 was_from_queue = FALSE;
2744 source_set = TRUE;
2745 jump_to_entry = TRUE;
2746 } else {
2747 if (player->priv->queue_only) {
2748 rb_source_add_to_queue (player->priv->selected_source,
2749 RB_SOURCE (player->priv->queue_source));
2750 rb_shell_player_set_playing_source (player, RB_SOURCE (player->priv->queue_source));
2751 source_set = TRUE;
2752 }
2753 }
2754
2755 g_object_unref (queue_sidebar);
2756 }
2757
2758 /* bail out if queue only */
2759 if (player->priv->queue_only) {
2760 return;
2761 }
2762
2763 if (!source_set) {
2764 rb_shell_player_set_playing_source (player, player->priv->selected_source);
2765 source_set = TRUE;
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2766 }
2767
2768 player->priv->jump_to_playing_entry = jump_to_entry;
2769 if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
2770 rb_shell_player_error (player, FALSE, error);
2771 g_clear_error (&error);
2772 }
2773
2774 /* if we were previously playing from the queue, clear its playing entry,
2775 * so we'll start again from the start.
2776 */
2777 if (was_from_queue && prev_entry != NULL) {
2778 rb_play_order_set_playing_entry (player->priv->queue_play_order, NULL);
2779 }
2780
2781 if (prev_entry != NULL) {
2782 rhythmdb_entry_unref (prev_entry);
2783 }
2784 }
2785
2786 static void
2787 rb_shell_player_property_row_activated_cb (RBPropertyView *view,
2788 const char *name,
2789 RBShellPlayer *player)
2790 {
2791 RBPlayOrder *porder;
2792 RhythmDBEntry *entry = NULL;
2793 GError *error = NULL;
2794
2795 rb_debug ("got property activated");
2796
2797 rb_shell_player_set_playing_source (player, player->priv->selected_source);
2798
2799 /* RHYTHMDBFIXME - do we need to wait here until the query is finished?
2800 * in theory, yes, but in practice the query is started when the row is
2801 * selected (on the first click when doubleclicking, or when using the
2802 * keyboard to select then activate) and is pretty much always done by
2803 * the time we get in here.
2804 */
2805
2806 g_object_get (player->priv->selected_source, "play-order", &porder, NULL);
2807 if (porder == NULL)
2808 porder = g_object_ref (player->priv->play_order);
2809
2810 entry = rb_play_order_get_next (porder);
2811 if (entry != NULL) {
2812 rb_play_order_go_next (porder);
2813
2814 player->priv->jump_to_playing_entry = TRUE; /* ? */
2815 if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
2816 rb_shell_player_error (player, FALSE, error);
2817 g_clear_error (&error);
2818 }
2819 }
2820
2821 rhythmdb_entry_unref (entry);
2822 g_object_unref (porder);
2823 }
2824
2825 static void
2826 rb_shell_player_entry_changed_cb (RhythmDB *db,
2827 RhythmDBEntry *entry,
2828 GArray *changes,
2829 RBShellPlayer *player)
2830 {
2831 gboolean synced = FALSE;
2832 const char *location;
2833 RhythmDBEntry *playing_entry;
2834 int i;
2835
2836 playing_entry = rb_shell_player_get_playing_entry (player);
2837
2838 /* We try to update only if the changed entry is currently playing */
2839 if (entry != playing_entry) {
2840 if (playing_entry != NULL) {
2841 rhythmdb_entry_unref (playing_entry);
2842 }
2843 return;
2844 }
2845
2846 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
2847 for (i = 0; i < changes->len; i++) {
2848 GValue *v = &g_array_index (changes, GValue, i);
2849 RhythmDBEntryChange *change = g_value_get_boxed (v);
2850
2851 /* update UI if the artist, title or album has changed */
2852 switch (change->prop) {
2853 case RHYTHMDB_PROP_TITLE:
2854 case RHYTHMDB_PROP_ARTIST:
2855 case RHYTHMDB_PROP_ALBUM:
2856 if (!synced) {
2857 rb_shell_player_sync_with_source (player);
2858 synced = TRUE;
2859 }
2860 break;
2861 default:
2862 break;
2863 }
2864
2865 /* emit dbus signals for changes with easily marshallable types */
2866 switch (rhythmdb_get_property_type (db, change->prop)) {
2867 case G_TYPE_STRING:
2868 case G_TYPE_BOOLEAN:
2869 case G_TYPE_ULONG:
2870 case G_TYPE_UINT64:
2871 case G_TYPE_DOUBLE:
2872 g_signal_emit (G_OBJECT (player),
2873 rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
2874 location,
2875 rhythmdb_nice_elt_name_from_propid (db, change->prop),
2876 &change->old,
2877 &change->new);
2878 break;
2879 default:
2880 break;
2881 }
2882 }
2883
2884 if (playing_entry != NULL) {
2885 rhythmdb_entry_unref (playing_entry);
2886 }
2887 }
2888
2889 static void
2890 rb_shell_player_extra_metadata_cb (RhythmDB *db,
2891 RhythmDBEntry *entry,
2892 const char *field,
2893 GValue *metadata,
2894 RBShellPlayer *player)
2895 {
2896
2897 RhythmDBEntry *playing_entry;
2898
2899 playing_entry = rb_shell_player_get_playing_entry (player);
2900 if (entry != playing_entry) {
2901 if (playing_entry != NULL) {
2902 rhythmdb_entry_unref (playing_entry);
2903 }
2904 return;
2905 }
2906
2907 rb_shell_player_sync_with_source (player);
2908
2909 /* emit dbus signals for changes with easily marshallable types */
2910 switch (G_VALUE_TYPE (metadata)) {
2911 case G_TYPE_STRING:
2912 /* make sure it's valid utf8, otherwise dbus barfs */
2913 if (g_utf8_validate (g_value_get_string (metadata), -1, NULL) == FALSE) {
2914 rb_debug ("not emitting extra metadata field %s as value is not valid utf8", field);
2915 return;
2916 }
2917 case G_TYPE_BOOLEAN:
2918 case G_TYPE_ULONG:
2919 case G_TYPE_UINT64:
2920 case G_TYPE_DOUBLE:
2921 g_signal_emit (G_OBJECT (player),
2922 rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
2923 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
2924 field,
2925 metadata, /* slightly silly */
2926 metadata);
2927 break;
2928 default:
2929 break;
2930 }
2931 }
2932
2933
2934 static void
2935 rb_shell_player_sync_with_source (RBShellPlayer *player)
2936 {
2937 const char *entry_title = NULL;
2938 const char *artist = NULL;
2939 const char *stream_name = NULL;
2940 char *streaming_title = NULL;
2941 char *streaming_artist = NULL;
2942 RhythmDBEntry *entry;
2943 char *title = NULL;
2944 gint64 elapsed;
2945
2946 entry = rb_shell_player_get_playing_entry (player);
2947 rb_debug ("playing source: %p, active entry: %p", player->priv->current_playing_source, entry);
2948
2949 if (entry != NULL) {
2950 GValue *value;
2951
2952 entry_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
2953 artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
2954
2955 value = rhythmdb_entry_request_extra_metadata (player->priv->db,
2956 entry,
2957 RHYTHMDB_PROP_STREAM_SONG_TITLE);
2958 if (value != NULL) {
2959 streaming_title = g_value_dup_string (value);
2960 g_value_unset (value);
2961 g_free (value);
2962
2963 rb_debug ("got streaming title \"%s\"", streaming_title);
2964 /* use entry title for stream name */
2965 stream_name = entry_title;
2966 entry_title = streaming_title;
2967 }
2968
2969 value = rhythmdb_entry_request_extra_metadata (player->priv->db,
2970 entry,
2971 RHYTHMDB_PROP_STREAM_SONG_ARTIST);
2972 if (value != NULL) {
2973 streaming_artist = g_value_dup_string (value);
2974 g_value_unset (value);
2975 g_free (value);
2976
2977 rb_debug ("got streaming artist \"%s\"", streaming_artist);
2978 /* override artist from entry */
2979 artist = streaming_artist;
2980 }
2981
2982 rhythmdb_entry_unref (entry);
2983 }
2984
2985 if ((artist && artist[0] != '\0') || entry_title || stream_name) {
2986
2987 GString *title_str = g_string_sized_new (100);
2988 if (artist && artist[0] != '\0') {
2989 g_string_append (title_str, artist);
2990 g_string_append (title_str, " - ");
2991 }
2992 if (entry_title != NULL)
2993 g_string_append (title_str, entry_title);
2994
2995 if (stream_name != NULL)
2996 g_string_append_printf (title_str, " (%s)", stream_name);
2997
2998 title = g_string_free (title_str, FALSE);
2999 }
3000
3001 elapsed = rb_player_get_time (player->priv->mmplayer);
3002 if (elapsed < 0)
3003 elapsed = 0;
3004 player->priv->elapsed = elapsed / RB_PLAYER_SECOND;
3005
3006 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[WINDOW_TITLE_CHANGED], 0,
3007 title);
3008 g_free (title);
3009
3010 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED], 0,
3011 player->priv->elapsed);
3012
3013 g_free (streaming_artist);
3014 g_free (streaming_title);
3015 }
3016
3017 static void
3018 rb_shell_player_sync_buttons (RBShellPlayer *player)
3019 {
3020 GtkAction *action;
3021 RBSource *source;
3022 RBEntryView *view;
3023 int entry_view_state;
3024 RhythmDBEntry *entry;
3025
3026 entry = rb_shell_player_get_playing_entry (player);
3027 if (entry != NULL) {
3028 source = player->priv->current_playing_source;
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
3029 entry_view_state = rb_player_playing (player->priv->mmplayer) ?
3030 RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_PAUSED;
3031 } else {
3032 source = player->priv->selected_source;
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
3033 entry_view_state = RB_ENTRY_VIEW_NOT_PLAYING;
3034 }
3035
3036 source = (entry == NULL) ? player->priv->selected_source : player->priv->current_playing_source;
3037
3038 rb_debug ("syncing with source %p", source);
3039
3040 action = gtk_action_group_get_action (player->priv->actiongroup,
3041 "ViewJumpToPlaying");
3042 g_object_set (action, "sensitive", entry != NULL, NULL);
3043
3044 action = gtk_action_group_get_action (player->priv->actiongroup,
3045 "ControlPlay");
3046 g_object_set (action, "sensitive", entry != NULL || source != NULL, NULL);
3047
3048 if (source != NULL) {
3049 view = rb_source_get_entry_view (source);
3050 if (view)
3051 rb_entry_view_set_state (view, entry_view_state);
3052 }
3053
3054 if (entry != NULL) {
3055 rhythmdb_entry_unref (entry);
3056 }
3057 }
3058
3059 /**
3060 * rb_shell_player_set_playing_source:
3061 * @player: the #RBShellPlayer
3062 * @source: the new playing #RBSource
3063 *
3064 * Replaces the current playing source.
3065 */
3066 void
3067 rb_shell_player_set_playing_source (RBShellPlayer *player,
3068 RBSource *source)
3069 {
3070 rb_shell_player_set_playing_source_internal (player, source, TRUE);
3071 }
3072
3073 static void
3074 actually_set_playing_source (RBShellPlayer *player,
3075 RBSource *source,
3076 gboolean sync_entry_view)
3077 {
3078 RBPlayOrder *porder;
3079
3080 player->priv->source = source;
3081 player->priv->current_playing_source = source;
3082
3083 if (source != NULL) {
3084 RBEntryView *songs = rb_source_get_entry_view (player->priv->source);
3085 if (sync_entry_view && songs) {
3086 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
3087 }
3088 }
3089
3090 if (source != RB_SOURCE (player->priv->queue_source)) {
3091 if (source == NULL)
3092 source = player->priv->selected_source;
3093
3094 if (source != NULL) {
3095 g_object_get (source, "play-order", &porder, NULL);
3096 if (porder == NULL)
3097 porder = g_object_ref (player->priv->play_order);
3098
3099 rb_play_order_playing_source_changed (porder, source);
3100 g_object_unref (porder);
3101 }
3102 }
3103
3104 rb_shell_player_play_order_update_cb (player->priv->play_order,
3105 FALSE, FALSE,
3106 player);
3107 }
3108
3109 static void
3110 rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
3111 RBSource *source,
3112 gboolean sync_entry_view)
3113
3114 {
3115 gboolean emit_source_changed = TRUE;
3116 gboolean emit_playing_from_queue_changed = FALSE;
3117
3118 if (player->priv->source == source &&
3119 player->priv->current_playing_source == source &&
3120 source != NULL)
3121 return;
3122
3123 rb_debug ("setting playing source to %p", source);
3124
3125 if (RB_SOURCE (player->priv->queue_source) == source) {
3126
3127 if (player->priv->current_playing_source != source)
3128 emit_playing_from_queue_changed = TRUE;
3129
3130 if (player->priv->source == NULL) {
3131 actually_set_playing_source (player, source, sync_entry_view);
3132 } else {
3133 emit_source_changed = FALSE;
3134 player->priv->current_playing_source = source;
3135 }
3136
3137 } else {
3138 if (player->priv->current_playing_source != source) {
3139 if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
3140 emit_playing_from_queue_changed = TRUE;
3141
3142 /* stop the old source */
3143 if (player->priv->current_playing_source != NULL) {
3144 if (sync_entry_view) {
3145 RBEntryView *songs = rb_source_get_entry_view (player->priv->current_playing_source);
3146 rb_debug ("source is already playing, stopping it");
3147
3148 /* clear the playing entry if we're switching between non-queue sources */
3149 if (player->priv->current_playing_source != RB_SOURCE (player->priv->queue_source))
3150 rb_play_order_set_playing_entry (player->priv->play_order, NULL);
3151
3152 if (songs)
3153 rb_entry_view_set_state (songs, RB_ENTRY_VIEW_NOT_PLAYING);
3154 }
3155 }
3156 }
3157 actually_set_playing_source (player, source, sync_entry_view);
3158 }
3159
3160 rb_shell_player_sync_with_source (player);
3161 /*g_object_notify (G_OBJECT (player), "playing");*/
3162 if (player->priv->selected_source)
3163 rb_shell_player_sync_buttons (player);
3164
3165 if (emit_source_changed) {
3166 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SOURCE_CHANGED],
3167 0, player->priv->source);
3168 }
3169 if (emit_playing_from_queue_changed) {
3170 g_object_notify (G_OBJECT (player), "playing-from-queue");
3171 }
3172 }
3173
3174 /**
3175 * rb_shell_player_stop:
3176 * @player: a #RBShellPlayer.
3177 *
3178 * Completely stops playback, freeing resources and unloading the file.
3179 *
3180 * In general rb_shell_player_pause() should be used instead, as it stops the
3181 * audio, but does not completely free resources.
3182 **/
3183 void
3184 rb_shell_player_stop (RBShellPlayer *player)
3185 {
3186 GError *error = NULL;
3187 rb_debug ("stopping");
3188
3189 g_return_if_fail (RB_IS_SHELL_PLAYER (player));
3190
3191 if (error == NULL)
3192 rb_player_close (player->priv->mmplayer, NULL, &error);
3193 if (error) {
3194 rb_error_dialog (NULL,
3195 _("Couldn't stop playback"),
3196 "%s", error->message);
3197 g_error_free (error);
3198 }
3199
3200 if (player->priv->parser_cancellable != NULL) {
3201 rb_debug ("cancelling playlist parser");
3202 g_cancellable_cancel (player->priv->parser_cancellable);
3203 g_object_unref (player->priv->parser_cancellable);
3204 player->priv->parser_cancellable = NULL;
3205 }
3206
3207 if (player->priv->playing_entry != NULL) {
3208 rhythmdb_entry_unref (player->priv->playing_entry);
3209 player->priv->playing_entry = NULL;
3210 }
3211
3212 rb_shell_player_set_playing_source (player, NULL);
3213 rb_shell_player_sync_with_source (player);
3214 g_signal_emit (G_OBJECT (player),
3215 rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
3216 NULL);
3217 g_signal_emit (G_OBJECT (player),
3218 rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
3219 NULL);
3220 g_object_notify (G_OBJECT (player), "playing");
3221 rb_shell_player_sync_buttons (player);
3222 }
3223
3224 /**
3225 * rb_shell_player_pause:
3226 * @player: a #RBShellPlayer
3227 * @error: error return
3228 *
3229 * Pauses playback if possible, completely stopping if not.
3230 *
3231 * Return value: whether playback is not occurring (TRUE when successfully
3232 * paused/stopped or playback was not occurring).
3233 **/
3234
3235 gboolean
3236 rb_shell_player_pause (RBShellPlayer *player,
3237 GError **error)
3238 {
3239 if (rb_player_playing (player->priv->mmplayer))
3240 return rb_shell_player_playpause (player, FALSE, error);
3241 else
3242 return TRUE;
3243 }
3244
3245 /**
3246 * rb_shell_player_get_playing:
3247 * @player: a #RBShellPlayer
3248 * @playing: (out): playback state return
3249 * @error: error return
3250 *
3251 * Reports whether playback is occuring by setting #playing.
3252 *
3253 * Return value: %TRUE if successful
3254 **/
3255 gboolean
3256 rb_shell_player_get_playing (RBShellPlayer *player,
3257 gboolean *playing,
3258 GError **error)
3259 {
3260 if (playing != NULL)
3261 *playing = rb_player_playing (player->priv->mmplayer);
3262
3263 return TRUE;
3264 }
3265
3266 /**
3267 * rb_shell_player_get_playing_time_string:
3268 * @player: the #RBShellPlayer
3269 *
3270 * Constructs a string showing the current playback position,
3271 * taking the time display settings into account.
3272 *
3273 * Return value: allocated playing time string
3274 */
3275 char *
3276 rb_shell_player_get_playing_time_string (RBShellPlayer *player)
3277 {
3278 gboolean elapsed;
3279 elapsed = g_settings_get_boolean (player->priv->ui_settings, "time-display");
3280 return rb_make_elapsed_time_string (player->priv->elapsed,
3281 rb_shell_player_get_playing_song_duration (player),
3282 elapsed);
3283 }
3284
3285 /**
3286 * rb_shell_player_get_playing_time:
3287 * @player: the #RBShellPlayer
3288 * @time: (out): returns the current playback position
3289 * @error: returns error information
3290 *
3291 * Retrieves the current playback position. Fails if
3292 * the player currently cannot provide the playback
3293 * position.
3294 *
3295 * Return value: %TRUE if successful
3296 */
3297 gboolean
3298 rb_shell_player_get_playing_time (RBShellPlayer *player,
3299 guint *time,
3300 GError **error)
3301 {
3302 gint64 ptime;
3303
3304 ptime = rb_player_get_time (player->priv->mmplayer);
3305 if (ptime >= 0) {
3306 if (time != NULL) {
3307 *time = (guint)(ptime / RB_PLAYER_SECOND);
3308 }
3309 return TRUE;
3310 } else {
3311 g_set_error (error,
3312 RB_SHELL_PLAYER_ERROR,
3313 RB_SHELL_PLAYER_ERROR_POSITION_NOT_AVAILABLE,
3314 _("Playback position not available"));
3315 return FALSE;
3316 }
3317 }
3318
3319 /**
3320 * rb_shell_player_set_playing_time:
3321 * @player: the #RBShellPlayer
3322 * @time: the target playback position (in seconds)
3323 * @error: returns error information
3324 *
3325 * Attempts to set the playback position. Fails if the
3326 * current song is not seekable.
3327 *
3328 * Return value: %TRUE if successful
3329 */
3330 gboolean
3331 rb_shell_player_set_playing_time (RBShellPlayer *player,
3332 guint time,
3333 GError **error)
3334 {
3335 if (rb_player_seekable (player->priv->mmplayer)) {
3336 if (player->priv->playing_entry_eos) {
3337 rb_debug ("forgetting that playing entry had EOS'd due to seek");
3338 player->priv->playing_entry_eos = FALSE;
3339 }
3340 rb_player_set_time (player->priv->mmplayer, ((gint64) time) * RB_PLAYER_SECOND);
3341 return TRUE;
3342 } else {
3343 g_set_error (error,
3344 RB_SHELL_PLAYER_ERROR,
3345 RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
3346 _("Current song is not seekable"));
3347 return FALSE;
3348 }
3349 }
3350
3351 /**
3352 * rb_shell_player_seek:
3353 * @player: the #RBShellPlayer
3354 * @offset: relative seek target (in seconds)
3355 * @error: returns error information
3356 *
3357 * Seeks forwards or backwards in the current playing
3358 * song. Fails if the current song is not seekable.
3359 *
3360 * Return value: %TRUE if successful
3361 */
3362 gboolean
3363 rb_shell_player_seek (RBShellPlayer *player,
3364 gint32 offset,
3365 GError **error)
3366 {
3367 g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), FALSE);
3368
3369 if (rb_player_seekable (player->priv->mmplayer)) {
3370 gint64 target_time = rb_player_get_time (player->priv->mmplayer) +
3371 (((gint64)offset) * RB_PLAYER_SECOND);
3372 if (target_time < 0)
3373 target_time = 0;
3374 rb_player_set_time (player->priv->mmplayer, target_time);
3375 return TRUE;
3376 } else {
3377 g_set_error (error,
3378 RB_SHELL_PLAYER_ERROR,
3379 RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
3380 _("Current song is not seekable"));
3381 return FALSE;
3382 }
3383 }
3384
3385 /**
3386 * rb_shell_player_get_playing_song_duration:
3387 * @player: the #RBShellPlayer
3388 *
3389 * Retrieves the duration of the current playing song.
3390 *
3391 * Return value: duration, or -1 if not playing
3392 */
3393 long
3394 rb_shell_player_get_playing_song_duration (RBShellPlayer *player)
3395 {
3396 RhythmDBEntry *current_entry;
3397 long val;
3398
3399 g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), -1);
3400
3401 current_entry = rb_shell_player_get_playing_entry (player);
3402
3403 if (current_entry == NULL) {
3404 rb_debug ("Did not get playing entry : return -1 as length");
3405 return -1;
3406 }
3407
3408 val = rhythmdb_entry_get_ulong (current_entry, RHYTHMDB_PROP_DURATION);
3409
3410 rhythmdb_entry_unref (current_entry);
3411
3412 return val;
3413 }
3414
3415 static void
3416 rb_shell_player_sync_with_selected_source (RBShellPlayer *player)
3417 {
3418 rb_debug ("syncing with selected source: %p", player->priv->selected_source);
3419 if (player->priv->source == NULL)
3420 {
3421 rb_debug ("no playing source, new source is %p", player->priv->selected_source);
3422 rb_shell_player_sync_with_source (player);
3423 }
3424 }
3425
3426 static gboolean
3427 do_next_idle (RBShellPlayer *player)
3428 {
3429 /* use the EOS callback, so that EOF_SOURCE_ conditions are handled properly */
3430 rb_shell_player_handle_eos (NULL, NULL, FALSE, player);
3431 player->priv->do_next_idle_id = 0;
3432
3433 return FALSE;
3434 }
3435
3436 static gboolean
3437 do_next_not_found_idle (RBShellPlayer *player)
3438 {
3439 RhythmDBEntry *entry;
3440 entry = rb_shell_player_get_playing_entry (player);
3441
3442 do_next_idle (player);
3443
3444 if (entry != NULL) {
3445 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_NOT_FOUND);
3446 rhythmdb_commit (player->priv->db);
3447 rhythmdb_entry_unref (entry);
3448 }
3449
3450 return FALSE;
3451 }
3452
3453 static void
3454 rb_shell_player_error (RBShellPlayer *player,
3455 gboolean async,
3456 const GError *err)
3457 {
3458 RhythmDBEntry *entry;
3459 gboolean do_next;
3460
3461 g_return_if_fail (player->priv->handling_error == FALSE);
3462
3463 player->priv->handling_error = TRUE;
3464
3465 entry = rb_shell_player_get_playing_entry (player);
3466
3467 rb_debug ("playback error while playing: %s", err->message);
3468 /* For synchronous errors the entry playback error has already been set */
3469 if (entry && async)
3470 rb_shell_player_set_entry_playback_error (player, entry, err->message);
3471
3472 if (entry == NULL) {
3473 do_next = TRUE;
3474 } else if (err->domain == RB_PLAYER_ERROR && err->code == RB_PLAYER_ERROR_NOT_FOUND) {
3475 /* process not found errors after we've started the next track */
3476 if (player->priv->do_next_idle_id != 0) {
3477 g_source_remove (player->priv->do_next_idle_id);
3478 }
3479 player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_not_found_idle, player);
3480 do_next = FALSE;
3481 } else if (err->domain == RB_PLAYER_ERROR && err->code == RB_PLAYER_ERROR_NO_AUDIO) {
3482
3483 /* stream has completely ended */
3484 rb_shell_player_stop (player);
3485 do_next = FALSE;
3486 } else if ((player->priv->current_playing_source != NULL) &&
3487 (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_RETRY)) {
3488 /* receiving an error means a broken stream or non-audio stream, so abort
3489 * unless we've got more URLs to try */
3490 if (g_queue_is_empty (player->priv->playlist_urls)) {
3491 rb_error_dialog (NULL,
3492 _("Couldn't start playback"),
3493 "%s", (err) ? err->message : "(null)");
3494 rb_shell_player_stop (player);
3495 do_next = FALSE;
3496 } else {
3497 rb_debug ("haven't yet exhausted the URLs from the playlist");
3498 do_next = TRUE;
3499 }
3500 } else {
3501 do_next = TRUE;
3502 }
3503
3504 if (do_next && player->priv->do_next_idle_id == 0) {
3505 player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_idle, player);
3506 }
3507
3508 player->priv->handling_error = FALSE;
3509
3510 if (entry != NULL) {
3511 rhythmdb_entry_unref (entry);
3512 }
3513 }
3514
3515 static void
3516 playing_stream_cb (RBPlayer *mmplayer,
3517 RhythmDBEntry *entry,
3518 RBShellPlayer *player)
3519 {
3520 gboolean entry_changed;
3521
3522 g_return_if_fail (entry != NULL);
3523
3524 GDK_THREADS_ENTER ();
3525
3526 entry_changed = (player->priv->playing_entry != entry);
3527
3528 /* update playing entry */
3529 if (player->priv->playing_entry)
3530 rhythmdb_entry_unref (player->priv->playing_entry);
3531 player->priv->playing_entry = rhythmdb_entry_ref (entry);
3532 player->priv->playing_entry_eos = FALSE;
3533
3534 if (entry_changed) {
3535 const char *location;
3536
3537 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3538 rb_debug ("new playing stream: %s", location);
3539 g_signal_emit (G_OBJECT (player),
3540 rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
3541 entry);
3542 g_signal_emit (G_OBJECT (player),
3543 rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
3544 location);
3545 }
3546
3547 /* resync UI */
3548 rb_shell_player_sync_with_source (player);
3549 rb_shell_player_sync_buttons (player);
3550 g_object_notify (G_OBJECT (player), "playing");
3551
3552 if (player->priv->jump_to_playing_entry) {
3553 rb_shell_player_jump_to_current (player);
3554 player->priv->jump_to_playing_entry = FALSE;
3555 }
3556
3557 GDK_THREADS_LEAVE ();
3558 }
3559
3560 static void
3561 error_cb (RBPlayer *mmplayer,
3562 RhythmDBEntry *entry,
3563 const GError *err,
3564 gpointer data)
3565 {
3566 RBShellPlayer *player = RB_SHELL_PLAYER (data);
3567
3568 if (player->priv->handling_error)
3569 return;
3570
3571 if (player->priv->source == NULL) {
3572 rb_debug ("ignoring error (no source): %s", err->message);
3573 return;
3574 }
3575
3576 GDK_THREADS_ENTER ();
3577
3578 if (entry != player->priv->playing_entry) {
3579 rb_debug ("got error for unexpected entry %p (expected %p)", entry, player->priv->playing_entry);
3580 } else {
3581 rb_shell_player_error (player, TRUE, err);
3582 rb_debug ("exiting error hander");
3583 }
3584
3585 GDK_THREADS_LEAVE ();
3586 }
3587
3588 static void
3589 tick_cb (RBPlayer *mmplayer,
3590 RhythmDBEntry *entry,
3591 gint64 elapsed,
3592 gint64 duration,
3593 gpointer data)
3594 {
3595 RBShellPlayer *player = RB_SHELL_PLAYER (data);
3596 gint64 remaining_check = 0;
3597 gboolean duration_from_player = TRUE;
3598 const char *uri;
3599 long elapsed_sec;
3600
3601 GDK_THREADS_ENTER ();
3602
3603 if (player->priv->playing_entry != entry) {
3604 rb_debug ("got tick for unexpected entry %p (expected %p)", entry, player->priv->playing_entry);
3605 GDK_THREADS_LEAVE ();
3606 return;
3607 }
3608
3609 /* if we aren't getting a duration value from the player, use the
3610 * value from the entry, if any.
3611 */
3612 if (duration < 1) {
3613 duration = ((gint64)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION)) * RB_PLAYER_SECOND;
3614 duration_from_player = FALSE;
3615 }
3616
3617 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3618 rb_debug ("tick: [%s, %" G_GINT64_FORMAT ":%" G_GINT64_FORMAT "(%d)]",
3619 uri,
3620 elapsed,
3621 duration,
3622 duration_from_player);
3623
3624 if (elapsed < 0) {
3625 elapsed_sec = 0;
3626 } else {
3627 elapsed_sec = elapsed / RB_PLAYER_SECOND;
3628 }
3629
3630 if (player->priv->elapsed != elapsed_sec) {
3631 player->priv->elapsed = elapsed_sec;
3632 g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED],
3633 0, player->priv->elapsed);
3634 }
3635 g_signal_emit (player, rb_shell_player_signals[ELAPSED_NANO_CHANGED], 0, elapsed);
3636
3637 if (duration_from_player) {
3638 /* XXX update duration in various things? */
3639 }
3640
3641 /* check if we should start a crossfade */
3642 if (rb_player_multiple_open (mmplayer)) {
3643 if (player->priv->track_transition_time < PREROLL_TIME) {
3644 remaining_check = PREROLL_TIME;
3645 } else {
3646 remaining_check = player->priv->track_transition_time;
3647 }
3648 }
3649
3650 /*
3651 * just pretending we got an EOS will do exactly what we want
3652 * here. if we don't want to crossfade, we'll just leave the stream
3653 * prerolled until the current stream really ends.
3654 */
3655 if (remaining_check > 0 &&
3656 duration > 0 &&
3657 elapsed > 0 &&
3658 ((duration - elapsed) <= remaining_check)) {
3659 rb_debug ("%" G_GINT64_FORMAT " ns remaining in stream %s; need %" G_GINT64_FORMAT " for transition",
3660 duration - elapsed,
3661 uri,
3662 remaining_check);
3663 rb_shell_player_handle_eos_unlocked (player, entry, FALSE);
3664 }
3665
3666 GDK_THREADS_LEAVE ();
3667 }
3668
3669 typedef struct {
3670 RhythmDBEntry *entry;
3671 RBShellPlayer *player;
3672 } MissingPluginRetryData;
3673
3674 static void
3675 missing_plugins_retry_cb (gpointer inst,
3676 gboolean retry,
3677 MissingPluginRetryData *retry_data)
3678 {
3679 GError *error = NULL;
3680 if (retry == FALSE) {
3681 /* next? or stop playback? */
3682 rb_debug ("not retrying playback; stopping player");
3683 rb_shell_player_stop (retry_data->player);
3684 return;
3685 }
3686
3687 rb_debug ("retrying playback");
3688 rb_shell_player_set_playing_entry (retry_data->player,
3689 retry_data->entry,
3690 FALSE, FALSE,
3691 &error);
3692 if (error != NULL) {
3693 rb_shell_player_error (retry_data->player, FALSE, error);
3694 g_clear_error (&error);
3695 }
3696 }
3697
3698 static void
3699 missing_plugins_retry_cleanup (MissingPluginRetryData *retry)
3700 {
3701 retry->player->priv->handling_error = FALSE;
3702
3703 g_object_unref (retry->player);
3704 rhythmdb_entry_unref (retry->entry);
3705 g_free (retry);
3706 }
3707
3708
3709 static void
3710 missing_plugins_cb (RBPlayer *player,
3711 RhythmDBEntry *entry,
3712 const char **details,
3713 const char **descriptions,
3714 RBShellPlayer *sp)
3715 {
3716 gboolean processing;
3717 GClosure *retry;
3718 MissingPluginRetryData *retry_data;
3719
3720 retry_data = g_new0 (MissingPluginRetryData, 1);
3721 retry_data->player = g_object_ref (sp);
3722 retry_data->entry = rhythmdb_entry_ref (entry);
3723
3724 retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
3725 retry_data,
3726 (GClosureNotify) missing_plugins_retry_cleanup);
3727 g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN);
3728 processing = rb_missing_plugins_install (details, FALSE, retry);
3729 if (processing) {
3730 /* don't handle any further errors */
3731 sp->priv->handling_error = TRUE;
3732
3733 /* probably specify the URI here.. */
3734 rb_debug ("stopping player while processing missing plugins");
3735 rb_player_close (retry_data->player->priv->mmplayer, NULL, NULL);
3736 } else {
3737 rb_debug ("not processing missing plugins; simulating EOS");
3738 rb_shell_player_handle_eos (NULL, NULL, FALSE, retry_data->player);
3739 }
3740
3741 g_closure_sink (retry);
3742 }
3743
3744 static void
3745 player_image_cb (RBPlayer *player,
3746 RhythmDBEntry *entry,
3747 GdkPixbuf *image,
3748 RBShellPlayer *shell_player)
3749 {
3750 RBExtDB *store;
3751 RBExtDBKey *key;
3752 const char *artist;
3753 GValue v = G_VALUE_INIT;
3754
3755 if (image == NULL)
3756 return;
3757
3758 artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
3759 if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
3760 artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
3761 if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
3762 return;
3763 }
3764 }
3765
3766 store = rb_ext_db_new ("album-art");
3767
3768 key = rb_ext_db_key_create_storage ("album", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
3769 rb_ext_db_key_add_field (key, "artist", artist);
3770
3771 g_value_init (&v, GDK_TYPE_PIXBUF);
3772 g_value_set_object (&v, image);
3773 rb_ext_db_store (store, key, RB_EXT_DB_SOURCE_EMBEDDED, &v);
3774 g_value_unset (&v);
3775
3776 g_object_unref (store);
3777 rb_ext_db_key_free (key);
3778 }
3779
3780 /**
3781 * rb_shell_player_get_playing_path:
3782 * @player: the #RBShellPlayer
3783 * @path: (out callee-allocates) (transfer full): returns the URI of the current playing entry
3784 * @error: returns error information
3785 *
3786 * Retrieves the URI of the current playing entry. The
3787 * caller must not free the returned string.
3788 *
3789 * Return value: %TRUE if successful
3790 */
3791 gboolean
3792 rb_shell_player_get_playing_path (RBShellPlayer *player,
3793 const gchar **path,
3794 GError **error)
3795 {
3796 RhythmDBEntry *entry;
3797
3798 entry = rb_shell_player_get_playing_entry (player);
3799 if (entry != NULL) {
3800 *path = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3801 } else {
3802 *path = NULL;
3803 }
3804
3805 if (entry != NULL) {
3806 rhythmdb_entry_unref (entry);
3807 }
3808
3809 return TRUE;
3810 }
3811
3812 static gboolean
3813 _idle_unblock_signal_cb (gpointer data)
3814 {
3815 RBShellPlayer *player = (RBShellPlayer *)data;
3816 GtkAction *action;
3817 gboolean playing;
3818
3819 GDK_THREADS_ENTER ();
3820
3821 player->priv->unblock_play_id = 0;
3822
3823 action = gtk_action_group_get_action (player->priv->actiongroup,
3824 "ControlPlay");
3825
3826 /* sync the active state of the action again */
3827 g_object_get (player, "playing", &playing, NULL);
3828 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing);
3829
3830 g_signal_handlers_unblock_by_func (action, rb_shell_player_cmd_play, player);
3831
3832 GDK_THREADS_LEAVE ();
3833 return FALSE;
3834 }
3835
3836 static void
3837 rb_shell_player_playing_changed_cb (RBShellPlayer *player,
3838 GParamSpec *arg1,
3839 gpointer user_data)
3840 {
3841 GtkAction *action;
3842 gboolean playing;
3843 char *tooltip;
3844
3845 g_object_get (player, "playing", &playing, NULL);
3846 action = gtk_action_group_get_action (player->priv->actiongroup,
3847 "ControlPlay");
3848 if (playing) {
3849 if (rb_source_can_pause (player->priv->source)) {
3850 tooltip = g_strdup (_("Pause playback"));
3851 } else {
3852 tooltip = g_strdup (_("Stop playback"));
3853 }
3854 } else {
3855 tooltip = g_strdup (_("Start playback"));
3856 }
3857 g_object_set (action, "tooltip", tooltip, NULL);
3858 g_free (tooltip);
3859
3860 /* block the signal, so that it doesn't get stuck by triggering recursively,
3861 * and don't unblock it until whatever else is happening has finished.
3862 * don't block it again if it's already blocked, though.
3863 */
3864 if (player->priv->unblock_play_id == 0) {
3865 g_signal_handlers_block_by_func (action, rb_shell_player_cmd_play, player);
3866 }
3867 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing);
3868
3869 if (player->priv->unblock_play_id == 0) {
3870 player->priv->unblock_play_id = g_idle_add (_idle_unblock_signal_cb, player);
3871 }
3872 }
3873
3874 /* This should really be standard. */
3875 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3876
3877 GType
3878 rb_shell_player_error_get_type (void)
3879 {
3880 static GType etype = 0;
3881
3882 if (etype == 0) {
3883 static const GEnumValue values[] = {
3884 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_PLAYLIST_PARSE_ERROR, "playlist-parse-failed"),
3885 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST, "end-of-playlist"),
3886 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_PLAYING, "not-playing"),
3887 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE, "not-seekable"),
3888 ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_POSITION_NOT_AVAILABLE, "position-not-available"),
3889 { 0, 0, 0 }
3890 };
3891
3892 etype = g_enum_register_static ("RBShellPlayerError", values);
3893 }
3894
3895 return etype;
3896 }
3897
3898 static void
3899 _play_order_description_free (RBPlayOrderDescription *order)
3900 {
3901 g_free (order->name);
3902 g_free (order->description);
3903 g_free (order);
3904 }
3905
3906 /**
3907 * rb_play_order_new:
3908 * @porder_name: Play order type name
3909 * @player: #RBShellPlayer instance to attach to
3910 *
3911 * Creates a new #RBPlayOrder of the specified type.
3912 *
3913 * Returns: #RBPlayOrder instance
3914 **/
3915
3916 #define DEFAULT_PLAY_ORDER "linear"
3917
3918 static RBPlayOrder *
3919 rb_play_order_new (RBShellPlayer *player, const char* porder_name)
3920 {
3921 RBPlayOrderDescription *order;
3922
3923 g_return_val_if_fail (porder_name != NULL, NULL);
3924 g_return_val_if_fail (player != NULL, NULL);
3925
3926 order = g_hash_table_lookup (player->priv->play_orders, porder_name);
3927
3928 if (order == NULL) {
3929 g_warning ("Unknown value \"%s\" in GSettings key \"play-order"
3930 "\". Using %s play order.", porder_name, DEFAULT_PLAY_ORDER);
3931 order = g_hash_table_lookup (player->priv->play_orders, DEFAULT_PLAY_ORDER);
3932 }
3933
3934 return RB_PLAY_ORDER (g_object_new (order->order_type, "player", player, NULL));
3935 }
3936
3937 /**
3938 * rb_shell_player_add_play_order:
3939 * @player: the #RBShellPlayer
3940 * @name: name of the new play order
3941 * @description: description of the new play order
3942 * @order_type: the #GType of the play order class
3943 * @hidden: if %TRUE, don't display the play order in the UI
3944 *
3945 * Adds a new play order to the set of available play orders.
3946 */
3947 void
3948 rb_shell_player_add_play_order (RBShellPlayer *player, const char *name,
3949 const char *description, GType order_type, gboolean hidden)
3950 {
3951 RBPlayOrderDescription *order;
3952
3953 g_return_if_fail (g_type_is_a (order_type, RB_TYPE_PLAY_ORDER));
3954
3955 order = g_new0(RBPlayOrderDescription, 1);
3956 order->name = g_strdup (name);
3957 order->description = g_strdup (description);
3958 order->order_type = order_type;
3959 order->is_in_dropdown = !hidden;
3960
3961 g_hash_table_insert (player->priv->play_orders, order->name, order);
3962 }
3963
3964 /**
3965 * rb_shell_player_remove_play_order:
3966 * @player: the #RBShellPlayer
3967 * @name: name of the play order to remove
3968 *
3969 * Removes a play order previously added with #rb_shell_player_add_play_order
3970 * from the set of available play orders.
3971 */
3972 void
3973 rb_shell_player_remove_play_order (RBShellPlayer *player, const char *name)
3974 {
3975 g_hash_table_remove (player->priv->play_orders, name);
3976 }