2 * @file mediamanager.c Media Manager API
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
32 #include "mediamanager.h"
35 #include "marshallers.h"
36 #include "media-gst.h"
40 #include <media/backend-fs2.h>
43 #include <gst/farsight/fs-element-added-notifier.h>
45 #include <farstream/fs-element-added-notifier.h>
47 #ifdef HAVE_MEDIA_APPLICATION
48 #include <gst/app/app.h>
51 #if GST_CHECK_VERSION(1,0,0)
52 #include <gst/video/videooverlay.h>
54 #include <gst/interfaces/xoverlay.h>
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
87 struct _PurpleMediaManagerPrivate
90 PurpleMediaCaps ui_caps
;
92 GList
*private_medias
;
94 GList
*output_windows
;
95 gulong next_output_window_id
;
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 */
112 #ifdef HAVE_MEDIA_APPLICATION
118 PurpleMediaAppDataCallbacks callbacks
;
120 GDestroyNotify notify
;
124 GstSample
*current_sample
;
128 guint writable_cb_token
;
129 guint readable_cb_token
;
130 guint writable_timer_id
;
131 guint readable_timer_id
;
133 } PurpleMediaAppDataInfo
;
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
);
146 static GObjectClass
*parent_class
= NULL
;
156 static guint purple_media_manager_signals
[LAST_SIGNAL
] = {0};
160 purple_media_manager_get_type()
163 static GType type
= 0;
166 static const GTypeInfo info
= {
167 sizeof(PurpleMediaManagerClass
),
170 (GClassInitFunc
) purple_media_manager_class_init
,
173 sizeof(PurpleMediaManager
),
175 (GInstanceInitFunc
) purple_media_manager_init
,
178 type
= g_type_register_static(G_TYPE_OBJECT
, "PurpleMediaManager", &info
, 0);
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
),
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
),
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
),
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
));
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
);
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);
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
);
269 parent_class
->finalize(media
);
274 purple_media_manager_get()
277 static PurpleMediaManager
*manager
= NULL
;
280 manager
= PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL
));
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");
295 case GST_MESSAGE_ERROR
: {
299 gst_message_parse_error(msg
, &err
, &debug
);
301 purple_debug_error("mediamanager",
302 "gst pipeline error: %s\n",
307 purple_debug_error("mediamanager",
308 "Debug details: %s\n", debug
);
322 purple_media_manager_get_pipeline(PurpleMediaManager
*manager
)
325 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager
), NULL
);
327 if (manager
->priv
->pipeline
== NULL
) {
328 FsElementAddedNotifier
*notifier
;
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
);
343 gst_bus_set_sync_handler(bus
, gst_bus_sync_signal_handler
, NULL
);
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
)) {
353 purple_debug_info("mediamanager",
355 "fs-element.conf: %s\n",
358 purple_debug_error("mediamanager",
360 "fs-element.conf: %s\n",
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(
379 gst_element_set_state(manager
->priv
->pipeline
,
383 return manager
->priv
->pipeline
;
388 #endif /* USE_GSTREAMER */
391 create_media(PurpleMediaManager
*manager
,
392 PurpleAccount
*account
,
393 const char *conference_type
,
394 const char *remote_user
,
402 media
= PURPLE_MEDIA(g_object_new(purple_media_get_type(),
405 "conference-type", conference_type
,
406 "initiator", initiator
,
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
)) {
416 g_signal_emit(manager
, signal_id
, 0, media
, account
, remote_user
,
418 if (signal_ret
== FALSE
) {
419 g_object_unref(media
);
425 manager
->priv
->private_medias
= g_list_append(
426 manager
->priv
->private_medias
, media
);
428 manager
->priv
->medias
= g_list_append(manager
->priv
->medias
, media
);
436 get_media(PurpleMediaManager
*manager
, gboolean
private)
440 return manager
->priv
->private_medias
;
442 return manager
->priv
->medias
;
449 get_media_by_account(PurpleMediaManager
*manager
,
450 PurpleAccount
*account
, gboolean
private)
456 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager
), NULL
);
459 iter
= manager
->priv
->private_medias
;
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
);
475 purple_media_manager_remove_media(PurpleMediaManager
*manager
, PurpleMedia
*media
)
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
;
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
;
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
);
507 g_mutex_unlock (&manager
->priv
->appdata_mutex
);
514 purple_media_manager_create_media(PurpleMediaManager
*manager
,
515 PurpleAccount
*account
,
516 const char *conference_type
,
517 const char *remote_user
,
520 return create_media (manager
, account
, conference_type
,
521 remote_user
, initiator
, FALSE
);
525 purple_media_manager_get_media(PurpleMediaManager
*manager
)
527 return get_media (manager
, FALSE
);
531 purple_media_manager_get_media_by_account(PurpleMediaManager
*manager
,
532 PurpleAccount
*account
)
534 return get_media_by_account (manager
, account
, FALSE
);
538 purple_media_manager_create_private_media(PurpleMediaManager
*manager
,
539 PurpleAccount
*account
,
540 const char *conference_type
,
541 const char *remote_user
,
544 return create_media (manager
, account
, conference_type
,
545 remote_user
, initiator
, TRUE
);
549 purple_media_manager_get_private_media(PurpleMediaManager
*manager
)
551 return get_media (manager
, TRUE
);
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
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
} };
569 info
->notify (info
->user_data
);
573 /* Will call appsrc_destroyed. */
574 gst_app_src_set_callbacks (info
->appsrc
, &null_src_cb
,
578 /* Will call appsink_destroyed. */
579 gst_app_sink_set_callbacks (info
->appsink
, &null_sink_cb
,
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
)
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
))) {
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
);
652 info
= g_slice_new0 (PurpleMediaAppDataInfo
);
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
);
669 request_pad_unlinked_cb(GstPad
*pad
, GstPad
*peer
, gpointer user_data
)
671 GstElement
*parent
= GST_ELEMENT_PARENT(pad
);
673 #if GST_CHECK_VERSION(1,0,0)
674 GValue tmp
= G_VALUE_INIT
;
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
);
686 result
= gst_iterator_next(iter
, (gpointer
)&remaining_pad
);
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
);
698 gst_object_unref(remaining_pad
);
701 gst_iterator_free(iter
);
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
);
718 purple_media_manager_set_video_caps(PurpleMediaManager
*manager
, GstCaps
*caps
)
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
);
731 GstElement
*capsfilter
= gst_bin_get_by_name(GST_BIN(src
), "prpl_video_caps");
733 g_object_set(G_OBJECT(capsfilter
), "caps", caps
, NULL
);
734 gst_object_unref (capsfilter
);
736 gst_object_unref (src
);
745 purple_media_manager_get_video_caps(PurpleMediaManager
*manager
)
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,"
752 manager
->priv
->video_caps
= gst_caps_from_string("video/x-raw-yuv,"
754 "width=[250,352], height=[200,288], framerate=[1/1,20/1]");
755 return manager
->priv
->video_caps
;
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.
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
,
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
);
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
,
815 g_object_unref (media
);
817 g_free (participant
);
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
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
)
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
846 info
->writable_cb_token
= ++manager
->priv
->appdata_cb_token
;
847 info
->writable_timer_id
= purple_timeout_add (0, appsrc_writable
, info
);
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 */
861 call_appsrc_writable_locked (info
);
863 g_mutex_unlock (&manager
->priv
->appdata_mutex
);
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
);
881 appsrc_seek_data (GstAppSrc
*appsrc
, guint64 offset
, gpointer user_data
)
887 appsrc_destroyed (PurpleMediaAppDataInfo
*info
)
889 PurpleMediaManager
*manager
;
892 /* PurpleMediaAppDataInfo is being freed. Return at once. */
896 manager
= purple_media_manager_get ();
898 g_mutex_lock (&manager
->priv
->appdata_mutex
);
900 if (info
->writable
) {
901 info
->writable
= FALSE
;
902 call_appsrc_writable_locked (info
);
904 g_mutex_unlock (&manager
->priv
->appdata_mutex
);
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
919 call_appsrc_writable_locked (info
);
920 g_mutex_unlock (&manager
->priv
->appdata_mutex
);
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
);
954 appsink_eos (GstAppSink
*appsink
, gpointer user_data
)
959 appsink_new_preroll (GstAppSink
*appsink
, gpointer user_data
)
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
);
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
);
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
);
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
);
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
) {
1014 info
->readable_cb_token
= 0;
1017 g_mutex_unlock (&manager
->priv
->appdata_mutex
);
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
)
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
);
1052 appsink_destroyed (PurpleMediaAppDataInfo
*info
)
1054 PurpleMediaManager
*manager
;
1057 /* PurpleMediaAppDataInfo is being freed. Return at once. */
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
);
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
);
1099 static PurpleMediaElementInfo
*
1100 get_send_application_element_info ()
1102 static PurpleMediaElementInfo
*info
= NULL
;
1104 #ifdef HAVE_MEDIA_APPLICATION
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
);
1120 static PurpleMediaElementInfo
*
1121 get_recv_application_element_info ()
1123 static PurpleMediaElementInfo
*info
= NULL
;
1125 #ifdef HAVE_MEDIA_APPLICATION
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
);
1141 purple_media_manager_get_element(PurpleMediaManager
*manager
,
1142 PurpleMediaSessionType type
, PurpleMedia
*media
,
1143 const gchar
*session_id
, const gchar
*participant
)
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 ();
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
) {
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(
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
);
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
,
1210 "enable-last-sample", FALSE
,
1212 gst_bin_add(GST_BIN(bin
), fakesink
);
1213 gst_element_link(tee
, fakesink
);
1216 gst_object_ref(ret
);
1217 gst_bin_add(GST_BIN(purple_media_manager_get_pipeline(
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");
1226 pad
= gst_element_get_request_pad(tee
, "src%d");
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
);
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
)),
1250 purple_debug_error("media", "Error creating source or sink\n");
1258 PurpleMediaElementInfo
*
1259 purple_media_manager_get_element_info(PurpleMediaManager
*manager
,
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
)) {
1271 purple_media_element_info_get_id(iter
->data
);
1272 if (purple_strequal(element_id
, id
)) {
1274 g_object_ref(iter
->data
);
1285 purple_media_manager_register_element(PurpleMediaManager
*manager
,
1286 PurpleMediaElementInfo
*info
)
1289 PurpleMediaElementInfo
*info2
;
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
);
1299 if (info2
!= NULL
) {
1300 g_object_unref(info2
);
1304 manager
->priv
->elements
=
1305 g_list_prepend(manager
->priv
->elements
, info
);
1313 purple_media_manager_unregister_element(PurpleMediaManager
*manager
,
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
);
1324 g_object_unref(info
);
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
);
1347 purple_media_manager_set_active_element(PurpleMediaManager
*manager
,
1348 PurpleMediaElementInfo
*info
)
1351 PurpleMediaElementInfo
*info2
;
1352 PurpleMediaElementType type
;
1353 gboolean ret
= FALSE
;
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
);
1364 purple_media_manager_register_element(manager
, info
);
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
;
1375 if (type
& PURPLE_MEDIA_ELEMENT_VIDEO
) {
1376 manager
->priv
->video_src
= info
;
1380 if (type
& PURPLE_MEDIA_ELEMENT_SINK
) {
1381 if (type
& PURPLE_MEDIA_ELEMENT_AUDIO
) {
1382 manager
->priv
->audio_sink
= info
;
1385 if (type
& PURPLE_MEDIA_ELEMENT_VIDEO
) {
1386 manager
->priv
->video_sink
= info
;
1397 PurpleMediaElementInfo
*
1398 purple_media_manager_get_active_element(PurpleMediaManager
*manager
,
1399 PurpleMediaElementType type
)
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 ();
1424 #endif /* USE_GSTREAMER */
1428 window_id_cb(GstBus
*bus
, GstMessage
*msg
, PurpleMediaOutputWindow
*ow
)
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
))
1436 || !gst_structure_has_name(msg
->structure
, "prepare-xwindow-id"))
1440 sink
= GST_ELEMENT(GST_MESSAGE_SRC(msg
));
1441 while (sink
!= ow
->sink
) {
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
,
1451 #if GST_CHECK_VERSION(1,0,0)
1452 gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(msg
)),
1454 #elif GST_CHECK_VERSION(0,10,31)
1455 gst_x_overlay_set_window_handle(GST_X_OVERLAY(GST_MESSAGE_SRC(msg
)),
1458 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(msg
)),
1465 purple_media_manager_create_output_window(PurpleMediaManager
*manager
,
1466 PurpleMedia
*media
, const gchar
*session_id
,
1467 const gchar
*participant
)
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
)) {
1482 GstElement
*queue
, *convert
, *scale
;
1483 GstElement
*tee
= purple_media_get_tee(media
,
1484 session_id
, participant
);
1489 queue
= gst_element_factory_make("queue", NULL
);
1490 #if GST_CHECK_VERSION(1,0,0)
1491 convert
= gst_element_factory_make("videoconvert", NULL
);
1493 convert
= gst_element_factory_make("ffmpegcolorspace", NULL
);
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
,
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
,
1507 g_object_set(G_OBJECT(ow
->sink
),
1508 "sync", FALSE
, NULL
);
1509 if (g_object_class_find_property(klass
,
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
);
1541 purple_media_manager_set_output_window(PurpleMediaManager
*manager
,
1542 PurpleMedia
*media
, const gchar
*session_id
,
1543 const gchar
*participant
, gulong window_id
)
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
;
1572 purple_media_manager_remove_output_window(PurpleMediaManager
*manager
,
1573 gulong output_window_id
)
1576 PurpleMediaOutputWindow
*output_window
= NULL
;
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
);
1592 if (output_window
== NULL
)
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. */
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
);
1612 /* Output is disconnected from the pipeline. */
1613 gst_object_unref(pad
);
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")) {
1623 element
= GST_PAD_PARENT(peer
);
1625 gst_object_unref(pad
);
1626 gst_object_unref(peer
);
1630 gst_element_release_request_pad(GST_PAD_PARENT(teepad
),
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
)),
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
);
1656 purple_media_manager_remove_output_windows(PurpleMediaManager
*manager
,
1657 PurpleMedia
*media
, const gchar
*session_id
,
1658 const gchar
*participant
)
1663 g_return_if_fail(PURPLE_IS_MEDIA(media
));
1665 iter
= manager
->priv
->output_windows
;
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(
1681 purple_media_manager_set_ui_caps(PurpleMediaManager
*manager
,
1682 PurpleMediaCaps caps
)
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
],
1700 purple_media_manager_get_ui_caps(PurpleMediaManager
*manager
)
1703 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager
),
1704 PURPLE_MEDIA_CAPS_NONE
);
1705 return manager
->priv
->ui_caps
;
1707 return PURPLE_MEDIA_CAPS_NONE
;
1712 purple_media_manager_set_backend_type(PurpleMediaManager
*manager
,
1716 g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager
));
1718 manager
->priv
->backend_type
= backend_type
;
1723 purple_media_manager_get_backend_type(PurpleMediaManager
*manager
)
1726 g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager
),
1727 PURPLE_MEDIA_CAPS_NONE
);
1729 return manager
->priv
->backend_type
;
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
);
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;
1759 info
->callbacks
= *callbacks
;
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
);
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
),
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
) {
1794 srcpad
= gst_element_get_static_pad (GST_ELEMENT (appsrc
),
1797 gst_pad_peer_query (srcpad
, gst_query_new_drain ());
1798 gst_object_unref (srcpad
);
1801 gst_object_unref (appsrc
);
1804 gst_object_unref (appsrc
);
1808 g_mutex_unlock (&manager
->priv
->appdata_mutex
);
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
,
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;
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
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
);
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;
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
);
1890 } while (bytes_read
< max_size
&&
1891 (blocking
|| info
->num_samples
> 0));
1893 g_mutex_unlock (&manager
->priv
->appdata_mutex
);
1896 g_mutex_unlock (&manager
->priv
->appdata_mutex
);
1903 #ifdef USE_GSTREAMER
1906 * PurpleMediaElementType
1910 purple_media_element_type_get_type()
1912 static GType 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",
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",
1931 { PURPLE_MEDIA_ELEMENT_REQUEST_SRC
,
1932 "PURPLE_MEDIA_ELEMENT_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",
1941 { PURPLE_MEDIA_ELEMENT_REQUEST_SINK
,
1942 "PURPLE_MEDIA_ELEMENT_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" },
1954 type
= g_flags_register_static(
1955 "PurpleMediaElementType", values
);
1961 * PurpleMediaElementInfo
1964 struct _PurpleMediaElementInfoClass
1966 GObjectClass parent_class
;
1969 struct _PurpleMediaElementInfo
1975 struct _PurpleMediaElementInfoPrivate
1979 PurpleMediaElementType type
;
1980 PurpleMediaElementCreateCallback create
;
1992 purple_media_element_info_init(PurpleMediaElementInfo
*info
)
1994 PurpleMediaElementInfoPrivate
*priv
=
1995 PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info
);
1998 priv
->type
= PURPLE_MEDIA_ELEMENT_NONE
;
1999 priv
->create
= NULL
;
2003 purple_media_element_info_finalize(GObject
*info
)
2005 PurpleMediaElementInfoPrivate
*priv
=
2006 PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info
);
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
);
2023 priv
->id
= g_value_dup_string(value
);
2027 priv
->name
= g_value_dup_string(value
);
2030 priv
->type
= g_value_get_flags(value
);
2033 case PROP_CREATE_CB
:
2034 priv
->create
= g_value_get_pointer(value
);
2037 G_OBJECT_WARN_INVALID_PROPERTY_ID(
2038 object
, prop_id
, pspec
);
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
);
2054 g_value_set_string(value
, priv
->id
);
2057 g_value_set_string(value
, priv
->name
);
2060 g_value_set_flags(value
, priv
->type
);
2062 case PROP_CREATE_CB
:
2063 g_value_set_pointer(value
, priv
->create
);
2066 G_OBJECT_WARN_INVALID_PROPERTY_ID(
2067 object
, prop_id
, pspec
);
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",
2084 "The unique identifier of the element.",
2086 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_READWRITE
));
2088 g_object_class_install_property(gobject_class
, PROP_NAME
,
2089 g_param_spec_string("name",
2091 "The friendly/display name of this element.",
2093 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_READWRITE
));
2095 g_object_class_install_property(gobject_class
, PROP_TYPE
,
2096 g_param_spec_flags("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",
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
);
2116 purple_media_element_info_get_type()
2123 purple_media_element_info_get_id(PurpleMediaElementInfo
*info
)
2127 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info
), NULL
);
2128 g_object_get(info
, "id", &id
, NULL
);
2136 purple_media_element_info_get_name(PurpleMediaElementInfo
*info
)
2140 g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info
), NULL
);
2141 g_object_get(info
, "name", &name
, NULL
);
2148 PurpleMediaElementType
2149 purple_media_element_info_get_element_type(PurpleMediaElementInfo
*info
)
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
);
2158 return PURPLE_MEDIA_ELEMENT_NONE
;
2163 purple_media_element_info_call_create(PurpleMediaElementInfo
*info
,
2164 PurpleMedia
*media
, const gchar
*session_id
,
2165 const gchar
*participant
)
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
);
2172 return create(media
, session_id
, participant
);
2178 #endif /* USE_GSTREAMER */