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 (¤t);
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