rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / media / backend-fs2.c
blob5237d96e9063cb17a54b851e18e5627fab2b4641
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "internal.h"
23 #include "glibcompat.h"
25 #include "backend-fs2.h"
27 #ifdef USE_VV
28 #include "backend-iface.h"
29 #include "debug.h"
30 #include "network.h"
31 #include "media-gst.h"
33 #include <farstream/fs-conference.h>
34 #include <farstream/fs-element-added-notifier.h>
35 #include <farstream/fs-utils.h>
36 #include <gst/gststructure.h>
38 /** @copydoc _PurpleMediaBackendFs2Session */
39 typedef struct _PurpleMediaBackendFs2Session PurpleMediaBackendFs2Session;
40 /** @copydoc _PurpleMediaBackendFs2Stream */
41 typedef struct _PurpleMediaBackendFs2Stream PurpleMediaBackendFs2Stream;
43 static void purple_media_backend_iface_init(PurpleMediaBackendInterface *iface);
45 static gboolean
46 gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self);
47 static void
48 state_changed_cb(PurpleMedia *media, PurpleMediaState state,
49 gchar *sid, gchar *name, PurpleMediaBackendFs2 *self);
50 static void
51 stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
52 gchar *sid, gchar *name, gboolean local,
53 PurpleMediaBackendFs2 *self);
55 static gboolean purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
56 const gchar *sess_id, const gchar *who,
57 PurpleMediaSessionType type, gboolean initiator,
58 const gchar *transmitter,
59 guint num_params, GParameter *params);
60 static void purple_media_backend_fs2_add_remote_candidates(
61 PurpleMediaBackend *self,
62 const gchar *sess_id, const gchar *participant,
63 GList *remote_candidates);
64 static gboolean purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
65 const gchar *sess_id);
66 static GList *purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
67 const gchar *sess_id);
68 static GList *purple_media_backend_fs2_get_local_candidates(
69 PurpleMediaBackend *self,
70 const gchar *sess_id, const gchar *participant);
71 static gboolean purple_media_backend_fs2_set_encryption_parameters (
72 PurpleMediaBackend *self, const gchar *sess_id, const gchar *cipher,
73 const gchar *auth, const gchar *key, gsize key_len);
74 static gboolean purple_media_backend_fs2_set_decryption_parameters(
75 PurpleMediaBackend *self, const gchar *sess_id,
76 const gchar *participant, const gchar *cipher,
77 const gchar *auth, const gchar *key, gsize key_len);
78 static gboolean purple_media_backend_fs2_set_remote_codecs(
79 PurpleMediaBackend *self,
80 const gchar *sess_id, const gchar *participant,
81 GList *codecs);
82 static gboolean purple_media_backend_fs2_set_send_codec(
83 PurpleMediaBackend *self, const gchar *sess_id,
84 PurpleMediaCodec *codec);
85 static void purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
86 guint num_params, GParameter *params);
87 static const gchar **purple_media_backend_fs2_get_available_params(void);
88 static gboolean purple_media_backend_fs2_send_dtmf(
89 PurpleMediaBackend *self, const gchar *sess_id,
90 gchar dtmf, guint8 volume, guint16 duration);
91 static gboolean purple_media_backend_fs2_set_send_rtcp_mux(
92 PurpleMediaBackend *self,
93 const gchar *sess_id, const gchar *participant,
94 gboolean send_rtcp_mux);
96 static void free_stream(PurpleMediaBackendFs2Stream *stream);
97 static void free_session(PurpleMediaBackendFs2Session *session);
99 /**
100 * PurpleMediaBackendFs2:
102 * An opaque structure representing the Farstream media backend.
104 struct _PurpleMediaBackendFs2
106 GObject parent;
109 struct _PurpleMediaBackendFs2Stream
111 PurpleMediaBackendFs2Session *session;
112 gchar *participant;
113 FsStream *stream;
115 gboolean supports_add;
117 GstElement *src;
118 GstElement *tee;
119 GstElement *volume;
120 GstElement *level;
121 GstElement *fakesink;
122 GstElement *queue;
124 GList *local_candidates;
125 GList *remote_candidates;
127 guint connected_cb_id;
130 struct _PurpleMediaBackendFs2Session
132 PurpleMediaBackendFs2 *backend;
133 gchar *id;
134 FsSession *session;
136 GstElement *src;
137 GstElement *tee;
138 GstElement *srcvalve;
140 GstPad *srcpad;
142 PurpleMediaSessionType type;
145 typedef struct
147 PurpleMedia *media;
148 GstElement *confbin;
149 FsConference *conference;
150 gchar *conference_type;
152 FsElementAddedNotifier *notifier;
154 GHashTable *sessions;
155 GHashTable *participants;
157 GList *streams;
159 gdouble silence_threshold;
160 } PurpleMediaBackendFs2Private;
162 enum {
163 PROP_0,
164 PROP_CONFERENCE_TYPE,
165 PROP_MEDIA,
168 G_DEFINE_TYPE_WITH_CODE(PurpleMediaBackendFs2, purple_media_backend_fs2,
169 G_TYPE_OBJECT,
170 G_ADD_PRIVATE(PurpleMediaBackendFs2)
171 G_IMPLEMENT_INTERFACE(PURPLE_TYPE_MEDIA_BACKEND,
172 purple_media_backend_iface_init));
174 static void
175 purple_media_backend_fs2_init(PurpleMediaBackendFs2 *self)
179 static FsCandidateType
180 purple_media_candidate_type_to_fs(PurpleMediaCandidateType type)
182 switch (type) {
183 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST:
184 return FS_CANDIDATE_TYPE_HOST;
185 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX:
186 return FS_CANDIDATE_TYPE_SRFLX;
187 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX:
188 return FS_CANDIDATE_TYPE_PRFLX;
189 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY:
190 return FS_CANDIDATE_TYPE_RELAY;
191 case PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST:
192 return FS_CANDIDATE_TYPE_MULTICAST;
194 g_return_val_if_reached(FS_CANDIDATE_TYPE_HOST);
197 static PurpleMediaCandidateType
198 purple_media_candidate_type_from_fs(FsCandidateType type)
200 switch (type) {
201 case FS_CANDIDATE_TYPE_HOST:
202 return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
203 case FS_CANDIDATE_TYPE_SRFLX:
204 return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
205 case FS_CANDIDATE_TYPE_PRFLX:
206 return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
207 case FS_CANDIDATE_TYPE_RELAY:
208 return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
209 case FS_CANDIDATE_TYPE_MULTICAST:
210 return PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST;
212 g_return_val_if_reached(PURPLE_MEDIA_CANDIDATE_TYPE_HOST);
215 static FsNetworkProtocol
216 purple_media_network_protocol_to_fs(PurpleMediaNetworkProtocol protocol)
218 switch (protocol) {
219 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
220 return FS_NETWORK_PROTOCOL_UDP;
221 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE:
222 return FS_NETWORK_PROTOCOL_TCP_PASSIVE;
223 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE:
224 return FS_NETWORK_PROTOCOL_TCP_ACTIVE;
225 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO:
226 return FS_NETWORK_PROTOCOL_TCP_SO;
227 default:
228 g_return_val_if_reached(FS_NETWORK_PROTOCOL_TCP);
232 static PurpleMediaNetworkProtocol
233 purple_media_network_protocol_from_fs(FsNetworkProtocol protocol)
235 switch (protocol) {
236 case FS_NETWORK_PROTOCOL_UDP:
237 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
238 case FS_NETWORK_PROTOCOL_TCP_PASSIVE:
239 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE;
240 case FS_NETWORK_PROTOCOL_TCP_ACTIVE:
241 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE;
242 case FS_NETWORK_PROTOCOL_TCP_SO:
243 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO;
244 default:
245 g_return_val_if_reached(PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE);
249 static GstPadProbeReturn
250 event_probe_cb(GstPad *srcpad, GstPadProbeInfo *info, gpointer unused)
252 GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
253 if (GST_EVENT_TYPE(event) == GST_EVENT_CUSTOM_DOWNSTREAM
254 && gst_event_has_name(event, "purple-unlink-tee")) {
256 const GstStructure *s = gst_event_get_structure(event);
258 gst_pad_unlink(srcpad, gst_pad_get_peer(srcpad));
260 gst_pad_remove_probe(srcpad,
261 g_value_get_ulong(gst_structure_get_value(s, "handler-id")));
263 if (g_value_get_boolean(gst_structure_get_value(s, "release-pad")))
264 gst_element_release_request_pad(GST_ELEMENT_PARENT(srcpad), srcpad);
266 return GST_PAD_PROBE_DROP;
269 return GST_PAD_PROBE_OK;
272 static void
273 unlink_teepad_dynamic(GstPad *srcpad, gboolean release_pad)
275 gulong id = gst_pad_add_probe(srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
276 event_probe_cb, NULL, NULL);
278 if (GST_IS_GHOST_PAD(srcpad))
279 srcpad = gst_ghost_pad_get_target(GST_GHOST_PAD(srcpad));
281 gst_element_send_event(gst_pad_get_parent_element(srcpad),
282 gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM,
283 gst_structure_new("purple-unlink-tee",
284 "release-pad", G_TYPE_BOOLEAN, release_pad,
285 "handler-id", G_TYPE_ULONG, id,
286 NULL)));
289 static void
290 purple_media_backend_fs2_dispose(GObject *obj)
292 PurpleMediaBackendFs2Private *priv =
293 purple_media_backend_fs2_get_instance_private(
294 PURPLE_MEDIA_BACKEND_FS2(obj));
295 GList *iter = NULL;
297 purple_debug_info("backend-fs2", "purple_media_backend_fs2_dispose\n");
299 if (priv->notifier) {
300 g_object_unref(priv->notifier);
301 priv->notifier = NULL;
304 if (priv->confbin) {
305 GstElement *pipeline;
307 pipeline = purple_media_manager_get_pipeline(
308 purple_media_get_manager(priv->media));
310 /* All connections to media sources should be blocked before confbin is
311 * removed, to prevent freezing of any other simultaneously running
312 * media calls. */
313 if (priv->sessions) {
314 GList *sessions = g_hash_table_get_values(priv->sessions);
315 for (; sessions; sessions =
316 g_list_delete_link(sessions, sessions)) {
317 PurpleMediaBackendFs2Session *session = sessions->data;
318 if (session->srcpad) {
319 unlink_teepad_dynamic(session->srcpad, FALSE);
320 gst_object_unref(session->srcpad);
321 session->srcpad = NULL;
326 gst_element_set_locked_state(priv->confbin, TRUE);
327 gst_element_set_state(GST_ELEMENT(priv->confbin),
328 GST_STATE_NULL);
330 if (pipeline) {
331 GstBus *bus;
332 gst_bin_remove(GST_BIN(pipeline), priv->confbin);
333 bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
334 g_signal_handlers_disconnect_matched(G_OBJECT(bus),
335 G_SIGNAL_MATCH_FUNC |
336 G_SIGNAL_MATCH_DATA,
337 0, 0, 0, gst_bus_cb, obj);
338 gst_object_unref(bus);
339 } else {
340 purple_debug_warning("backend-fs2", "Unable to "
341 "properly dispose the conference. "
342 "Couldn't get the pipeline.\n");
345 priv->confbin = NULL;
346 priv->conference = NULL;
350 if (priv->sessions) {
351 GList *sessions = g_hash_table_get_values(priv->sessions);
353 for (; sessions; sessions =
354 g_list_delete_link(sessions, sessions)) {
355 PurpleMediaBackendFs2Session *session =
356 sessions->data;
358 if (session->session) {
359 g_object_unref(session->session);
360 session->session = NULL;
365 if (priv->participants) {
366 g_hash_table_destroy(priv->participants);
367 priv->participants = NULL;
370 for (iter = priv->streams; iter; iter = g_list_next(iter)) {
371 PurpleMediaBackendFs2Stream *stream = iter->data;
372 if (stream->stream) {
373 g_object_unref(stream->stream);
374 stream->stream = NULL;
378 if (priv->media) {
379 g_object_remove_weak_pointer(G_OBJECT(priv->media),
380 (gpointer*)&priv->media);
381 priv->media = NULL;
384 G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->dispose(obj);
387 static void
388 purple_media_backend_fs2_finalize(GObject *obj)
390 PurpleMediaBackendFs2Private *priv =
391 purple_media_backend_fs2_get_instance_private(
392 PURPLE_MEDIA_BACKEND_FS2(obj));
394 purple_debug_info("backend-fs2", "purple_media_backend_fs2_finalize\n");
396 g_free(priv->conference_type);
398 for (; priv->streams; priv->streams =
399 g_list_delete_link(priv->streams, priv->streams)) {
400 PurpleMediaBackendFs2Stream *stream = priv->streams->data;
401 free_stream(stream);
404 if (priv->sessions) {
405 GList *sessions = g_hash_table_get_values(priv->sessions);
407 for (; sessions; sessions =
408 g_list_delete_link(sessions, sessions)) {
409 PurpleMediaBackendFs2Session *session =
410 sessions->data;
411 free_session(session);
414 g_hash_table_destroy(priv->sessions);
417 G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->finalize(obj);
420 static void
421 purple_media_backend_fs2_set_property(GObject *object, guint prop_id,
422 const GValue *value, GParamSpec *pspec)
424 PurpleMediaBackendFs2Private *priv;
425 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object));
427 priv = purple_media_backend_fs2_get_instance_private(
428 PURPLE_MEDIA_BACKEND_FS2(object));
430 switch (prop_id) {
431 case PROP_CONFERENCE_TYPE:
432 priv->conference_type = g_value_dup_string(value);
433 break;
434 case PROP_MEDIA:
435 priv->media = g_value_get_object(value);
437 if (priv->media == NULL)
438 break;
440 g_object_add_weak_pointer(G_OBJECT(priv->media),
441 (gpointer*)&priv->media);
443 g_signal_connect(G_OBJECT(priv->media),
444 "state-changed",
445 G_CALLBACK(state_changed_cb),
446 PURPLE_MEDIA_BACKEND_FS2(object));
447 g_signal_connect(G_OBJECT(priv->media), "stream-info",
448 G_CALLBACK(stream_info_cb),
449 PURPLE_MEDIA_BACKEND_FS2(object));
450 break;
451 default:
452 G_OBJECT_WARN_INVALID_PROPERTY_ID(
453 object, prop_id, pspec);
454 break;
458 static void
459 purple_media_backend_fs2_get_property(GObject *object, guint prop_id,
460 GValue *value, GParamSpec *pspec)
462 PurpleMediaBackendFs2Private *priv;
463 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object));
465 priv = purple_media_backend_fs2_get_instance_private(
466 PURPLE_MEDIA_BACKEND_FS2(object));
468 switch (prop_id) {
469 case PROP_CONFERENCE_TYPE:
470 g_value_set_string(value, priv->conference_type);
471 break;
472 case PROP_MEDIA:
473 g_value_set_object(value, priv->media);
474 break;
475 default:
476 G_OBJECT_WARN_INVALID_PROPERTY_ID(
477 object, prop_id, pspec);
478 break;
482 static void
483 purple_media_backend_fs2_class_init(PurpleMediaBackendFs2Class *klass)
485 GObjectClass *gobject_class = (GObjectClass*)klass;
486 GList *features;
487 GList *it;
489 gobject_class->dispose = purple_media_backend_fs2_dispose;
490 gobject_class->finalize = purple_media_backend_fs2_finalize;
491 gobject_class->set_property = purple_media_backend_fs2_set_property;
492 gobject_class->get_property = purple_media_backend_fs2_get_property;
494 g_object_class_override_property(gobject_class, PROP_CONFERENCE_TYPE,
495 "conference-type");
496 g_object_class_override_property(gobject_class, PROP_MEDIA, "media");
498 /* VA-API elements aren't well supported in Farstream. Ignore them. */
499 features = gst_registry_get_feature_list_by_plugin(gst_registry_get(),
500 "vaapi");
501 for (it = features; it; it = it->next) {
502 gst_plugin_feature_set_rank((GstPluginFeature *)it->data,
503 GST_RANK_NONE);
505 gst_plugin_feature_list_free(features);
508 static void
509 purple_media_backend_iface_init(PurpleMediaBackendInterface *iface)
511 iface->add_stream = purple_media_backend_fs2_add_stream;
512 iface->add_remote_candidates =
513 purple_media_backend_fs2_add_remote_candidates;
514 iface->codecs_ready = purple_media_backend_fs2_codecs_ready;
515 iface->get_codecs = purple_media_backend_fs2_get_codecs;
516 iface->get_local_candidates =
517 purple_media_backend_fs2_get_local_candidates;
518 iface->set_remote_codecs = purple_media_backend_fs2_set_remote_codecs;
519 iface->set_send_codec = purple_media_backend_fs2_set_send_codec;
520 iface->set_encryption_parameters =
521 purple_media_backend_fs2_set_encryption_parameters;
522 iface->set_decryption_parameters =
523 purple_media_backend_fs2_set_decryption_parameters;
524 iface->set_params = purple_media_backend_fs2_set_params;
525 iface->get_available_params = purple_media_backend_fs2_get_available_params;
526 iface->send_dtmf = purple_media_backend_fs2_send_dtmf;
527 iface->set_send_rtcp_mux = purple_media_backend_fs2_set_send_rtcp_mux;
530 static FsMediaType
531 session_type_to_fs_media_type(PurpleMediaSessionType type)
533 if (type & PURPLE_MEDIA_AUDIO)
534 return FS_MEDIA_TYPE_AUDIO;
535 else if (type & PURPLE_MEDIA_VIDEO)
536 return FS_MEDIA_TYPE_VIDEO;
537 #ifdef HAVE_MEDIA_APPLICATION
538 else if (type & PURPLE_MEDIA_APPLICATION)
539 return FS_MEDIA_TYPE_APPLICATION;
540 #endif
541 else
542 return 0;
545 static FsStreamDirection
546 session_type_to_fs_stream_direction(PurpleMediaSessionType type)
548 if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO ||
549 (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
550 return FS_DIRECTION_BOTH;
551 else if ((type & PURPLE_MEDIA_SEND_AUDIO) ||
552 (type & PURPLE_MEDIA_SEND_VIDEO))
553 return FS_DIRECTION_SEND;
554 else if ((type & PURPLE_MEDIA_RECV_AUDIO) ||
555 (type & PURPLE_MEDIA_RECV_VIDEO))
556 return FS_DIRECTION_RECV;
557 #ifdef HAVE_MEDIA_APPLICATION
558 else if ((type & PURPLE_MEDIA_APPLICATION) == PURPLE_MEDIA_APPLICATION)
559 return FS_DIRECTION_BOTH;
560 else if (type & PURPLE_MEDIA_SEND_APPLICATION)
561 return FS_DIRECTION_SEND;
562 else if (type & PURPLE_MEDIA_RECV_APPLICATION)
563 return FS_DIRECTION_RECV;
564 #endif
565 else
566 return FS_DIRECTION_NONE;
569 static PurpleMediaSessionType
570 session_type_from_fs(FsMediaType type, FsStreamDirection direction)
572 PurpleMediaSessionType result = PURPLE_MEDIA_NONE;
573 if (type == FS_MEDIA_TYPE_AUDIO) {
574 if (direction & FS_DIRECTION_SEND)
575 result |= PURPLE_MEDIA_SEND_AUDIO;
576 if (direction & FS_DIRECTION_RECV)
577 result |= PURPLE_MEDIA_RECV_AUDIO;
578 } else if (type == FS_MEDIA_TYPE_VIDEO) {
579 if (direction & FS_DIRECTION_SEND)
580 result |= PURPLE_MEDIA_SEND_VIDEO;
581 if (direction & FS_DIRECTION_RECV)
582 result |= PURPLE_MEDIA_RECV_VIDEO;
583 #ifdef HAVE_MEDIA_APPLICATION
584 } else if (type == FS_MEDIA_TYPE_APPLICATION) {
585 if (direction & FS_DIRECTION_SEND)
586 result |= PURPLE_MEDIA_SEND_APPLICATION;
587 if (direction & FS_DIRECTION_RECV)
588 result |= PURPLE_MEDIA_RECV_APPLICATION;
589 #endif
591 return result;
594 static FsCandidate *
595 candidate_to_fs(PurpleMediaCandidate *candidate)
597 FsCandidate *fscandidate;
598 gchar *foundation;
599 guint component_id;
600 gchar *ip;
601 guint port;
602 gchar *base_ip;
603 guint base_port;
604 PurpleMediaNetworkProtocol proto;
605 guint32 priority;
606 PurpleMediaCandidateType type;
607 gchar *username;
608 gchar *password;
609 guint ttl;
611 if (candidate == NULL)
612 return NULL;
614 g_object_get(G_OBJECT(candidate),
615 "foundation", &foundation,
616 "component-id", &component_id,
617 "ip", &ip,
618 "port", &port,
619 "base-ip", &base_ip,
620 "base-port", &base_port,
621 "protocol", &proto,
622 "priority", &priority,
623 "type", &type,
624 "username", &username,
625 "password", &password,
626 "ttl", &ttl,
627 NULL);
629 fscandidate = fs_candidate_new(foundation,
630 component_id, purple_media_candidate_type_to_fs(type),
631 purple_media_network_protocol_to_fs(proto), ip, port);
633 fscandidate->base_ip = base_ip;
634 fscandidate->base_port = base_port;
635 fscandidate->priority = priority;
636 fscandidate->username = username;
637 fscandidate->password = password;
638 fscandidate->ttl = ttl;
640 g_free(foundation);
641 g_free(ip);
642 return fscandidate;
645 static GList *
646 candidate_list_to_fs(GList *candidates)
648 GList *new_list = NULL;
650 for (; candidates; candidates = g_list_next(candidates)) {
651 new_list = g_list_prepend(new_list,
652 candidate_to_fs(candidates->data));
655 new_list = g_list_reverse(new_list);
656 return new_list;
659 static PurpleMediaCandidate *
660 candidate_from_fs(FsCandidate *fscandidate)
662 PurpleMediaCandidate *candidate;
664 if (fscandidate == NULL)
665 return NULL;
667 candidate = purple_media_candidate_new(fscandidate->foundation,
668 fscandidate->component_id,
669 purple_media_candidate_type_from_fs(fscandidate->type),
670 purple_media_network_protocol_from_fs(fscandidate->proto),
671 fscandidate->ip, fscandidate->port);
672 g_object_set(candidate,
673 "base-ip", fscandidate->base_ip,
674 "base-port", fscandidate->base_port,
675 "priority", fscandidate->priority,
676 "username", fscandidate->username,
677 "password", fscandidate->password,
678 "ttl", fscandidate->ttl, NULL);
679 return candidate;
682 static GList *
683 candidate_list_from_fs(GList *candidates)
685 GList *new_list = NULL;
687 for (; candidates; candidates = g_list_next(candidates)) {
688 new_list = g_list_prepend(new_list,
689 candidate_from_fs(candidates->data));
692 new_list = g_list_reverse(new_list);
693 return new_list;
696 static FsCodec *
697 codec_to_fs(const PurpleMediaCodec *codec)
699 FsCodec *new_codec;
700 gint id;
701 char *encoding_name;
702 PurpleMediaSessionType media_type;
703 guint clock_rate;
704 guint channels;
705 GList *iter;
707 if (codec == NULL)
708 return NULL;
710 g_object_get(G_OBJECT(codec),
711 "id", &id,
712 "encoding-name", &encoding_name,
713 "media-type", &media_type,
714 "clock-rate", &clock_rate,
715 "channels", &channels,
716 "optional-params", &iter,
717 NULL);
719 new_codec = fs_codec_new(id, encoding_name,
720 session_type_to_fs_media_type(media_type),
721 clock_rate);
722 new_codec->channels = channels;
724 for (; iter; iter = g_list_next(iter)) {
725 PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data;
726 fs_codec_add_optional_parameter(new_codec,
727 param->key, param->value);
730 g_free(encoding_name);
731 return new_codec;
734 static PurpleMediaCodec *
735 codec_from_fs(const FsCodec *codec)
737 PurpleMediaCodec *new_codec;
738 GList *iter;
740 if (codec == NULL)
741 return NULL;
743 new_codec = purple_media_codec_new(codec->id, codec->encoding_name,
744 session_type_from_fs(codec->media_type,
745 FS_DIRECTION_BOTH), codec->clock_rate);
746 g_object_set(new_codec, "channels", codec->channels, NULL);
748 for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
749 FsCodecParameter *param = (FsCodecParameter*)iter->data;
750 purple_media_codec_add_optional_parameter(new_codec,
751 param->name, param->value);
754 return new_codec;
757 static GList *
758 codec_list_from_fs(GList *codecs)
760 GList *new_list = NULL;
762 for (; codecs; codecs = g_list_next(codecs)) {
763 new_list = g_list_prepend(new_list,
764 codec_from_fs(codecs->data));
767 new_list = g_list_reverse(new_list);
768 return new_list;
771 static GList *
772 codec_list_to_fs(GList *codecs)
774 GList *new_list = NULL;
776 for (; codecs; codecs = g_list_next(codecs)) {
777 new_list = g_list_prepend(new_list,
778 codec_to_fs(codecs->data));
781 new_list = g_list_reverse(new_list);
782 return new_list;
785 static PurpleMediaBackendFs2Session *
786 get_session(PurpleMediaBackendFs2 *self, const gchar *sess_id)
788 PurpleMediaBackendFs2Private *priv;
789 PurpleMediaBackendFs2Session *session = NULL;
791 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
793 priv = purple_media_backend_fs2_get_instance_private(self);
795 if (priv->sessions != NULL)
796 session = g_hash_table_lookup(priv->sessions, sess_id);
798 return session;
801 static FsParticipant *
802 get_participant(PurpleMediaBackendFs2 *self, const gchar *name)
804 PurpleMediaBackendFs2Private *priv;
805 FsParticipant *participant = NULL;
807 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
809 priv = purple_media_backend_fs2_get_instance_private(self);
811 if (priv->participants != NULL)
812 participant = g_hash_table_lookup(priv->participants, name);
814 return participant;
817 static PurpleMediaBackendFs2Stream *
818 get_stream(PurpleMediaBackendFs2 *self,
819 const gchar *sess_id, const gchar *name)
821 PurpleMediaBackendFs2Private *priv;
822 GList *streams;
824 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
826 priv = purple_media_backend_fs2_get_instance_private(self);
827 streams = priv->streams;
829 for (; streams; streams = g_list_next(streams)) {
830 PurpleMediaBackendFs2Stream *stream = streams->data;
831 if (purple_strequal(stream->session->id, sess_id) &&
832 purple_strequal(stream->participant, name))
833 return stream;
836 return NULL;
839 static GList *
840 get_streams(PurpleMediaBackendFs2 *self,
841 const gchar *sess_id, const gchar *name)
843 PurpleMediaBackendFs2Private *priv;
844 GList *streams, *ret = NULL;
846 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
848 priv = purple_media_backend_fs2_get_instance_private(self);
849 streams = priv->streams;
851 for (; streams; streams = g_list_next(streams)) {
852 PurpleMediaBackendFs2Stream *stream = streams->data;
854 if (sess_id != NULL && !purple_strequal(stream->session->id, sess_id))
855 continue;
856 else if (name != NULL && !purple_strequal(stream->participant, name))
857 continue;
858 else
859 ret = g_list_prepend(ret, stream);
862 ret = g_list_reverse(ret);
863 return ret;
866 static PurpleMediaBackendFs2Session *
867 get_session_from_fs_stream(PurpleMediaBackendFs2 *self, FsStream *stream)
869 PurpleMediaBackendFs2Private *priv =
870 purple_media_backend_fs2_get_instance_private(self);
871 FsSession *fssession;
872 GList *values;
874 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
875 g_return_val_if_fail(FS_IS_STREAM(stream), NULL);
877 g_object_get(stream, "session", &fssession, NULL);
879 values = g_hash_table_get_values(priv->sessions);
881 for (; values; values = g_list_delete_link(values, values)) {
882 PurpleMediaBackendFs2Session *session = values->data;
884 if (session->session == fssession) {
885 g_list_free(values);
886 g_object_unref(fssession);
887 return session;
891 g_object_unref(fssession);
892 return NULL;
895 static gdouble
896 gst_msg_db_to_percent(GstMessage *msg, gchar *value_name)
898 const GValue *list;
899 const GValue *value;
900 gdouble value_db;
901 gdouble percent;
903 list = gst_structure_get_value(gst_message_get_structure(msg), value_name);
904 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
905 value = g_value_array_get_nth(g_value_get_boxed(list), 0);
906 G_GNUC_END_IGNORE_DEPRECATIONS
907 value_db = g_value_get_double(value);
908 percent = pow(10, value_db / 20);
909 return (percent > 1.0) ? 1.0 : percent;
912 static void
913 purple_media_error_fs(PurpleMedia *media, const gchar *error,
914 const GstStructure *fs_error)
916 const gchar *error_msg = gst_structure_get_string(fs_error, "error-msg");
918 purple_media_error(media, "%s%s%s", error,
919 error_msg ? _("\n\nMessage from Farstream: ") : "",
920 error_msg ? error_msg : "");
923 static void
924 gst_handle_message_element(GstBus *bus, GstMessage *msg,
925 PurpleMediaBackendFs2 *self)
927 PurpleMediaBackendFs2Private *priv =
928 purple_media_backend_fs2_get_instance_private(self);
929 GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
930 static guint level_id = 0;
931 const GstStructure *structure = gst_message_get_structure(msg);
933 if (level_id == 0)
934 level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA);
936 if (gst_structure_has_name(structure, "level")) {
937 GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
938 gchar *name;
939 gchar *participant = NULL;
940 PurpleMediaBackendFs2Session *session = NULL;
941 gdouble percent;
943 if (!PURPLE_IS_MEDIA(priv->media) ||
944 GST_ELEMENT_PARENT(src) != priv->confbin)
945 return;
947 name = gst_element_get_name(src);
949 if (!strncmp(name, "sendlevel_", 10)) {
950 session = get_session(self, name+10);
951 if (priv->silence_threshold > 0) {
952 percent = gst_msg_db_to_percent(msg, "decay");
953 g_object_set(session->srcvalve,
954 "drop", (percent < priv->silence_threshold), NULL);
958 g_free(name);
960 if (!g_signal_has_handler_pending(priv->media, level_id, 0, FALSE))
961 return;
963 if (!session) {
964 GList *iter = priv->streams;
965 PurpleMediaBackendFs2Stream *stream;
966 for (; iter; iter = g_list_next(iter)) {
967 stream = iter->data;
968 if (stream->level == src) {
969 session = stream->session;
970 participant = stream->participant;
971 break;
976 if (!session)
977 return;
979 percent = gst_msg_db_to_percent(msg, "rms");
981 g_signal_emit(priv->media, level_id, 0,
982 session->id, participant, percent);
983 return;
986 if (!FS_IS_CONFERENCE(src) || !PURPLE_IS_MEDIA_BACKEND(self) ||
987 priv->conference != FS_CONFERENCE(src))
988 return;
990 if (gst_structure_has_name(structure, "farstream-error")) {
991 FsError error_no;
992 gboolean error_emitted = FALSE;
993 gst_structure_get_enum(structure, "error-no",
994 FS_TYPE_ERROR, (gint*)&error_no);
995 switch (error_no) {
996 case FS_ERROR_CONSTRUCTION:
997 purple_media_error_fs(priv->media,
998 _("Error initializing the call. "
999 "This probably denotes problem in "
1000 "installation of GStreamer or Farstream."),
1001 structure);
1002 error_emitted = TRUE;
1003 break;
1004 case FS_ERROR_NETWORK:
1005 purple_media_error_fs(priv->media, _("Network error."),
1006 structure);
1007 error_emitted = TRUE;
1008 purple_media_end(priv->media, NULL, NULL);
1009 break;
1010 case FS_ERROR_NEGOTIATION_FAILED:
1011 purple_media_error_fs(priv->media,
1012 _("Codec negotiation failed. "
1013 "This problem might be resolved by installing "
1014 "more GStreamer codecs."),
1015 structure);
1016 error_emitted = TRUE;
1017 purple_media_end(priv->media, NULL, NULL);
1018 break;
1019 case FS_ERROR_NO_CODECS:
1020 purple_media_error(priv->media,
1021 _("No codecs found. "
1022 "Install some GStreamer codecs found "
1023 "in GStreamer plugins packages."));
1024 error_emitted = TRUE;
1025 purple_media_end(priv->media, NULL, NULL);
1026 break;
1027 default:
1028 purple_debug_error("backend-fs2",
1029 "farstream-error: %i: %s\n",
1030 error_no,
1031 gst_structure_get_string(structure, "error-msg"));
1032 break;
1035 if (FS_ERROR_IS_FATAL(error_no)) {
1036 if (!error_emitted)
1037 purple_media_error(priv->media,
1038 _("A non-recoverable Farstream error has occurred."));
1039 purple_media_end(priv->media, NULL, NULL);
1041 } else if (gst_structure_has_name(structure,
1042 "farstream-new-local-candidate")) {
1043 const GValue *value;
1044 FsStream *stream;
1045 FsCandidate *local_candidate;
1046 PurpleMediaCandidate *candidate;
1047 FsParticipant *participant;
1048 PurpleMediaBackendFs2Session *session;
1049 PurpleMediaBackendFs2Stream *media_stream;
1050 const gchar *name;
1052 value = gst_structure_get_value(structure, "stream");
1053 stream = g_value_get_object(value);
1054 value = gst_structure_get_value(structure, "candidate");
1055 local_candidate = g_value_get_boxed(value);
1057 session = get_session_from_fs_stream(self, stream);
1059 purple_debug_info("backend-fs2",
1060 "got new local candidate: %s\n",
1061 local_candidate->foundation);
1063 g_object_get(stream, "participant", &participant, NULL);
1064 name = g_object_get_data(G_OBJECT(participant), "purple-name");
1066 media_stream = get_stream(self, session->id, name);
1067 media_stream->local_candidates = g_list_append(
1068 media_stream->local_candidates,
1069 fs_candidate_copy(local_candidate));
1071 candidate = candidate_from_fs(local_candidate);
1072 g_signal_emit_by_name(self, "new-candidate",
1073 session->id, name, candidate);
1074 g_object_unref(candidate);
1075 g_object_unref(participant);
1076 } else if (gst_structure_has_name(structure,
1077 "farstream-local-candidates-prepared")) {
1078 const GValue *value;
1079 FsStream *stream;
1080 FsParticipant *participant;
1081 PurpleMediaBackendFs2Session *session;
1083 value = gst_structure_get_value(structure, "stream");
1084 stream = g_value_get_object(value);
1085 session = get_session_from_fs_stream(self, stream);
1087 g_object_get(stream, "participant", &participant, NULL);
1089 g_signal_emit_by_name(self, "candidates-prepared",
1090 session->id,
1091 g_object_get_data(G_OBJECT(participant), "purple-name"));
1093 g_object_unref(participant);
1094 } else if (gst_structure_has_name(structure,
1095 "farstream-new-active-candidate-pair")) {
1096 const GValue *value;
1097 FsStream *stream;
1098 FsCandidate *local_candidate;
1099 FsCandidate *remote_candidate;
1100 FsParticipant *participant;
1101 PurpleMediaBackendFs2Session *session;
1102 PurpleMediaCandidate *lcandidate, *rcandidate;
1104 value = gst_structure_get_value(structure, "stream");
1105 stream = g_value_get_object(value);
1106 value = gst_structure_get_value(structure, "local-candidate");
1107 local_candidate = g_value_get_boxed(value);
1108 value = gst_structure_get_value(structure, "remote-candidate");
1109 remote_candidate = g_value_get_boxed(value);
1111 g_object_get(stream, "participant", &participant, NULL);
1113 session = get_session_from_fs_stream(self, stream);
1115 lcandidate = candidate_from_fs(local_candidate);
1116 rcandidate = candidate_from_fs(remote_candidate);
1118 g_signal_emit_by_name(self, "active-candidate-pair",
1119 session->id,
1120 g_object_get_data(G_OBJECT(participant), "purple-name"),
1121 lcandidate, rcandidate);
1123 g_object_unref(participant);
1124 g_object_unref(lcandidate);
1125 g_object_unref(rcandidate);
1126 } else if (gst_structure_has_name(structure,
1127 "farstream-recv-codecs-changed")) {
1128 const GValue *value;
1129 GList *codecs;
1130 FsCodec *codec;
1132 value = gst_structure_get_value(structure, "codecs");
1133 codecs = g_value_get_boxed(value);
1134 codec = codecs->data;
1136 purple_debug_info("backend-fs2",
1137 "farstream-recv-codecs-changed: %s\n",
1138 codec->encoding_name);
1139 } else if (gst_structure_has_name(structure,
1140 "farstream-component-state-changed")) {
1141 const GValue *value;
1142 FsStreamState fsstate;
1143 guint component;
1144 const gchar *state;
1146 value = gst_structure_get_value(structure, "state");
1147 fsstate = g_value_get_enum(value);
1148 value = gst_structure_get_value(structure, "component");
1149 component = g_value_get_uint(value);
1151 switch (fsstate) {
1152 case FS_STREAM_STATE_FAILED:
1153 state = "FAILED";
1154 break;
1155 case FS_STREAM_STATE_DISCONNECTED:
1156 state = "DISCONNECTED";
1157 break;
1158 case FS_STREAM_STATE_GATHERING:
1159 state = "GATHERING";
1160 break;
1161 case FS_STREAM_STATE_CONNECTING:
1162 state = "CONNECTING";
1163 break;
1164 case FS_STREAM_STATE_CONNECTED:
1165 state = "CONNECTED";
1166 break;
1167 case FS_STREAM_STATE_READY:
1168 state = "READY";
1169 break;
1170 default:
1171 state = "UNKNOWN";
1172 break;
1175 purple_debug_info("backend-fs2",
1176 "farstream-component-state-changed: "
1177 "component: %u state: %s\n",
1178 component, state);
1179 } else if (gst_structure_has_name(structure,
1180 "farstream-send-codec-changed")) {
1181 const GValue *value;
1182 FsCodec *codec;
1183 gchar *codec_str;
1185 value = gst_structure_get_value(structure, "codec");
1186 codec = g_value_get_boxed(value);
1187 codec_str = fs_codec_to_string(codec);
1189 purple_debug_info("backend-fs2",
1190 "farstream-send-codec-changed: codec: %s\n",
1191 codec_str);
1193 g_free(codec_str);
1194 } else if (gst_structure_has_name(structure,
1195 "farstream-codecs-changed")) {
1196 const GValue *value;
1197 FsSession *fssession;
1198 GList *sessions;
1200 value = gst_structure_get_value(structure, "session");
1201 fssession = g_value_get_object(value);
1202 sessions = g_hash_table_get_values(priv->sessions);
1204 for (; sessions; sessions =
1205 g_list_delete_link(sessions, sessions)) {
1206 PurpleMediaBackendFs2Session *session = sessions->data;
1207 gchar *session_id;
1209 if (session->session != fssession)
1210 continue;
1212 session_id = g_strdup(session->id);
1213 g_signal_emit_by_name(self, "codecs-changed",
1214 session_id);
1215 g_free(session_id);
1216 g_list_free(sessions);
1217 break;
1222 static void
1223 gst_handle_message_error(GstBus *bus, GstMessage *msg,
1224 PurpleMediaBackendFs2 *self)
1226 PurpleMediaBackendFs2Private *priv =
1227 purple_media_backend_fs2_get_instance_private(self);
1228 GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg));
1229 GstElement *lastElement = NULL;
1230 GList *sessions;
1232 GError *error = NULL;
1233 gchar *debug_msg = NULL;
1235 gst_message_parse_error(msg, &error, &debug_msg);
1236 purple_debug_error("backend-fs2", "gst error %s\ndebugging: %s\n",
1237 error->message, debug_msg);
1239 g_error_free(error);
1240 g_free(debug_msg);
1242 while (element && !GST_IS_PIPELINE(element)) {
1243 if (element == priv->confbin)
1244 break;
1246 lastElement = element;
1247 element = GST_ELEMENT_PARENT(element);
1250 if (!element || !GST_IS_PIPELINE(element))
1251 return;
1253 sessions = purple_media_get_session_ids(priv->media);
1255 for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
1256 if (purple_media_get_src(priv->media, sessions->data)
1257 != lastElement)
1258 continue;
1260 if (purple_media_get_session_type(priv->media, sessions->data)
1261 & PURPLE_MEDIA_AUDIO)
1262 purple_media_error(priv->media,
1263 _("Error with your microphone"));
1264 else if (purple_media_get_session_type(priv->media,
1265 sessions->data) & PURPLE_MEDIA_VIDEO)
1266 purple_media_error(priv->media,
1267 _("Error with your webcam"));
1269 break;
1272 g_list_free(sessions);
1274 purple_media_error(priv->media, _("Conference error"));
1275 purple_media_end(priv->media, NULL, NULL);
1278 static gboolean
1279 gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self)
1281 switch(GST_MESSAGE_TYPE(msg)) {
1282 case GST_MESSAGE_ELEMENT:
1283 gst_handle_message_element(bus, msg, self);
1284 break;
1285 case GST_MESSAGE_ERROR:
1286 gst_handle_message_error(bus, msg, self);
1287 break;
1288 default:
1289 break;
1292 return TRUE;
1295 static void
1296 remove_element(GstElement *element)
1298 if (element) {
1299 gst_element_set_locked_state(element, TRUE);
1300 gst_element_set_state(element, GST_STATE_NULL);
1301 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
1305 static void
1306 state_changed_cb(PurpleMedia *media, PurpleMediaState state,
1307 gchar *sid, gchar *name, PurpleMediaBackendFs2 *self)
1309 if (state == PURPLE_MEDIA_STATE_END) {
1310 PurpleMediaBackendFs2Private *priv =
1311 purple_media_backend_fs2_get_instance_private(
1312 self);
1314 if (sid && name) {
1315 PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name);
1316 gst_object_unref(stream->stream);
1318 priv->streams = g_list_remove(priv->streams, stream);
1320 remove_element(stream->src);
1321 remove_element(stream->tee);
1322 remove_element(stream->volume);
1323 remove_element(stream->level);
1324 remove_element(stream->fakesink);
1325 remove_element(stream->queue);
1327 free_stream(stream);
1328 } else if (sid && !name) {
1329 PurpleMediaBackendFs2Session *session = get_session(self, sid);
1330 GstPad *pad;
1332 g_object_get(session->session, "sink-pad", &pad, NULL);
1333 gst_pad_unlink(GST_PAD_PEER(pad), pad);
1334 gst_object_unref(pad);
1336 gst_object_unref(session->session);
1337 g_hash_table_remove(priv->sessions, session->id);
1339 pad = gst_pad_get_peer(session->srcpad);
1340 gst_element_remove_pad(GST_ELEMENT_PARENT(pad), pad);
1341 gst_object_unref(pad);
1342 gst_object_unref(session->srcpad);
1344 remove_element(session->srcvalve);
1345 remove_element(session->tee);
1347 free_session(session);
1350 purple_media_manager_remove_output_windows(
1351 purple_media_get_manager(media), media, sid, name);
1355 static void
1356 stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
1357 gchar *sid, gchar *name, gboolean local,
1358 PurpleMediaBackendFs2 *self)
1360 if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) {
1361 PurpleMediaBackendFs2Stream *stream =
1362 get_stream(self, sid, name);
1363 GError *err = NULL;
1365 g_object_set(G_OBJECT(stream->stream), "direction",
1366 session_type_to_fs_stream_direction(
1367 stream->session->type), NULL);
1369 if (stream->remote_candidates == NULL ||
1370 purple_media_is_initiator(media, sid, name))
1371 return;
1373 if (stream->supports_add)
1374 fs_stream_add_remote_candidates(stream->stream,
1375 stream->remote_candidates, &err);
1376 else
1377 fs_stream_force_remote_candidates(stream->stream,
1378 stream->remote_candidates, &err);
1380 if (err == NULL)
1381 return;
1383 purple_debug_error("backend-fs2", "Error adding "
1384 "remote candidates: %s\n",
1385 err->message);
1386 g_error_free(err);
1387 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE ||
1388 type == PURPLE_MEDIA_INFO_UNMUTE)) {
1389 PurpleMediaBackendFs2Private *priv =
1390 purple_media_backend_fs2_get_instance_private(
1391 self);
1392 gboolean active = (type == PURPLE_MEDIA_INFO_MUTE);
1393 GList *sessions;
1395 if (sid == NULL)
1396 sessions = g_hash_table_get_values(priv->sessions);
1397 else
1398 sessions = g_list_prepend(NULL,
1399 get_session(self, sid));
1401 purple_debug_info("media", "Turning mute %s\n",
1402 active ? "on" : "off");
1404 for (; sessions; sessions = g_list_delete_link(
1405 sessions, sessions)) {
1406 PurpleMediaBackendFs2Session *session =
1407 sessions->data;
1409 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
1410 gchar *name = g_strdup_printf("volume_%s",
1411 session->id);
1412 GstElement *volume = gst_bin_get_by_name(
1413 GST_BIN(priv->confbin), name);
1414 g_free(name);
1415 g_object_set(volume, "mute", active, NULL);
1418 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
1419 type == PURPLE_MEDIA_INFO_UNHOLD)) {
1420 gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
1421 GList *streams = get_streams(self, sid, name);
1422 for (; streams; streams =
1423 g_list_delete_link(streams, streams)) {
1424 PurpleMediaBackendFs2Stream *stream = streams->data;
1425 if (stream->session->type & PURPLE_MEDIA_SEND_AUDIO) {
1426 g_object_set(stream->stream, "direction",
1427 session_type_to_fs_stream_direction(
1428 stream->session->type & ((active) ?
1429 ~PURPLE_MEDIA_SEND_AUDIO :
1430 PURPLE_MEDIA_AUDIO)), NULL);
1433 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE ||
1434 type == PURPLE_MEDIA_INFO_UNPAUSE)) {
1435 gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE);
1436 GList *streams = get_streams(self, sid, name);
1437 for (; streams; streams =
1438 g_list_delete_link(streams, streams)) {
1439 PurpleMediaBackendFs2Stream *stream = streams->data;
1440 if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
1441 g_object_set(stream->stream, "direction",
1442 session_type_to_fs_stream_direction(
1443 stream->session->type & ((active) ?
1444 ~PURPLE_MEDIA_SEND_VIDEO :
1445 PURPLE_MEDIA_VIDEO)), NULL);
1451 static gboolean
1452 init_conference(PurpleMediaBackendFs2 *self)
1454 PurpleMediaBackendFs2Private *priv =
1455 purple_media_backend_fs2_get_instance_private(self);
1456 GstElement *pipeline;
1457 GstBus *bus;
1458 gchar *name;
1459 GKeyFile *default_props;
1461 priv->conference = FS_CONFERENCE(
1462 gst_element_factory_make(priv->conference_type, NULL));
1464 if (priv->conference == NULL) {
1465 purple_debug_error("backend-fs2", "Conference == NULL\n");
1466 return FALSE;
1469 if (purple_account_get_silence_suppression(
1470 purple_media_get_account(priv->media)))
1471 priv->silence_threshold = purple_prefs_get_int(
1472 "/purple/media/audio/silence_threshold") / 100.0;
1473 else
1474 priv->silence_threshold = 0;
1476 pipeline = purple_media_manager_get_pipeline(
1477 purple_media_get_manager(priv->media));
1479 if (pipeline == NULL) {
1480 purple_debug_error("backend-fs2",
1481 "Couldn't retrieve pipeline.\n");
1482 return FALSE;
1485 name = g_strdup_printf("conf_%p", priv->conference);
1486 priv->confbin = gst_bin_new(name);
1487 if (priv->confbin == NULL) {
1488 purple_debug_error("backend-fs2",
1489 "Couldn't create confbin.\n");
1490 return FALSE;
1493 g_free(name);
1495 bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
1496 if (bus == NULL) {
1497 purple_debug_error("backend-fs2",
1498 "Couldn't get the pipeline's bus.\n");
1499 return FALSE;
1502 default_props = fs_utils_get_default_element_properties(GST_ELEMENT(priv->conference));
1503 if (default_props != NULL) {
1504 priv->notifier = fs_element_added_notifier_new();
1505 fs_element_added_notifier_add(priv->notifier,
1506 GST_BIN(priv->confbin));
1507 fs_element_added_notifier_set_properties_from_keyfile(priv->notifier, default_props);
1510 g_signal_connect(G_OBJECT(bus), "message",
1511 G_CALLBACK(gst_bus_cb), self);
1512 gst_object_unref(bus);
1514 if (!gst_bin_add(GST_BIN(pipeline),
1515 GST_ELEMENT(priv->confbin))) {
1516 purple_debug_error("backend-fs2", "Couldn't add confbin "
1517 "element to the pipeline\n");
1518 return FALSE;
1521 if (!gst_bin_add(GST_BIN(priv->confbin),
1522 GST_ELEMENT(priv->conference))) {
1523 purple_debug_error("backend-fs2", "Couldn't add conference "
1524 "element to the confbin\n");
1525 return FALSE;
1528 if (gst_element_set_state(GST_ELEMENT(priv->confbin),
1529 GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
1530 purple_debug_error("backend-fs2",
1531 "Failed to start conference.\n");
1532 return FALSE;
1535 return TRUE;
1538 static gboolean
1539 create_src(PurpleMediaBackendFs2 *self, const gchar *sess_id,
1540 PurpleMediaSessionType type)
1542 PurpleMediaBackendFs2Private *priv =
1543 purple_media_backend_fs2_get_instance_private(self);
1544 PurpleMediaBackendFs2Session *session;
1545 PurpleMediaSessionType session_type;
1546 FsMediaType media_type = session_type_to_fs_media_type(type);
1547 FsStreamDirection type_direction =
1548 session_type_to_fs_stream_direction(type);
1549 GstElement *src;
1550 GstPad *sinkpad, *srcpad;
1551 GstPad *ghost = NULL;
1553 if ((type_direction & FS_DIRECTION_SEND) == 0)
1554 return TRUE;
1556 session_type = session_type_from_fs(
1557 media_type, FS_DIRECTION_SEND);
1558 src = purple_media_manager_get_element(
1559 purple_media_get_manager(priv->media),
1560 session_type, priv->media, sess_id, NULL);
1562 if (!GST_IS_ELEMENT(src)) {
1563 purple_debug_error("backend-fs2",
1564 "Error creating src for session %s\n",
1565 sess_id);
1566 return FALSE;
1569 session = get_session(self, sess_id);
1571 if (session == NULL) {
1572 purple_debug_warning("backend-fs2",
1573 "purple_media_set_src: trying to set"
1574 " src on non-existent session\n");
1575 return FALSE;
1578 if (session->src)
1579 gst_object_unref(session->src);
1581 session->src = src;
1582 gst_element_set_locked_state(session->src, TRUE);
1584 session->tee = gst_element_factory_make("tee", NULL);
1585 gst_bin_add(GST_BIN(priv->confbin), session->tee);
1587 /* This supposedly isn't necessary, but it silences some warnings */
1588 if (GST_ELEMENT_PARENT(priv->confbin)
1589 == GST_ELEMENT_PARENT(session->src)) {
1590 GstPad *pad = gst_element_get_static_pad(session->tee, "sink");
1591 ghost = gst_ghost_pad_new(NULL, pad);
1592 gst_object_unref(pad);
1593 gst_pad_set_active(ghost, TRUE);
1594 gst_element_add_pad(priv->confbin, ghost);
1597 gst_element_set_state(session->tee, GST_STATE_PLAYING);
1598 gst_element_link(session->src, priv->confbin);
1599 if (ghost)
1600 session->srcpad = gst_pad_get_peer(ghost);
1602 g_object_get(session->session, "sink-pad", &sinkpad, NULL);
1603 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
1604 gchar *name = g_strdup_printf("volume_%s", session->id);
1605 GstElement *level;
1606 GstElement *volume = gst_element_factory_make("volume", name);
1607 double input_volume = purple_prefs_get_int(
1608 "/purple/media/audio/volume/input")/10.0;
1609 g_free(name);
1610 name = g_strdup_printf("sendlevel_%s", session->id);
1611 level = gst_element_factory_make("level", name);
1612 g_free(name);
1613 session->srcvalve = gst_element_factory_make("valve", NULL);
1614 gst_bin_add(GST_BIN(priv->confbin), volume);
1615 gst_bin_add(GST_BIN(priv->confbin), level);
1616 gst_bin_add(GST_BIN(priv->confbin), session->srcvalve);
1617 gst_element_set_state(level, GST_STATE_PLAYING);
1618 gst_element_set_state(volume, GST_STATE_PLAYING);
1619 gst_element_set_state(session->srcvalve, GST_STATE_PLAYING);
1620 gst_element_link(level, session->srcvalve);
1621 gst_element_link(volume, level);
1622 gst_element_link(session->tee, volume);
1623 srcpad = gst_element_get_static_pad(session->srcvalve, "src");
1624 g_object_set(volume, "volume", input_volume, NULL);
1625 } else {
1626 srcpad = gst_element_get_request_pad(session->tee, "src_%u");
1629 purple_debug_info("backend-fs2", "connecting pad: %s\n",
1630 gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
1631 ? "success" : "failure");
1632 gst_element_set_locked_state(session->src, FALSE);
1633 gst_object_unref(session->src);
1634 gst_object_unref(sinkpad);
1636 purple_media_manager_create_output_window(purple_media_get_manager(
1637 priv->media), priv->media, sess_id, NULL);
1639 purple_debug_info("backend-fs2", "create_src: setting source "
1640 "state to GST_STATE_PLAYING - it may hang here on win32\n");
1641 gst_element_set_state(session->src, GST_STATE_PLAYING);
1642 purple_debug_info("backend-fs2", "create_src: state set\n");
1644 return TRUE;
1647 static gboolean
1648 create_session(PurpleMediaBackendFs2 *self, const gchar *sess_id,
1649 PurpleMediaSessionType type, gboolean initiator,
1650 const gchar *transmitter)
1652 PurpleMediaBackendFs2Private *priv =
1653 purple_media_backend_fs2_get_instance_private(self);
1654 PurpleMediaBackendFs2Session *session;
1655 GError *err = NULL;
1656 GList *codec_conf = NULL;
1657 gchar *filename = NULL;
1659 session = g_new0(PurpleMediaBackendFs2Session, 1);
1661 session->session = fs_conference_new_session(priv->conference,
1662 session_type_to_fs_media_type(type), &err);
1664 #ifdef HAVE_MEDIA_APPLICATION
1665 if (type == PURPLE_MEDIA_APPLICATION) {
1666 GstCaps *caps;
1667 GObject *rtpsession = NULL;
1669 caps = gst_caps_new_empty_simple ("application/octet-stream");
1670 fs_session_set_allowed_caps (session->session, caps, caps, NULL);
1671 gst_caps_unref (caps);
1672 g_object_get (session->session, "internal-session", &rtpsession, NULL);
1673 if (rtpsession) {
1674 g_object_set (rtpsession, "probation", 0, NULL);
1675 g_object_unref (rtpsession);
1678 #endif
1679 if (err != NULL) {
1680 purple_media_error(priv->media,
1681 _("Error creating session: %s"),
1682 err->message);
1683 g_error_free(err);
1684 g_free(session);
1685 return FALSE;
1688 filename = g_build_filename(purple_config_dir(), "fs-codec.conf", NULL);
1689 codec_conf = fs_codec_list_from_keyfile(filename, &err);
1690 g_free(filename);
1692 if (err != NULL) {
1693 if (err->code == G_KEY_FILE_ERROR_NOT_FOUND)
1694 purple_debug_info("backend-fs2", "Couldn't read "
1695 "fs-codec.conf: %s\n",
1696 err->message);
1697 else
1698 purple_debug_error("backend-fs2", "Error reading "
1699 "fs-codec.conf: %s\n",
1700 err->message);
1701 g_error_free(err);
1703 purple_debug_info("backend-fs2",
1704 "Loading default codec conf instead\n");
1705 codec_conf = fs_utils_get_default_codec_preferences(
1706 GST_ELEMENT(priv->conference));
1709 fs_session_set_codec_preferences(session->session, codec_conf, NULL);
1710 fs_codec_list_destroy(codec_conf);
1713 * Removes a 5-7 second delay before
1714 * receiving the src-pad-added signal.
1715 * Only works for non-multicast FsRtpSessions.
1717 if (!purple_strequal(transmitter, "multicast"))
1718 g_object_set(G_OBJECT(session->session),
1719 "no-rtcp-timeout", 0, NULL);
1721 session->id = g_strdup(sess_id);
1722 session->backend = self;
1723 session->type = type;
1725 if (!priv->sessions) {
1726 purple_debug_info("backend-fs2",
1727 "Creating hash table for sessions\n");
1728 priv->sessions = g_hash_table_new_full(g_str_hash, g_str_equal,
1729 g_free, NULL);
1732 g_hash_table_insert(priv->sessions, g_strdup(session->id), session);
1734 if (!create_src(self, sess_id, type)) {
1735 purple_debug_info("backend-fs2", "Error creating the src\n");
1736 return FALSE;
1739 return TRUE;
1742 static void
1743 free_session(PurpleMediaBackendFs2Session *session)
1745 g_free(session->id);
1746 g_free(session);
1749 static gboolean
1750 create_participant(PurpleMediaBackendFs2 *self, const gchar *name)
1752 PurpleMediaBackendFs2Private *priv =
1753 purple_media_backend_fs2_get_instance_private(self);
1754 FsParticipant *participant;
1755 GError *err = NULL;
1757 participant = fs_conference_new_participant(
1758 priv->conference, &err);
1760 if (err) {
1761 purple_debug_error("backend-fs2",
1762 "Error creating participant: %s\n",
1763 err->message);
1764 g_error_free(err);
1765 return FALSE;
1768 g_object_set_data_full(G_OBJECT(participant), "purple-name",
1769 g_strdup(name), g_free);
1771 if (g_object_class_find_property(G_OBJECT_GET_CLASS(participant),
1772 "cname")) {
1773 g_object_set(participant, "cname", name, NULL);
1776 if (!priv->participants) {
1777 purple_debug_info("backend-fs2",
1778 "Creating hash table for participants\n");
1779 priv->participants = g_hash_table_new_full(g_str_hash,
1780 g_str_equal, g_free, g_object_unref);
1783 g_hash_table_insert(priv->participants, g_strdup(name), participant);
1785 return TRUE;
1788 static gboolean
1789 src_pad_added_cb_cb(PurpleMediaBackendFs2Stream *stream)
1791 PurpleMediaBackendFs2Private *priv;
1793 g_return_val_if_fail(stream != NULL, FALSE);
1795 priv = purple_media_backend_fs2_get_instance_private(
1796 stream->session->backend);
1797 stream->connected_cb_id = 0;
1799 purple_media_manager_create_output_window(
1800 purple_media_get_manager(priv->media), priv->media,
1801 stream->session->id, stream->participant);
1803 g_signal_emit_by_name(priv->media, "state-changed",
1804 PURPLE_MEDIA_STATE_CONNECTED,
1805 stream->session->id, stream->participant);
1806 return FALSE;
1809 static void
1810 src_pad_added_cb(FsStream *fsstream, GstPad *srcpad,
1811 FsCodec *codec, PurpleMediaBackendFs2Stream *stream)
1813 PurpleMediaBackendFs2Private *priv;
1814 GstPad *sinkpad;
1816 g_return_if_fail(FS_IS_STREAM(fsstream));
1817 g_return_if_fail(stream != NULL);
1819 priv = purple_media_backend_fs2_get_instance_private(
1820 stream->session->backend);
1822 if (stream->src == NULL) {
1823 GstElement *sink = NULL;
1825 if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
1826 double output_volume = purple_prefs_get_int(
1827 "/purple/media/audio/volume/output")/10.0;
1829 * Should this instead be:
1830 * audioconvert ! audioresample ! liveadder !
1831 * audioresample ! audioconvert ! realsink
1833 stream->queue = gst_element_factory_make("queue", NULL);
1834 stream->volume = gst_element_factory_make("volume", NULL);
1835 g_object_set(stream->volume, "volume", output_volume, NULL);
1836 stream->level = gst_element_factory_make("level", NULL);
1837 stream->src = gst_element_factory_make("liveadder", NULL);
1838 sink = purple_media_manager_get_element(
1839 purple_media_get_manager(priv->media),
1840 PURPLE_MEDIA_RECV_AUDIO, priv->media,
1841 stream->session->id,
1842 stream->participant);
1843 gst_bin_add(GST_BIN(priv->confbin), stream->queue);
1844 gst_bin_add(GST_BIN(priv->confbin), stream->volume);
1845 gst_bin_add(GST_BIN(priv->confbin), stream->level);
1846 gst_bin_add(GST_BIN(priv->confbin), sink);
1847 gst_element_set_state(sink, GST_STATE_PLAYING);
1848 gst_element_set_state(stream->level, GST_STATE_PLAYING);
1849 gst_element_set_state(stream->volume, GST_STATE_PLAYING);
1850 gst_element_set_state(stream->queue, GST_STATE_PLAYING);
1851 gst_element_link(stream->level, sink);
1852 gst_element_link(stream->volume, stream->level);
1853 gst_element_link(stream->queue, stream->volume);
1854 sink = stream->queue;
1855 } else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) {
1856 stream->src = gst_element_factory_make("funnel", NULL);
1857 sink = gst_element_factory_make("fakesink", NULL);
1858 g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
1859 gst_bin_add(GST_BIN(priv->confbin), sink);
1860 gst_element_set_state(sink, GST_STATE_PLAYING);
1861 stream->fakesink = sink;
1862 #ifdef HAVE_MEDIA_APPLICATION
1863 } else if (codec->media_type == FS_MEDIA_TYPE_APPLICATION) {
1864 stream->src = gst_element_factory_make("funnel", NULL);
1865 sink = purple_media_manager_get_element(
1866 purple_media_get_manager(priv->media),
1867 PURPLE_MEDIA_RECV_APPLICATION, priv->media,
1868 stream->session->id,
1869 stream->participant);
1870 gst_bin_add(GST_BIN(priv->confbin), sink);
1871 gst_element_set_state(sink, GST_STATE_PLAYING);
1872 #endif
1874 stream->tee = gst_element_factory_make("tee", NULL);
1875 gst_bin_add_many(GST_BIN(priv->confbin),
1876 stream->src, stream->tee, NULL);
1877 gst_element_set_state(stream->tee, GST_STATE_PLAYING);
1878 gst_element_set_state(stream->src, GST_STATE_PLAYING);
1879 gst_element_link_many(stream->src, stream->tee, sink, NULL);
1882 sinkpad = gst_element_get_request_pad(stream->src, "sink_%u");
1883 gst_pad_link(srcpad, sinkpad);
1884 gst_object_unref(sinkpad);
1886 stream->connected_cb_id = g_timeout_add(0,
1887 (GSourceFunc)src_pad_added_cb_cb, stream);
1890 static gboolean
1891 create_stream(PurpleMediaBackendFs2 *self,
1892 const gchar *sess_id, const gchar *who,
1893 PurpleMediaSessionType type, gboolean initiator,
1894 const gchar *transmitter,
1895 guint num_params, GParameter *params)
1897 PurpleMediaBackendFs2Private *priv =
1898 purple_media_backend_fs2_get_instance_private(self);
1899 GError *err = NULL;
1900 FsStream *fsstream = NULL;
1901 const gchar *stun_ip = purple_network_get_stun_ip();
1902 const gchar *turn_ip = purple_network_get_turn_ip();
1903 guint _num_params = num_params;
1904 GParameter *_params;
1905 FsStreamDirection type_direction =
1906 session_type_to_fs_stream_direction(type);
1907 PurpleMediaBackendFs2Session *session;
1908 PurpleMediaBackendFs2Stream *stream;
1909 FsParticipant *participant;
1910 /* check if the protocol has already specified a relay-info
1911 we need to do this to allow them to override when using non-standard
1912 TURN modes, like Google f.ex. */
1913 gboolean got_turn_from_protocol = FALSE;
1914 guint i;
1915 GPtrArray *relay_info = g_ptr_array_new_full (1, (GDestroyNotify) gst_structure_free);
1916 gboolean ret;
1918 session = get_session(self, sess_id);
1920 if (session == NULL) {
1921 purple_debug_error("backend-fs2",
1922 "Couldn't find session to create stream.\n");
1923 return FALSE;
1926 participant = get_participant(self, who);
1928 if (participant == NULL) {
1929 purple_debug_error("backend-fs2", "Couldn't find "
1930 "participant to create stream.\n");
1931 return FALSE;
1934 fsstream = fs_session_new_stream(session->session, participant,
1935 initiator == TRUE ? type_direction :
1936 (type_direction & FS_DIRECTION_RECV), &err);
1938 if (fsstream == NULL) {
1939 if (err) {
1940 purple_debug_error("backend-fs2", "Error creating stream: %s\n",
1941 err->message ? err->message : "NULL");
1942 g_error_free(err);
1943 } else
1944 purple_debug_error("backend-fs2",
1945 "Error creating stream\n");
1946 return FALSE;
1949 for (i = 0 ; i < num_params ; i++) {
1950 if (purple_strequal(params[i].name, "relay-info")) {
1951 got_turn_from_protocol = TRUE;
1952 break;
1956 _params = g_new0(GParameter, num_params + 3);
1957 memcpy(_params, params, sizeof(GParameter) * num_params);
1959 /* set the controlling mode parameter */
1960 _params[_num_params].name = "controlling-mode";
1961 g_value_init(&_params[_num_params].value, G_TYPE_BOOLEAN);
1962 g_value_set_boolean(&_params[_num_params].value, initiator);
1963 ++_num_params;
1965 if (stun_ip) {
1966 purple_debug_info("backend-fs2",
1967 "Setting stun-ip on new stream: %s\n", stun_ip);
1969 _params[_num_params].name = "stun-ip";
1970 g_value_init(&_params[_num_params].value, G_TYPE_STRING);
1971 g_value_set_string(&_params[_num_params].value, stun_ip);
1972 ++_num_params;
1975 if (turn_ip && purple_strequal("nice", transmitter) && !got_turn_from_protocol) {
1976 gint port;
1977 const gchar *username = purple_prefs_get_string(
1978 "/purple/network/turn_username");
1979 const gchar *password = purple_prefs_get_string(
1980 "/purple/network/turn_password");
1982 /* UDP */
1983 port = purple_prefs_get_int("/purple/network/turn_port");
1984 if (port > 0) {
1985 g_ptr_array_add (relay_info,
1986 gst_structure_new ("relay-info",
1987 "ip", G_TYPE_STRING, turn_ip,
1988 "port", G_TYPE_UINT, port,
1989 "username", G_TYPE_STRING, username,
1990 "password", G_TYPE_STRING, password,
1991 "relay-type", G_TYPE_STRING, "udp",
1992 NULL));
1995 /* TCP */
1996 port = purple_prefs_get_int("/purple/network/turn_port_tcp");
1997 if (port > 0) {
1998 g_ptr_array_add (relay_info,
1999 gst_structure_new ("relay-info",
2000 "ip", G_TYPE_STRING, turn_ip,
2001 "port", G_TYPE_UINT, port,
2002 "username", G_TYPE_STRING, username,
2003 "password", G_TYPE_STRING, password,
2004 "relay-type", G_TYPE_STRING, "tcp",
2005 NULL));
2008 /* TURN over SSL is only supported by libnice for Google's "psuedo" SSL mode
2009 at this time */
2011 purple_debug_info("backend-fs2",
2012 "Setting relay-info on new stream\n");
2013 _params[_num_params].name = "relay-info";
2014 g_value_init(&_params[_num_params].value, G_TYPE_PTR_ARRAY);
2015 g_value_set_boxed(&_params[_num_params].value, relay_info);
2016 _num_params++;
2019 ret = fs_stream_set_transmitter(fsstream, transmitter,
2020 _params, _num_params, &err);
2021 for (i = 0 ; i < _num_params ; i++)
2022 g_value_unset (&_params[i].value);
2023 g_free(_params);
2024 if (relay_info)
2025 g_ptr_array_unref (relay_info);
2026 if (ret == FALSE) {
2027 purple_debug_error("backend-fs2",
2028 "Could not set transmitter %s: %s.\n",
2029 transmitter, err ? err->message : NULL);
2030 g_clear_error(&err);
2031 return FALSE;
2034 stream = g_new0(PurpleMediaBackendFs2Stream, 1);
2035 stream->participant = g_strdup(who);
2036 stream->session = session;
2037 stream->stream = fsstream;
2038 stream->supports_add = purple_strequal(transmitter, "nice");
2040 priv->streams = g_list_append(priv->streams, stream);
2042 g_signal_connect(G_OBJECT(fsstream), "src-pad-added",
2043 G_CALLBACK(src_pad_added_cb), stream);
2045 return TRUE;
2048 static void
2049 free_stream(PurpleMediaBackendFs2Stream *stream)
2051 /* Remove the connected_cb timeout */
2052 if (stream->connected_cb_id != 0)
2053 g_source_remove(stream->connected_cb_id);
2055 g_free(stream->participant);
2057 if (stream->local_candidates)
2058 fs_candidate_list_destroy(stream->local_candidates);
2060 if (stream->remote_candidates)
2061 fs_candidate_list_destroy(stream->remote_candidates);
2063 g_free(stream);
2066 static gboolean
2067 purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
2068 const gchar *sess_id, const gchar *who,
2069 PurpleMediaSessionType type, gboolean initiator,
2070 const gchar *transmitter,
2071 guint num_params, GParameter *params)
2073 PurpleMediaBackendFs2 *backend = PURPLE_MEDIA_BACKEND_FS2(self);
2074 PurpleMediaBackendFs2Private *priv =
2075 purple_media_backend_fs2_get_instance_private(backend);
2076 PurpleMediaBackendFs2Stream *stream;
2078 if (priv->conference == NULL && !init_conference(backend)) {
2079 purple_debug_error("backend-fs2",
2080 "Error initializing the conference.\n");
2081 return FALSE;
2084 if (get_session(backend, sess_id) == NULL &&
2085 !create_session(backend, sess_id, type,
2086 initiator, transmitter)) {
2087 purple_debug_error("backend-fs2",
2088 "Error creating the session.\n");
2089 return FALSE;
2092 if (get_participant(backend, who) == NULL &&
2093 !create_participant(backend, who)) {
2094 purple_debug_error("backend-fs2",
2095 "Error creating the participant.\n");
2096 return FALSE;
2099 stream = get_stream(backend, sess_id, who);
2101 if (stream != NULL) {
2102 FsStreamDirection type_direction =
2103 session_type_to_fs_stream_direction(type);
2105 if (session_type_to_fs_stream_direction(
2106 stream->session->type) != type_direction) {
2107 /* change direction */
2108 g_object_set(stream->stream, "direction",
2109 type_direction, NULL);
2111 } else if (!create_stream(backend, sess_id, who, type,
2112 initiator, transmitter, num_params, params)) {
2113 purple_debug_error("backend-fs2",
2114 "Error creating the stream.\n");
2115 return FALSE;
2118 return TRUE;
2121 static void
2122 purple_media_backend_fs2_add_remote_candidates(PurpleMediaBackend *self,
2123 const gchar *sess_id, const gchar *participant,
2124 GList *remote_candidates)
2126 PurpleMediaBackendFs2Private *priv;
2127 PurpleMediaBackendFs2Stream *stream;
2128 GError *err = NULL;
2130 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2132 priv = purple_media_backend_fs2_get_instance_private(
2133 PURPLE_MEDIA_BACKEND_FS2(self));
2134 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2135 sess_id, participant);
2137 if (stream == NULL) {
2138 purple_debug_error("backend-fs2",
2139 "purple_media_add_remote_candidates: "
2140 "couldn't find stream %s %s.\n",
2141 sess_id ? sess_id : "(null)",
2142 participant ? participant : "(null)");
2143 return;
2146 stream->remote_candidates = g_list_concat(stream->remote_candidates,
2147 candidate_list_to_fs(remote_candidates));
2149 if (purple_media_is_initiator(priv->media, sess_id, participant) ||
2150 purple_media_accepted(
2151 priv->media, sess_id, participant)) {
2153 if (stream->supports_add)
2154 fs_stream_add_remote_candidates(stream->stream,
2155 stream->remote_candidates, &err);
2156 else
2157 fs_stream_force_remote_candidates(stream->stream,
2158 stream->remote_candidates, &err);
2160 if (err) {
2161 purple_debug_error("backend-fs2", "Error adding remote"
2162 " candidates: %s\n", err->message);
2163 g_error_free(err);
2168 static gboolean
2169 purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
2170 const gchar *sess_id)
2172 PurpleMediaBackendFs2Private *priv;
2173 gboolean ret = FALSE;
2175 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2177 priv = purple_media_backend_fs2_get_instance_private(
2178 PURPLE_MEDIA_BACKEND_FS2(self));
2180 if (sess_id != NULL) {
2181 PurpleMediaBackendFs2Session *session = get_session(
2182 PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2184 if (session == NULL)
2185 return FALSE;
2187 if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
2188 #ifdef HAVE_MEDIA_APPLICATION
2189 PURPLE_MEDIA_SEND_APPLICATION |
2190 #endif
2191 PURPLE_MEDIA_SEND_VIDEO)) {
2193 GList *codecs = NULL;
2195 g_object_get(session->session,
2196 "codecs", &codecs, NULL);
2197 if (codecs) {
2198 fs_codec_list_destroy (codecs);
2199 ret = TRUE;
2201 } else
2202 ret = TRUE;
2203 } else {
2204 GList *values = g_hash_table_get_values(priv->sessions);
2206 for (; values; values = g_list_delete_link(values, values)) {
2207 PurpleMediaBackendFs2Session *session = values->data;
2209 if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
2210 #ifdef HAVE_MEDIA_APPLICATION
2211 PURPLE_MEDIA_SEND_APPLICATION |
2212 #endif
2213 PURPLE_MEDIA_SEND_VIDEO)) {
2215 GList *codecs = NULL;
2217 g_object_get(session->session,
2218 "codecs", &codecs, NULL);
2219 if (codecs) {
2220 fs_codec_list_destroy (codecs);
2221 ret = TRUE;
2222 } else {
2223 ret = FALSE;
2224 break;
2226 } else
2227 ret = TRUE;
2230 if (values != NULL)
2231 g_list_free(values);
2234 return ret;
2237 static GList *
2238 purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
2239 const gchar *sess_id)
2241 PurpleMediaBackendFs2Session *session;
2242 GList *fscodecs;
2243 GList *codecs;
2245 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
2247 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2249 if (session == NULL)
2250 return NULL;
2252 g_object_get(G_OBJECT(session->session),
2253 "codecs", &fscodecs, NULL);
2254 codecs = codec_list_from_fs(fscodecs);
2255 fs_codec_list_destroy(fscodecs);
2257 return codecs;
2260 static GList *
2261 purple_media_backend_fs2_get_local_candidates(PurpleMediaBackend *self,
2262 const gchar *sess_id, const gchar *participant)
2264 PurpleMediaBackendFs2Stream *stream;
2265 GList *candidates = NULL;
2267 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
2269 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2270 sess_id, participant);
2272 if (stream != NULL)
2273 candidates = candidate_list_from_fs(
2274 stream->local_candidates);
2275 return candidates;
2278 static gboolean
2279 purple_media_backend_fs2_set_remote_codecs(PurpleMediaBackend *self,
2280 const gchar *sess_id, const gchar *participant,
2281 GList *codecs)
2283 PurpleMediaBackendFs2Stream *stream;
2284 GList *fscodecs;
2285 GError *err = NULL;
2287 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2288 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2289 sess_id, participant);
2291 if (stream == NULL)
2292 return FALSE;
2294 fscodecs = codec_list_to_fs(codecs);
2295 fs_stream_set_remote_codecs(stream->stream, fscodecs, &err);
2296 fs_codec_list_destroy(fscodecs);
2298 if (err) {
2299 purple_debug_error("backend-fs2",
2300 "Error setting remote codecs: %s\n",
2301 err->message);
2302 g_error_free(err);
2303 return FALSE;
2306 return TRUE;
2309 static GstStructure *
2310 create_fs2_srtp_structure(const gchar *cipher, const gchar *auth,
2311 const gchar *key, gsize key_len)
2313 GstStructure *result;
2314 GstBuffer *buffer;
2315 GstMapInfo info;
2317 buffer = gst_buffer_new_allocate(NULL, key_len, NULL);
2318 gst_buffer_map(buffer, &info, GST_MAP_WRITE);
2319 memcpy(info.data, key, key_len);
2320 gst_buffer_unmap(buffer, &info);
2322 result = gst_structure_new("FarstreamSRTP",
2323 "cipher", G_TYPE_STRING, cipher,
2324 "auth", G_TYPE_STRING, auth,
2325 "key", GST_TYPE_BUFFER, buffer,
2326 NULL);
2327 gst_buffer_unref(buffer);
2329 return result;
2332 static gboolean
2333 purple_media_backend_fs2_set_encryption_parameters (PurpleMediaBackend *self,
2334 const gchar *sess_id, const gchar *cipher, const gchar *auth,
2335 const gchar *key, gsize key_len)
2337 PurpleMediaBackendFs2Session *session;
2338 GstStructure *srtp;
2339 GError *err = NULL;
2340 gboolean result;
2342 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2344 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2345 if (!session)
2346 return FALSE;
2348 srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
2349 if (!srtp)
2350 return FALSE;
2352 result = fs_session_set_encryption_parameters(session->session, srtp,
2353 &err);
2354 if (!result) {
2355 purple_debug_error("backend-fs2",
2356 "Error setting encryption parameters: %s\n", err->message);
2357 g_error_free(err);
2360 gst_structure_free(srtp);
2361 return result;
2364 static gboolean
2365 purple_media_backend_fs2_set_decryption_parameters (PurpleMediaBackend *self,
2366 const gchar *sess_id, const gchar *participant,
2367 const gchar *cipher, const gchar *auth,
2368 const gchar *key, gsize key_len)
2370 PurpleMediaBackendFs2Stream *stream;
2371 GstStructure *srtp;
2372 GError *err = NULL;
2373 gboolean result;
2375 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2377 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), sess_id,
2378 participant);
2379 if (!stream)
2380 return FALSE;
2382 srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
2383 if (!srtp)
2384 return FALSE;
2386 result = fs_stream_set_decryption_parameters(stream->stream, srtp,
2387 &err);
2388 if (!result) {
2389 purple_debug_error("backend-fs2",
2390 "Error setting decryption parameters: %s\n", err->message);
2391 g_error_free(err);
2394 gst_structure_free(srtp);
2395 return result;
2398 static gboolean
2399 purple_media_backend_fs2_set_send_codec(PurpleMediaBackend *self,
2400 const gchar *sess_id, PurpleMediaCodec *codec)
2402 PurpleMediaBackendFs2Session *session;
2403 FsCodec *fscodec;
2404 GError *err = NULL;
2406 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2408 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2410 if (session == NULL)
2411 return FALSE;
2413 fscodec = codec_to_fs(codec);
2414 fs_session_set_send_codec(session->session, fscodec, &err);
2415 fs_codec_destroy(fscodec);
2417 if (err) {
2418 purple_debug_error("media", "Error setting send codec\n");
2419 g_error_free(err);
2420 return FALSE;
2423 return TRUE;
2426 static const gchar **
2427 purple_media_backend_fs2_get_available_params(void)
2429 static const gchar *supported_params[] = {
2430 "sdes-cname", "sdes-email", "sdes-location", "sdes-name", "sdes-note",
2431 "sdes-phone", "sdes-tool", NULL
2434 return supported_params;
2437 static const gchar*
2438 param_to_sdes_type(const gchar *param)
2440 const gchar **supported = purple_media_backend_fs2_get_available_params();
2441 static const gchar *sdes_types[] = {
2442 "cname", "email", "location", "name", "note", "phone", "tool", NULL
2444 guint i;
2446 for (i = 0; supported[i] != NULL; ++i) {
2447 if (purple_strequal(param, supported[i])) {
2448 return sdes_types[i];
2452 return NULL;
2455 static void
2456 purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
2457 guint num_params, GParameter *params)
2459 PurpleMediaBackendFs2Private *priv;
2460 guint i;
2461 GstStructure *sdes;
2463 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2465 priv = purple_media_backend_fs2_get_instance_private(
2466 PURPLE_MEDIA_BACKEND_FS2(self));
2468 if (priv->conference == NULL &&
2469 !init_conference(PURPLE_MEDIA_BACKEND_FS2(self))) {
2470 purple_debug_error("backend-fs2",
2471 "Error initializing the conference.\n");
2472 return;
2475 g_object_get(G_OBJECT(priv->conference), "sdes", &sdes, NULL);
2477 for (i = 0; i != num_params; ++i) {
2478 const gchar *sdes_type = param_to_sdes_type(params[i].name);
2479 if (!sdes_type)
2480 continue;
2482 gst_structure_set(sdes, sdes_type,
2483 G_TYPE_STRING, g_value_get_string(&params[i].value),
2484 NULL);
2487 g_object_set(G_OBJECT(priv->conference), "sdes", sdes, NULL);
2488 gst_structure_free(sdes);
2490 static gboolean
2491 send_dtmf_callback(gpointer userdata)
2493 FsSession *session = userdata;
2495 fs_session_stop_telephony_event(session);
2497 return FALSE;
2499 static gboolean
2500 purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
2501 const gchar *sess_id, gchar dtmf, guint8 volume,
2502 guint16 duration)
2504 PurpleMediaBackendFs2Session *session;
2505 FsDTMFEvent event;
2507 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2509 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2510 if (session == NULL)
2511 return FALSE;
2513 /* Convert DTMF char into FsDTMFEvent enum */
2514 switch(dtmf) {
2515 case '0': event = FS_DTMF_EVENT_0; break;
2516 case '1': event = FS_DTMF_EVENT_1; break;
2517 case '2': event = FS_DTMF_EVENT_2; break;
2518 case '3': event = FS_DTMF_EVENT_3; break;
2519 case '4': event = FS_DTMF_EVENT_4; break;
2520 case '5': event = FS_DTMF_EVENT_5; break;
2521 case '6': event = FS_DTMF_EVENT_6; break;
2522 case '7': event = FS_DTMF_EVENT_7; break;
2523 case '8': event = FS_DTMF_EVENT_8; break;
2524 case '9': event = FS_DTMF_EVENT_9; break;
2525 case '*': event = FS_DTMF_EVENT_STAR; break;
2526 case '#': event = FS_DTMF_EVENT_POUND; break;
2527 case 'A': event = FS_DTMF_EVENT_A; break;
2528 case 'B': event = FS_DTMF_EVENT_B; break;
2529 case 'C': event = FS_DTMF_EVENT_C; break;
2530 case 'D': event = FS_DTMF_EVENT_D; break;
2531 default:
2532 return FALSE;
2535 if (!fs_session_start_telephony_event(session->session,
2536 event, volume)) {
2537 return FALSE;
2540 if (duration <= 50) {
2541 fs_session_stop_telephony_event(session->session);
2542 } else {
2543 g_timeout_add(duration, send_dtmf_callback,
2544 session->session);
2547 return TRUE;
2549 #else
2550 GType
2551 purple_media_backend_fs2_get_type(void)
2553 return G_TYPE_NONE;
2555 #endif /* USE_VV */
2557 GstElement *
2558 purple_media_backend_fs2_get_src(PurpleMediaBackendFs2 *self,
2559 const gchar *sess_id)
2561 #ifdef USE_VV
2562 PurpleMediaBackendFs2Session *session = get_session(self, sess_id);
2563 return session != NULL ? session->src : NULL;
2564 #else
2565 return NULL;
2566 #endif
2569 GstElement *
2570 purple_media_backend_fs2_get_tee(PurpleMediaBackendFs2 *self,
2571 const gchar *sess_id, const gchar *who)
2573 #ifdef USE_VV
2574 if (sess_id != NULL && who == NULL) {
2575 PurpleMediaBackendFs2Session *session =
2576 get_session(self, sess_id);
2577 return (session != NULL) ? session->tee : NULL;
2578 } else if (sess_id != NULL && who != NULL) {
2579 PurpleMediaBackendFs2Stream *stream =
2580 get_stream(self, sess_id, who);
2581 return (stream != NULL) ? stream->tee : NULL;
2584 #endif /* USE_VV */
2585 g_return_val_if_reached(NULL);
2588 void
2589 purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self,
2590 const gchar *sess_id, double level)
2592 #ifdef USE_VV
2593 PurpleMediaBackendFs2Private *priv;
2594 GList *sessions;
2596 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2598 priv = purple_media_backend_fs2_get_instance_private(self);
2600 purple_prefs_set_int("/purple/media/audio/volume/input", level);
2602 if (sess_id == NULL)
2603 sessions = g_hash_table_get_values(priv->sessions);
2604 else
2605 sessions = g_list_append(NULL, get_session(self, sess_id));
2607 for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
2608 PurpleMediaBackendFs2Session *session = sessions->data;
2610 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
2611 gchar *name = g_strdup_printf("volume_%s",
2612 session->id);
2613 GstElement *volume = gst_bin_get_by_name(
2614 GST_BIN(priv->confbin), name);
2615 g_free(name);
2616 g_object_set(volume, "volume", level/10.0, NULL);
2619 #endif /* USE_VV */
2622 void
2623 purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
2624 const gchar *sess_id, const gchar *who, double level)
2626 #ifdef USE_VV
2627 GList *streams;
2629 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2631 purple_prefs_set_int("/purple/media/audio/volume/output", level);
2633 streams = get_streams(self, sess_id, who);
2635 for (; streams; streams = g_list_delete_link(streams, streams)) {
2636 PurpleMediaBackendFs2Stream *stream = streams->data;
2638 if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO
2639 && GST_IS_ELEMENT(stream->volume)) {
2640 g_object_set(stream->volume, "volume",
2641 level/10.0, NULL);
2644 #endif /* USE_VV */
2647 #ifdef USE_VV
2648 static gboolean
2649 purple_media_backend_fs2_set_send_rtcp_mux(PurpleMediaBackend *self,
2650 const gchar *sess_id, const gchar *participant,
2651 gboolean send_rtcp_mux)
2653 PurpleMediaBackendFs2Stream *stream;
2655 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2656 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2657 sess_id, participant);
2659 if (stream != NULL &&
2660 g_object_class_find_property (G_OBJECT_GET_CLASS (stream->stream),
2661 "send-rtcp-mux") != NULL) {
2662 g_object_set (stream->stream, "send-rtcp-mux", send_rtcp_mux, NULL);
2663 return TRUE;
2666 return FALSE;
2668 #endif /* USE_VV */