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