Merge lp:~matttbe/ubuntu/quantal/rhythmbox/1010619_795765 into lp:ubuntu/quantal/rhythmbox

Proposed by Matthieu Baerts
Status: Merged
Merge reported by: Martin Pitt
Merged at revision: not available
Proposed branch: lp:~matttbe/ubuntu/quantal/rhythmbox/1010619_795765
Merge into: lp:ubuntu/quantal/rhythmbox
Diff against target: 9647 lines (+9557/-4)
9 files modified
.pc/11_fix_cd_pausing.patch/shell/rb-shell-player.c (+3978/-0)
.pc/applied-patches (+2/-0)
.pc/git_rhythmdb_fix_deadlock.patch/rhythmdb/rhythmdb.c (+5478/-0)
debian/changelog (+16/-0)
debian/patches/11_fix_cd_pausing.patch (+23/-0)
debian/patches/git_rhythmdb_fix_deadlock.patch (+46/-0)
debian/patches/series (+2/-0)
rhythmdb/rhythmdb.c (+8/-4)
shell/rb-shell-player.c (+4/-0)
To merge this branch: bzr merge lp:~matttbe/ubuntu/quantal/rhythmbox/1010619_795765
Reviewer Review Type Date Requested Status
Ubuntu Sponsors Pending
Review via email: mp+133343@code.launchpad.net

Description of the change

[IMPACT]

 * Rhythmbox freezes when using a large collection. (LP: #1010619)
 * Rhythmbox won't resume playing an audio CD if you pause it. (LP: #795765)

[TESTCASE]

 * Rhythmbox freezes when using a large collection. (LP: #1010619)
  * It happens with a large collection.
  * It's due to a deadlock between db load and rhythmdb_add_uri_with_types and it's possible that it's not easy to reproduce this bug. Maybe it can be interesting to slow down the hard disk (e.g. by copying a large file when opening Rhythmbox with a lot of files in its collection)
  * With the previous version and after a few seconds, Rhythmbox will freeze.
  * (this fix has been confirmed by a few people in the bug #1010619)

 * Rhythmbox won't resume playing an audio CD if you pause it. (LP: #795765)
  * Insert an audio CD (not an mp3 CD but a traditional one you'd buy from the music store)
  * Test if play and pause work.

[Regression Potential]

 * Rhythmbox freezes when using a large collection. (LP: #1010619)
  * According to RB devs: None => http://git.gnome.org/browse/rhythmbox/commit/?id=a2ead52faa45a37f28dff1e94cad5f0dadd28129
  * This deadlock has been fixed by RB devs and now it seems they understand why there was a deadlock

 * Rhythmbox won't resume playing an audio CD if you pause it. (LP: #795765)
  * None, audio CD playback is already very badly broken.

[ Other Info ]

 * A new version (2.98-0ubuntu2) has been uploaded to Raring repos and it contains the same patches to fix these bugs.
 * About the version: I wanted to use the version 2.97-1ubuntu5.1 just to be sure that the version number does not conflict with any later and future versions in other Ubuntu releases but a new version has been uploaded a few days ago with the version 2.97-1ubuntu6. I hope I can use the version 2.97-1ubuntu7

Thank you for your help!

To post a comment you must log in.
Revision history for this message
Sebastien Bacher (seb128) wrote :

Thank you for your work, the update has been uploaded and is waiting for SRU team review in the queue

Revision history for this message
Matthieu Baerts (matttbe) wrote :

Thank you for your help :)

Revision history for this message
Mathieu Cossette (mcossette) wrote :

critical bug here.. when reaching end of a song which causes to crash Rhythmbox

Ubuntu 13.10

Revision history for this message
Matthieu Baerts (matttbe) wrote :

@Mathieu: I guess it's not linked to this bug but I see that you also opened a new bug report (LP: #1179100). Let wait and see if other people have this critical bug.

PS: feel free to also report this bug upstream on bugzilla.gnome.org

Preview Diff

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

Subscribers

People subscribed via source and target branches