No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2002,2003 Colin Walters <walters@debian.org>
4 * Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.wh9.net>
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-streaming-source
32 * @short_description: Base class for streaming sources such as internet radio
33 *
34 * This class provides handling of buffering signals and streaming song metadata
35 * common to different types of sources that play continuous streaming media.
36 */
37
38 #include "config.h"
39
40 #include <string.h>
41
42 #include <glib/gi18n.h>
43 #include <gtk/gtk.h>
44
45 #include "rb-streaming-source.h"
46
47 #include "rhythmdb-query-model.h"
48 #include "rb-util.h"
49 #include "rb-file-helpers.h"
50 #include "rb-dialog.h"
51 #include "rb-debug.h"
52 #include "rb-shell.h"
53 #include "rb-shell-player.h"
54 #include "rb-player.h"
55 #include "rb-metadata.h"
56
57 #define STREAMING_METADATA_NOTIFY_TIMEOUT 350
58
59 static void rb_streaming_source_class_init (RBStreamingSourceClass *klass);
60 static void rb_streaming_source_init (RBStreamingSource *source);
61 static void rb_streaming_source_constructed (GObject *object);
62 static void rb_streaming_source_dispose (GObject *object);
63
64 /* source methods */
65 static RBSourceEOFType impl_handle_eos (RBSource *asource);
66
67 static void playing_entry_changed_cb (RBShellPlayer *player,
68 RhythmDBEntry *entry,
69 RBStreamingSource *source);
70 static GValue * streaming_title_request_cb (RhythmDB *db,
71 RhythmDBEntry *entry,
72 RBStreamingSource *source);
73 static GValue * streaming_artist_request_cb (RhythmDB *db,
74 RhythmDBEntry *entry,
75 RBStreamingSource *source);
76 static GValue * streaming_album_request_cb (RhythmDB *db,
77 RhythmDBEntry *entry,
78 RBStreamingSource *source);
79 static void extra_metadata_gather_cb (RhythmDB *db,
80 RhythmDBEntry *entry,
81 RBStringValueMap *data,
82 RBStreamingSource *source);
83
84 struct _RBStreamingSourcePrivate
85 {
86 RhythmDB *db;
87
88 gboolean initialized;
89 gboolean is_playing;
90
91 RBShellPlayer *player;
92 RhythmDBEntry *playing_stream;
93 char *streaming_title;
94 char *streaming_artist;
95 char *streaming_album;
96
97 gint emit_notify_id;
98 gint buffering_id;
99 guint buffering;
100
101 gboolean dispose_has_run;
102 };
103
104 G_DEFINE_TYPE (RBStreamingSource, rb_streaming_source, RB_TYPE_SOURCE)
105
106 static void
107 rb_streaming_source_class_init (RBStreamingSourceClass *klass)
108 {
109 GObjectClass *object_class = G_OBJECT_CLASS (klass);
110 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
111
112 object_class->dispose = rb_streaming_source_dispose;
113 object_class->constructed = rb_streaming_source_constructed;
114
115 source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
116 source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
117 source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
118 source_class->impl_handle_eos = impl_handle_eos;
119 source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_true_function;
120
121 g_type_class_add_private (klass, sizeof (RBStreamingSourcePrivate));
122 }
123
124 static void
125 rb_streaming_source_init (RBStreamingSource *source)
126 {
127 source->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((source),
128 RB_TYPE_STREAMING_SOURCE,
129 RBStreamingSourcePrivate));
130 }
131
132 static void
133 rb_streaming_source_dispose (GObject *object)
134 {
135 RBStreamingSource *source;
136
137 source = RB_STREAMING_SOURCE (object);
138
139 if (source->priv->player) {
140 g_object_unref (source->priv->player);
141 source->priv->player = NULL;
142 }
143
144 if (source->priv->db) {
145 g_object_unref (source->priv->db);
146 source->priv->db = NULL;
147 }
148
149 G_OBJECT_CLASS (rb_streaming_source_parent_class)->dispose (object);
150 }
151
152 static void
153 rb_streaming_source_constructed (GObject *object)
154 {
155 RBStreamingSource *source;
156 RBShell *shell;
157
158 RB_CHAIN_GOBJECT_METHOD (rb_streaming_source_parent_class, constructed, object);
159 source = RB_STREAMING_SOURCE (object);
160
161 g_object_get (G_OBJECT (source), "shell", &shell, NULL);
162 g_object_get (G_OBJECT (shell),
163 "db", &source->priv->db,
164 "shell-player", &source->priv->player,
165 NULL);
166 g_object_unref (shell);
167
168 g_signal_connect_object (G_OBJECT (source->priv->db),
169 "entry-extra-metadata-request::" RHYTHMDB_PROP_STREAM_SONG_TITLE,
170 G_CALLBACK (streaming_title_request_cb),
171 source, 0);
172
173 g_signal_connect_object (G_OBJECT (source->priv->db),
174 "entry-extra-metadata-request::" RHYTHMDB_PROP_STREAM_SONG_ARTIST,
175 G_CALLBACK (streaming_artist_request_cb),
176 source, 0);
177
178 g_signal_connect_object (G_OBJECT (source->priv->db),
179 "entry-extra-metadata-request::" RHYTHMDB_PROP_STREAM_SONG_ALBUM,
180 G_CALLBACK (streaming_album_request_cb),
181 source, 0);
182
183 g_signal_connect_object (G_OBJECT (source->priv->db),
184 "entry-extra-metadata-gather",
185 G_CALLBACK (extra_metadata_gather_cb),
186 source, 0);
187
188 /* g_signal_connect_object (source->priv->player, "playing-source-changed",
189 G_CALLBACK (playing_source_changed_cb),
190 source, 0); */
191 g_signal_connect_object (source->priv->player, "playing-song-changed",
192 G_CALLBACK (playing_entry_changed_cb),
193 source, 0);
194 }
195
196 static RBSourceEOFType
197 impl_handle_eos (RBSource *asource)
198 {
199 return RB_SOURCE_EOF_RETRY;
200 }
201
202 /**
203 * rb_streaming_source_get_progress:
204 * @source: a #RBStreamingSource
205 * @text: (out callee-allocates) (transfer full): returns buffering status text
206 * @progress: (out callee-allocates): returns buffering progress fraction
207 *
208 * Provides status text and progress fraction suitable for use in
209 * a streaming source's @rb_source_get_status method.
210 */
211 void
212 rb_streaming_source_get_progress (RBStreamingSource *source, char **text, float *progress)
213 {
214 if (source->priv->buffering == -1) {
215 *progress = 0.0;
216 g_free (*text);
217 *text = g_strdup (_("Connecting"));
218 } else if (source->priv->buffering > 0) {
219 *progress = ((float)source->priv->buffering)/100;
220 g_free (*text);
221 *text = g_strdup (_("Buffering"));
222 }
223 }
224
225 static void
226 buffering_cb (GObject *backend, gpointer whatever, guint progress, RBStreamingSource *source)
227 {
228 if (progress == 0)
229 progress = 1;
230 else if (progress == 100)
231 progress = 0;
232
233 GDK_THREADS_ENTER ();
234 source->priv->buffering = progress;
235 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
236 GDK_THREADS_LEAVE ();
237 }
238
239 static gboolean
240 check_entry_type (RBStreamingSource *source, RhythmDBEntry *entry)
241 {
242 RhythmDBEntryType *entry_type;
243 gboolean matches = FALSE;
244
245 g_object_get (source, "entry-type", &entry_type, NULL);
246 if (entry != NULL && rhythmdb_entry_get_entry_type (entry) == entry_type)
247 matches = TRUE;
248 g_object_unref (entry_type);
249
250 return matches;
251 }
252
253 static GValue *
254 streaming_title_request_cb (RhythmDB *db,
255 RhythmDBEntry *entry,
256 RBStreamingSource *source)
257 {
258 GValue *value;
259 if (check_entry_type (source, entry) == FALSE ||
260 entry != rb_shell_player_get_playing_entry (source->priv->player) ||
261 source->priv->streaming_title == NULL)
262 return NULL;
263
264 rb_debug ("returning streaming title \"%s\" to extra metadata request", source->priv->streaming_title);
265 value = g_new0 (GValue, 1);
266 g_value_init (value, G_TYPE_STRING);
267 g_value_set_string (value, source->priv->streaming_title);
268 return value;
269 }
270
271 static GValue *
272 streaming_artist_request_cb (RhythmDB *db,
273 RhythmDBEntry *entry,
274 RBStreamingSource *source)
275 {
276 GValue *value;
277
278 if (check_entry_type (source, entry) == FALSE ||
279 entry != rb_shell_player_get_playing_entry (source->priv->player) ||
280 source->priv->streaming_artist == NULL)
281 return NULL;
282
283 rb_debug ("returning streaming artist \"%s\" to extra metadata request", source->priv->streaming_artist);
284 value = g_new0 (GValue, 1);
285 g_value_init (value, G_TYPE_STRING);
286 g_value_set_string (value, source->priv->streaming_artist);
287 return value;
288 }
289
290 static GValue *
291 streaming_album_request_cb (RhythmDB *db,
292 RhythmDBEntry *entry,
293 RBStreamingSource *source)
294 {
295 GValue *value;
296
297 if (check_entry_type (source, entry) == FALSE ||
298 entry != rb_shell_player_get_playing_entry (source->priv->player) ||
299 source->priv->streaming_album == NULL)
300 return NULL;
301
302 rb_debug ("returning streaming album \"%s\" to extra metadata request", source->priv->streaming_album);
303 value = g_new0 (GValue, 1);
304 g_value_init (value, G_TYPE_STRING);
305 g_value_set_string (value, source->priv->streaming_album);
306 return value;
307 }
308 static void
309 extra_metadata_gather_cb (RhythmDB *db,
310 RhythmDBEntry *entry,
311 RBStringValueMap *data,
312 RBStreamingSource *source)
313 {
314 /* our extra metadata only applies to the playing entry */
315 if (entry != rb_shell_player_get_playing_entry (source->priv->player) ||
316 check_entry_type (source, entry) == FALSE)
317 return;
318
319 if (source->priv->streaming_title != NULL) {
320 GValue value = {0,};
321
322 g_value_init (&value, G_TYPE_STRING);
323 g_value_set_string (&value, source->priv->streaming_title);
324 rb_string_value_map_set (data, RHYTHMDB_PROP_STREAM_SONG_TITLE, &value);
325 g_value_unset (&value);
326 }
327
328 if (source->priv->streaming_artist != NULL) {
329 GValue value = {0,};
330
331 g_value_init (&value, G_TYPE_STRING);
332 g_value_set_string (&value, source->priv->streaming_artist);
333 rb_string_value_map_set (data, RHYTHMDB_PROP_STREAM_SONG_ARTIST, &value);
334 g_value_unset (&value);
335 }
336
337 if (source->priv->streaming_album != NULL) {
338 GValue value = {0,};
339
340 g_value_init (&value, G_TYPE_STRING);
341 g_value_set_string (&value, source->priv->streaming_album);
342 rb_string_value_map_set (data, RHYTHMDB_PROP_STREAM_SONG_ALBUM, &value);
343 g_value_unset (&value);
344 }
345 }
346
347 static gboolean
348 emit_notification_cb (RBStreamingSource *source)
349 {
350 RBShell *shell;
351
352 source->priv->emit_notify_id = 0;
353
354 g_object_get (G_OBJECT (source), "shell", &shell, NULL);
355 rb_shell_do_notify (shell, FALSE, NULL);
356 g_object_unref (shell);
357
358 return FALSE;
359 }
360
361 static void
362 set_streaming_metadata (RBStreamingSource *source,
363 char **field,
364 const char *metadata_field,
365 const char *value)
366 {
367 GValue v = {0,};
368
369 /* don't do anything if the value isn't changing */
370 if (*field != NULL && strcmp (*field, value) == 0) {
371 return;
372 }
373
374 g_free (*field);
375 *field = g_strdup (value);
376
377 g_value_init (&v, G_TYPE_STRING);
378 g_value_set_string (&v, value);
379 rhythmdb_emit_entry_extra_metadata_notify (source->priv->db,
380 source->priv->playing_stream,
381 metadata_field,
382 &v);
383 g_value_unset (&v);
384
385 if (source->priv->emit_notify_id != 0)
386 g_source_remove (source->priv->emit_notify_id);
387
388 source->priv->emit_notify_id = g_timeout_add (STREAMING_METADATA_NOTIFY_TIMEOUT,
389 (GSourceFunc) emit_notification_cb,
390 source);
391 }
392
393 /**
394 * rb_streaming_source_set_streaming_title:
395 * @source: a #RBStreamingSource
396 * @title: the new streaming song title
397 *
398 * Updates the streaming song title. Call this when an updated
399 * streaming song title is received from the stream.
400 */
401 void
402 rb_streaming_source_set_streaming_title (RBStreamingSource *source,
403 const char *title)
404 {
405 rb_debug ("streaming title: \"%s\"", title);
406 set_streaming_metadata (source,
407 &source->priv->streaming_title,
408 RHYTHMDB_PROP_STREAM_SONG_TITLE,
409 title);
410 }
411
412 /**
413 * rb_streaming_source_set_streaming_artist:
414 * @source: a #RBStreamingSource
415 * @artist: the new streaming song artist name
416 *
417 * Updates the streaming song artist name. Call this when an updated
418 * streaming song artist name is received from the stream.
419 */
420 void
421 rb_streaming_source_set_streaming_artist (RBStreamingSource *source,
422 const char *artist)
423 {
424 rb_debug ("streaming artist: \"%s\"", artist);
425 set_streaming_metadata (source,
426 &source->priv->streaming_artist,
427 RHYTHMDB_PROP_STREAM_SONG_ARTIST,
428 artist);
429 }
430
431 /**
432 * rb_streaming_source_set_streaming_album:
433 * @source: a #RBStreamingSource
434 * @album: the new streaming song album name
435 *
436 * Updates the streaming song album name. Call this when an updated
437 * streaming song album name is received from the stream.
438 */
439 void
440 rb_streaming_source_set_streaming_album (RBStreamingSource *source,
441 const char *album)
442 {
443 rb_debug ("streaming album: \"%s\"", album);
444 set_streaming_metadata (source,
445 &source->priv->streaming_album,
446 RHYTHMDB_PROP_STREAM_SONG_ALBUM,
447 album);
448 }
449
450
451 static void
452 playing_entry_changed_cb (RBShellPlayer *player,
453 RhythmDBEntry *entry,
454 RBStreamingSource *source)
455 {
456 GObject *backend;
457
458 if (source->priv->playing_stream == entry)
459 return;
460
461 g_free (source->priv->streaming_title);
462 g_free (source->priv->streaming_artist);
463 g_free (source->priv->streaming_album);
464 source->priv->streaming_title = NULL;
465 source->priv->streaming_artist = NULL;
466 source->priv->streaming_album = NULL;
467
468 if (source->priv->playing_stream != NULL) {
469 rb_source_update_play_statistics (RB_SOURCE (source),
470 source->priv->db,
471 source->priv->playing_stream);
472
473 rhythmdb_entry_unref (source->priv->playing_stream);
474 source->priv->playing_stream = NULL;
475 }
476
477 g_object_get (source->priv->player, "player", &backend, NULL);
478
479 if (check_entry_type (source, entry) == FALSE) {
480 source->priv->buffering = 0;
481 if (source->priv->buffering_id) {
482 g_signal_handler_disconnect (backend,
483 source->priv->buffering_id);
484 source->priv->buffering_id = 0;
485
486 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
487 }
488 } else {
489 rb_debug ("playing new stream; resetting buffering");
490 if (source->priv->buffering_id == 0) {
491 source->priv->buffering_id =
492 g_signal_connect_object (backend, "buffering",
493 G_CALLBACK (buffering_cb),
494 source, 0);
495 }
496
497 source->priv->buffering = -1;
498
499 source->priv->playing_stream = rhythmdb_entry_ref (entry);
500 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
501 }
502
503 g_object_unref (backend);
504 }