hythmbox-2.98/plugins/visualizer/rb-visualizer-plugin.c

No issues found

  1 /*
  2  * Copyright (C) 2010 Jonathan Matthew <jonathan@d14n.org>
  3  *
  4  * This program is free software; you can redistribute it and/or modify
  5  * it under the terms of the GNU General Public License as published by
  6  * the Free Software Foundation; either version 2, or (at your option)
  7  * any later version.
  8  *
  9  * The Rhythmbox authors hereby grant permission for non-GPL compatible
 10  * GStreamer plugins to be used and distributed together with GStreamer
 11  * and Rhythmbox. This permission is above and beyond the permissions granted
 12  * by the GPL license by which Rhythmbox is covered. If you modify this code
 13  * you may extend this exception to your version of the code, but you are not
 14  * obligated to do so. If you do not wish to do so, delete this exception
 15  * statement from your version.
 16  *
 17  * This program is distributed in the hope that it will be useful,
 18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20  * GNU General Public License for more details.
 21  *
 22  * You should have received a copy of the GNU General Public License
 23  * along with this program; if not, write to the Free Software
 24  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 25  */
 26 
 27 #include <config.h>
 28 
 29 #include <glib/gi18n-lib.h>
 30 #include <gmodule.h>
 31 #include <gtk/gtk.h>
 32 #include <glib.h>
 33 #include <glib-object.h>
 34 #include <libpeas/peas.h>
 35 
 36 #include <plugins/rb-plugin-macros.h>
 37 #include <shell/rb-shell-player.h>
 38 #include <sources/rb-display-page.h>
 39 #include <sources/rb-display-page-group.h>
 40 #include <sources/rb-display-page-model.h>
 41 #include <backends/rb-player.h>
 42 #include <backends/rb-player-gst-tee.h>
 43 #include <lib/rb-debug.h>
 44 
 45 #include "rb-visualizer-page.h"
 46 #include "rb-visualizer-fullscreen.h"
 47 #include "rb-visualizer-menu.h"
 48 
 49 #define RB_TYPE_VISUALIZER_PLUGIN		(rb_visualizer_plugin_get_type ())
 50 #define RB_VISUALIZER_PLUGIN(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_VISUALIZER_PLUGIN, RBVisualizerPlugin))
 51 #define RB_VISUALIZER_PLUGIN_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_VISUALIZER_PLUGIN, RBVisualizerPluginClass))
 52 #define RB_IS_VISUALIZER_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_VISUALIZER_PLUGIN))
 53 #define RB_IS_VISUALIZER_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_VISUALIZER_PLUGIN))
 54 #define RB_VISUALIZER_PLUGIN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_VISUALIZER_PLUGIN, RBVisualizerPluginClass))
 55 
 56 /* playbin2 flag(s) */
 57 #define PLAYBIN2_FLAG_VIS	0x08
 58 
 59 typedef struct
 60 {
 61 	PeasExtensionBase parent;
 62 
 63 	RBShellPlayer *shell_player;
 64 	RBPlayer *player;
 65 
 66 	/* pipeline stuff */
 67 	GstElement *visualizer;
 68 	GstElement *sink;
 69 
 70 	GstElement *identity;
 71 	GstElement *capsfilter;
 72 	GstElement *vis_plugin;
 73 
 74 	GstElement *playbin;
 75 	gulong playbin_notify_id;
 76 
 77 	/* ui */
 78 	RBVisualizerPage *page;
 79 
 80 	GSettings *settings;
 81 } RBVisualizerPlugin;
 82 
 83 typedef struct
 84 {
 85 	PeasExtensionBaseClass parent_class;
 86 } RBVisualizerPluginClass;
 87 
 88 
 89 G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
 90 
 91 RB_DEFINE_PLUGIN(RB_TYPE_VISUALIZER_PLUGIN, RBVisualizerPlugin, rb_visualizer_plugin,)
 92 
 93 static void
 94 fixate_vis_caps (RBVisualizerPlugin *plugin)
 95 {
 96 	GstPad *pad;
 97 	GstCaps *caps = NULL;
 98 	const GstCaps *template_caps;
 99 
100 	pad = gst_element_get_static_pad (plugin->vis_plugin, "src");
101 	template_caps = gst_pad_get_pad_template_caps (pad);
102 	gst_object_unref (pad);
103 
104 	if (template_caps == NULL) {
105 		rb_debug ("vis element has no template caps?");
106 		return;
107 	}
108 
109 	caps = gst_caps_copy (template_caps);
110 
111 	if (gst_caps_is_fixed (caps) == FALSE) {
112 		guint i;
113 		char *dbg;
114 		const VisualizerQuality *q = &rb_visualizer_quality[g_settings_get_enum (plugin->settings, "quality")];
115 
116 		rb_debug ("fixating caps towards %dx%d, %d/%d", q->width, q->height, q->fps_n, q->fps_d);
117 		caps = gst_caps_make_writable (caps);
118 		for (i = 0; i < gst_caps_get_size (caps); i++) {
119 			GstStructure *s = gst_caps_get_structure (caps, i);
120 
121 			gst_structure_fixate_field_nearest_int (s, "width", q->width);
122 			gst_structure_fixate_field_nearest_int (s, "height", q->height);
123 			gst_structure_fixate_field_nearest_fraction (s, "framerate", q->fps_n, q->fps_d);
124 		}
125 
126 		dbg = gst_caps_to_string (caps);
127 		rb_debug ("setting fixed caps on capsfilter: %s", dbg);
128 		g_free (dbg);
129 
130 		g_object_set (plugin->capsfilter, "caps", caps, NULL);
131 	} else {
132 		char *dbg = gst_caps_to_string (caps);
133 		rb_debug ("vis element caps already fixed: %s", dbg);
134 		g_free (dbg);
135 	}
136 
137 	gst_caps_unref (caps);
138 }
139 
140 static void
141 mutate_playbin (RBVisualizerPlugin *plugin, GstElement *playbin)
142 {
143 	GstElement *current_vis_plugin;
144 	GstElement *current_video_sink;
145 	int playbin_flags;
146 
147 	if (playbin == plugin->playbin)
148 		return;
149 
150 	rb_debug ("mutating playbin");
151 
152 	/* check no one has already set the playbin properties we're interested in */
153 	g_object_get (playbin,
154 		      "vis-plugin", &current_vis_plugin,
155 		      "video-sink", &current_video_sink,
156 		      "flags", &playbin_flags,
157 		      NULL);
158 
159 	/* ignore fakesinks */
160 	if (current_video_sink != NULL) {
161 		const char *factoryname;
162 		GstElementFactory *factory;
163 
164 		factory = gst_element_get_factory (current_video_sink);
165 		factoryname = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory));
166 		if (strcmp (factoryname, "fakesink") == 0) {
167 			g_object_unref (current_video_sink);
168 			current_video_sink = NULL;
169 		}
170 	}
171 
172 	if ((current_vis_plugin != NULL) || (current_video_sink != NULL)) {
173 		g_warning ("sink and/or vis plugin already set on playbin");
174 		if (current_vis_plugin)
175 			g_object_unref (current_vis_plugin);
176 		if (current_video_sink)
177 			g_object_unref (current_video_sink);
178 		return;
179 	}
180 
181 	/* detach from old playbin (this should never really happen) */
182 	if (plugin->playbin) {
183 		g_object_unref (plugin->playbin);
184 	}
185 
186 	/* attach to new playbin */
187 	plugin->playbin = g_object_ref (playbin);
188 	g_object_set (plugin->playbin, "video-sink", plugin->sink, NULL);
189 
190 	/* start visualizer if it's supposed to be running */
191 	if (plugin->visualizer != NULL) {
192 		playbin_flags |= PLAYBIN2_FLAG_VIS;
193 		g_object_set (plugin->playbin,
194 			      "flags", playbin_flags,
195 			      "vis-plugin", plugin->visualizer,
196 			      NULL);
197 	}
198 }
199 
200 static void
201 playbin_notify_cb (GObject *object, GParamSpec *arg, RBVisualizerPlugin *pi)
202 {
203 	GstElement *playbin;
204 
205 	g_object_get (object, "playbin", &playbin, NULL);
206 	if (playbin) {
207 		mutate_playbin (pi, playbin);
208 		g_object_unref (playbin);
209 	}
210 }
211 
212 
213 static void
214 update_visualizer (RBVisualizerPlugin *plugin)
215 {
216 	if (plugin->visualizer == NULL) {
217 		return;
218 	}
219 
220 	/* pad blocking and other such nonsense, i guess */
221 }
222 
223 static void
224 start_visualizer_cb (RBVisualizerPage *page, RBVisualizerPlugin *plugin)
225 {
226 	GstPad *pad;
227 	char *plugin_name;
228 
229 	if (plugin->visualizer) {
230 		g_object_unref (plugin->visualizer);
231 		plugin->visualizer = NULL;
232 		plugin->identity = NULL;
233 		plugin->capsfilter = NULL;
234 		plugin->vis_plugin = NULL;
235 	}
236 	plugin->visualizer = gst_bin_new (NULL);
237 
238 	/* create common bits of visualizer bin: identity ! <effect> ! capsfilter */
239 	plugin->identity = gst_element_factory_make ("identity", NULL);
240 	plugin->capsfilter = gst_element_factory_make ("capsfilter", NULL);
241 
242 	plugin_name = g_settings_get_string (plugin->settings, "vis-plugin");
243 	if (plugin_name != NULL) {
244 		plugin->vis_plugin = gst_element_factory_make (plugin_name, NULL);
245 		if (plugin->vis_plugin == NULL) {
246 			g_warning ("Configured visualizer plugin %s not available", plugin_name);
247 		}
248 		g_free (plugin_name);
249 	}
250 	if (plugin->vis_plugin == NULL) {
251 		plugin->vis_plugin = gst_element_factory_make ("goom", NULL);
252 		if (plugin->vis_plugin == NULL) {
253 			g_warning ("Fallback visualizer plugin (goom) not available");
254 			return;
255 		}
256 	}
257 
258 	/* set up capsfilter */
259 	gst_bin_add_many (GST_BIN (plugin->visualizer), plugin->identity, plugin->vis_plugin, plugin->capsfilter, NULL);
260 
261 	pad = gst_element_get_static_pad (plugin->identity, "sink");
262 	gst_element_add_pad (plugin->visualizer, gst_ghost_pad_new ("sink", pad));
263 	gst_object_unref (pad);
264 
265 	/* XXX check errors etc. */
266 	if (gst_element_link_many (plugin->identity, plugin->vis_plugin, plugin->capsfilter, NULL) == FALSE) {
267 		g_warning ("couldn't link visualizer bin elements");
268 		return;
269 	}
270 	fixate_vis_caps (plugin);
271 
272 	g_object_ref (plugin->visualizer);
273 
274 	if (plugin->playbin_notify_id) {
275 		GstPad *pad;
276 		int playbin_flags;
277 
278 		pad = gst_element_get_static_pad (plugin->capsfilter, "src");
279 		gst_element_add_pad (plugin->visualizer, gst_ghost_pad_new ("src", pad));
280 		gst_object_unref (pad);
281 
282 		g_object_get (plugin->playbin, "flags", &playbin_flags, NULL);
283 		if (plugin->playbin != NULL) {
284 			playbin_flags |= PLAYBIN2_FLAG_VIS;
285 			rb_debug ("enabling vis; new playbin2 flags %x", playbin_flags);
286 			g_object_set (plugin->playbin,
287 				      "vis-plugin", plugin->visualizer,
288 				      "flags", playbin_flags,
289 				      NULL);
290 		} else {
291 			rb_debug ("playback hasn't started yet");
292 		}
293 	} else {
294 		GstElement *colorspace;
295 		GstElement *queue;
296 
297 		colorspace = gst_element_factory_make ("ffmpegcolorspace", NULL);
298 		queue = gst_element_factory_make ("queue", NULL);
299 
300 		g_object_set (queue, "max-size-buffers", 3, "max-size-bytes", 0, "max-size-time", (gint64) 0, NULL);
301 
302 		gst_bin_add_many (GST_BIN (plugin->visualizer), queue, colorspace, plugin->sink, NULL);
303 		gst_element_link_many (plugin->capsfilter, queue, colorspace, plugin->sink, NULL);
304 
305 		rb_debug ("adding visualizer bin to the pipeline");
306 		rb_player_gst_tee_add_tee (RB_PLAYER_GST_TEE (plugin->player),
307 					   plugin->visualizer);
308 	}
309 }
310 
311 static void
312 stop_visualizer_cb (RBVisualizerPage *page, RBVisualizerPlugin *plugin)
313 {
314 	if (plugin->visualizer == NULL) {
315 		return;
316 	}
317 
318 	if (plugin->playbin_notify_id) {
319 		int playbin_flags;
320 
321 		g_object_get (plugin->playbin, "flags", &playbin_flags, NULL);
322 		playbin_flags &= ~PLAYBIN2_FLAG_VIS;
323 		rb_debug ("disabling vis; new playbin2 flags %d", playbin_flags);
324 		g_object_set (plugin->playbin,
325 			      "flags", playbin_flags,
326 			      "vis-plugin", NULL,
327 			      NULL);
328 	} else {
329 		rb_debug ("removing visualizer bin from pipeline");
330 		rb_player_gst_tee_remove_tee (RB_PLAYER_GST_TEE (plugin->player),
331 					      plugin->visualizer);
332 	}
333 
334 	if (plugin->visualizer) {
335 		g_object_unref (plugin->visualizer);
336 		plugin->visualizer = NULL;
337 	}
338 }
339 
340 static void
341 settings_changed_cb (GSettings *settings, const char *key, RBVisualizerPlugin *plugin)
342 {
343 	update_visualizer (plugin);
344 }
345 
346 static void
347 playing_song_changed_cb (RBShellPlayer *player, RhythmDBEntry *entry, RBVisualizerPlugin *plugin)
348 {
349 	g_object_set (plugin->page, "visibility", (entry != NULL), NULL);
350 }
351 
352 static void
353 impl_activate (PeasActivatable *activatable)
354 {
355 	RBVisualizerPlugin *pi = RB_VISUALIZER_PLUGIN (activatable);
356 	RBDisplayPageGroup *page_group;
357 	RhythmDBEntry *entry;
358 	GtkToggleAction *fullscreen;
359 	GtkWidget *menu;
360 	RBShell *shell;
361 
362 	g_object_get (pi, "object", &shell, NULL);
363 
364 	pi->settings = g_settings_new ("org.gnome.rhythmbox.plugins.visualizer");
365 	g_signal_connect_object (pi->settings, "changed", G_CALLBACK (settings_changed_cb), pi, 0);
366 
367 	/* create UI actions and menus and stuff */
368 	fullscreen = gtk_toggle_action_new ("VisualizerFullscreen",
369 					    _("Fullscreen"),
370 					    _("Toggle fullscreen visual effects"),
371 					    GTK_STOCK_FULLSCREEN);
372 	menu = rb_visualizer_create_popup_menu (fullscreen);
373 	g_object_ref_sink (menu);
374 
375 	/* create visualizer page */
376 	pi->page = rb_visualizer_page_new (G_OBJECT (pi), shell, fullscreen, menu);
377 	g_signal_connect_object (pi->page, "start", G_CALLBACK (start_visualizer_cb), pi, 0);
378 	g_signal_connect_object (pi->page, "stop", G_CALLBACK (stop_visualizer_cb), pi, 0);
379 
380 	/* don't do anything if we couldn't create a video sink (clutter is broken, etc.) */
381 	g_object_get (pi->page, "sink", &pi->sink, NULL);
382 	if (pi->sink == NULL) {
383 		g_object_unref (shell);
384 		return;
385 	}
386 
387 	/* prepare style stuff for fullscreen display */
388 	rb_visualizer_fullscreen_load_style (G_OBJECT (pi));
389 
390 	/* add the visualizer page to the UI */
391 	page_group = rb_display_page_group_get_by_id ("display");
392 	if (page_group == NULL) {
393 		page_group = rb_display_page_group_new (G_OBJECT (shell),
394 							"display",
395 							_("Display"),
396 							RB_DISPLAY_PAGE_GROUP_CATEGORY_TOOLS);
397 		rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (page_group), NULL);
398 	}
399 	g_object_set (pi->page, "visibility", FALSE, NULL);
400 
401 	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (pi->page), RB_DISPLAY_PAGE (page_group));
402 
403 	/* get player objects */
404 	g_object_get (shell, "shell-player", &pi->shell_player, NULL);
405 	g_object_get (pi->shell_player, "player", &pi->player, NULL);
406 
407 	/* only show the page in the page tree when playing something */
408 	g_signal_connect_object (pi->shell_player, "playing-song-changed", G_CALLBACK (playing_song_changed_cb), pi, 0);
409 	entry = rb_shell_player_get_playing_entry (pi->shell_player);
410 	playing_song_changed_cb (pi->shell_player, entry, pi);
411 	if (entry != NULL) {
412 		rhythmdb_entry_unref (entry);
413 	}
414 
415 	/* figure out how to insert the visualizer into the playback pipeline */
416 	if (g_object_class_find_property (G_OBJECT_GET_CLASS (pi->player), "playbin")) {
417 
418 		rb_debug ("using playbin-based visualization");
419 		pi->playbin_notify_id = g_signal_connect_object (pi->player,
420 								 "notify::playbin",
421 								 G_CALLBACK (playbin_notify_cb),
422 								 pi,
423 								 0);
424 		g_object_get (pi->player, "playbin", &pi->playbin, NULL);
425 		if (pi->playbin != NULL) {
426 			mutate_playbin (pi, pi->playbin);
427 		}
428 	} else if (RB_IS_PLAYER_GST_TEE (pi->player)) {
429 		rb_debug ("using tee-based visualization");
430 	} else {
431 		g_warning ("unknown player backend type");
432 		g_object_unref (pi->player);
433 		pi->player = NULL;
434 	}
435 
436 	g_object_unref (shell);
437 }
438 
439 static void
440 impl_deactivate	(PeasActivatable *activatable)
441 {
442 	RBVisualizerPlugin *pi = RB_VISUALIZER_PLUGIN (activatable);
443 
444 	if (pi->page != NULL) {
445 		stop_visualizer_cb (pi->page, pi);
446 
447 		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (pi->page));
448 		pi->page = NULL;
449 	}
450 
451 	if (pi->sink != NULL) {
452 		g_object_unref (pi->sink);
453 		pi->sink = NULL;
454 	}
455 
456 	if (pi->settings != NULL) {
457 		g_object_unref (pi->settings);
458 		pi->settings = NULL;
459 	}
460 }
461 
462 static void
463 rb_visualizer_plugin_init (RBVisualizerPlugin *plugin)
464 {
465 	rb_debug ("RBVisualizerPlugin initialising");
466 
467 	/* for uninstalled builds, add plugins/visualizer/icons as an icon search path */
468 #ifdef USE_UNINSTALLED_DIRS
469 	gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
470 					   PLUGIN_SRC_DIR G_DIR_SEPARATOR_S "icons");
471 #endif
472 }
473 
474 G_MODULE_EXPORT void
475 peas_register_types (PeasObjectModule *module)
476 {
477 	rb_visualizer_plugin_register_type (G_TYPE_MODULE (module));
478 	_rb_visualizer_page_register_type (G_TYPE_MODULE (module));
479 	peas_object_module_register_extension_type (module,
480 						    PEAS_TYPE_ACTIVATABLE,
481 						    RB_TYPE_VISUALIZER_PLUGIN);
482 }