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", ¤t_vis_plugin,
155 "video-sink", ¤t_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 }