Remove found redundant NULL checks
[pidgin-git.git] / libpurple / mediamanager.c
blob47cd808c975028a32c200a56fbd3a16177870a4f
1 /**
2 * @file mediamanager.c Media Manager API
3 * @ingroup core
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
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.
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 Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "internal.h"
29 #include "account.h"
30 #include "debug.h"
31 #include "media.h"
32 #include "mediamanager.h"
34 #ifdef USE_GSTREAMER
35 #include "marshallers.h"
36 #include "media-gst.h"
37 #endif
39 #ifdef USE_VV
40 #include <media/backend-fs2.h>
42 #ifdef HAVE_FARSIGHT
43 #include <gst/farsight/fs-element-added-notifier.h>
44 #else
45 #include <farstream/fs-element-added-notifier.h>
46 #endif
47 #ifdef HAVE_MEDIA_APPLICATION
48 #include <gst/app/app.h>
49 #endif
51 #if GST_CHECK_VERSION(1,0,0)
52 #include <gst/video/videooverlay.h>
53 #else
54 #include <gst/interfaces/xoverlay.h>
55 #endif
57 /** @copydoc _PurpleMediaManagerPrivate */
58 typedef struct _PurpleMediaManagerPrivate PurpleMediaManagerPrivate;
59 /** @copydoc _PurpleMediaOutputWindow */
60 typedef struct _PurpleMediaOutputWindow PurpleMediaOutputWindow;
61 /** @copydoc _PurpleMediaManagerPrivate */
62 typedef struct _PurpleMediaElementInfoPrivate PurpleMediaElementInfoPrivate;
64 /** The media manager class. */
65 struct _PurpleMediaManagerClass
67 GObjectClass parent_class; /**< The parent class. */
70 /** The media manager's data. */
71 struct _PurpleMediaManager
73 GObject parent; /**< The parent of this manager. */
74 PurpleMediaManagerPrivate *priv; /**< Private data for the manager. */
77 struct _PurpleMediaOutputWindow
79 gulong id;
80 PurpleMedia *media;
81 gchar *session_id;
82 gchar *participant;
83 gulong window_id;
84 GstElement *sink;
87 struct _PurpleMediaManagerPrivate
89 GstElement *pipeline;
90 PurpleMediaCaps ui_caps;
91 GList *medias;
92 GList *private_medias;
93 GList *elements;
94 GList *output_windows;
95 gulong next_output_window_id;
96 GType backend_type;
97 GstCaps *video_caps;
99 PurpleMediaElementInfo *video_src;
100 PurpleMediaElementInfo *video_sink;
101 PurpleMediaElementInfo *audio_src;
102 PurpleMediaElementInfo *audio_sink;
104 #ifdef HAVE_MEDIA_APPLICATION
105 /* Application data streams */
106 GList *appdata_info; /* holds PurpleMediaAppDataInfo */
107 GMutex appdata_mutex;
108 guint appdata_cb_token; /* last used read/write callback token */
109 #endif
112 #ifdef HAVE_MEDIA_APPLICATION
113 typedef struct {
114 PurpleMedia *media;
115 GWeakRef media_ref;
116 gchar *session_id;
117 gchar *participant;
118 PurpleMediaAppDataCallbacks callbacks;
119 gpointer user_data;
120 GDestroyNotify notify;
121 GstAppSrc *appsrc;
122 GstAppSink *appsink;
123 gint num_samples;
124 GstSample *current_sample;
125 guint sample_offset;
126 gboolean writable;
127 gboolean connected;
128 guint writable_cb_token;
129 guint readable_cb_token;
130 guint writable_timer_id;
131 guint readable_timer_id;
132 GCond readable_cond;
133 } PurpleMediaAppDataInfo;
134 #endif
136 #define PURPLE_MEDIA_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerPrivate))
137 #define PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_ELEMENT_INFO, PurpleMediaElementInfoPrivate))
139 static void purple_media_manager_class_init (PurpleMediaManagerClass *klass);
140 static void purple_media_manager_init (PurpleMediaManager *media);
141 static void purple_media_manager_finalize (GObject *object);
142 #ifdef HAVE_MEDIA_APPLICATION
143 static void free_appdata_info_locked (PurpleMediaAppDataInfo *info);
144 #endif
146 static GObjectClass *parent_class = NULL;
150 enum {
151 INIT_MEDIA,
152 INIT_PRIVATE_MEDIA,
153 UI_CAPS_CHANGED,
154 LAST_SIGNAL
156 static guint purple_media_manager_signals[LAST_SIGNAL] = {0};
157 #endif
159 GType
160 purple_media_manager_get_type()
162 #ifdef USE_VV
163 static GType type = 0;
165 if (type == 0) {
166 static const GTypeInfo info = {
167 sizeof(PurpleMediaManagerClass),
168 NULL,
169 NULL,
170 (GClassInitFunc) purple_media_manager_class_init,
171 NULL,
172 NULL,
173 sizeof(PurpleMediaManager),
175 (GInstanceInitFunc) purple_media_manager_init,
176 NULL
178 type = g_type_register_static(G_TYPE_OBJECT, "PurpleMediaManager", &info, 0);
180 return type;
181 #else
182 return G_TYPE_NONE;
183 #endif
186 #ifdef USE_VV
187 static void
188 purple_media_manager_class_init (PurpleMediaManagerClass *klass)
190 GObjectClass *gobject_class = (GObjectClass*)klass;
191 parent_class = g_type_class_peek_parent(klass);
193 gobject_class->finalize = purple_media_manager_finalize;
195 purple_media_manager_signals[INIT_MEDIA] = g_signal_new ("init-media",
196 G_TYPE_FROM_CLASS (klass),
197 G_SIGNAL_RUN_LAST,
198 0, NULL, NULL,
199 purple_smarshal_BOOLEAN__OBJECT_POINTER_STRING,
200 G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA,
201 G_TYPE_POINTER, G_TYPE_STRING);
203 purple_media_manager_signals[INIT_PRIVATE_MEDIA] =
204 g_signal_new ("init-private-media",
205 G_TYPE_FROM_CLASS (klass),
206 G_SIGNAL_RUN_LAST,
207 0, NULL, NULL,
208 purple_smarshal_BOOLEAN__OBJECT_POINTER_STRING,
209 G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA,
210 G_TYPE_POINTER, G_TYPE_STRING);
212 purple_media_manager_signals[UI_CAPS_CHANGED] = g_signal_new ("ui-caps-changed",
213 G_TYPE_FROM_CLASS (klass),
214 G_SIGNAL_RUN_LAST,
215 0, NULL, NULL,
216 purple_smarshal_VOID__FLAGS_FLAGS,
217 G_TYPE_NONE, 2, PURPLE_MEDIA_TYPE_CAPS,
218 PURPLE_MEDIA_TYPE_CAPS);
220 g_type_class_add_private(klass, sizeof(PurpleMediaManagerPrivate));
223 static void
224 purple_media_manager_init (PurpleMediaManager *media)
226 media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
227 media->priv->medias = NULL;
228 media->priv->private_medias = NULL;
229 media->priv->next_output_window_id = 1;
230 media->priv->backend_type = PURPLE_TYPE_MEDIA_BACKEND_FS2;
231 #ifdef HAVE_MEDIA_APPLICATION
232 media->priv->appdata_info = NULL;
233 g_mutex_init (&media->priv->appdata_mutex);
234 #endif
236 purple_prefs_add_none("/purple/media");
237 purple_prefs_add_none("/purple/media/audio");
238 purple_prefs_add_int("/purple/media/audio/silence_threshold", 5);
239 purple_prefs_add_none("/purple/media/audio/volume");
240 purple_prefs_add_int("/purple/media/audio/volume/input", 10);
241 purple_prefs_add_int("/purple/media/audio/volume/output", 10);
244 static void
245 purple_media_manager_finalize (GObject *media)
247 PurpleMediaManagerPrivate *priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
248 for (; priv->medias; priv->medias =
249 g_list_delete_link(priv->medias, priv->medias)) {
250 g_object_unref(priv->medias->data);
252 for (; priv->private_medias; priv->private_medias =
253 g_list_delete_link(priv->private_medias, priv->private_medias)) {
254 g_object_unref(priv->private_medias->data);
256 for (; priv->elements; priv->elements =
257 g_list_delete_link(priv->elements, priv->elements)) {
258 g_object_unref(priv->elements->data);
260 if (priv->video_caps)
261 gst_caps_unref(priv->video_caps);
262 #ifdef HAVE_MEDIA_APPLICATION
263 if (priv->appdata_info)
264 g_list_free_full (priv->appdata_info,
265 (GDestroyNotify) free_appdata_info_locked);
266 g_mutex_clear (&priv->appdata_mutex);
267 #endif
269 parent_class->finalize(media);
271 #endif
273 PurpleMediaManager *
274 purple_media_manager_get()
276 #ifdef USE_VV
277 static PurpleMediaManager *manager = NULL;
279 if (manager == NULL)
280 manager = PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL));
281 return manager;
282 #else
283 return NULL;
284 #endif
287 #ifdef USE_VV
288 static gboolean
289 pipeline_bus_call(GstBus *bus, GstMessage *msg, PurpleMediaManager *manager)
291 switch(GST_MESSAGE_TYPE(msg)) {
292 case GST_MESSAGE_EOS:
293 purple_debug_info("mediamanager", "End of Stream\n");
294 break;
295 case GST_MESSAGE_ERROR: {
296 gchar *debug = NULL;
297 GError *err = NULL;
299 gst_message_parse_error(msg, &err, &debug);
301 purple_debug_error("mediamanager",
302 "gst pipeline error: %s\n",
303 err->message);
304 g_error_free(err);
306 if (debug) {
307 purple_debug_error("mediamanager",
308 "Debug details: %s\n", debug);
309 g_free (debug);
311 break;
313 default:
314 break;
316 return TRUE;
318 #endif
320 #ifdef USE_GSTREAMER
321 GstElement *
322 purple_media_manager_get_pipeline(PurpleMediaManager *manager)
324 #ifdef USE_VV
325 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
327 if (manager->priv->pipeline == NULL) {
328 FsElementAddedNotifier *notifier;
329 gchar *filename;
330 GError *err = NULL;
331 GKeyFile *keyfile;
332 GstBus *bus;
333 manager->priv->pipeline = gst_pipeline_new(NULL);
335 bus = gst_pipeline_get_bus(
336 GST_PIPELINE(manager->priv->pipeline));
337 gst_bus_add_signal_watch(GST_BUS(bus));
338 g_signal_connect(G_OBJECT(bus), "message",
339 G_CALLBACK(pipeline_bus_call), manager);
340 #if GST_CHECK_VERSION(1,0,0)
341 gst_bus_set_sync_handler(bus, gst_bus_sync_signal_handler, NULL, NULL);
342 #else
343 gst_bus_set_sync_handler(bus, gst_bus_sync_signal_handler, NULL);
344 #endif
345 gst_object_unref(bus);
347 filename = g_build_filename(purple_user_dir(),
348 "fs-element.conf", NULL);
349 keyfile = g_key_file_new();
350 if (!g_key_file_load_from_file(keyfile, filename,
351 G_KEY_FILE_NONE, &err)) {
352 if (err->code == 4)
353 purple_debug_info("mediamanager",
354 "Couldn't read "
355 "fs-element.conf: %s\n",
356 err->message);
357 else
358 purple_debug_error("mediamanager",
359 "Error reading "
360 "fs-element.conf: %s\n",
361 err->message);
362 g_error_free(err);
364 g_free(filename);
366 /* Hack to make alsasrc stop messing up audio timestamps */
367 if (!g_key_file_has_key(keyfile,
368 "alsasrc", "slave-method", NULL)) {
369 g_key_file_set_integer(keyfile,
370 "alsasrc", "slave-method", 2);
373 notifier = fs_element_added_notifier_new();
374 fs_element_added_notifier_add(notifier,
375 GST_BIN(manager->priv->pipeline));
376 fs_element_added_notifier_set_properties_from_keyfile(
377 notifier, keyfile);
379 gst_element_set_state(manager->priv->pipeline,
380 GST_STATE_PLAYING);
383 return manager->priv->pipeline;
384 #else
385 return NULL;
386 #endif
388 #endif /* USE_GSTREAMER */
390 static PurpleMedia *
391 create_media(PurpleMediaManager *manager,
392 PurpleAccount *account,
393 const char *conference_type,
394 const char *remote_user,
395 gboolean initiator,
396 gboolean private)
398 #ifdef USE_VV
399 PurpleMedia *media;
400 guint signal_id;
402 media = PURPLE_MEDIA(g_object_new(purple_media_get_type(),
403 "manager", manager,
404 "account", account,
405 "conference-type", conference_type,
406 "initiator", initiator,
407 NULL));
409 signal_id = private ?
410 purple_media_manager_signals[INIT_PRIVATE_MEDIA] :
411 purple_media_manager_signals[INIT_MEDIA];
413 if (g_signal_has_handler_pending(manager, signal_id, 0, FALSE)) {
414 gboolean signal_ret;
416 g_signal_emit(manager, signal_id, 0, media, account, remote_user,
417 &signal_ret);
418 if (signal_ret == FALSE) {
419 g_object_unref(media);
420 return NULL;
424 if (private)
425 manager->priv->private_medias = g_list_append(
426 manager->priv->private_medias, media);
427 else
428 manager->priv->medias = g_list_append(manager->priv->medias, media);
429 return media;
430 #else
431 return NULL;
432 #endif
435 static GList *
436 get_media(PurpleMediaManager *manager, gboolean private)
438 #ifdef USE_VV
439 if (private)
440 return manager->priv->private_medias;
441 else
442 return manager->priv->medias;
443 #else
444 return NULL;
445 #endif
448 static GList *
449 get_media_by_account(PurpleMediaManager *manager,
450 PurpleAccount *account, gboolean private)
452 #ifdef USE_VV
453 GList *media = NULL;
454 GList *iter;
456 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
458 if (private)
459 iter = manager->priv->private_medias;
460 else
461 iter = manager->priv->medias;
462 for (; iter; iter = g_list_next(iter)) {
463 if (purple_media_get_account(iter->data) == account) {
464 media = g_list_prepend(media, iter->data);
468 return media;
469 #else
470 return NULL;
471 #endif
474 void
475 purple_media_manager_remove_media(PurpleMediaManager *manager, PurpleMedia *media)
477 #ifdef USE_VV
478 GList *list;
479 GList **medias;
481 g_return_if_fail(manager != NULL);
483 if ((list = g_list_find(manager->priv->medias, media))) {
484 medias = &manager->priv->medias;
485 } else if ((list = g_list_find(manager->priv->private_medias, media))) {
486 medias = &manager->priv->private_medias;
489 if (list) {
490 *medias = g_list_delete_link(*medias, list);
492 #ifdef HAVE_MEDIA_APPLICATION
493 g_mutex_lock (&manager->priv->appdata_mutex);
494 list = manager->priv->appdata_info;
495 while (list) {
496 PurpleMediaAppDataInfo *info = list->data;
497 GList *next = list->next;
499 if (info->media == media) {
500 manager->priv->appdata_info = g_list_delete_link (
501 manager->priv->appdata_info, list);
502 free_appdata_info_locked (info);
505 list = next;
507 g_mutex_unlock (&manager->priv->appdata_mutex);
508 #endif
510 #endif
513 PurpleMedia *
514 purple_media_manager_create_media(PurpleMediaManager *manager,
515 PurpleAccount *account,
516 const char *conference_type,
517 const char *remote_user,
518 gboolean initiator)
520 return create_media (manager, account, conference_type,
521 remote_user, initiator, FALSE);
524 GList *
525 purple_media_manager_get_media(PurpleMediaManager *manager)
527 return get_media (manager, FALSE);
530 GList *
531 purple_media_manager_get_media_by_account(PurpleMediaManager *manager,
532 PurpleAccount *account)
534 return get_media_by_account (manager, account, FALSE);
537 PurpleMedia *
538 purple_media_manager_create_private_media(PurpleMediaManager *manager,
539 PurpleAccount *account,
540 const char *conference_type,
541 const char *remote_user,
542 gboolean initiator)
544 return create_media (manager, account, conference_type,
545 remote_user, initiator, TRUE);
548 GList *
549 purple_media_manager_get_private_media(PurpleMediaManager *manager)
551 return get_media (manager, TRUE);
554 GList *
555 purple_media_manager_get_private_media_by_account(PurpleMediaManager *manager,
556 PurpleAccount *account)
558 return get_media_by_account (manager, account, TRUE);
561 #ifdef HAVE_MEDIA_APPLICATION
562 static void
563 free_appdata_info_locked (PurpleMediaAppDataInfo *info)
565 GstAppSrcCallbacks null_src_cb = { NULL, NULL, NULL, { NULL } };
566 GstAppSinkCallbacks null_sink_cb = { NULL, NULL, NULL , { NULL } };
568 if (info->notify)
569 info->notify (info->user_data);
571 info->media = NULL;
572 if (info->appsrc) {
573 /* Will call appsrc_destroyed. */
574 gst_app_src_set_callbacks (info->appsrc, &null_src_cb,
575 NULL, NULL);
577 if (info->appsink) {
578 /* Will call appsink_destroyed. */
579 gst_app_sink_set_callbacks (info->appsink, &null_sink_cb,
580 NULL, NULL);
583 /* Make sure no other thread is using the structure */
584 g_free (info->session_id);
585 g_free (info->participant);
587 /* This lets the potential read or write callbacks waiting for appdata_mutex
588 * know the info structure has been destroyed. */
589 info->readable_cb_token = 0;
590 info->writable_cb_token = 0;
592 if (info->readable_timer_id) {
593 purple_timeout_remove (info->readable_timer_id);
594 info->readable_timer_id = 0;
597 if (info->writable_timer_id) {
598 purple_timeout_remove (info->writable_timer_id);
599 info->writable_timer_id = 0;
602 if (info->current_sample)
603 gst_sample_unref (info->current_sample);
604 info->current_sample = NULL;
606 /* Unblock any reading thread before destroying the GCond */
607 g_cond_broadcast (&info->readable_cond);
609 g_cond_clear (&info->readable_cond);
611 g_slice_free (PurpleMediaAppDataInfo, info);
615 * Get an app data info struct associated with a session and lock the mutex
616 * We don't want to return an info struct and unlock then it gets destroyed
617 * so we need to return it with the lock still taken
619 static PurpleMediaAppDataInfo *
620 get_app_data_info_and_lock (PurpleMediaManager *manager,
621 PurpleMedia *media, const gchar *session_id, const gchar *participant)
623 GList *i;
625 g_mutex_lock (&manager->priv->appdata_mutex);
626 for (i = manager->priv->appdata_info; i; i = i->next) {
627 PurpleMediaAppDataInfo *info = i->data;
629 if (info->media == media &&
630 purple_strequal (info->session_id, session_id) &&
631 (participant == NULL ||
632 purple_strequal (info->participant, participant))) {
633 return info;
637 return NULL;
641 * Get an app data info struct associated with a session and lock the mutex
642 * if it doesn't exist, we create it.
644 static PurpleMediaAppDataInfo *
645 ensure_app_data_info_and_lock (PurpleMediaManager *manager, PurpleMedia *media,
646 const gchar *session_id, const gchar *participant)
648 PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager, media,
649 session_id, participant);
651 if (info == NULL) {
652 info = g_slice_new0 (PurpleMediaAppDataInfo);
653 info->media = media;
654 g_weak_ref_init (&info->media_ref, media);
655 info->session_id = g_strdup (session_id);
656 info->participant = g_strdup (participant);
657 g_cond_init (&info->readable_cond);
658 manager->priv->appdata_info = g_list_prepend (
659 manager->priv->appdata_info, info);
662 return info;
664 #endif
667 #ifdef USE_VV
668 static void
669 request_pad_unlinked_cb(GstPad *pad, GstPad *peer, gpointer user_data)
671 GstElement *parent = GST_ELEMENT_PARENT(pad);
672 GstIterator *iter;
673 #if GST_CHECK_VERSION(1,0,0)
674 GValue tmp = G_VALUE_INIT;
675 #endif
676 GstPad *remaining_pad;
677 GstIteratorResult result;
679 gst_element_release_request_pad(parent, pad);
681 iter = gst_element_iterate_src_pads(parent);
683 #if GST_CHECK_VERSION(1,0,0)
684 result = gst_iterator_next(iter, &tmp);
685 #else
686 result = gst_iterator_next(iter, (gpointer)&remaining_pad);
687 #endif
689 if (result == GST_ITERATOR_DONE) {
690 gst_element_set_locked_state(parent, TRUE);
691 gst_element_set_state(parent, GST_STATE_NULL);
692 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(parent)), parent);
693 } else if (result == GST_ITERATOR_OK) {
694 #if GST_CHECK_VERSION(1,0,0)
695 remaining_pad = g_value_get_object(&tmp);
696 g_value_reset(&tmp);
697 #endif
698 gst_object_unref(remaining_pad);
701 gst_iterator_free(iter);
704 static void
705 nonunique_src_unlinked_cb(GstPad *pad, GstPad *peer, gpointer user_data)
707 GstElement *element = GST_ELEMENT_PARENT(pad);
708 gst_element_set_locked_state(element, TRUE);
709 gst_element_set_state(element, GST_STATE_NULL);
710 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
713 #endif
715 #ifdef USE_GSTREAMER
717 void
718 purple_media_manager_set_video_caps(PurpleMediaManager *manager, GstCaps *caps)
720 #ifdef USE_VV
721 if (manager->priv->video_caps)
722 gst_caps_unref(manager->priv->video_caps);
724 manager->priv->video_caps = caps;
726 if (manager->priv->pipeline && manager->priv->video_src) {
727 gchar *id = purple_media_element_info_get_id(manager->priv->video_src);
728 GstElement *src = gst_bin_get_by_name(GST_BIN(manager->priv->pipeline), id);
730 if (src) {
731 GstElement *capsfilter = gst_bin_get_by_name(GST_BIN(src), "prpl_video_caps");
732 if (capsfilter) {
733 g_object_set(G_OBJECT(capsfilter), "caps", caps, NULL);
734 gst_object_unref (capsfilter);
736 gst_object_unref (src);
739 g_free(id);
741 #endif
744 GstCaps *
745 purple_media_manager_get_video_caps(PurpleMediaManager *manager)
747 #ifdef USE_VV
748 if (manager->priv->video_caps == NULL)
749 #if GST_CHECK_VERSION(1,0,0)
750 manager->priv->video_caps = gst_caps_from_string("video/x-raw,"
751 #else
752 manager->priv->video_caps = gst_caps_from_string("video/x-raw-yuv,"
753 #endif
754 "width=[250,352], height=[200,288], framerate=[1/1,20/1]");
755 return manager->priv->video_caps;
756 #else
757 return NULL;
758 #endif
761 #ifdef HAVE_MEDIA_APPLICATION
763 * Calls the appdata writable callback from the main thread.
764 * This needs to grab the appdata lock and make sure it didn't get destroyed
765 * before calling the callback.
767 static gboolean
768 appsrc_writable (gpointer user_data)
770 PurpleMediaManager *manager = purple_media_manager_get ();
771 PurpleMediaAppDataInfo *info = user_data;
772 void (*writable_cb) (PurpleMediaManager *manager, PurpleMedia *media,
773 const gchar *session_id, const gchar *participant, gboolean writable,
774 gpointer user_data);
775 PurpleMedia *media;
776 gchar *session_id;
777 gchar *participant;
778 gboolean writable;
779 gpointer cb_data;
780 guint *cb_token_ptr = &info->writable_cb_token;
781 guint cb_token = *cb_token_ptr;
784 g_mutex_lock (&manager->priv->appdata_mutex);
785 if (cb_token == 0 || cb_token != *cb_token_ptr) {
786 /* In case info was freed while we were waiting for the mutex to unlock
787 * we still have a pointer to the cb_token which should still be
788 * accessible since it's in the Glib slice allocator. It gets set to 0
789 * just after the timeout is canceled which happens also before the
790 * AppDataInfo is freed, so even if that memory slice gets reused, the
791 * cb_token would be different from its previous value (unless
792 * extremely unlucky). So checking if the value for the cb_token changed
793 * should be enough to prevent any kind of race condition in which the
794 * media/AppDataInfo gets destroyed in one thread while the timeout was
795 * triggered and is waiting on the mutex to get unlocked in this thread
797 g_mutex_unlock (&manager->priv->appdata_mutex);
798 return FALSE;
800 writable_cb = info->callbacks.writable;
801 media = g_weak_ref_get (&info->media_ref);
802 session_id = g_strdup (info->session_id);
803 participant = g_strdup (info->participant);
804 writable = info->writable && info->connected;
805 cb_data = info->user_data;
807 info->writable_cb_token = 0;
808 g_mutex_unlock (&manager->priv->appdata_mutex);
811 if (writable_cb && media)
812 writable_cb (manager, media, session_id, participant, writable,
813 cb_data);
815 g_object_unref (media);
816 g_free (session_id);
817 g_free (participant);
819 return FALSE;
823 * Schedule a writable callback to be called from the main thread.
824 * We need to do this because need-data/enough-data signals from appsrc
825 * will come from the streaming thread and we need to create
826 * a source that we attach to the main context but we can't use
827 * g_main_context_invoke since we need to be able to cancel the source if the
828 * media gets destroyed.
829 * We use a timeout source instead of idle source, so the callback gets a higher
830 * priority
832 static void
833 call_appsrc_writable_locked (PurpleMediaAppDataInfo *info)
835 PurpleMediaManager *manager = purple_media_manager_get ();
837 /* We already have a writable callback scheduled, don't create another one */
838 if (info->writable_cb_token || info->callbacks.writable == NULL)
839 return;
841 /* We can't use writable_timer_id as a token, because the timeout is added
842 * into libpurple's main event loop, which runs in a different thread than
843 * from where call_appsrc_writable_locked() was called. Consequently, the
844 * callback may run even before purple_timeout_add() returns the timer ID
845 * to us. */
846 info->writable_cb_token = ++manager->priv->appdata_cb_token;
847 info->writable_timer_id = purple_timeout_add (0, appsrc_writable, info);
850 static void
851 appsrc_need_data (GstAppSrc *appsrc, guint length, gpointer user_data)
853 PurpleMediaAppDataInfo *info = user_data;
854 PurpleMediaManager *manager = purple_media_manager_get ();
856 g_mutex_lock (&manager->priv->appdata_mutex);
857 if (!info->writable) {
858 info->writable = TRUE;
859 /* Only signal writable if we also established a connection */
860 if (info->connected)
861 call_appsrc_writable_locked (info);
863 g_mutex_unlock (&manager->priv->appdata_mutex);
866 static void
867 appsrc_enough_data (GstAppSrc *appsrc, gpointer user_data)
869 PurpleMediaAppDataInfo *info = user_data;
870 PurpleMediaManager *manager = purple_media_manager_get ();
872 g_mutex_lock (&manager->priv->appdata_mutex);
873 if (info->writable) {
874 info->writable = FALSE;
875 call_appsrc_writable_locked (info);
877 g_mutex_unlock (&manager->priv->appdata_mutex);
880 static gboolean
881 appsrc_seek_data (GstAppSrc *appsrc, guint64 offset, gpointer user_data)
883 return FALSE;
886 static void
887 appsrc_destroyed (PurpleMediaAppDataInfo *info)
889 PurpleMediaManager *manager;
891 if (!info->media) {
892 /* PurpleMediaAppDataInfo is being freed. Return at once. */
893 return;
896 manager = purple_media_manager_get ();
898 g_mutex_lock (&manager->priv->appdata_mutex);
899 info->appsrc = NULL;
900 if (info->writable) {
901 info->writable = FALSE;
902 call_appsrc_writable_locked (info);
904 g_mutex_unlock (&manager->priv->appdata_mutex);
907 static void
908 media_established_cb (PurpleMedia *media,const gchar *session_id,
909 const gchar *participant, PurpleMediaCandidate *local_candidate,
910 PurpleMediaCandidate *remote_candidate, PurpleMediaAppDataInfo *info)
912 PurpleMediaManager *manager = purple_media_manager_get ();
914 g_mutex_lock (&manager->priv->appdata_mutex);
915 info->connected = TRUE;
916 /* We established the connection, if we were writable, then we need to
917 * signal it now */
918 if (info->writable)
919 call_appsrc_writable_locked (info);
920 g_mutex_unlock (&manager->priv->appdata_mutex);
923 static GstElement *
924 create_send_appsrc(PurpleMedia *media,
925 const gchar *session_id, const gchar *participant)
927 PurpleMediaManager *manager = purple_media_manager_get ();
928 PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
929 media, session_id, participant);
930 GstElement *appsrc = (GstElement *)info->appsrc;
932 if (appsrc == NULL) {
933 GstAppSrcCallbacks callbacks = {appsrc_need_data, appsrc_enough_data,
934 appsrc_seek_data, {NULL}};
935 GstCaps *caps = gst_caps_new_empty_simple ("application/octet-stream");
937 appsrc = gst_element_factory_make("appsrc", NULL);
939 info->appsrc = (GstAppSrc *)appsrc;
941 gst_app_src_set_caps (info->appsrc, caps);
942 gst_app_src_set_callbacks (info->appsrc,
943 &callbacks, info, (GDestroyNotify) appsrc_destroyed);
944 g_signal_connect (media, "candidate-pair-established",
945 (GCallback) media_established_cb, info);
946 gst_caps_unref (caps);
949 g_mutex_unlock (&manager->priv->appdata_mutex);
950 return appsrc;
953 static void
954 appsink_eos (GstAppSink *appsink, gpointer user_data)
958 static GstFlowReturn
959 appsink_new_preroll (GstAppSink *appsink, gpointer user_data)
961 return GST_FLOW_OK;
964 static gboolean
965 appsink_readable (gpointer user_data)
967 PurpleMediaManager *manager = purple_media_manager_get ();
968 PurpleMediaAppDataInfo *info = user_data;
969 void (*readable_cb) (PurpleMediaManager *manager, PurpleMedia *media,
970 const gchar *session_id, const gchar *participant, gpointer user_data);
971 PurpleMedia *media;
972 gchar *session_id;
973 gchar *participant;
974 gpointer cb_data;
975 guint *cb_token_ptr = &info->readable_cb_token;
976 guint cb_token = *cb_token_ptr;
977 gboolean run_again = FALSE;
979 g_mutex_lock (&manager->priv->appdata_mutex);
980 if (cb_token == 0 || cb_token != *cb_token_ptr) {
981 /* Avoided a race condition (see writable callback) */
982 g_mutex_unlock (&manager->priv->appdata_mutex);
983 return FALSE;
986 if (info->callbacks.readable &&
987 (info->num_samples > 0 || info->current_sample != NULL)) {
988 readable_cb = info->callbacks.readable;
989 media = g_weak_ref_get (&info->media_ref);
990 session_id = g_strdup (info->session_id);
991 participant = g_strdup (info->participant);
992 cb_data = info->user_data;
993 g_mutex_unlock (&manager->priv->appdata_mutex);
995 if (readable_cb)
996 readable_cb (manager, media, session_id, participant, cb_data);
998 g_mutex_lock (&manager->priv->appdata_mutex);
999 g_object_unref (media);
1000 g_free (session_id);
1001 g_free (participant);
1002 if (cb_token == 0 || cb_token != *cb_token_ptr) {
1003 /* We got cancelled */
1004 g_mutex_unlock (&manager->priv->appdata_mutex);
1005 return FALSE;
1009 /* Do we still have samples? Schedule appsink_readable again. We break here
1010 * so that other events get a chance to be processed too. */
1011 if (info->num_samples > 0 || info->current_sample != NULL) {
1012 run_again = TRUE;
1013 } else {
1014 info->readable_cb_token = 0;
1017 g_mutex_unlock (&manager->priv->appdata_mutex);
1018 return run_again;
1021 static void
1022 call_appsink_readable_locked (PurpleMediaAppDataInfo *info)
1024 PurpleMediaManager *manager = purple_media_manager_get ();
1026 /* We must signal that a new sample has arrived to release blocking reads */
1027 g_cond_broadcast (&info->readable_cond);
1029 /* We already have a writable callback scheduled, don't create another one */
1030 if (info->readable_cb_token || info->callbacks.readable == NULL)
1031 return;
1033 info->readable_cb_token = ++manager->priv->appdata_cb_token;
1034 info->readable_timer_id = purple_timeout_add (0, appsink_readable, info);
1037 static GstFlowReturn
1038 appsink_new_sample (GstAppSink *appsink, gpointer user_data)
1040 PurpleMediaManager *manager = purple_media_manager_get ();
1041 PurpleMediaAppDataInfo *info = user_data;
1043 g_mutex_lock (&manager->priv->appdata_mutex);
1044 info->num_samples++;
1045 call_appsink_readable_locked (info);
1046 g_mutex_unlock (&manager->priv->appdata_mutex);
1048 return GST_FLOW_OK;
1051 static void
1052 appsink_destroyed (PurpleMediaAppDataInfo *info)
1054 PurpleMediaManager *manager;
1056 if (!info->media) {
1057 /* PurpleMediaAppDataInfo is being freed. Return at once. */
1058 return;
1061 manager = purple_media_manager_get ();
1063 g_mutex_lock (&manager->priv->appdata_mutex);
1064 info->appsink = NULL;
1065 info->num_samples = 0;
1066 g_mutex_unlock (&manager->priv->appdata_mutex);
1069 static GstElement *
1070 create_recv_appsink(PurpleMedia *media,
1071 const gchar *session_id, const gchar *participant)
1073 PurpleMediaManager *manager = purple_media_manager_get ();
1074 PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
1075 media, session_id, participant);
1076 GstElement *appsink = (GstElement *)info->appsink;
1078 if (appsink == NULL) {
1079 GstAppSinkCallbacks callbacks = {appsink_eos, appsink_new_preroll,
1080 appsink_new_sample, {NULL}};
1081 GstCaps *caps = gst_caps_new_empty_simple ("application/octet-stream");
1083 appsink = gst_element_factory_make("appsink", NULL);
1085 info->appsink = (GstAppSink *)appsink;
1087 gst_app_sink_set_caps (info->appsink, caps);
1088 gst_app_sink_set_callbacks (info->appsink,
1089 &callbacks, info, (GDestroyNotify) appsink_destroyed);
1090 gst_caps_unref (caps);
1094 g_mutex_unlock (&manager->priv->appdata_mutex);
1095 return appsink;
1097 #endif
1099 static PurpleMediaElementInfo *
1100 get_send_application_element_info ()
1102 static PurpleMediaElementInfo *info = NULL;
1104 #ifdef HAVE_MEDIA_APPLICATION
1105 if (info == NULL) {
1106 info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1107 "id", "pidginappsrc",
1108 "name", "Pidgin Application Source",
1109 "type", PURPLE_MEDIA_ELEMENT_APPLICATION
1110 | PURPLE_MEDIA_ELEMENT_SRC
1111 | PURPLE_MEDIA_ELEMENT_ONE_SRC,
1112 "create-cb", create_send_appsrc, NULL);
1114 #endif
1116 return info;
1120 static PurpleMediaElementInfo *
1121 get_recv_application_element_info ()
1123 static PurpleMediaElementInfo *info = NULL;
1125 #ifdef HAVE_MEDIA_APPLICATION
1126 if (info == NULL) {
1127 info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1128 "id", "pidginappsink",
1129 "name", "Pidgin Application Sink",
1130 "type", PURPLE_MEDIA_ELEMENT_APPLICATION
1131 | PURPLE_MEDIA_ELEMENT_SINK
1132 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
1133 "create-cb", create_recv_appsink, NULL);
1135 #endif
1137 return info;
1140 GstElement *
1141 purple_media_manager_get_element(PurpleMediaManager *manager,
1142 PurpleMediaSessionType type, PurpleMedia *media,
1143 const gchar *session_id, const gchar *participant)
1145 #ifdef USE_VV
1146 GstElement *ret = NULL;
1147 PurpleMediaElementInfo *info = NULL;
1148 PurpleMediaElementType element_type;
1150 if (type & PURPLE_MEDIA_SEND_AUDIO)
1151 info = manager->priv->audio_src;
1152 else if (type & PURPLE_MEDIA_RECV_AUDIO)
1153 info = manager->priv->audio_sink;
1154 else if (type & PURPLE_MEDIA_SEND_VIDEO)
1155 info = manager->priv->video_src;
1156 else if (type & PURPLE_MEDIA_RECV_VIDEO)
1157 info = manager->priv->video_sink;
1158 else if (type & PURPLE_MEDIA_SEND_APPLICATION)
1159 info = get_send_application_element_info ();
1160 else if (type & PURPLE_MEDIA_RECV_APPLICATION)
1161 info = get_recv_application_element_info ();
1163 if (info == NULL)
1164 return NULL;
1166 element_type = purple_media_element_info_get_element_type(info);
1168 if (element_type & PURPLE_MEDIA_ELEMENT_UNIQUE &&
1169 element_type & PURPLE_MEDIA_ELEMENT_SRC) {
1170 GstElement *tee;
1171 GstPad *pad;
1172 GstPad *ghost;
1173 gchar *id = purple_media_element_info_get_id(info);
1175 ret = gst_bin_get_by_name(GST_BIN(
1176 purple_media_manager_get_pipeline(
1177 manager)), id);
1179 if (ret == NULL) {
1180 GstElement *bin, *fakesink;
1181 ret = purple_media_element_info_call_create(info,
1182 media, session_id, participant);
1183 bin = gst_bin_new(id);
1184 tee = gst_element_factory_make("tee", "tee");
1185 gst_bin_add_many(GST_BIN(bin), ret, tee, NULL);
1187 if (type & PURPLE_MEDIA_SEND_VIDEO) {
1188 GstElement *videoscale;
1189 GstElement *capsfilter;
1191 videoscale = gst_element_factory_make("videoscale", NULL);
1192 capsfilter = gst_element_factory_make("capsfilter", "prpl_video_caps");
1194 g_object_set(G_OBJECT(capsfilter),
1195 "caps", purple_media_manager_get_video_caps(manager), NULL);
1197 gst_bin_add_many(GST_BIN(bin), videoscale, capsfilter, NULL);
1198 gst_element_link_many(ret, videoscale, capsfilter, tee, NULL);
1199 } else
1200 gst_element_link(ret, tee);
1203 * This shouldn't be necessary, but it stops it from
1204 * giving a not-linked error upon destruction
1206 fakesink = gst_element_factory_make("fakesink", NULL);
1207 g_object_set(fakesink,
1208 "async", FALSE,
1209 "sync", FALSE,
1210 "enable-last-sample", FALSE,
1211 NULL);
1212 gst_bin_add(GST_BIN(bin), fakesink);
1213 gst_element_link(tee, fakesink);
1215 ret = bin;
1216 gst_object_ref(ret);
1217 gst_bin_add(GST_BIN(purple_media_manager_get_pipeline(
1218 manager)), ret);
1220 g_free(id);
1222 tee = gst_bin_get_by_name(GST_BIN(ret), "tee");
1223 #if GST_CHECK_VERSION(1,0,0)
1224 pad = gst_element_get_request_pad(tee, "src_%u");
1225 #else
1226 pad = gst_element_get_request_pad(tee, "src%d");
1227 #endif
1228 gst_object_unref(tee);
1229 ghost = gst_ghost_pad_new(NULL, pad);
1230 gst_object_unref(pad);
1231 g_signal_connect(GST_PAD(ghost), "unlinked",
1232 G_CALLBACK(request_pad_unlinked_cb), NULL);
1233 gst_pad_set_active(ghost, TRUE);
1234 gst_element_add_pad(ret, ghost);
1235 } else {
1236 ret = purple_media_element_info_call_create(info,
1237 media, session_id, participant);
1238 if (element_type & PURPLE_MEDIA_ELEMENT_SRC) {
1239 GstPad *pad = gst_element_get_static_pad(ret, "src");
1240 g_signal_connect(pad, "unlinked",
1241 G_CALLBACK(nonunique_src_unlinked_cb), NULL);
1242 gst_object_unref(pad);
1243 gst_object_ref(ret);
1244 gst_bin_add(GST_BIN(purple_media_manager_get_pipeline(manager)),
1245 ret);
1249 if (ret == NULL)
1250 purple_debug_error("media", "Error creating source or sink\n");
1252 return ret;
1253 #else
1254 return NULL;
1255 #endif
1258 PurpleMediaElementInfo *
1259 purple_media_manager_get_element_info(PurpleMediaManager *manager,
1260 const gchar *id)
1262 #ifdef USE_VV
1263 GList *iter;
1265 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
1267 iter = manager->priv->elements;
1269 for (; iter; iter = g_list_next(iter)) {
1270 gchar *element_id =
1271 purple_media_element_info_get_id(iter->data);
1272 if (purple_strequal(element_id, id)) {
1273 g_free(element_id);
1274 g_object_ref(iter->data);
1275 return iter->data;
1277 g_free(element_id);
1279 #endif
1281 return NULL;
1284 gboolean
1285 purple_media_manager_register_element(PurpleMediaManager *manager,
1286 PurpleMediaElementInfo *info)
1288 #ifdef USE_VV
1289 PurpleMediaElementInfo *info2;
1290 gchar *id;
1292 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1293 g_return_val_if_fail(info != NULL, FALSE);
1295 id = purple_media_element_info_get_id(info);
1296 info2 = purple_media_manager_get_element_info(manager, id);
1297 g_free(id);
1299 if (info2 != NULL) {
1300 g_object_unref(info2);
1301 return FALSE;
1304 manager->priv->elements =
1305 g_list_prepend(manager->priv->elements, info);
1306 return TRUE;
1307 #else
1308 return FALSE;
1309 #endif
1312 gboolean
1313 purple_media_manager_unregister_element(PurpleMediaManager *manager,
1314 const gchar *id)
1316 #ifdef USE_VV
1317 PurpleMediaElementInfo *info;
1319 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1321 info = purple_media_manager_get_element_info(manager, id);
1323 if (info == NULL) {
1324 g_object_unref(info);
1325 return FALSE;
1328 if (manager->priv->audio_src == info)
1329 manager->priv->audio_src = NULL;
1330 if (manager->priv->audio_sink == info)
1331 manager->priv->audio_sink = NULL;
1332 if (manager->priv->video_src == info)
1333 manager->priv->video_src = NULL;
1334 if (manager->priv->video_sink == info)
1335 manager->priv->video_sink = NULL;
1337 manager->priv->elements = g_list_remove(
1338 manager->priv->elements, info);
1339 g_object_unref(info);
1340 return TRUE;
1341 #else
1342 return FALSE;
1343 #endif
1346 gboolean
1347 purple_media_manager_set_active_element(PurpleMediaManager *manager,
1348 PurpleMediaElementInfo *info)
1350 #ifdef USE_VV
1351 PurpleMediaElementInfo *info2;
1352 PurpleMediaElementType type;
1353 gboolean ret = FALSE;
1354 gchar *id;
1356 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1357 g_return_val_if_fail(info != NULL, FALSE);
1359 id = purple_media_element_info_get_id(info);
1360 info2 = purple_media_manager_get_element_info(manager, id);
1361 g_free(id);
1363 if (info2 == NULL)
1364 purple_media_manager_register_element(manager, info);
1365 else
1366 g_object_unref(info2);
1368 type = purple_media_element_info_get_element_type(info);
1370 if (type & PURPLE_MEDIA_ELEMENT_SRC) {
1371 if (type & PURPLE_MEDIA_ELEMENT_AUDIO) {
1372 manager->priv->audio_src = info;
1373 ret = TRUE;
1375 if (type & PURPLE_MEDIA_ELEMENT_VIDEO) {
1376 manager->priv->video_src = info;
1377 ret = TRUE;
1380 if (type & PURPLE_MEDIA_ELEMENT_SINK) {
1381 if (type & PURPLE_MEDIA_ELEMENT_AUDIO) {
1382 manager->priv->audio_sink = info;
1383 ret = TRUE;
1385 if (type & PURPLE_MEDIA_ELEMENT_VIDEO) {
1386 manager->priv->video_sink = info;
1387 ret = TRUE;
1391 return ret;
1392 #else
1393 return FALSE;
1394 #endif
1397 PurpleMediaElementInfo *
1398 purple_media_manager_get_active_element(PurpleMediaManager *manager,
1399 PurpleMediaElementType type)
1401 #ifdef USE_VV
1402 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
1404 if (type & PURPLE_MEDIA_ELEMENT_SRC) {
1405 if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
1406 return manager->priv->audio_src;
1407 else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
1408 return manager->priv->video_src;
1409 else if (type & PURPLE_MEDIA_ELEMENT_APPLICATION)
1410 return get_send_application_element_info ();
1411 } else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
1412 if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
1413 return manager->priv->audio_sink;
1414 else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
1415 return manager->priv->video_sink;
1416 else if (type & PURPLE_MEDIA_ELEMENT_APPLICATION)
1417 return get_recv_application_element_info ();
1420 #endif
1422 return NULL;
1424 #endif /* USE_GSTREAMER */
1426 #ifdef USE_VV
1427 static void
1428 window_id_cb(GstBus *bus, GstMessage *msg, PurpleMediaOutputWindow *ow)
1430 GstElement *sink;
1432 if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT
1433 #if GST_CHECK_VERSION(1,0,0)
1434 || !gst_is_video_overlay_prepare_window_handle_message(msg))
1435 #else
1436 || !gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
1437 #endif
1438 return;
1440 sink = GST_ELEMENT(GST_MESSAGE_SRC(msg));
1441 while (sink != ow->sink) {
1442 if (sink == NULL)
1443 return;
1444 sink = GST_ELEMENT_PARENT(sink);
1447 g_signal_handlers_disconnect_matched(bus, G_SIGNAL_MATCH_FUNC
1448 | G_SIGNAL_MATCH_DATA, 0, 0, NULL,
1449 window_id_cb, ow);
1451 #if GST_CHECK_VERSION(1,0,0)
1452 gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(msg)),
1453 ow->window_id);
1454 #elif GST_CHECK_VERSION(0,10,31)
1455 gst_x_overlay_set_window_handle(GST_X_OVERLAY(GST_MESSAGE_SRC(msg)),
1456 ow->window_id);
1457 #else
1458 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(msg)),
1459 ow->window_id);
1460 #endif
1462 #endif
1464 gboolean
1465 purple_media_manager_create_output_window(PurpleMediaManager *manager,
1466 PurpleMedia *media, const gchar *session_id,
1467 const gchar *participant)
1469 #ifdef USE_VV
1470 GList *iter;
1472 g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
1474 iter = manager->priv->output_windows;
1475 for(; iter; iter = g_list_next(iter)) {
1476 PurpleMediaOutputWindow *ow = iter->data;
1478 if (ow->sink == NULL && ow->media == media &&
1479 purple_strequal(participant, ow->participant) &&
1480 purple_strequal(session_id, ow->session_id)) {
1481 GstBus *bus;
1482 GstElement *queue, *convert, *scale;
1483 GstElement *tee = purple_media_get_tee(media,
1484 session_id, participant);
1486 if (tee == NULL)
1487 continue;
1489 queue = gst_element_factory_make("queue", NULL);
1490 #if GST_CHECK_VERSION(1,0,0)
1491 convert = gst_element_factory_make("videoconvert", NULL);
1492 #else
1493 convert = gst_element_factory_make("ffmpegcolorspace", NULL);
1494 #endif
1495 scale = gst_element_factory_make("videoscale", NULL);
1496 ow->sink = purple_media_manager_get_element(
1497 manager, PURPLE_MEDIA_RECV_VIDEO,
1498 ow->media, ow->session_id,
1499 ow->participant);
1501 if (participant == NULL) {
1502 /* aka this is a preview sink */
1503 GObjectClass *klass =
1504 G_OBJECT_GET_CLASS(ow->sink);
1505 if (g_object_class_find_property(klass,
1506 "sync"))
1507 g_object_set(G_OBJECT(ow->sink),
1508 "sync", FALSE, NULL);
1509 if (g_object_class_find_property(klass,
1510 "async"))
1511 g_object_set(G_OBJECT(ow->sink),
1512 "async", FALSE, NULL);
1515 gst_bin_add_many(GST_BIN(GST_ELEMENT_PARENT(tee)),
1516 queue, convert, scale, ow->sink, NULL);
1518 bus = gst_pipeline_get_bus(GST_PIPELINE(
1519 manager->priv->pipeline));
1520 g_signal_connect(bus, "sync-message::element",
1521 G_CALLBACK(window_id_cb), ow);
1522 gst_object_unref(bus);
1524 gst_element_set_state(ow->sink, GST_STATE_PLAYING);
1525 gst_element_set_state(scale, GST_STATE_PLAYING);
1526 gst_element_set_state(convert, GST_STATE_PLAYING);
1527 gst_element_set_state(queue, GST_STATE_PLAYING);
1528 gst_element_link(scale, ow->sink);
1529 gst_element_link(convert, scale);
1530 gst_element_link(queue, convert);
1531 gst_element_link(tee, queue);
1534 return TRUE;
1535 #else
1536 return FALSE;
1537 #endif
1540 gulong
1541 purple_media_manager_set_output_window(PurpleMediaManager *manager,
1542 PurpleMedia *media, const gchar *session_id,
1543 const gchar *participant, gulong window_id)
1545 #ifdef USE_VV
1546 PurpleMediaOutputWindow *output_window;
1548 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1549 g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
1551 output_window = g_new0(PurpleMediaOutputWindow, 1);
1552 output_window->id = manager->priv->next_output_window_id++;
1553 output_window->media = media;
1554 output_window->session_id = g_strdup(session_id);
1555 output_window->participant = g_strdup(participant);
1556 output_window->window_id = window_id;
1558 manager->priv->output_windows = g_list_prepend(
1559 manager->priv->output_windows, output_window);
1561 if (purple_media_get_tee(media, session_id, participant) != NULL)
1562 purple_media_manager_create_output_window(manager,
1563 media, session_id, participant);
1565 return output_window->id;
1566 #else
1567 return 0;
1568 #endif
1571 gboolean
1572 purple_media_manager_remove_output_window(PurpleMediaManager *manager,
1573 gulong output_window_id)
1575 #ifdef USE_VV
1576 PurpleMediaOutputWindow *output_window = NULL;
1577 GList *iter;
1579 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
1581 iter = manager->priv->output_windows;
1582 for (; iter; iter = g_list_next(iter)) {
1583 PurpleMediaOutputWindow *ow = iter->data;
1584 if (ow->id == output_window_id) {
1585 manager->priv->output_windows = g_list_delete_link(
1586 manager->priv->output_windows, iter);
1587 output_window = ow;
1588 break;
1592 if (output_window == NULL)
1593 return FALSE;
1595 if (output_window->sink != NULL) {
1596 GstElement *element = output_window->sink;
1597 GstPad *teepad = NULL;
1598 GSList *to_remove = NULL;
1600 /* Find the tee element this output is connected to. */
1601 while (!teepad) {
1602 GstPad *pad;
1603 GstPad *peer;
1604 GstElementFactory *factory;
1605 const gchar *factory_name;
1607 to_remove = g_slist_append(to_remove, element);
1609 pad = gst_element_get_static_pad(element, "sink");
1610 peer = gst_pad_get_peer(pad);
1611 if (!peer) {
1612 /* Output is disconnected from the pipeline. */
1613 gst_object_unref(pad);
1614 break;
1617 factory = gst_element_get_factory(GST_PAD_PARENT(peer));
1618 factory_name = gst_plugin_feature_get_name(factory);
1619 if (purple_strequal(factory_name, "tee")) {
1620 teepad = peer;
1623 element = GST_PAD_PARENT(peer);
1625 gst_object_unref(pad);
1626 gst_object_unref(peer);
1629 if (teepad) {
1630 gst_element_release_request_pad(GST_PAD_PARENT(teepad),
1631 teepad);
1634 while (to_remove) {
1635 GstElement *element = to_remove->data;
1637 gst_element_set_locked_state(element, TRUE);
1638 gst_element_set_state(element, GST_STATE_NULL);
1639 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)),
1640 element);
1641 to_remove = g_slist_delete_link(to_remove, to_remove);
1645 g_free(output_window->session_id);
1646 g_free(output_window->participant);
1647 g_free(output_window);
1649 return TRUE;
1650 #else
1651 return FALSE;
1652 #endif
1655 void
1656 purple_media_manager_remove_output_windows(PurpleMediaManager *manager,
1657 PurpleMedia *media, const gchar *session_id,
1658 const gchar *participant)
1660 #ifdef USE_VV
1661 GList *iter;
1663 g_return_if_fail(PURPLE_IS_MEDIA(media));
1665 iter = manager->priv->output_windows;
1667 for (; iter;) {
1668 PurpleMediaOutputWindow *ow = iter->data;
1669 iter = g_list_next(iter);
1671 if (media == ow->media &&
1672 purple_strequal(session_id, ow->session_id) &&
1673 purple_strequal(participant, ow->participant))
1674 purple_media_manager_remove_output_window(
1675 manager, ow->id);
1677 #endif
1680 void
1681 purple_media_manager_set_ui_caps(PurpleMediaManager *manager,
1682 PurpleMediaCaps caps)
1684 #ifdef USE_VV
1685 PurpleMediaCaps oldcaps;
1687 g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager));
1689 oldcaps = manager->priv->ui_caps;
1690 manager->priv->ui_caps = caps;
1692 if (caps != oldcaps)
1693 g_signal_emit(manager,
1694 purple_media_manager_signals[UI_CAPS_CHANGED],
1695 0, caps, oldcaps);
1696 #endif
1699 PurpleMediaCaps
1700 purple_media_manager_get_ui_caps(PurpleMediaManager *manager)
1702 #ifdef USE_VV
1703 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager),
1704 PURPLE_MEDIA_CAPS_NONE);
1705 return manager->priv->ui_caps;
1706 #else
1707 return PURPLE_MEDIA_CAPS_NONE;
1708 #endif
1711 void
1712 purple_media_manager_set_backend_type(PurpleMediaManager *manager,
1713 GType backend_type)
1715 #ifdef USE_VV
1716 g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager));
1718 manager->priv->backend_type = backend_type;
1719 #endif
1722 GType
1723 purple_media_manager_get_backend_type(PurpleMediaManager *manager)
1725 #ifdef USE_VV
1726 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager),
1727 PURPLE_MEDIA_CAPS_NONE);
1729 return manager->priv->backend_type;
1730 #else
1731 return G_TYPE_NONE;
1732 #endif
1735 void
1736 purple_media_manager_set_application_data_callbacks(PurpleMediaManager *manager,
1737 PurpleMedia *media, const gchar *session_id,
1738 const gchar *participant, PurpleMediaAppDataCallbacks *callbacks,
1739 gpointer user_data, GDestroyNotify notify)
1741 #ifdef HAVE_MEDIA_APPLICATION
1742 PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
1743 media, session_id, participant);
1745 if (info->notify)
1746 info->notify (info->user_data);
1748 if (info->readable_cb_token) {
1749 purple_timeout_remove (info->readable_timer_id);
1750 info->readable_cb_token = 0;
1753 if (info->writable_cb_token) {
1754 purple_timeout_remove (info->writable_timer_id);
1755 info->writable_cb_token = 0;
1758 if (callbacks) {
1759 info->callbacks = *callbacks;
1760 } else {
1761 info->callbacks.writable = NULL;
1762 info->callbacks.readable = NULL;
1764 info->user_data = user_data;
1765 info->notify = notify;
1767 call_appsrc_writable_locked (info);
1768 if (info->num_samples > 0 || info->current_sample != NULL)
1769 call_appsink_readable_locked (info);
1771 g_mutex_unlock (&manager->priv->appdata_mutex);
1772 #endif
1775 gint
1776 purple_media_manager_send_application_data (
1777 PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
1778 const gchar *participant, gpointer buffer, guint size, gboolean blocking)
1780 #ifdef HAVE_MEDIA_APPLICATION
1781 PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager,
1782 media, session_id, participant);
1784 if (info && info->appsrc && info->connected) {
1785 GstBuffer *gstbuffer = gst_buffer_new_wrapped (g_memdup (buffer, size),
1786 size);
1787 GstAppSrc *appsrc = gst_object_ref (info->appsrc);
1789 g_mutex_unlock (&manager->priv->appdata_mutex);
1790 if (gst_app_src_push_buffer (appsrc, gstbuffer) == GST_FLOW_OK) {
1791 if (blocking) {
1792 GstPad *srcpad;
1794 srcpad = gst_element_get_static_pad (GST_ELEMENT (appsrc),
1795 "src");
1796 if (srcpad) {
1797 gst_pad_peer_query (srcpad, gst_query_new_drain ());
1798 gst_object_unref (srcpad);
1801 gst_object_unref (appsrc);
1802 return size;
1803 } else {
1804 gst_object_unref (appsrc);
1805 return -1;
1808 g_mutex_unlock (&manager->priv->appdata_mutex);
1809 return -1;
1810 #else
1811 return -1;
1812 #endif
1815 gint
1816 purple_media_manager_receive_application_data (
1817 PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
1818 const gchar *participant, gpointer buffer, guint max_size,
1819 gboolean blocking)
1821 #ifdef HAVE_MEDIA_APPLICATION
1822 PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager,
1823 media, session_id, participant);
1824 guint bytes_read = 0;
1826 if (info) {
1827 /* If we are in a blocking read, we need to loop until max_size data
1828 * is read into the buffer, if we're not, then we need to read as much
1829 * data as possible
1831 do {
1832 if (!info->current_sample && info->appsink && info->num_samples > 0) {
1833 info->current_sample = gst_app_sink_pull_sample (info->appsink);
1834 info->sample_offset = 0;
1835 if (info->current_sample)
1836 info->num_samples--;
1839 if (info->current_sample) {
1840 GstBuffer *gstbuffer = gst_sample_get_buffer (
1841 info->current_sample);
1843 if (gstbuffer) {
1844 GstMapInfo mapinfo;
1845 guint bytes_to_copy;
1847 gst_buffer_map (gstbuffer, &mapinfo, GST_MAP_READ);
1848 /* We must copy only the data remaining in the buffer without
1849 * overflowing the buffer */
1850 bytes_to_copy = max_size - bytes_read;
1851 if (bytes_to_copy > mapinfo.size - info->sample_offset)
1852 bytes_to_copy = mapinfo.size - info->sample_offset;
1853 memcpy ((guint8 *)buffer + bytes_read,
1854 mapinfo.data + info->sample_offset, bytes_to_copy);
1856 gst_buffer_unmap (gstbuffer, &mapinfo);
1857 info->sample_offset += bytes_to_copy;
1858 bytes_read += bytes_to_copy;
1859 if (info->sample_offset == mapinfo.size) {
1860 gst_sample_unref (info->current_sample);
1861 info->current_sample = NULL;
1862 info->sample_offset = 0;
1864 } else {
1865 /* In case there's no buffer in the sample (should never
1866 * happen), we need to at least unref it */
1867 gst_sample_unref (info->current_sample);
1868 info->current_sample = NULL;
1869 info->sample_offset = 0;
1873 /* If blocking, wait until there's an available sample */
1874 while (bytes_read < max_size && blocking &&
1875 info->current_sample == NULL && info->num_samples == 0) {
1876 g_cond_wait (&info->readable_cond, &manager->priv->appdata_mutex);
1878 /* We've been signaled, we need to unlock and regrab the info
1879 * struct to make sure nothing changed */
1880 g_mutex_unlock (&manager->priv->appdata_mutex);
1881 info = get_app_data_info_and_lock (manager,
1882 media, session_id, participant);
1883 if (info == NULL || info->appsink == NULL) {
1884 /* The session was destroyed while we were waiting, we
1885 * should return here */
1886 g_mutex_unlock (&manager->priv->appdata_mutex);
1887 return bytes_read;
1890 } while (bytes_read < max_size &&
1891 (blocking || info->num_samples > 0));
1893 g_mutex_unlock (&manager->priv->appdata_mutex);
1894 return bytes_read;
1896 g_mutex_unlock (&manager->priv->appdata_mutex);
1897 return -1;
1898 #else
1899 return -1;
1900 #endif
1903 #ifdef USE_GSTREAMER
1906 * PurpleMediaElementType
1909 GType
1910 purple_media_element_type_get_type()
1912 static GType type = 0;
1913 if (type == 0) {
1914 static const GFlagsValue values[] = {
1915 { PURPLE_MEDIA_ELEMENT_NONE,
1916 "PURPLE_MEDIA_ELEMENT_NONE", "none" },
1917 { PURPLE_MEDIA_ELEMENT_AUDIO,
1918 "PURPLE_MEDIA_ELEMENT_AUDIO", "audio" },
1919 { PURPLE_MEDIA_ELEMENT_VIDEO,
1920 "PURPLE_MEDIA_ELEMENT_VIDEO", "video" },
1921 { PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO,
1922 "PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO",
1923 "audio-video" },
1924 { PURPLE_MEDIA_ELEMENT_NO_SRCS,
1925 "PURPLE_MEDIA_ELEMENT_NO_SRCS", "no-srcs" },
1926 { PURPLE_MEDIA_ELEMENT_ONE_SRC,
1927 "PURPLE_MEDIA_ELEMENT_ONE_SRC", "one-src" },
1928 { PURPLE_MEDIA_ELEMENT_MULTI_SRC,
1929 "PURPLE_MEDIA_ELEMENT_MULTI_SRC",
1930 "multi-src" },
1931 { PURPLE_MEDIA_ELEMENT_REQUEST_SRC,
1932 "PURPLE_MEDIA_ELEMENT_REQUEST_SRC",
1933 "request-src" },
1934 { PURPLE_MEDIA_ELEMENT_NO_SINKS,
1935 "PURPLE_MEDIA_ELEMENT_NO_SINKS", "no-sinks" },
1936 { PURPLE_MEDIA_ELEMENT_ONE_SINK,
1937 "PURPLE_MEDIA_ELEMENT_ONE_SINK", "one-sink" },
1938 { PURPLE_MEDIA_ELEMENT_MULTI_SINK,
1939 "PURPLE_MEDIA_ELEMENT_MULTI_SINK",
1940 "multi-sink" },
1941 { PURPLE_MEDIA_ELEMENT_REQUEST_SINK,
1942 "PURPLE_MEDIA_ELEMENT_REQUEST_SINK",
1943 "request-sink" },
1944 { PURPLE_MEDIA_ELEMENT_UNIQUE,
1945 "PURPLE_MEDIA_ELEMENT_UNIQUE", "unique" },
1946 { PURPLE_MEDIA_ELEMENT_SRC,
1947 "PURPLE_MEDIA_ELEMENT_SRC", "src" },
1948 { PURPLE_MEDIA_ELEMENT_SINK,
1949 "PURPLE_MEDIA_ELEMENT_SINK", "sink" },
1950 { PURPLE_MEDIA_ELEMENT_APPLICATION,
1951 "PURPLE_MEDIA_ELEMENT_APPLICATION", "application" },
1952 { 0, NULL, NULL }
1954 type = g_flags_register_static(
1955 "PurpleMediaElementType", values);
1957 return type;
1961 * PurpleMediaElementInfo
1964 struct _PurpleMediaElementInfoClass
1966 GObjectClass parent_class;
1969 struct _PurpleMediaElementInfo
1971 GObject parent;
1974 #ifdef USE_VV
1975 struct _PurpleMediaElementInfoPrivate
1977 gchar *id;
1978 gchar *name;
1979 PurpleMediaElementType type;
1980 PurpleMediaElementCreateCallback create;
1983 enum {
1984 PROP_0,
1985 PROP_ID,
1986 PROP_NAME,
1987 PROP_TYPE,
1988 PROP_CREATE_CB,
1991 static void
1992 purple_media_element_info_init(PurpleMediaElementInfo *info)
1994 PurpleMediaElementInfoPrivate *priv =
1995 PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info);
1996 priv->id = NULL;
1997 priv->name = NULL;
1998 priv->type = PURPLE_MEDIA_ELEMENT_NONE;
1999 priv->create = NULL;
2002 static void
2003 purple_media_element_info_finalize(GObject *info)
2005 PurpleMediaElementInfoPrivate *priv =
2006 PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info);
2007 g_free(priv->id);
2008 g_free(priv->name);
2011 static void
2012 purple_media_element_info_set_property (GObject *object, guint prop_id,
2013 const GValue *value, GParamSpec *pspec)
2015 PurpleMediaElementInfoPrivate *priv;
2016 g_return_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(object));
2018 priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(object);
2020 switch (prop_id) {
2021 case PROP_ID:
2022 g_free(priv->id);
2023 priv->id = g_value_dup_string(value);
2024 break;
2025 case PROP_NAME:
2026 g_free(priv->name);
2027 priv->name = g_value_dup_string(value);
2028 break;
2029 case PROP_TYPE: {
2030 priv->type = g_value_get_flags(value);
2031 break;
2033 case PROP_CREATE_CB:
2034 priv->create = g_value_get_pointer(value);
2035 break;
2036 default:
2037 G_OBJECT_WARN_INVALID_PROPERTY_ID(
2038 object, prop_id, pspec);
2039 break;
2043 static void
2044 purple_media_element_info_get_property (GObject *object, guint prop_id,
2045 GValue *value, GParamSpec *pspec)
2047 PurpleMediaElementInfoPrivate *priv;
2048 g_return_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(object));
2050 priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(object);
2052 switch (prop_id) {
2053 case PROP_ID:
2054 g_value_set_string(value, priv->id);
2055 break;
2056 case PROP_NAME:
2057 g_value_set_string(value, priv->name);
2058 break;
2059 case PROP_TYPE:
2060 g_value_set_flags(value, priv->type);
2061 break;
2062 case PROP_CREATE_CB:
2063 g_value_set_pointer(value, priv->create);
2064 break;
2065 default:
2066 G_OBJECT_WARN_INVALID_PROPERTY_ID(
2067 object, prop_id, pspec);
2068 break;
2072 static void
2073 purple_media_element_info_class_init(PurpleMediaElementInfoClass *klass)
2075 GObjectClass *gobject_class = (GObjectClass*)klass;
2077 gobject_class->finalize = purple_media_element_info_finalize;
2078 gobject_class->set_property = purple_media_element_info_set_property;
2079 gobject_class->get_property = purple_media_element_info_get_property;
2081 g_object_class_install_property(gobject_class, PROP_ID,
2082 g_param_spec_string("id",
2083 "ID",
2084 "The unique identifier of the element.",
2085 NULL,
2086 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
2088 g_object_class_install_property(gobject_class, PROP_NAME,
2089 g_param_spec_string("name",
2090 "Name",
2091 "The friendly/display name of this element.",
2092 NULL,
2093 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
2095 g_object_class_install_property(gobject_class, PROP_TYPE,
2096 g_param_spec_flags("type",
2097 "Element Type",
2098 "The type of element this is.",
2099 PURPLE_TYPE_MEDIA_ELEMENT_TYPE,
2100 PURPLE_MEDIA_ELEMENT_NONE,
2101 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
2103 g_object_class_install_property(gobject_class, PROP_CREATE_CB,
2104 g_param_spec_pointer("create-cb",
2105 "Create Callback",
2106 "The function called to create this element.",
2107 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
2109 g_type_class_add_private(klass, sizeof(PurpleMediaElementInfoPrivate));
2112 G_DEFINE_TYPE(PurpleMediaElementInfo,
2113 purple_media_element_info, G_TYPE_OBJECT);
2114 #else
2115 GType
2116 purple_media_element_info_get_type()
2118 return G_TYPE_NONE;
2120 #endif
2122 gchar *
2123 purple_media_element_info_get_id(PurpleMediaElementInfo *info)
2125 #ifdef USE_VV
2126 gchar *id;
2127 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL);
2128 g_object_get(info, "id", &id, NULL);
2129 return id;
2130 #else
2131 return NULL;
2132 #endif
2135 gchar *
2136 purple_media_element_info_get_name(PurpleMediaElementInfo *info)
2138 #ifdef USE_VV
2139 gchar *name;
2140 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL);
2141 g_object_get(info, "name", &name, NULL);
2142 return name;
2143 #else
2144 return NULL;
2145 #endif
2148 PurpleMediaElementType
2149 purple_media_element_info_get_element_type(PurpleMediaElementInfo *info)
2151 #ifdef USE_VV
2152 PurpleMediaElementType type;
2153 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info),
2154 PURPLE_MEDIA_ELEMENT_NONE);
2155 g_object_get(info, "type", &type, NULL);
2156 return type;
2157 #else
2158 return PURPLE_MEDIA_ELEMENT_NONE;
2159 #endif
2162 GstElement *
2163 purple_media_element_info_call_create(PurpleMediaElementInfo *info,
2164 PurpleMedia *media, const gchar *session_id,
2165 const gchar *participant)
2167 #ifdef USE_VV
2168 PurpleMediaElementCreateCallback create;
2169 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL);
2170 g_object_get(info, "create-cb", &create, NULL);
2171 if (create)
2172 return create(media, session_id, participant);
2173 #endif
2174 return NULL;
2178 #endif /* USE_GSTREAMER */