Merged pidgin/main into default
[pidgin-git.git] / libpurple / mediamanager.c
blob09a9e08d04f13de35e6a9892a1fdcfa0f6493782
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "internal.h"
24 #include "account.h"
25 #include "debug.h"
26 #include "media.h"
27 #include "mediamanager.h"
29 #ifdef USE_GSTREAMER
30 #include "media-gst.h"
31 #include <media/backend-fs2.h>
32 #endif /* USE_GSTREAMER */
34 #ifdef USE_VV
35 #include <farstream/fs-element-added-notifier.h>
36 #include <gst/video/videooverlay.h>
37 #ifdef HAVE_MEDIA_APPLICATION
38 #include <gst/app/app.h>
39 #endif
40 #endif /* USE_VV */
42 /** @copydoc _PurpleMediaOutputWindow */
43 typedef struct _PurpleMediaOutputWindow PurpleMediaOutputWindow;
44 /** @copydoc _PurpleMediaManagerPrivate */
45 typedef struct _PurpleMediaElementInfoPrivate PurpleMediaElementInfoPrivate;
47 struct _PurpleMediaOutputWindow
49 gulong id;
50 PurpleMedia *media;
51 gchar *session_id;
52 gchar *participant;
53 gulong window_id;
54 GstElement *sink;
57 struct _PurpleMediaManagerPrivate
59 GstElement *pipeline;
60 PurpleMediaCaps ui_caps;
61 GList *medias;
62 GList *private_medias;
63 GList *elements;
64 GList *output_windows;
65 gulong next_output_window_id;
66 GType backend_type;
67 GstCaps *video_caps;
69 PurpleMediaElementInfo *video_src;
70 PurpleMediaElementInfo *video_sink;
71 PurpleMediaElementInfo *audio_src;
72 PurpleMediaElementInfo *audio_sink;
74 #if GST_CHECK_VERSION(1, 4, 0)
75 GstDeviceMonitor *device_monitor;
76 #endif /* GST_CHECK_VERSION(1, 4, 0) */
78 #ifdef HAVE_MEDIA_APPLICATION
79 /* Application data streams */
80 GList *appdata_info; /* holds PurpleMediaAppDataInfo */
81 GMutex appdata_mutex;
82 guint appdata_cb_token; /* last used read/write callback token */
83 #endif
86 #ifdef HAVE_MEDIA_APPLICATION
87 typedef struct {
88 PurpleMedia *media;
89 GWeakRef media_ref;
90 gchar *session_id;
91 gchar *participant;
92 PurpleMediaAppDataCallbacks callbacks;
93 gpointer user_data;
94 GDestroyNotify notify;
95 GstAppSrc *appsrc;
96 GstAppSink *appsink;
97 gint num_samples;
98 GstSample *current_sample;
99 guint sample_offset;
100 gboolean writable;
101 gboolean connected;
102 guint writable_cb_token;
103 guint readable_cb_token;
104 guint writable_timer_id;
105 guint readable_timer_id;
106 GCond readable_cond;
107 } PurpleMediaAppDataInfo;
108 #endif
110 #define PURPLE_MEDIA_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerPrivate))
111 #define PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_ELEMENT_INFO, PurpleMediaElementInfoPrivate))
113 static void purple_media_manager_class_init (PurpleMediaManagerClass *klass);
114 static void purple_media_manager_init (PurpleMediaManager *media);
115 static void purple_media_manager_finalize (GObject *object);
116 #ifdef HAVE_MEDIA_APPLICATION
117 static void free_appdata_info_locked (PurpleMediaAppDataInfo *info);
118 #endif
119 static void purple_media_manager_init_device_monitor(PurpleMediaManager *manager);
120 static void purple_media_manager_register_static_elements(PurpleMediaManager *manager);
122 static GObjectClass *parent_class = NULL;
126 enum {
127 INIT_MEDIA,
128 INIT_PRIVATE_MEDIA,
129 UI_CAPS_CHANGED,
130 ELEMENTS_CHANGED,
131 LAST_SIGNAL
133 static guint purple_media_manager_signals[LAST_SIGNAL] = {0};
135 GType
136 purple_media_manager_get_type()
138 #ifdef USE_VV
139 static GType type = 0;
141 if (type == 0) {
142 static const GTypeInfo info = {
143 sizeof(PurpleMediaManagerClass),
144 NULL,
145 NULL,
146 (GClassInitFunc) purple_media_manager_class_init,
147 NULL,
148 NULL,
149 sizeof(PurpleMediaManager),
151 (GInstanceInitFunc) purple_media_manager_init,
152 NULL
154 type = g_type_register_static(G_TYPE_OBJECT, "PurpleMediaManager", &info, 0);
156 return type;
157 #else
158 return G_TYPE_NONE;
159 #endif
162 #ifdef USE_VV
163 static void
164 purple_media_manager_class_init (PurpleMediaManagerClass *klass)
166 GObjectClass *gobject_class = (GObjectClass*)klass;
167 parent_class = g_type_class_peek_parent(klass);
169 gobject_class->finalize = purple_media_manager_finalize;
171 purple_media_manager_signals[INIT_MEDIA] = g_signal_new ("init-media",
172 G_TYPE_FROM_CLASS (klass),
173 G_SIGNAL_RUN_LAST,
174 0, NULL, NULL, NULL,
175 G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA,
176 G_TYPE_POINTER, G_TYPE_STRING);
178 purple_media_manager_signals[INIT_PRIVATE_MEDIA] =
179 g_signal_new ("init-private-media",
180 G_TYPE_FROM_CLASS (klass),
181 G_SIGNAL_RUN_LAST,
182 0, NULL, NULL, NULL,
183 G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA,
184 G_TYPE_POINTER, G_TYPE_STRING);
186 purple_media_manager_signals[UI_CAPS_CHANGED] = g_signal_new ("ui-caps-changed",
187 G_TYPE_FROM_CLASS (klass),
188 G_SIGNAL_RUN_LAST,
189 0, NULL, NULL, NULL,
190 G_TYPE_NONE, 2, PURPLE_MEDIA_TYPE_CAPS,
191 PURPLE_MEDIA_TYPE_CAPS);
193 purple_media_manager_signals[ELEMENTS_CHANGED] =
194 g_signal_new("elements-changed",
195 G_TYPE_FROM_CLASS(klass),
196 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
197 0, NULL, NULL, NULL,
198 G_TYPE_NONE, 0);
200 g_type_class_add_private(klass, sizeof(PurpleMediaManagerPrivate));
203 static void
204 purple_media_manager_init (PurpleMediaManager *media)
206 GError *error;
208 media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
209 media->priv->medias = NULL;
210 media->priv->private_medias = NULL;
211 media->priv->next_output_window_id = 1;
212 media->priv->backend_type = PURPLE_TYPE_MEDIA_BACKEND_FS2;
213 #ifdef HAVE_MEDIA_APPLICATION
214 media->priv->appdata_info = NULL;
215 g_mutex_init (&media->priv->appdata_mutex);
216 #endif
217 if (gst_init_check(NULL, NULL, &error)) {
218 purple_media_manager_register_static_elements(media);
219 purple_media_manager_init_device_monitor(media);
220 } else {
221 purple_debug_error("mediamanager",
222 "GStreamer failed to initialize: %s.",
223 error ? error->message : "");
224 if (error) {
225 g_error_free(error);
229 purple_prefs_add_none("/purple/media");
230 purple_prefs_add_none("/purple/media/audio");
231 purple_prefs_add_int("/purple/media/audio/silence_threshold", 5);
232 purple_prefs_add_none("/purple/media/audio/volume");
233 purple_prefs_add_int("/purple/media/audio/volume/input", 10);
234 purple_prefs_add_int("/purple/media/audio/volume/output", 10);
237 static void
238 purple_media_manager_finalize (GObject *media)
240 PurpleMediaManagerPrivate *priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
241 for (; priv->medias; priv->medias =
242 g_list_delete_link(priv->medias, priv->medias)) {
243 g_object_unref(priv->medias->data);
245 for (; priv->private_medias; priv->private_medias =
246 g_list_delete_link(priv->private_medias, priv->private_medias)) {
247 g_object_unref(priv->private_medias->data);
249 for (; priv->elements; priv->elements =
250 g_list_delete_link(priv->elements, priv->elements)) {
251 g_object_unref(priv->elements->data);
253 if (priv->video_caps)
254 gst_caps_unref(priv->video_caps);
255 #ifdef HAVE_MEDIA_APPLICATION
256 if (priv->appdata_info)
257 g_list_free_full (priv->appdata_info,
258 (GDestroyNotify) free_appdata_info_locked);
259 g_mutex_clear (&priv->appdata_mutex);
260 #endif
261 #if GST_CHECK_VERSION(1, 4, 0)
262 if (priv->device_monitor) {
263 gst_device_monitor_stop(priv->device_monitor);
264 g_object_unref(priv->device_monitor);
266 #endif /* GST_CHECK_VERSION(1, 4, 0) */
268 parent_class->finalize(media);
270 #endif
272 PurpleMediaManager *
273 purple_media_manager_get()
275 #ifdef USE_VV
276 static PurpleMediaManager *manager = NULL;
278 if (manager == NULL)
279 manager = PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL));
280 return manager;
281 #else
282 return NULL;
283 #endif
286 #ifdef USE_VV
287 static gboolean
288 pipeline_bus_call(GstBus *bus, GstMessage *msg, PurpleMediaManager *manager)
290 switch(GST_MESSAGE_TYPE(msg)) {
291 case GST_MESSAGE_EOS:
292 purple_debug_info("mediamanager", "End of Stream\n");
293 break;
294 case GST_MESSAGE_ERROR: {
295 gchar *debug = NULL;
296 GError *err = NULL;
298 gst_message_parse_error(msg, &err, &debug);
300 purple_debug_error("mediamanager",
301 "gst pipeline error: %s\n",
302 err->message);
303 g_error_free(err);
305 if (debug) {
306 purple_debug_error("mediamanager",
307 "Debug details: %s\n", debug);
308 g_free (debug);
310 break;
312 default:
313 break;
315 return TRUE;
317 #endif
319 #ifdef USE_VV
320 GstElement *
321 purple_media_manager_get_pipeline(PurpleMediaManager *manager)
323 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
325 if (manager->priv->pipeline == NULL) {
326 FsElementAddedNotifier *notifier;
327 gchar *filename;
328 GError *err = NULL;
329 GKeyFile *keyfile;
330 GstBus *bus;
331 manager->priv->pipeline = gst_pipeline_new(NULL);
333 bus = gst_pipeline_get_bus(
334 GST_PIPELINE(manager->priv->pipeline));
335 gst_bus_add_signal_watch(GST_BUS(bus));
336 g_signal_connect(G_OBJECT(bus), "message",
337 G_CALLBACK(pipeline_bus_call), manager);
338 gst_bus_set_sync_handler(bus, gst_bus_sync_signal_handler, NULL, NULL);
339 gst_object_unref(bus);
341 filename = g_build_filename(purple_user_dir(),
342 "fs-element.conf", NULL);
343 keyfile = g_key_file_new();
344 if (!g_key_file_load_from_file(keyfile, filename,
345 G_KEY_FILE_NONE, &err)) {
346 if (err->code == 4)
347 purple_debug_info("mediamanager",
348 "Couldn't read "
349 "fs-element.conf: %s\n",
350 err->message);
351 else
352 purple_debug_error("mediamanager",
353 "Error reading "
354 "fs-element.conf: %s\n",
355 err->message);
356 g_error_free(err);
358 g_free(filename);
360 /* Hack to make alsasrc stop messing up audio timestamps */
361 if (!g_key_file_has_key(keyfile,
362 "alsasrc", "slave-method", NULL)) {
363 g_key_file_set_integer(keyfile,
364 "alsasrc", "slave-method", 2);
367 notifier = fs_element_added_notifier_new();
368 fs_element_added_notifier_add(notifier,
369 GST_BIN(manager->priv->pipeline));
370 fs_element_added_notifier_set_properties_from_keyfile(
371 notifier, keyfile);
373 gst_element_set_state(manager->priv->pipeline,
374 GST_STATE_PLAYING);
377 return manager->priv->pipeline;
379 #endif /* USE_VV */
381 static PurpleMedia *
382 create_media(PurpleMediaManager *manager,
383 PurpleAccount *account,
384 const char *conference_type,
385 const char *remote_user,
386 gboolean initiator,
387 gboolean private)
389 #ifdef USE_VV
390 PurpleMedia *media;
391 guint signal_id;
393 media = PURPLE_MEDIA(g_object_new(purple_media_get_type(),
394 "manager", manager,
395 "account", account,
396 "conference-type", conference_type,
397 "initiator", initiator,
398 NULL));
400 signal_id = private ?
401 purple_media_manager_signals[INIT_PRIVATE_MEDIA] :
402 purple_media_manager_signals[INIT_MEDIA];
404 if (g_signal_has_handler_pending(manager, signal_id, 0, FALSE)) {
405 gboolean signal_ret;
407 g_signal_emit(manager, signal_id, 0, media, account, remote_user,
408 &signal_ret);
409 if (signal_ret == FALSE) {
410 g_object_unref(media);
411 return NULL;
415 if (private)
416 manager->priv->private_medias = g_list_append(
417 manager->priv->private_medias, media);
418 else
419 manager->priv->medias = g_list_append(manager->priv->medias, media);
420 return media;
421 #else
422 return NULL;
423 #endif
426 static GList *
427 get_media(PurpleMediaManager *manager, gboolean private)
429 #ifdef USE_VV
430 if (private)
431 return manager->priv->private_medias;
432 else
433 return manager->priv->medias;
434 #else
435 return NULL;
436 #endif
439 static GList *
440 get_media_by_account(PurpleMediaManager *manager,
441 PurpleAccount *account, gboolean private)
443 #ifdef USE_VV
444 GList *media = NULL;
445 GList *iter;
447 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
449 if (private)
450 iter = manager->priv->private_medias;
451 else
452 iter = manager->priv->medias;
453 for (; iter; iter = g_list_next(iter)) {
454 if (purple_media_get_account(iter->data) == account) {
455 media = g_list_prepend(media, iter->data);
459 return media;
460 #else
461 return NULL;
462 #endif
465 void
466 purple_media_manager_remove_media(PurpleMediaManager *manager, PurpleMedia *media)
468 #ifdef USE_VV
469 GList *list;
470 GList **medias = NULL;
472 g_return_if_fail(manager != NULL);
474 if ((list = g_list_find(manager->priv->medias, media))) {
475 medias = &manager->priv->medias;
476 } else if ((list = g_list_find(manager->priv->private_medias, media))) {
477 medias = &manager->priv->private_medias;
480 if (list) {
481 *medias = g_list_delete_link(*medias, list);
483 #ifdef HAVE_MEDIA_APPLICATION
484 g_mutex_lock (&manager->priv->appdata_mutex);
485 list = manager->priv->appdata_info;
486 while (list) {
487 PurpleMediaAppDataInfo *info = list->data;
488 GList *next = list->next;
490 if (info->media == media) {
491 manager->priv->appdata_info = g_list_delete_link (
492 manager->priv->appdata_info, list);
493 free_appdata_info_locked (info);
496 list = next;
498 g_mutex_unlock (&manager->priv->appdata_mutex);
499 #endif
501 #endif
504 PurpleMedia *
505 purple_media_manager_create_media(PurpleMediaManager *manager,
506 PurpleAccount *account,
507 const char *conference_type,
508 const char *remote_user,
509 gboolean initiator)
511 return create_media (manager, account, conference_type,
512 remote_user, initiator, FALSE);
515 GList *
516 purple_media_manager_get_media(PurpleMediaManager *manager)
518 return get_media (manager, FALSE);
521 GList *
522 purple_media_manager_get_media_by_account(PurpleMediaManager *manager,
523 PurpleAccount *account)
525 return get_media_by_account (manager, account, FALSE);
528 PurpleMedia *
529 purple_media_manager_create_private_media(PurpleMediaManager *manager,
530 PurpleAccount *account,
531 const char *conference_type,
532 const char *remote_user,
533 gboolean initiator)
535 return create_media (manager, account, conference_type,
536 remote_user, initiator, TRUE);
539 GList *
540 purple_media_manager_get_private_media(PurpleMediaManager *manager)
542 return get_media (manager, TRUE);
545 GList *
546 purple_media_manager_get_private_media_by_account(PurpleMediaManager *manager,
547 PurpleAccount *account)
549 return get_media_by_account (manager, account, TRUE);
552 #ifdef HAVE_MEDIA_APPLICATION
553 static void
554 free_appdata_info_locked (PurpleMediaAppDataInfo *info)
556 GstAppSrcCallbacks null_src_cb = { NULL, NULL, NULL, { NULL } };
557 GstAppSinkCallbacks null_sink_cb = { NULL, NULL, NULL , { NULL } };
559 if (info->notify)
560 info->notify (info->user_data);
562 info->media = NULL;
563 if (info->appsrc) {
564 /* Will call appsrc_destroyed. */
565 gst_app_src_set_callbacks (info->appsrc, &null_src_cb,
566 NULL, NULL);
568 if (info->appsink) {
569 /* Will call appsink_destroyed. */
570 gst_app_sink_set_callbacks (info->appsink, &null_sink_cb,
571 NULL, NULL);
574 /* Make sure no other thread is using the structure */
575 g_free (info->session_id);
576 g_free (info->participant);
578 /* This lets the potential read or write callbacks waiting for appdata_mutex
579 * know the info structure has been destroyed. */
580 info->readable_cb_token = 0;
581 info->writable_cb_token = 0;
583 if (info->readable_timer_id) {
584 g_source_remove (info->readable_timer_id);
585 info->readable_timer_id = 0;
588 if (info->writable_timer_id) {
589 g_source_remove (info->writable_timer_id);
590 info->writable_timer_id = 0;
593 if (info->current_sample)
594 gst_sample_unref (info->current_sample);
595 info->current_sample = NULL;
597 /* Unblock any reading thread before destroying the GCond */
598 g_cond_broadcast (&info->readable_cond);
600 g_cond_clear (&info->readable_cond);
602 g_slice_free (PurpleMediaAppDataInfo, info);
606 * Get an app data info struct associated with a session and lock the mutex
607 * We don't want to return an info struct and unlock then it gets destroyed
608 * so we need to return it with the lock still taken
610 static PurpleMediaAppDataInfo *
611 get_app_data_info_and_lock (PurpleMediaManager *manager,
612 PurpleMedia *media, const gchar *session_id, const gchar *participant)
614 GList *i;
616 g_mutex_lock (&manager->priv->appdata_mutex);
617 for (i = manager->priv->appdata_info; i; i = i->next) {
618 PurpleMediaAppDataInfo *info = i->data;
620 if (info->media == media &&
621 purple_strequal (info->session_id, session_id) &&
622 (participant == NULL ||
623 purple_strequal (info->participant, participant))) {
624 return info;
628 return NULL;
632 * Get an app data info struct associated with a session and lock the mutex
633 * if it doesn't exist, we create it.
635 static PurpleMediaAppDataInfo *
636 ensure_app_data_info_and_lock (PurpleMediaManager *manager, PurpleMedia *media,
637 const gchar *session_id, const gchar *participant)
639 PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager, media,
640 session_id, participant);
642 if (info == NULL) {
643 info = g_slice_new0 (PurpleMediaAppDataInfo);
644 info->media = media;
645 g_weak_ref_init (&info->media_ref, media);
646 info->session_id = g_strdup (session_id);
647 info->participant = g_strdup (participant);
648 g_cond_init (&info->readable_cond);
649 manager->priv->appdata_info = g_list_prepend (
650 manager->priv->appdata_info, info);
653 return info;
655 #endif
658 #ifdef USE_VV
659 static void
660 request_pad_unlinked_cb(GstPad *pad, GstPad *peer, gpointer user_data)
662 GstElement *parent = GST_ELEMENT_PARENT(pad);
663 GstIterator *iter;
664 GValue tmp = G_VALUE_INIT;
665 GstPad *remaining_pad;
666 GstIteratorResult result;
668 gst_element_release_request_pad(parent, pad);
670 iter = gst_element_iterate_src_pads(parent);
672 result = gst_iterator_next(iter, &tmp);
674 if (result == GST_ITERATOR_DONE) {
675 gst_element_set_locked_state(parent, TRUE);
676 gst_element_set_state(parent, GST_STATE_NULL);
677 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(parent)), parent);
678 } else if (result == GST_ITERATOR_OK) {
679 remaining_pad = g_value_get_object(&tmp);
680 g_value_reset(&tmp);
681 gst_object_unref(remaining_pad);
684 gst_iterator_free(iter);
687 static void
688 nonunique_src_unlinked_cb(GstPad *pad, GstPad *peer, gpointer user_data)
690 GstElement *element = GST_ELEMENT_PARENT(pad);
691 gst_element_set_locked_state(element, TRUE);
692 gst_element_set_state(element, GST_STATE_NULL);
693 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
696 void
697 purple_media_manager_set_video_caps(PurpleMediaManager *manager, GstCaps *caps)
699 if (manager->priv->video_caps)
700 gst_caps_unref(manager->priv->video_caps);
702 manager->priv->video_caps = caps;
704 if (manager->priv->pipeline && manager->priv->video_src) {
705 gchar *id = purple_media_element_info_get_id(manager->priv->video_src);
706 GstElement *src = gst_bin_get_by_name(GST_BIN(manager->priv->pipeline), id);
708 if (src) {
709 GstElement *capsfilter = gst_bin_get_by_name(GST_BIN(src), "protocol_video_caps");
710 if (capsfilter) {
711 g_object_set(G_OBJECT(capsfilter), "caps", caps, NULL);
712 gst_object_unref (capsfilter);
714 gst_object_unref (src);
717 g_free(id);
721 GstCaps *
722 purple_media_manager_get_video_caps(PurpleMediaManager *manager)
724 if (manager->priv->video_caps == NULL)
725 manager->priv->video_caps = gst_caps_from_string("video/x-raw,"
726 "width=[250,352], height=[200,288], framerate=[1/1,20/1]");
727 return manager->priv->video_caps;
729 #endif /* USE_VV */
731 #ifdef HAVE_MEDIA_APPLICATION
733 * Calls the appdata writable callback from the main thread.
734 * This needs to grab the appdata lock and make sure it didn't get destroyed
735 * before calling the callback.
737 static gboolean
738 appsrc_writable (gpointer user_data)
740 PurpleMediaManager *manager = purple_media_manager_get ();
741 PurpleMediaAppDataInfo *info = user_data;
742 void (*writable_cb) (PurpleMediaManager *manager, PurpleMedia *media,
743 const gchar *session_id, const gchar *participant, gboolean writable,
744 gpointer user_data);
745 PurpleMedia *media;
746 gchar *session_id;
747 gchar *participant;
748 gboolean writable;
749 gpointer cb_data;
750 guint *cb_token_ptr = &info->writable_cb_token;
751 guint cb_token = *cb_token_ptr;
754 g_mutex_lock (&manager->priv->appdata_mutex);
755 if (cb_token == 0 || cb_token != *cb_token_ptr) {
756 /* In case info was freed while we were waiting for the mutex to unlock
757 * we still have a pointer to the cb_token which should still be
758 * accessible since it's in the Glib slice allocator. It gets set to 0
759 * just after the timeout is canceled which happens also before the
760 * AppDataInfo is freed, so even if that memory slice gets reused, the
761 * cb_token would be different from its previous value (unless
762 * extremely unlucky). So checking if the value for the cb_token changed
763 * should be enough to prevent any kind of race condition in which the
764 * media/AppDataInfo gets destroyed in one thread while the timeout was
765 * triggered and is waiting on the mutex to get unlocked in this thread
767 g_mutex_unlock (&manager->priv->appdata_mutex);
768 return FALSE;
770 writable_cb = info->callbacks.writable;
771 media = g_weak_ref_get (&info->media_ref);
772 session_id = g_strdup (info->session_id);
773 participant = g_strdup (info->participant);
774 writable = info->writable && info->connected;
775 cb_data = info->user_data;
777 info->writable_cb_token = 0;
778 g_mutex_unlock (&manager->priv->appdata_mutex);
781 if (writable_cb && media)
782 writable_cb (manager, media, session_id, participant, writable,
783 cb_data);
785 g_object_unref (media);
786 g_free (session_id);
787 g_free (participant);
789 return FALSE;
793 * Schedule a writable callback to be called from the main thread.
794 * We need to do this because need-data/enough-data signals from appsrc
795 * will come from the streaming thread and we need to create
796 * a source that we attach to the main context but we can't use
797 * g_main_context_invoke since we need to be able to cancel the source if the
798 * media gets destroyed.
799 * We use a timeout source instead of idle source, so the callback gets a higher
800 * priority
802 static void
803 call_appsrc_writable_locked (PurpleMediaAppDataInfo *info)
805 PurpleMediaManager *manager = purple_media_manager_get ();
807 /* We already have a writable callback scheduled, don't create another one */
808 if (info->writable_cb_token || info->callbacks.writable == NULL)
809 return;
811 /* We can't use writable_timer_id as a token, because the timeout is added
812 * into libpurple's main event loop, which runs in a different thread than
813 * from where call_appsrc_writable_locked() was called. Consequently, the
814 * callback may run even before g_timeout_add() returns the timer ID
815 * to us. */
816 info->writable_cb_token = ++manager->priv->appdata_cb_token;
817 info->writable_timer_id = g_timeout_add (0, appsrc_writable, info);
820 static void
821 appsrc_need_data (GstAppSrc *appsrc, guint length, gpointer user_data)
823 PurpleMediaAppDataInfo *info = user_data;
824 PurpleMediaManager *manager = purple_media_manager_get ();
826 g_mutex_lock (&manager->priv->appdata_mutex);
827 if (!info->writable) {
828 info->writable = TRUE;
829 /* Only signal writable if we also established a connection */
830 if (info->connected)
831 call_appsrc_writable_locked (info);
833 g_mutex_unlock (&manager->priv->appdata_mutex);
836 static void
837 appsrc_enough_data (GstAppSrc *appsrc, gpointer user_data)
839 PurpleMediaAppDataInfo *info = user_data;
840 PurpleMediaManager *manager = purple_media_manager_get ();
842 g_mutex_lock (&manager->priv->appdata_mutex);
843 if (info->writable) {
844 info->writable = FALSE;
845 call_appsrc_writable_locked (info);
847 g_mutex_unlock (&manager->priv->appdata_mutex);
850 static gboolean
851 appsrc_seek_data (GstAppSrc *appsrc, guint64 offset, gpointer user_data)
853 return FALSE;
856 static void
857 appsrc_destroyed (PurpleMediaAppDataInfo *info)
859 PurpleMediaManager *manager;
861 if (!info->media) {
862 /* PurpleMediaAppDataInfo is being freed. Return at once. */
863 return;
866 manager = purple_media_manager_get ();
868 g_mutex_lock (&manager->priv->appdata_mutex);
869 info->appsrc = NULL;
870 if (info->writable) {
871 info->writable = FALSE;
872 call_appsrc_writable_locked (info);
874 g_mutex_unlock (&manager->priv->appdata_mutex);
877 static void
878 media_established_cb (PurpleMedia *media,const gchar *session_id,
879 const gchar *participant, PurpleMediaCandidate *local_candidate,
880 PurpleMediaCandidate *remote_candidate, PurpleMediaAppDataInfo *info)
882 PurpleMediaManager *manager = purple_media_manager_get ();
884 g_mutex_lock (&manager->priv->appdata_mutex);
885 info->connected = TRUE;
886 /* We established the connection, if we were writable, then we need to
887 * signal it now */
888 if (info->writable)
889 call_appsrc_writable_locked (info);
890 g_mutex_unlock (&manager->priv->appdata_mutex);
893 static GstElement *
894 create_send_appsrc(PurpleMediaElementInfo *element_info, PurpleMedia *media,
895 const gchar *session_id, const gchar *participant)
897 PurpleMediaManager *manager = purple_media_manager_get ();
898 PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
899 media, session_id, participant);
900 GstElement *appsrc = (GstElement *)info->appsrc;
902 if (appsrc == NULL) {
903 GstAppSrcCallbacks callbacks = {appsrc_need_data, appsrc_enough_data,
904 appsrc_seek_data, {NULL}};
905 GstCaps *caps = gst_caps_new_empty_simple ("application/octet-stream");
907 appsrc = gst_element_factory_make("appsrc", NULL);
909 info->appsrc = (GstAppSrc *)appsrc;
911 gst_app_src_set_caps (info->appsrc, caps);
912 gst_app_src_set_callbacks (info->appsrc,
913 &callbacks, info, (GDestroyNotify) appsrc_destroyed);
914 g_signal_connect (media, "candidate-pair-established",
915 (GCallback) media_established_cb, info);
916 gst_caps_unref (caps);
919 g_mutex_unlock (&manager->priv->appdata_mutex);
920 return appsrc;
923 static void
924 appsink_eos (GstAppSink *appsink, gpointer user_data)
928 static GstFlowReturn
929 appsink_new_preroll (GstAppSink *appsink, gpointer user_data)
931 return GST_FLOW_OK;
934 static gboolean
935 appsink_readable (gpointer user_data)
937 PurpleMediaManager *manager = purple_media_manager_get ();
938 PurpleMediaAppDataInfo *info = user_data;
939 void (*readable_cb) (PurpleMediaManager *manager, PurpleMedia *media,
940 const gchar *session_id, const gchar *participant, gpointer user_data);
941 PurpleMedia *media;
942 gchar *session_id;
943 gchar *participant;
944 gpointer cb_data;
945 guint *cb_token_ptr = &info->readable_cb_token;
946 guint cb_token = *cb_token_ptr;
947 gboolean run_again = FALSE;
949 g_mutex_lock (&manager->priv->appdata_mutex);
950 if (cb_token == 0 || cb_token != *cb_token_ptr) {
951 /* Avoided a race condition (see writable callback) */
952 g_mutex_unlock (&manager->priv->appdata_mutex);
953 return FALSE;
956 if (info->callbacks.readable &&
957 (info->num_samples > 0 || info->current_sample != NULL)) {
958 readable_cb = info->callbacks.readable;
959 media = g_weak_ref_get (&info->media_ref);
960 session_id = g_strdup (info->session_id);
961 participant = g_strdup (info->participant);
962 cb_data = info->user_data;
963 g_mutex_unlock (&manager->priv->appdata_mutex);
965 if (readable_cb)
966 readable_cb (manager, media, session_id, participant, cb_data);
968 g_mutex_lock (&manager->priv->appdata_mutex);
969 g_object_unref (media);
970 g_free (session_id);
971 g_free (participant);
972 if (cb_token == 0 || cb_token != *cb_token_ptr) {
973 /* We got cancelled */
974 g_mutex_unlock (&manager->priv->appdata_mutex);
975 return FALSE;
979 /* Do we still have samples? Schedule appsink_readable again. We break here
980 * so that other events get a chance to be processed too. */
981 if (info->num_samples > 0 || info->current_sample != NULL) {
982 run_again = TRUE;
983 } else {
984 info->readable_cb_token = 0;
987 g_mutex_unlock (&manager->priv->appdata_mutex);
988 return run_again;
991 static void
992 call_appsink_readable_locked (PurpleMediaAppDataInfo *info)
994 PurpleMediaManager *manager = purple_media_manager_get ();
996 /* We must signal that a new sample has arrived to release blocking reads */
997 g_cond_broadcast (&info->readable_cond);
999 /* We already have a writable callback scheduled, don't create another one */
1000 if (info->readable_cb_token || info->callbacks.readable == NULL)
1001 return;
1003 info->readable_cb_token = ++manager->priv->appdata_cb_token;
1004 info->readable_timer_id = g_timeout_add (0, appsink_readable, info);
1007 static GstFlowReturn
1008 appsink_new_sample (GstAppSink *appsink, gpointer user_data)
1010 PurpleMediaManager *manager = purple_media_manager_get ();
1011 PurpleMediaAppDataInfo *info = user_data;
1013 g_mutex_lock (&manager->priv->appdata_mutex);
1014 info->num_samples++;
1015 call_appsink_readable_locked (info);
1016 g_mutex_unlock (&manager->priv->appdata_mutex);
1018 return GST_FLOW_OK;
1021 static void
1022 appsink_destroyed (PurpleMediaAppDataInfo *info)
1024 PurpleMediaManager *manager;
1026 if (!info->media) {
1027 /* PurpleMediaAppDataInfo is being freed. Return at once. */
1028 return;
1031 manager = purple_media_manager_get ();
1033 g_mutex_lock (&manager->priv->appdata_mutex);
1034 info->appsink = NULL;
1035 info->num_samples = 0;
1036 g_mutex_unlock (&manager->priv->appdata_mutex);
1039 static GstElement *
1040 create_recv_appsink(PurpleMediaElementInfo *element_info, PurpleMedia *media,
1041 const gchar *session_id, const gchar *participant)
1043 PurpleMediaManager *manager = purple_media_manager_get ();
1044 PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
1045 media, session_id, participant);
1046 GstElement *appsink = (GstElement *)info->appsink;
1048 if (appsink == NULL) {
1049 GstAppSinkCallbacks callbacks = {appsink_eos, appsink_new_preroll,
1050 appsink_new_sample, {NULL}};
1051 GstCaps *caps = gst_caps_new_empty_simple ("application/octet-stream");
1053 appsink = gst_element_factory_make("appsink", NULL);
1055 info->appsink = (GstAppSink *)appsink;
1057 gst_app_sink_set_caps (info->appsink, caps);
1058 gst_app_sink_set_callbacks (info->appsink,
1059 &callbacks, info, (GDestroyNotify) appsink_destroyed);
1060 gst_caps_unref (caps);
1064 g_mutex_unlock (&manager->priv->appdata_mutex);
1065 return appsink;
1067 #endif /* HAVE_MEDIA_APPLICATION */
1069 #ifdef USE_VV
1070 static PurpleMediaElementInfo *
1071 get_send_application_element_info ()
1073 static PurpleMediaElementInfo *info = NULL;
1075 #ifdef HAVE_MEDIA_APPLICATION
1076 if (info == NULL) {
1077 info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1078 "id", "pidginappsrc",
1079 "name", "Pidgin Application Source",
1080 "type", PURPLE_MEDIA_ELEMENT_APPLICATION
1081 | PURPLE_MEDIA_ELEMENT_SRC
1082 | PURPLE_MEDIA_ELEMENT_ONE_SRC,
1083 "create-cb", create_send_appsrc, NULL);
1085 #endif
1087 return info;
1090 static PurpleMediaElementInfo *
1091 get_recv_application_element_info ()
1093 static PurpleMediaElementInfo *info = NULL;
1095 #ifdef HAVE_MEDIA_APPLICATION
1096 if (info == NULL) {
1097 info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1098 "id", "pidginappsink",
1099 "name", "Pidgin Application Sink",
1100 "type", PURPLE_MEDIA_ELEMENT_APPLICATION
1101 | PURPLE_MEDIA_ELEMENT_SINK
1102 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
1103 "create-cb", create_recv_appsink, NULL);
1105 #endif
1107 return info;
1110 GstElement *
1111 purple_media_manager_get_element(PurpleMediaManager *manager,
1112 PurpleMediaSessionType type, PurpleMedia *media,
1113 const gchar *session_id, const gchar *participant)
1115 GstElement *ret = NULL;
1116 PurpleMediaElementInfo *info = NULL;
1117 PurpleMediaElementType element_type;
1119 if (type & PURPLE_MEDIA_SEND_AUDIO)
1120 info = manager->priv->audio_src;
1121 else if (type & PURPLE_MEDIA_RECV_AUDIO)
1122 info = manager->priv->audio_sink;
1123 else if (type & PURPLE_MEDIA_SEND_VIDEO)
1124 info = manager->priv->video_src;
1125 else if (type & PURPLE_MEDIA_RECV_VIDEO)
1126 info = manager->priv->video_sink;
1127 else if (type & PURPLE_MEDIA_SEND_APPLICATION)
1128 info = get_send_application_element_info ();
1129 else if (type & PURPLE_MEDIA_RECV_APPLICATION)
1130 info = get_recv_application_element_info ();
1132 if (info == NULL)
1133 return NULL;
1135 element_type = purple_media_element_info_get_element_type(info);
1137 if (element_type & PURPLE_MEDIA_ELEMENT_UNIQUE &&
1138 element_type & PURPLE_MEDIA_ELEMENT_SRC) {
1139 GstElement *tee;
1140 GstPad *pad;
1141 GstPad *ghost;
1142 gchar *id = purple_media_element_info_get_id(info);
1144 ret = gst_bin_get_by_name(GST_BIN(
1145 purple_media_manager_get_pipeline(
1146 manager)), id);
1148 if (ret == NULL) {
1149 GstElement *bin, *fakesink;
1150 ret = purple_media_element_info_call_create(info,
1151 media, session_id, participant);
1152 bin = gst_bin_new(id);
1153 tee = gst_element_factory_make("tee", "tee");
1154 gst_bin_add_many(GST_BIN(bin), ret, tee, NULL);
1156 if (type & PURPLE_MEDIA_SEND_VIDEO) {
1157 GstElement *videoscale;
1158 GstElement *capsfilter;
1160 videoscale = gst_element_factory_make("videoscale", NULL);
1161 capsfilter = gst_element_factory_make("capsfilter", "protocol_video_caps");
1163 g_object_set(G_OBJECT(capsfilter),
1164 "caps", purple_media_manager_get_video_caps(manager), NULL);
1166 gst_bin_add_many(GST_BIN(bin), videoscale, capsfilter, NULL);
1167 gst_element_link_many(ret, videoscale, capsfilter, tee, NULL);
1168 } else
1169 gst_element_link(ret, tee);
1172 * This shouldn't be necessary, but it stops it from
1173 * giving a not-linked error upon destruction
1175 fakesink = gst_element_factory_make("fakesink", NULL);
1176 g_object_set(fakesink,
1177 "async", FALSE,
1178 "sync", FALSE,
1179 "enable-last-sample", FALSE,
1180 NULL);
1181 gst_bin_add(GST_BIN(bin), fakesink);
1182 gst_element_link(tee, fakesink);
1184 ret = bin;
1185 gst_object_ref(ret);
1186 gst_bin_add(GST_BIN(purple_media_manager_get_pipeline(
1187 manager)), ret);
1189 g_free(id);
1191 tee = gst_bin_get_by_name(GST_BIN(ret), "tee");
1192 pad = gst_element_get_request_pad(tee, "src_%u");
1193 gst_object_unref(tee);
1194 ghost = gst_ghost_pad_new(NULL, pad);
1195 gst_object_unref(pad);
1196 g_signal_connect(GST_PAD(ghost), "unlinked",
1197 G_CALLBACK(request_pad_unlinked_cb), NULL);
1198 gst_pad_set_active(ghost, TRUE);
1199 gst_element_add_pad(ret, ghost);
1200 } else {
1201 ret = purple_media_element_info_call_create(info,
1202 media, session_id, participant);
1203 if (element_type & PURPLE_MEDIA_ELEMENT_SRC) {
1204 GstPad *pad = gst_element_get_static_pad(ret, "src");
1205 g_signal_connect(pad, "unlinked",
1206 G_CALLBACK(nonunique_src_unlinked_cb), NULL);
1207 gst_object_unref(pad);
1208 gst_object_ref(ret);
1209 gst_bin_add(GST_BIN(purple_media_manager_get_pipeline(manager)),
1210 ret);
1214 if (ret == NULL)
1215 purple_debug_error("media", "Error creating source or sink\n");
1217 return ret;
1220 PurpleMediaElementInfo *
1221 purple_media_manager_get_element_info(PurpleMediaManager *manager,
1222 const gchar *id)
1224 GList *iter;
1226 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
1227 g_return_val_if_fail(id != NULL, NULL);
1229 iter = manager->priv->elements;
1231 for (; iter; iter = g_list_next(iter)) {
1232 gchar *element_id =
1233 purple_media_element_info_get_id(iter->data);
1234 if (purple_strequal(element_id, id)) {
1235 g_free(element_id);
1236 g_object_ref(iter->data);
1237 return iter->data;
1239 g_free(element_id);
1242 return NULL;
1245 static GQuark
1246 element_info_to_detail(PurpleMediaElementInfo *info)
1248 PurpleMediaElementType type;
1250 type = purple_media_element_info_get_element_type(info);
1252 if (type & PURPLE_MEDIA_ELEMENT_AUDIO) {
1253 if (type & PURPLE_MEDIA_ELEMENT_SRC) {
1254 return g_quark_from_string("audiosrc");
1255 } else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
1256 return g_quark_from_string("audiosink");
1258 } else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) {
1259 if (type & PURPLE_MEDIA_ELEMENT_SRC) {
1260 return g_quark_from_string("videosrc");
1261 } else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
1262 return g_quark_from_string("videosink");
1266 return 0;
1269 gboolean
1270 purple_media_manager_register_element(PurpleMediaManager *manager,
1271 PurpleMediaElementInfo *info)
1273 PurpleMediaElementInfo *info2;
1274 gchar *id;
1275 GQuark detail;
1277 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1278 g_return_val_if_fail(info != NULL, FALSE);
1280 id = purple_media_element_info_get_id(info);
1281 info2 = purple_media_manager_get_element_info(manager, id);
1282 g_free(id);
1284 if (info2 != NULL) {
1285 g_object_unref(info2);
1286 return FALSE;
1289 manager->priv->elements =
1290 g_list_prepend(manager->priv->elements, info);
1292 detail = element_info_to_detail(info);
1293 if (detail != 0) {
1294 g_signal_emit(manager,
1295 purple_media_manager_signals[ELEMENTS_CHANGED],
1296 detail);
1299 return TRUE;
1302 gboolean
1303 purple_media_manager_unregister_element(PurpleMediaManager *manager,
1304 const gchar *id)
1306 PurpleMediaElementInfo *info;
1307 GQuark detail;
1309 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1311 info = purple_media_manager_get_element_info(manager, id);
1313 if (info == NULL) {
1314 g_object_unref(info);
1315 return FALSE;
1318 if (manager->priv->audio_src == info)
1319 manager->priv->audio_src = NULL;
1320 if (manager->priv->audio_sink == info)
1321 manager->priv->audio_sink = NULL;
1322 if (manager->priv->video_src == info)
1323 manager->priv->video_src = NULL;
1324 if (manager->priv->video_sink == info)
1325 manager->priv->video_sink = NULL;
1327 detail = element_info_to_detail(info);
1329 manager->priv->elements = g_list_remove(
1330 manager->priv->elements, info);
1331 g_object_unref(info);
1333 if (detail != 0) {
1334 g_signal_emit(manager,
1335 purple_media_manager_signals[ELEMENTS_CHANGED],
1336 detail);
1339 return TRUE;
1342 gboolean
1343 purple_media_manager_set_active_element(PurpleMediaManager *manager,
1344 PurpleMediaElementInfo *info)
1346 PurpleMediaElementInfo *info2;
1347 PurpleMediaElementType type;
1348 gboolean ret = FALSE;
1349 gchar *id;
1351 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1352 g_return_val_if_fail(info != NULL, FALSE);
1354 id = purple_media_element_info_get_id(info);
1355 info2 = purple_media_manager_get_element_info(manager, id);
1356 g_free(id);
1358 if (info2 == NULL)
1359 purple_media_manager_register_element(manager, info);
1360 else
1361 g_object_unref(info2);
1363 type = purple_media_element_info_get_element_type(info);
1365 if (type & PURPLE_MEDIA_ELEMENT_SRC) {
1366 if (type & PURPLE_MEDIA_ELEMENT_AUDIO) {
1367 manager->priv->audio_src = info;
1368 ret = TRUE;
1370 if (type & PURPLE_MEDIA_ELEMENT_VIDEO) {
1371 manager->priv->video_src = info;
1372 ret = TRUE;
1375 if (type & PURPLE_MEDIA_ELEMENT_SINK) {
1376 if (type & PURPLE_MEDIA_ELEMENT_AUDIO) {
1377 manager->priv->audio_sink = info;
1378 ret = TRUE;
1380 if (type & PURPLE_MEDIA_ELEMENT_VIDEO) {
1381 manager->priv->video_sink = info;
1382 ret = TRUE;
1386 return ret;
1389 PurpleMediaElementInfo *
1390 purple_media_manager_get_active_element(PurpleMediaManager *manager,
1391 PurpleMediaElementType type)
1393 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
1395 if (type & PURPLE_MEDIA_ELEMENT_SRC) {
1396 if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
1397 return manager->priv->audio_src;
1398 else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
1399 return manager->priv->video_src;
1400 else if (type & PURPLE_MEDIA_ELEMENT_APPLICATION)
1401 return get_send_application_element_info ();
1402 } else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
1403 if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
1404 return manager->priv->audio_sink;
1405 else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
1406 return manager->priv->video_sink;
1407 else if (type & PURPLE_MEDIA_ELEMENT_APPLICATION)
1408 return get_recv_application_element_info ();
1412 return NULL;
1415 static void
1416 window_id_cb(GstBus *bus, GstMessage *msg, PurpleMediaOutputWindow *ow)
1418 GstElement *sink;
1420 if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT
1421 || !gst_is_video_overlay_prepare_window_handle_message(msg))
1422 return;
1424 sink = GST_ELEMENT(GST_MESSAGE_SRC(msg));
1425 while (sink != ow->sink) {
1426 if (sink == NULL)
1427 return;
1428 sink = GST_ELEMENT_PARENT(sink);
1431 g_signal_handlers_disconnect_matched(bus, G_SIGNAL_MATCH_FUNC
1432 | G_SIGNAL_MATCH_DATA, 0, 0, NULL,
1433 window_id_cb, ow);
1435 gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(msg)),
1436 ow->window_id);
1438 #endif
1440 gboolean
1441 purple_media_manager_create_output_window(PurpleMediaManager *manager,
1442 PurpleMedia *media, const gchar *session_id,
1443 const gchar *participant)
1445 #ifdef USE_VV
1446 GList *iter;
1448 g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
1450 iter = manager->priv->output_windows;
1451 for(; iter; iter = g_list_next(iter)) {
1452 PurpleMediaOutputWindow *ow = iter->data;
1454 if (ow->sink == NULL && ow->media == media &&
1455 purple_strequal(participant, ow->participant) &&
1456 purple_strequal(session_id, ow->session_id)) {
1457 GstBus *bus;
1458 GstElement *queue, *convert, *scale;
1459 GstElement *tee = purple_media_get_tee(media,
1460 session_id, participant);
1462 if (tee == NULL)
1463 continue;
1465 queue = gst_element_factory_make("queue", NULL);
1466 convert = gst_element_factory_make("videoconvert", NULL);
1467 scale = gst_element_factory_make("videoscale", NULL);
1468 ow->sink = purple_media_manager_get_element(
1469 manager, PURPLE_MEDIA_RECV_VIDEO,
1470 ow->media, ow->session_id,
1471 ow->participant);
1473 if (participant == NULL) {
1474 /* aka this is a preview sink */
1475 GObjectClass *klass =
1476 G_OBJECT_GET_CLASS(ow->sink);
1477 if (g_object_class_find_property(klass,
1478 "sync"))
1479 g_object_set(G_OBJECT(ow->sink),
1480 "sync", FALSE, NULL);
1481 if (g_object_class_find_property(klass,
1482 "async"))
1483 g_object_set(G_OBJECT(ow->sink),
1484 "async", FALSE, NULL);
1487 gst_bin_add_many(GST_BIN(GST_ELEMENT_PARENT(tee)),
1488 queue, convert, scale, ow->sink, NULL);
1490 bus = gst_pipeline_get_bus(GST_PIPELINE(
1491 manager->priv->pipeline));
1492 g_signal_connect(bus, "sync-message::element",
1493 G_CALLBACK(window_id_cb), ow);
1494 gst_object_unref(bus);
1496 gst_element_set_state(ow->sink, GST_STATE_PLAYING);
1497 gst_element_set_state(scale, GST_STATE_PLAYING);
1498 gst_element_set_state(convert, GST_STATE_PLAYING);
1499 gst_element_set_state(queue, GST_STATE_PLAYING);
1500 gst_element_link(scale, ow->sink);
1501 gst_element_link(convert, scale);
1502 gst_element_link(queue, convert);
1503 gst_element_link(tee, queue);
1506 return TRUE;
1507 #else
1508 return FALSE;
1509 #endif
1512 gulong
1513 purple_media_manager_set_output_window(PurpleMediaManager *manager,
1514 PurpleMedia *media, const gchar *session_id,
1515 const gchar *participant, gulong window_id)
1517 #ifdef USE_VV
1518 PurpleMediaOutputWindow *output_window;
1520 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1521 g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
1523 output_window = g_new0(PurpleMediaOutputWindow, 1);
1524 output_window->id = manager->priv->next_output_window_id++;
1525 output_window->media = media;
1526 output_window->session_id = g_strdup(session_id);
1527 output_window->participant = g_strdup(participant);
1528 output_window->window_id = window_id;
1530 manager->priv->output_windows = g_list_prepend(
1531 manager->priv->output_windows, output_window);
1533 if (purple_media_get_tee(media, session_id, participant) != NULL)
1534 purple_media_manager_create_output_window(manager,
1535 media, session_id, participant);
1537 return output_window->id;
1538 #else
1539 return 0;
1540 #endif
1543 gboolean
1544 purple_media_manager_remove_output_window(PurpleMediaManager *manager,
1545 gulong output_window_id)
1547 #ifdef USE_VV
1548 PurpleMediaOutputWindow *output_window = NULL;
1549 GList *iter;
1551 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1553 iter = manager->priv->output_windows;
1554 for (; iter; iter = g_list_next(iter)) {
1555 PurpleMediaOutputWindow *ow = iter->data;
1556 if (ow->id == output_window_id) {
1557 manager->priv->output_windows = g_list_delete_link(
1558 manager->priv->output_windows, iter);
1559 output_window = ow;
1560 break;
1564 if (output_window == NULL)
1565 return FALSE;
1567 if (output_window->sink != NULL) {
1568 GstElement *element = output_window->sink;
1569 GstPad *teepad = NULL;
1570 GSList *to_remove = NULL;
1572 /* Find the tee element this output is connected to. */
1573 while (!teepad) {
1574 GstPad *pad;
1575 GstPad *peer;
1576 GstElementFactory *factory;
1577 const gchar *factory_name;
1579 to_remove = g_slist_append(to_remove, element);
1581 pad = gst_element_get_static_pad(element, "sink");
1582 peer = gst_pad_get_peer(pad);
1583 if (!peer) {
1584 /* Output is disconnected from the pipeline. */
1585 gst_object_unref(pad);
1586 break;
1589 factory = gst_element_get_factory(GST_PAD_PARENT(peer));
1590 factory_name = gst_plugin_feature_get_name(factory);
1591 if (purple_strequal(factory_name, "tee")) {
1592 teepad = peer;
1595 element = GST_PAD_PARENT(peer);
1597 gst_object_unref(pad);
1598 gst_object_unref(peer);
1601 if (teepad) {
1602 gst_element_release_request_pad(GST_PAD_PARENT(teepad),
1603 teepad);
1606 while (to_remove) {
1607 GstElement *element = to_remove->data;
1609 gst_element_set_locked_state(element, TRUE);
1610 gst_element_set_state(element, GST_STATE_NULL);
1611 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)),
1612 element);
1613 to_remove = g_slist_delete_link(to_remove, to_remove);
1617 g_free(output_window->session_id);
1618 g_free(output_window->participant);
1619 g_free(output_window);
1621 return TRUE;
1622 #else
1623 return FALSE;
1624 #endif
1627 void
1628 purple_media_manager_remove_output_windows(PurpleMediaManager *manager,
1629 PurpleMedia *media, const gchar *session_id,
1630 const gchar *participant)
1632 #ifdef USE_VV
1633 GList *iter;
1635 g_return_if_fail(PURPLE_IS_MEDIA(media));
1637 iter = manager->priv->output_windows;
1639 for (; iter;) {
1640 PurpleMediaOutputWindow *ow = iter->data;
1641 iter = g_list_next(iter);
1643 if (media == ow->media &&
1644 purple_strequal(session_id, ow->session_id) &&
1645 purple_strequal(participant, ow->participant))
1646 purple_media_manager_remove_output_window(
1647 manager, ow->id);
1649 #endif
1652 void
1653 purple_media_manager_set_ui_caps(PurpleMediaManager *manager,
1654 PurpleMediaCaps caps)
1656 #ifdef USE_VV
1657 PurpleMediaCaps oldcaps;
1659 g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager));
1661 oldcaps = manager->priv->ui_caps;
1662 manager->priv->ui_caps = caps;
1664 if (caps != oldcaps)
1665 g_signal_emit(manager,
1666 purple_media_manager_signals[UI_CAPS_CHANGED],
1667 0, caps, oldcaps);
1668 #endif
1671 PurpleMediaCaps
1672 purple_media_manager_get_ui_caps(PurpleMediaManager *manager)
1674 #ifdef USE_VV
1675 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager),
1676 PURPLE_MEDIA_CAPS_NONE);
1677 return manager->priv->ui_caps;
1678 #else
1679 return PURPLE_MEDIA_CAPS_NONE;
1680 #endif
1683 void
1684 purple_media_manager_set_backend_type(PurpleMediaManager *manager,
1685 GType backend_type)
1687 #ifdef USE_VV
1688 g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager));
1690 manager->priv->backend_type = backend_type;
1691 #endif
1694 GType
1695 purple_media_manager_get_backend_type(PurpleMediaManager *manager)
1697 #ifdef USE_VV
1698 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager),
1699 PURPLE_MEDIA_CAPS_NONE);
1701 return manager->priv->backend_type;
1702 #else
1703 return G_TYPE_NONE;
1704 #endif
1707 void
1708 purple_media_manager_set_application_data_callbacks(PurpleMediaManager *manager,
1709 PurpleMedia *media, const gchar *session_id,
1710 const gchar *participant, PurpleMediaAppDataCallbacks *callbacks,
1711 gpointer user_data, GDestroyNotify notify)
1713 #ifdef HAVE_MEDIA_APPLICATION
1714 PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
1715 media, session_id, participant);
1717 if (info->notify)
1718 info->notify (info->user_data);
1720 if (info->readable_cb_token) {
1721 g_source_remove (info->readable_timer_id);
1722 info->readable_cb_token = 0;
1725 if (info->writable_cb_token) {
1726 g_source_remove (info->writable_timer_id);
1727 info->writable_cb_token = 0;
1730 if (callbacks) {
1731 info->callbacks = *callbacks;
1732 } else {
1733 info->callbacks.writable = NULL;
1734 info->callbacks.readable = NULL;
1736 info->user_data = user_data;
1737 info->notify = notify;
1739 call_appsrc_writable_locked (info);
1740 if (info->num_samples > 0 || info->current_sample != NULL)
1741 call_appsink_readable_locked (info);
1743 g_mutex_unlock (&manager->priv->appdata_mutex);
1744 #endif
1747 gint
1748 purple_media_manager_send_application_data (
1749 PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
1750 const gchar *participant, gpointer buffer, guint size, gboolean blocking)
1752 #ifdef HAVE_MEDIA_APPLICATION
1753 PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager,
1754 media, session_id, participant);
1756 if (info && info->appsrc && info->connected) {
1757 GstBuffer *gstbuffer = gst_buffer_new_wrapped (g_memdup (buffer, size),
1758 size);
1759 GstAppSrc *appsrc = gst_object_ref (info->appsrc);
1761 g_mutex_unlock (&manager->priv->appdata_mutex);
1762 if (gst_app_src_push_buffer (appsrc, gstbuffer) == GST_FLOW_OK) {
1763 if (blocking) {
1764 GstPad *srcpad;
1766 srcpad = gst_element_get_static_pad (GST_ELEMENT (appsrc),
1767 "src");
1768 if (srcpad) {
1769 gst_pad_peer_query (srcpad, gst_query_new_drain ());
1770 gst_object_unref (srcpad);
1773 gst_object_unref (appsrc);
1774 return size;
1775 } else {
1776 gst_object_unref (appsrc);
1777 return -1;
1780 g_mutex_unlock (&manager->priv->appdata_mutex);
1781 return -1;
1782 #else
1783 return -1;
1784 #endif
1787 gint
1788 purple_media_manager_receive_application_data (
1789 PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
1790 const gchar *participant, gpointer buffer, guint max_size,
1791 gboolean blocking)
1793 #ifdef HAVE_MEDIA_APPLICATION
1794 PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager,
1795 media, session_id, participant);
1796 guint bytes_read = 0;
1798 if (info) {
1799 /* If we are in a blocking read, we need to loop until max_size data
1800 * is read into the buffer, if we're not, then we need to read as much
1801 * data as possible
1803 do {
1804 if (!info->current_sample && info->appsink && info->num_samples > 0) {
1805 info->current_sample = gst_app_sink_pull_sample (info->appsink);
1806 info->sample_offset = 0;
1807 if (info->current_sample)
1808 info->num_samples--;
1811 if (info->current_sample) {
1812 GstBuffer *gstbuffer = gst_sample_get_buffer (
1813 info->current_sample);
1815 if (gstbuffer) {
1816 GstMapInfo mapinfo;
1817 guint bytes_to_copy;
1819 gst_buffer_map (gstbuffer, &mapinfo, GST_MAP_READ);
1820 /* We must copy only the data remaining in the buffer without
1821 * overflowing the buffer */
1822 bytes_to_copy = max_size - bytes_read;
1823 if (bytes_to_copy > mapinfo.size - info->sample_offset)
1824 bytes_to_copy = mapinfo.size - info->sample_offset;
1825 memcpy ((guint8 *)buffer + bytes_read,
1826 mapinfo.data + info->sample_offset, bytes_to_copy);
1828 gst_buffer_unmap (gstbuffer, &mapinfo);
1829 info->sample_offset += bytes_to_copy;
1830 bytes_read += bytes_to_copy;
1831 if (info->sample_offset == mapinfo.size) {
1832 gst_sample_unref (info->current_sample);
1833 info->current_sample = NULL;
1834 info->sample_offset = 0;
1836 } else {
1837 /* In case there's no buffer in the sample (should never
1838 * happen), we need to at least unref it */
1839 gst_sample_unref (info->current_sample);
1840 info->current_sample = NULL;
1841 info->sample_offset = 0;
1845 /* If blocking, wait until there's an available sample */
1846 while (bytes_read < max_size && blocking &&
1847 info->current_sample == NULL && info->num_samples == 0) {
1848 g_cond_wait (&info->readable_cond, &manager->priv->appdata_mutex);
1850 /* We've been signaled, we need to unlock and regrab the info
1851 * struct to make sure nothing changed */
1852 g_mutex_unlock (&manager->priv->appdata_mutex);
1853 info = get_app_data_info_and_lock (manager,
1854 media, session_id, participant);
1855 if (info == NULL || info->appsink == NULL) {
1856 /* The session was destroyed while we were waiting, we
1857 * should return here */
1858 g_mutex_unlock (&manager->priv->appdata_mutex);
1859 return bytes_read;
1862 } while (bytes_read < max_size &&
1863 (blocking || info->num_samples > 0));
1865 g_mutex_unlock (&manager->priv->appdata_mutex);
1866 return bytes_read;
1868 g_mutex_unlock (&manager->priv->appdata_mutex);
1869 return -1;
1870 #else
1871 return -1;
1872 #endif
1875 #ifdef USE_VV
1877 static void
1878 videosink_disable_last_sample(GstElement *sink)
1880 GObjectClass *klass = G_OBJECT_GET_CLASS(sink);
1882 if (g_object_class_find_property(klass, "enable-last-sample")) {
1883 g_object_set(sink, "enable-last-sample", FALSE, NULL);
1887 #if GST_CHECK_VERSION(1, 4, 0)
1889 static PurpleMediaElementType
1890 gst_class_to_purple_element_type(const gchar *device_class)
1892 if (purple_strequal(device_class, "Audio/Source")) {
1893 return PURPLE_MEDIA_ELEMENT_AUDIO
1894 | PURPLE_MEDIA_ELEMENT_SRC
1895 | PURPLE_MEDIA_ELEMENT_ONE_SRC
1896 | PURPLE_MEDIA_ELEMENT_UNIQUE;
1897 } else if (purple_strequal(device_class, "Audio/Sink")) {
1898 return PURPLE_MEDIA_ELEMENT_AUDIO
1899 | PURPLE_MEDIA_ELEMENT_SINK
1900 | PURPLE_MEDIA_ELEMENT_ONE_SINK;
1901 } else if (purple_strequal(device_class, "Video/Source")) {
1902 return PURPLE_MEDIA_ELEMENT_VIDEO
1903 | PURPLE_MEDIA_ELEMENT_SRC
1904 | PURPLE_MEDIA_ELEMENT_ONE_SRC
1905 | PURPLE_MEDIA_ELEMENT_UNIQUE;
1906 } else if (purple_strequal(device_class, "Video/Sink")) {
1907 return PURPLE_MEDIA_ELEMENT_VIDEO
1908 | PURPLE_MEDIA_ELEMENT_SINK
1909 | PURPLE_MEDIA_ELEMENT_ONE_SINK;
1912 return PURPLE_MEDIA_ELEMENT_NONE;
1915 static GstElement *
1916 gst_device_create_cb(PurpleMediaElementInfo *info, PurpleMedia *media,
1917 const gchar *session_id, const gchar *participant)
1919 GstDevice *device;
1920 GstElement *result;
1921 PurpleMediaElementType type;
1923 device = g_object_get_data(G_OBJECT(info), "gst-device");
1924 if (!device) {
1925 return NULL;
1928 result = gst_device_create_element(device, NULL);
1929 if (!result) {
1930 return NULL;
1933 type = purple_media_element_info_get_element_type(info);
1935 if ((type & PURPLE_MEDIA_ELEMENT_VIDEO) &&
1936 (type & PURPLE_MEDIA_ELEMENT_SINK)) {
1937 videosink_disable_last_sample(result);
1940 return result;
1943 static gboolean
1944 device_is_ignored(GstDevice *device)
1946 gboolean result = FALSE;
1948 #if GST_CHECK_VERSION(1, 6, 0)
1949 gchar *device_class;
1951 g_return_val_if_fail(device, TRUE);
1953 device_class = gst_device_get_device_class(device);
1955 /* Ignore PulseAudio monitor audio sources since they have little use
1956 * in the context of telephony.*/
1957 if (purple_strequal(device_class, "Audio/Source")) {
1958 GstStructure *properties;
1959 const gchar *pa_class;
1961 properties = gst_device_get_properties(device);
1963 pa_class = gst_structure_get_string(properties, "device.class");
1964 if (purple_strequal(pa_class, "monitor")) {
1965 result = TRUE;
1968 gst_structure_free(properties);
1971 g_free(device_class);
1972 #endif /* GST_CHECK_VERSION(1, 6, 0) */
1974 return result;
1977 static void
1978 purple_media_manager_register_gst_device(PurpleMediaManager *manager,
1979 GstDevice *device)
1981 PurpleMediaElementInfo *info;
1982 PurpleMediaElementType type;
1983 gchar *name;
1984 gchar *device_class;
1985 gchar *id;
1987 if (device_is_ignored(device)) {
1988 return;
1991 name = gst_device_get_display_name(device);
1992 device_class = gst_device_get_device_class(device);
1994 id = g_strdup_printf("%s %s", device_class, name);
1996 type = gst_class_to_purple_element_type(device_class);
1998 info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1999 "id", id,
2000 "name", name,
2001 "type", type,
2002 "create-cb", gst_device_create_cb,
2003 NULL);
2005 g_object_set_data(G_OBJECT(info), "gst-device", device);
2007 purple_media_manager_register_element(manager, info);
2009 purple_debug_info("mediamanager", "Registered %s device %s",
2010 device_class, name);
2012 g_free(name);
2013 g_free(device_class);
2014 g_free(id);
2017 static void
2018 purple_media_manager_unregister_gst_device(PurpleMediaManager *manager,
2019 GstDevice *device)
2021 GList *i;
2022 gchar *name;
2023 gchar *device_class;
2024 gboolean done = FALSE;
2026 name = gst_device_get_display_name(device);
2027 device_class = gst_device_get_device_class(device);
2029 for (i = manager->priv->elements; i && !done; i = i->next) {
2030 PurpleMediaElementInfo *info = i->data;
2031 GstDevice *device2;
2033 device2 = g_object_get_data(G_OBJECT(info), "gst-device");
2034 if (device2) {
2035 gchar *name2;
2036 gchar *device_class2;
2038 name2 = gst_device_get_display_name(device2);
2039 device_class2 = gst_device_get_device_class(device2);
2041 if (purple_strequal(name, name2) &&
2042 purple_strequal(device_class, device_class2)) {
2043 gchar *id;
2045 id = purple_media_element_info_get_id(info);
2046 purple_media_manager_unregister_element(manager,
2047 id);
2049 purple_debug_info("mediamanager",
2050 "Unregistered %s device %s",
2051 device_class, name);
2053 g_free(id);
2055 done = TRUE;
2058 g_free(name2);
2059 g_free(device_class2);
2063 g_free(name);
2064 g_free(device_class);
2067 static gboolean
2068 device_monitor_bus_cb(GstBus *bus, GstMessage *message, gpointer user_data)
2070 PurpleMediaManager *manager = user_data;
2071 GstMessageType message_type;
2072 GstDevice *device;
2074 message_type = GST_MESSAGE_TYPE(message);
2076 if (message_type == GST_MESSAGE_DEVICE_ADDED) {
2077 gst_message_parse_device_added(message, &device);
2078 purple_media_manager_register_gst_device(manager, device);
2079 } else if (message_type == GST_MESSAGE_DEVICE_REMOVED) {
2080 gst_message_parse_device_removed (message, &device);
2081 purple_media_manager_unregister_gst_device(manager, device);
2084 return G_SOURCE_CONTINUE;
2087 #endif /* GST_CHECK_VERSION(1, 4, 0) */
2089 static void
2090 purple_media_manager_init_device_monitor(PurpleMediaManager *manager)
2092 #if GST_CHECK_VERSION(1, 4, 0)
2093 GstBus *bus;
2094 GList *i;
2096 manager->priv->device_monitor = gst_device_monitor_new();
2098 bus = gst_device_monitor_get_bus(manager->priv->device_monitor);
2099 gst_bus_add_watch (bus, device_monitor_bus_cb, manager);
2100 gst_object_unref (bus);
2102 /* This avoids warning in GStreamer logs about no filters set */
2103 gst_device_monitor_add_filter(manager->priv->device_monitor, NULL, NULL);
2105 gst_device_monitor_start(manager->priv->device_monitor);
2107 i = gst_device_monitor_get_devices(manager->priv->device_monitor);
2108 for (; i; i = g_list_delete_link(i, i)) {
2109 GstDevice *device = i->data;
2111 purple_media_manager_register_gst_device(manager, device);
2112 gst_object_unref(device);
2114 #endif /* GST_CHECK_VERSION(1, 4, 0) */
2117 GList *
2118 purple_media_manager_enumerate_elements(PurpleMediaManager *manager,
2119 PurpleMediaElementType type)
2121 GList *result = NULL;
2122 GList *i;
2124 for (i = manager->priv->elements; i; i = i->next) {
2125 PurpleMediaElementInfo *info = i->data;
2126 PurpleMediaElementType type2;
2128 type2 = purple_media_element_info_get_element_type(info);
2130 if ((type2 & type) == type) {
2131 g_object_ref(info);
2132 result = g_list_prepend(result, info);
2136 return result;
2139 static GstElement *
2140 gst_factory_make_cb(PurpleMediaElementInfo *info, PurpleMedia *media,
2141 const gchar *session_id, const gchar *participant)
2143 gchar *id;
2144 GstElement *element;
2146 id = purple_media_element_info_get_id(info);
2148 element = gst_element_factory_make(id, NULL);
2150 g_free(id);
2152 return element;
2155 static void
2156 autovideosink_child_added_cb (GstChildProxy *child_proxy, GObject *object,
2157 gchar *name, gpointer user_data)
2159 videosink_disable_last_sample(GST_ELEMENT(object));
2162 static GstElement *
2163 default_video_sink_create_cb(PurpleMediaElementInfo *info, PurpleMedia *media,
2164 const gchar *session_id, const gchar *participant)
2166 GstElement *videosink = gst_element_factory_make("autovideosink", NULL);
2168 g_signal_connect(videosink, "child-added",
2169 G_CALLBACK(autovideosink_child_added_cb), NULL);
2171 return videosink;
2174 static GstElement *
2175 disabled_video_create_cb(PurpleMediaElementInfo *info, PurpleMedia *media,
2176 const gchar *session_id, const gchar *participant)
2178 GstElement *src = gst_element_factory_make("videotestsrc", NULL);
2180 /* GST_VIDEO_TEST_SRC_BLACK */
2181 g_object_set(src, "pattern", 2, NULL);
2183 return src;
2186 static GstElement *
2187 test_video_create_cb(PurpleMediaElementInfo *info, PurpleMedia *media,
2188 const gchar *session_id, const gchar *participant)
2190 GstElement *src = gst_element_factory_make("videotestsrc", NULL);
2192 g_object_set(src, "is-live", TRUE, NULL);
2194 return src;
2197 static void
2198 purple_media_manager_register_static_elements(PurpleMediaManager *manager)
2200 static const gchar *VIDEO_SINK_PLUGINS[] = {
2201 /* "aasink", "AALib", Didn't work for me */
2202 "directdrawsink", "DirectDraw",
2203 "glimagesink", "OpenGL",
2204 "ximagesink", "X Window System",
2205 "xvimagesink", "X Window System (Xv)",
2206 NULL
2208 const gchar **sinks = VIDEO_SINK_PLUGINS;
2210 /* Default auto* elements. */
2212 purple_media_manager_register_element(manager,
2213 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2214 "id", "autoaudiosrc",
2215 "name", N_("Default"),
2216 "type", PURPLE_MEDIA_ELEMENT_AUDIO
2217 | PURPLE_MEDIA_ELEMENT_SRC
2218 | PURPLE_MEDIA_ELEMENT_ONE_SRC
2219 | PURPLE_MEDIA_ELEMENT_UNIQUE,
2220 "create-cb", gst_factory_make_cb,
2221 NULL));
2223 purple_media_manager_register_element(manager,
2224 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2225 "id", "autoaudiosink",
2226 "name", N_("Default"),
2227 "type", PURPLE_MEDIA_ELEMENT_AUDIO
2228 | PURPLE_MEDIA_ELEMENT_SINK
2229 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
2230 "create-cb", gst_factory_make_cb,
2231 NULL));
2233 purple_media_manager_register_element(manager,
2234 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2235 "id", "autovideosrc",
2236 "name", N_("Default"),
2237 "type", PURPLE_MEDIA_ELEMENT_VIDEO
2238 | PURPLE_MEDIA_ELEMENT_SRC
2239 | PURPLE_MEDIA_ELEMENT_ONE_SRC
2240 | PURPLE_MEDIA_ELEMENT_UNIQUE,
2241 "create-cb", gst_factory_make_cb,
2242 NULL));
2244 purple_media_manager_register_element(manager,
2245 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2246 "id", "autovideosink",
2247 "name", N_("Default"),
2248 "type", PURPLE_MEDIA_ELEMENT_VIDEO
2249 | PURPLE_MEDIA_ELEMENT_SINK
2250 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
2251 "create-cb", default_video_sink_create_cb,
2252 NULL));
2254 /* Special elements */
2256 purple_media_manager_register_element(manager,
2257 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2258 "id", "audiotestsrc",
2259 /* Translators: This is a noun that refers to one
2260 * possible audio input device. The device can help the
2261 * user to check if her speakers or headphones have been
2262 * set up correctly for voice calling. */
2263 "name", N_("Test Sound"),
2264 "type", PURPLE_MEDIA_ELEMENT_AUDIO
2265 | PURPLE_MEDIA_ELEMENT_SRC
2266 | PURPLE_MEDIA_ELEMENT_ONE_SRC,
2267 "create-cb", gst_factory_make_cb,
2268 NULL));
2270 purple_media_manager_register_element(manager,
2271 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2272 "id", "disabledvideosrc",
2273 "name", N_("Disabled"),
2274 "type", PURPLE_MEDIA_ELEMENT_VIDEO
2275 | PURPLE_MEDIA_ELEMENT_SRC
2276 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
2277 "create-cb", disabled_video_create_cb,
2278 NULL));
2280 purple_media_manager_register_element(manager,
2281 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2282 "id", "videotestsrc",
2283 /* Translators: This is a noun that refers to one
2284 * possible video input device. The device produces
2285 * a test "monoscope" image that can help the user check
2286 * the video output has been set up correctly without
2287 * needing a webcam connected to the computer. */
2288 "name", N_("Test Pattern"),
2289 "type", PURPLE_MEDIA_ELEMENT_VIDEO
2290 | PURPLE_MEDIA_ELEMENT_SRC
2291 | PURPLE_MEDIA_ELEMENT_ONE_SRC,
2292 "create-cb", test_video_create_cb,
2293 NULL));
2295 for (sinks = VIDEO_SINK_PLUGINS; sinks[0]; sinks += 2) {
2296 GstElementFactory *factory;
2298 factory = gst_element_factory_find(sinks[0]);
2299 if (!factory) {
2300 continue;
2303 purple_media_manager_register_element(manager,
2304 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2305 "id", sinks[0],
2306 "name", sinks[1],
2307 "type", PURPLE_MEDIA_ELEMENT_VIDEO
2308 | PURPLE_MEDIA_ELEMENT_SINK
2309 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
2310 "create-cb", gst_factory_make_cb,
2311 NULL));
2313 gst_object_unref(factory);
2318 * PurpleMediaElementType
2321 GType
2322 purple_media_element_type_get_type()
2324 static GType type = 0;
2325 if (type == 0) {
2326 static const GFlagsValue values[] = {
2327 { PURPLE_MEDIA_ELEMENT_NONE,
2328 "PURPLE_MEDIA_ELEMENT_NONE", "none" },
2329 { PURPLE_MEDIA_ELEMENT_AUDIO,
2330 "PURPLE_MEDIA_ELEMENT_AUDIO", "audio" },
2331 { PURPLE_MEDIA_ELEMENT_VIDEO,
2332 "PURPLE_MEDIA_ELEMENT_VIDEO", "video" },
2333 { PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO,
2334 "PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO",
2335 "audio-video" },
2336 { PURPLE_MEDIA_ELEMENT_NO_SRCS,
2337 "PURPLE_MEDIA_ELEMENT_NO_SRCS", "no-srcs" },
2338 { PURPLE_MEDIA_ELEMENT_ONE_SRC,
2339 "PURPLE_MEDIA_ELEMENT_ONE_SRC", "one-src" },
2340 { PURPLE_MEDIA_ELEMENT_MULTI_SRC,
2341 "PURPLE_MEDIA_ELEMENT_MULTI_SRC",
2342 "multi-src" },
2343 { PURPLE_MEDIA_ELEMENT_REQUEST_SRC,
2344 "PURPLE_MEDIA_ELEMENT_REQUEST_SRC",
2345 "request-src" },
2346 { PURPLE_MEDIA_ELEMENT_NO_SINKS,
2347 "PURPLE_MEDIA_ELEMENT_NO_SINKS", "no-sinks" },
2348 { PURPLE_MEDIA_ELEMENT_ONE_SINK,
2349 "PURPLE_MEDIA_ELEMENT_ONE_SINK", "one-sink" },
2350 { PURPLE_MEDIA_ELEMENT_MULTI_SINK,
2351 "PURPLE_MEDIA_ELEMENT_MULTI_SINK",
2352 "multi-sink" },
2353 { PURPLE_MEDIA_ELEMENT_REQUEST_SINK,
2354 "PURPLE_MEDIA_ELEMENT_REQUEST_SINK",
2355 "request-sink" },
2356 { PURPLE_MEDIA_ELEMENT_UNIQUE,
2357 "PURPLE_MEDIA_ELEMENT_UNIQUE", "unique" },
2358 { PURPLE_MEDIA_ELEMENT_SRC,
2359 "PURPLE_MEDIA_ELEMENT_SRC", "src" },
2360 { PURPLE_MEDIA_ELEMENT_SINK,
2361 "PURPLE_MEDIA_ELEMENT_SINK", "sink" },
2362 { PURPLE_MEDIA_ELEMENT_APPLICATION,
2363 "PURPLE_MEDIA_ELEMENT_APPLICATION", "application" },
2364 { 0, NULL, NULL }
2366 type = g_flags_register_static(
2367 "PurpleMediaElementType", values);
2369 return type;
2371 #endif /* USE_VV */
2374 * PurpleMediaElementInfo
2377 struct _PurpleMediaElementInfoClass
2379 GObjectClass parent_class;
2382 struct _PurpleMediaElementInfo
2384 GObject parent;
2387 #ifdef USE_VV
2388 struct _PurpleMediaElementInfoPrivate
2390 gchar *id;
2391 gchar *name;
2392 PurpleMediaElementType type;
2393 PurpleMediaElementCreateCallback create;
2396 enum {
2397 PROP_0,
2398 PROP_ID,
2399 PROP_NAME,
2400 PROP_TYPE,
2401 PROP_CREATE_CB,
2404 static void
2405 purple_media_element_info_init(PurpleMediaElementInfo *info)
2407 PurpleMediaElementInfoPrivate *priv =
2408 PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info);
2409 priv->id = NULL;
2410 priv->name = NULL;
2411 priv->type = PURPLE_MEDIA_ELEMENT_NONE;
2412 priv->create = NULL;
2415 static void
2416 purple_media_element_info_finalize(GObject *info)
2418 PurpleMediaElementInfoPrivate *priv =
2419 PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info);
2420 g_free(priv->id);
2421 g_free(priv->name);
2424 static void
2425 purple_media_element_info_set_property (GObject *object, guint prop_id,
2426 const GValue *value, GParamSpec *pspec)
2428 PurpleMediaElementInfoPrivate *priv;
2429 g_return_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(object));
2431 priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(object);
2433 switch (prop_id) {
2434 case PROP_ID:
2435 g_free(priv->id);
2436 priv->id = g_value_dup_string(value);
2437 break;
2438 case PROP_NAME:
2439 g_free(priv->name);
2440 priv->name = g_value_dup_string(value);
2441 break;
2442 case PROP_TYPE: {
2443 priv->type = g_value_get_flags(value);
2444 break;
2446 case PROP_CREATE_CB:
2447 priv->create = g_value_get_pointer(value);
2448 break;
2449 default:
2450 G_OBJECT_WARN_INVALID_PROPERTY_ID(
2451 object, prop_id, pspec);
2452 break;
2456 static void
2457 purple_media_element_info_get_property (GObject *object, guint prop_id,
2458 GValue *value, GParamSpec *pspec)
2460 PurpleMediaElementInfoPrivate *priv;
2461 g_return_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(object));
2463 priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(object);
2465 switch (prop_id) {
2466 case PROP_ID:
2467 g_value_set_string(value, priv->id);
2468 break;
2469 case PROP_NAME:
2470 g_value_set_string(value, priv->name);
2471 break;
2472 case PROP_TYPE:
2473 g_value_set_flags(value, priv->type);
2474 break;
2475 case PROP_CREATE_CB:
2476 g_value_set_pointer(value, priv->create);
2477 break;
2478 default:
2479 G_OBJECT_WARN_INVALID_PROPERTY_ID(
2480 object, prop_id, pspec);
2481 break;
2485 static void
2486 purple_media_element_info_class_init(PurpleMediaElementInfoClass *klass)
2488 GObjectClass *gobject_class = (GObjectClass*)klass;
2490 gobject_class->finalize = purple_media_element_info_finalize;
2491 gobject_class->set_property = purple_media_element_info_set_property;
2492 gobject_class->get_property = purple_media_element_info_get_property;
2494 g_object_class_install_property(gobject_class, PROP_ID,
2495 g_param_spec_string("id",
2496 "ID",
2497 "The unique identifier of the element.",
2498 NULL,
2499 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
2500 G_PARAM_STATIC_STRINGS));
2502 g_object_class_install_property(gobject_class, PROP_NAME,
2503 g_param_spec_string("name",
2504 "Name",
2505 "The friendly/display name of this element.",
2506 NULL,
2507 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
2508 G_PARAM_STATIC_STRINGS));
2510 g_object_class_install_property(gobject_class, PROP_TYPE,
2511 g_param_spec_flags("type",
2512 "Element Type",
2513 "The type of element this is.",
2514 PURPLE_TYPE_MEDIA_ELEMENT_TYPE,
2515 PURPLE_MEDIA_ELEMENT_NONE,
2516 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
2517 G_PARAM_STATIC_STRINGS));
2519 g_object_class_install_property(gobject_class, PROP_CREATE_CB,
2520 g_param_spec_pointer("create-cb",
2521 "Create Callback",
2522 "The function called to create this element.",
2523 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
2524 G_PARAM_STATIC_STRINGS));
2526 g_type_class_add_private(klass, sizeof(PurpleMediaElementInfoPrivate));
2529 G_DEFINE_TYPE(PurpleMediaElementInfo,
2530 purple_media_element_info, G_TYPE_OBJECT);
2532 gchar *
2533 purple_media_element_info_get_id(PurpleMediaElementInfo *info)
2535 gchar *id;
2537 #if GLIB_CHECK_VERSION(2, 37, 3)
2538 /* Silence a warning. This could be anywhere below G_DEFINE_TYPE */
2539 (void)purple_media_element_info_get_instance_private;
2540 #endif
2542 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL);
2543 g_object_get(info, "id", &id, NULL);
2544 return id;
2547 gchar *
2548 purple_media_element_info_get_name(PurpleMediaElementInfo *info)
2550 gchar *name;
2551 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL);
2552 g_object_get(info, "name", &name, NULL);
2553 return name;
2556 PurpleMediaElementType
2557 purple_media_element_info_get_element_type(PurpleMediaElementInfo *info)
2559 PurpleMediaElementType type;
2560 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info),
2561 PURPLE_MEDIA_ELEMENT_NONE);
2562 g_object_get(info, "type", &type, NULL);
2563 return type;
2566 GstElement *
2567 purple_media_element_info_call_create(PurpleMediaElementInfo *info,
2568 PurpleMedia *media, const gchar *session_id,
2569 const gchar *participant)
2571 PurpleMediaElementCreateCallback create;
2572 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL);
2573 g_object_get(info, "create-cb", &create, NULL);
2574 if (create)
2575 return create(info, media, session_id, participant);
2576 return NULL;
2578 #endif /* USE_VV */