hythmbox-2.98/sources/rb-streaming-source.c

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 }