Replace g_list_remove_link+g_list_free_1 with g_list_delete_link
[pidgin-git.git] / libpurple / media / backend-fs2.c
blob8e28b42213f465a694d32c81064bcf4747c678d9
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 g_list_foreach(features, (GFunc)gst_plugin_feature_set_rank, GINT_TO_POINTER(GST_RANK_NONE));
502 gst_plugin_feature_list_free(features);
505 static void
506 purple_media_backend_iface_init(PurpleMediaBackendInterface *iface)
508 iface->add_stream = purple_media_backend_fs2_add_stream;
509 iface->add_remote_candidates =
510 purple_media_backend_fs2_add_remote_candidates;
511 iface->codecs_ready = purple_media_backend_fs2_codecs_ready;
512 iface->get_codecs = purple_media_backend_fs2_get_codecs;
513 iface->get_local_candidates =
514 purple_media_backend_fs2_get_local_candidates;
515 iface->set_remote_codecs = purple_media_backend_fs2_set_remote_codecs;
516 iface->set_send_codec = purple_media_backend_fs2_set_send_codec;
517 iface->set_encryption_parameters =
518 purple_media_backend_fs2_set_encryption_parameters;
519 iface->set_decryption_parameters =
520 purple_media_backend_fs2_set_decryption_parameters;
521 iface->set_params = purple_media_backend_fs2_set_params;
522 iface->get_available_params = purple_media_backend_fs2_get_available_params;
523 iface->send_dtmf = purple_media_backend_fs2_send_dtmf;
524 iface->set_send_rtcp_mux = purple_media_backend_fs2_set_send_rtcp_mux;
527 static FsMediaType
528 session_type_to_fs_media_type(PurpleMediaSessionType type)
530 if (type & PURPLE_MEDIA_AUDIO)
531 return FS_MEDIA_TYPE_AUDIO;
532 else if (type & PURPLE_MEDIA_VIDEO)
533 return FS_MEDIA_TYPE_VIDEO;
534 #ifdef HAVE_MEDIA_APPLICATION
535 else if (type & PURPLE_MEDIA_APPLICATION)
536 return FS_MEDIA_TYPE_APPLICATION;
537 #endif
538 else
539 return 0;
542 static FsStreamDirection
543 session_type_to_fs_stream_direction(PurpleMediaSessionType type)
545 if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO ||
546 (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
547 return FS_DIRECTION_BOTH;
548 else if ((type & PURPLE_MEDIA_SEND_AUDIO) ||
549 (type & PURPLE_MEDIA_SEND_VIDEO))
550 return FS_DIRECTION_SEND;
551 else if ((type & PURPLE_MEDIA_RECV_AUDIO) ||
552 (type & PURPLE_MEDIA_RECV_VIDEO))
553 return FS_DIRECTION_RECV;
554 #ifdef HAVE_MEDIA_APPLICATION
555 else if ((type & PURPLE_MEDIA_APPLICATION) == PURPLE_MEDIA_APPLICATION)
556 return FS_DIRECTION_BOTH;
557 else if (type & PURPLE_MEDIA_SEND_APPLICATION)
558 return FS_DIRECTION_SEND;
559 else if (type & PURPLE_MEDIA_RECV_APPLICATION)
560 return FS_DIRECTION_RECV;
561 #endif
562 else
563 return FS_DIRECTION_NONE;
566 static PurpleMediaSessionType
567 session_type_from_fs(FsMediaType type, FsStreamDirection direction)
569 PurpleMediaSessionType result = PURPLE_MEDIA_NONE;
570 if (type == FS_MEDIA_TYPE_AUDIO) {
571 if (direction & FS_DIRECTION_SEND)
572 result |= PURPLE_MEDIA_SEND_AUDIO;
573 if (direction & FS_DIRECTION_RECV)
574 result |= PURPLE_MEDIA_RECV_AUDIO;
575 } else if (type == FS_MEDIA_TYPE_VIDEO) {
576 if (direction & FS_DIRECTION_SEND)
577 result |= PURPLE_MEDIA_SEND_VIDEO;
578 if (direction & FS_DIRECTION_RECV)
579 result |= PURPLE_MEDIA_RECV_VIDEO;
580 #ifdef HAVE_MEDIA_APPLICATION
581 } else if (type == FS_MEDIA_TYPE_APPLICATION) {
582 if (direction & FS_DIRECTION_SEND)
583 result |= PURPLE_MEDIA_SEND_APPLICATION;
584 if (direction & FS_DIRECTION_RECV)
585 result |= PURPLE_MEDIA_RECV_APPLICATION;
586 #endif
588 return result;
591 static FsCandidate *
592 candidate_to_fs(PurpleMediaCandidate *candidate)
594 FsCandidate *fscandidate;
595 gchar *foundation;
596 guint component_id;
597 gchar *ip;
598 guint port;
599 gchar *base_ip;
600 guint base_port;
601 PurpleMediaNetworkProtocol proto;
602 guint32 priority;
603 PurpleMediaCandidateType type;
604 gchar *username;
605 gchar *password;
606 guint ttl;
608 if (candidate == NULL)
609 return NULL;
611 g_object_get(G_OBJECT(candidate),
612 "foundation", &foundation,
613 "component-id", &component_id,
614 "ip", &ip,
615 "port", &port,
616 "base-ip", &base_ip,
617 "base-port", &base_port,
618 "protocol", &proto,
619 "priority", &priority,
620 "type", &type,
621 "username", &username,
622 "password", &password,
623 "ttl", &ttl,
624 NULL);
626 fscandidate = fs_candidate_new(foundation,
627 component_id, purple_media_candidate_type_to_fs(type),
628 purple_media_network_protocol_to_fs(proto), ip, port);
630 fscandidate->base_ip = base_ip;
631 fscandidate->base_port = base_port;
632 fscandidate->priority = priority;
633 fscandidate->username = username;
634 fscandidate->password = password;
635 fscandidate->ttl = ttl;
637 g_free(foundation);
638 g_free(ip);
639 return fscandidate;
642 static GList *
643 candidate_list_to_fs(GList *candidates)
645 GList *new_list = NULL;
647 for (; candidates; candidates = g_list_next(candidates)) {
648 new_list = g_list_prepend(new_list,
649 candidate_to_fs(candidates->data));
652 new_list = g_list_reverse(new_list);
653 return new_list;
656 static PurpleMediaCandidate *
657 candidate_from_fs(FsCandidate *fscandidate)
659 PurpleMediaCandidate *candidate;
661 if (fscandidate == NULL)
662 return NULL;
664 candidate = purple_media_candidate_new(fscandidate->foundation,
665 fscandidate->component_id,
666 purple_media_candidate_type_from_fs(fscandidate->type),
667 purple_media_network_protocol_from_fs(fscandidate->proto),
668 fscandidate->ip, fscandidate->port);
669 g_object_set(candidate,
670 "base-ip", fscandidate->base_ip,
671 "base-port", fscandidate->base_port,
672 "priority", fscandidate->priority,
673 "username", fscandidate->username,
674 "password", fscandidate->password,
675 "ttl", fscandidate->ttl, NULL);
676 return candidate;
679 static GList *
680 candidate_list_from_fs(GList *candidates)
682 GList *new_list = NULL;
684 for (; candidates; candidates = g_list_next(candidates)) {
685 new_list = g_list_prepend(new_list,
686 candidate_from_fs(candidates->data));
689 new_list = g_list_reverse(new_list);
690 return new_list;
693 static FsCodec *
694 codec_to_fs(const PurpleMediaCodec *codec)
696 FsCodec *new_codec;
697 gint id;
698 char *encoding_name;
699 PurpleMediaSessionType media_type;
700 guint clock_rate;
701 guint channels;
702 GList *iter;
704 if (codec == NULL)
705 return NULL;
707 g_object_get(G_OBJECT(codec),
708 "id", &id,
709 "encoding-name", &encoding_name,
710 "media-type", &media_type,
711 "clock-rate", &clock_rate,
712 "channels", &channels,
713 "optional-params", &iter,
714 NULL);
716 new_codec = fs_codec_new(id, encoding_name,
717 session_type_to_fs_media_type(media_type),
718 clock_rate);
719 new_codec->channels = channels;
721 for (; iter; iter = g_list_next(iter)) {
722 PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data;
723 fs_codec_add_optional_parameter(new_codec,
724 param->key, param->value);
727 g_free(encoding_name);
728 return new_codec;
731 static PurpleMediaCodec *
732 codec_from_fs(const FsCodec *codec)
734 PurpleMediaCodec *new_codec;
735 GList *iter;
737 if (codec == NULL)
738 return NULL;
740 new_codec = purple_media_codec_new(codec->id, codec->encoding_name,
741 session_type_from_fs(codec->media_type,
742 FS_DIRECTION_BOTH), codec->clock_rate);
743 g_object_set(new_codec, "channels", codec->channels, NULL);
745 for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
746 FsCodecParameter *param = (FsCodecParameter*)iter->data;
747 purple_media_codec_add_optional_parameter(new_codec,
748 param->name, param->value);
751 return new_codec;
754 static GList *
755 codec_list_from_fs(GList *codecs)
757 GList *new_list = NULL;
759 for (; codecs; codecs = g_list_next(codecs)) {
760 new_list = g_list_prepend(new_list,
761 codec_from_fs(codecs->data));
764 new_list = g_list_reverse(new_list);
765 return new_list;
768 static GList *
769 codec_list_to_fs(GList *codecs)
771 GList *new_list = NULL;
773 for (; codecs; codecs = g_list_next(codecs)) {
774 new_list = g_list_prepend(new_list,
775 codec_to_fs(codecs->data));
778 new_list = g_list_reverse(new_list);
779 return new_list;
782 static PurpleMediaBackendFs2Session *
783 get_session(PurpleMediaBackendFs2 *self, const gchar *sess_id)
785 PurpleMediaBackendFs2Private *priv;
786 PurpleMediaBackendFs2Session *session = NULL;
788 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
790 priv = purple_media_backend_fs2_get_instance_private(self);
792 if (priv->sessions != NULL)
793 session = g_hash_table_lookup(priv->sessions, sess_id);
795 return session;
798 static FsParticipant *
799 get_participant(PurpleMediaBackendFs2 *self, const gchar *name)
801 PurpleMediaBackendFs2Private *priv;
802 FsParticipant *participant = NULL;
804 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
806 priv = purple_media_backend_fs2_get_instance_private(self);
808 if (priv->participants != NULL)
809 participant = g_hash_table_lookup(priv->participants, name);
811 return participant;
814 static PurpleMediaBackendFs2Stream *
815 get_stream(PurpleMediaBackendFs2 *self,
816 const gchar *sess_id, const gchar *name)
818 PurpleMediaBackendFs2Private *priv;
819 GList *streams;
821 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
823 priv = purple_media_backend_fs2_get_instance_private(self);
824 streams = priv->streams;
826 for (; streams; streams = g_list_next(streams)) {
827 PurpleMediaBackendFs2Stream *stream = streams->data;
828 if (purple_strequal(stream->session->id, sess_id) &&
829 purple_strequal(stream->participant, name))
830 return stream;
833 return NULL;
836 static GList *
837 get_streams(PurpleMediaBackendFs2 *self,
838 const gchar *sess_id, const gchar *name)
840 PurpleMediaBackendFs2Private *priv;
841 GList *streams, *ret = NULL;
843 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
845 priv = purple_media_backend_fs2_get_instance_private(self);
846 streams = priv->streams;
848 for (; streams; streams = g_list_next(streams)) {
849 PurpleMediaBackendFs2Stream *stream = streams->data;
851 if (sess_id != NULL && !purple_strequal(stream->session->id, sess_id))
852 continue;
853 else if (name != NULL && !purple_strequal(stream->participant, name))
854 continue;
855 else
856 ret = g_list_prepend(ret, stream);
859 ret = g_list_reverse(ret);
860 return ret;
863 static PurpleMediaBackendFs2Session *
864 get_session_from_fs_stream(PurpleMediaBackendFs2 *self, FsStream *stream)
866 PurpleMediaBackendFs2Private *priv =
867 purple_media_backend_fs2_get_instance_private(self);
868 FsSession *fssession;
869 GList *values;
871 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
872 g_return_val_if_fail(FS_IS_STREAM(stream), NULL);
874 g_object_get(stream, "session", &fssession, NULL);
876 values = g_hash_table_get_values(priv->sessions);
878 for (; values; values = g_list_delete_link(values, values)) {
879 PurpleMediaBackendFs2Session *session = values->data;
881 if (session->session == fssession) {
882 g_list_free(values);
883 g_object_unref(fssession);
884 return session;
888 g_object_unref(fssession);
889 return NULL;
892 static gdouble
893 gst_msg_db_to_percent(GstMessage *msg, gchar *value_name)
895 const GValue *list;
896 const GValue *value;
897 gdouble value_db;
898 gdouble percent;
900 list = gst_structure_get_value(gst_message_get_structure(msg), value_name);
901 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
902 value = g_value_array_get_nth(g_value_get_boxed(list), 0);
903 G_GNUC_END_IGNORE_DEPRECATIONS
904 value_db = g_value_get_double(value);
905 percent = pow(10, value_db / 20);
906 return (percent > 1.0) ? 1.0 : percent;
909 static void
910 purple_media_error_fs(PurpleMedia *media, const gchar *error,
911 const GstStructure *fs_error)
913 const gchar *error_msg = gst_structure_get_string(fs_error, "error-msg");
915 purple_media_error(media, "%s%s%s", error,
916 error_msg ? _("\n\nMessage from Farstream: ") : "",
917 error_msg ? error_msg : "");
920 static void
921 gst_handle_message_element(GstBus *bus, GstMessage *msg,
922 PurpleMediaBackendFs2 *self)
924 PurpleMediaBackendFs2Private *priv =
925 purple_media_backend_fs2_get_instance_private(self);
926 GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
927 static guint level_id = 0;
928 const GstStructure *structure = gst_message_get_structure(msg);
930 if (level_id == 0)
931 level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA);
933 if (gst_structure_has_name(structure, "level")) {
934 GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
935 gchar *name;
936 gchar *participant = NULL;
937 PurpleMediaBackendFs2Session *session = NULL;
938 gdouble percent;
940 if (!PURPLE_IS_MEDIA(priv->media) ||
941 GST_ELEMENT_PARENT(src) != priv->confbin)
942 return;
944 name = gst_element_get_name(src);
946 if (!strncmp(name, "sendlevel_", 10)) {
947 session = get_session(self, name+10);
948 if (priv->silence_threshold > 0) {
949 percent = gst_msg_db_to_percent(msg, "decay");
950 g_object_set(session->srcvalve,
951 "drop", (percent < priv->silence_threshold), NULL);
955 g_free(name);
957 if (!g_signal_has_handler_pending(priv->media, level_id, 0, FALSE))
958 return;
960 if (!session) {
961 GList *iter = priv->streams;
962 PurpleMediaBackendFs2Stream *stream;
963 for (; iter; iter = g_list_next(iter)) {
964 stream = iter->data;
965 if (stream->level == src) {
966 session = stream->session;
967 participant = stream->participant;
968 break;
973 if (!session)
974 return;
976 percent = gst_msg_db_to_percent(msg, "rms");
978 g_signal_emit(priv->media, level_id, 0,
979 session->id, participant, percent);
980 return;
983 if (!FS_IS_CONFERENCE(src) || !PURPLE_IS_MEDIA_BACKEND(self) ||
984 priv->conference != FS_CONFERENCE(src))
985 return;
987 if (gst_structure_has_name(structure, "farstream-error")) {
988 FsError error_no;
989 gboolean error_emitted = FALSE;
990 gst_structure_get_enum(structure, "error-no",
991 FS_TYPE_ERROR, (gint*)&error_no);
992 switch (error_no) {
993 case FS_ERROR_CONSTRUCTION:
994 purple_media_error_fs(priv->media,
995 _("Error initializing the call. "
996 "This probably denotes problem in "
997 "installation of GStreamer or Farstream."),
998 structure);
999 error_emitted = TRUE;
1000 break;
1001 case FS_ERROR_NETWORK:
1002 purple_media_error_fs(priv->media, _("Network error."),
1003 structure);
1004 error_emitted = TRUE;
1005 purple_media_end(priv->media, NULL, NULL);
1006 break;
1007 case FS_ERROR_NEGOTIATION_FAILED:
1008 purple_media_error_fs(priv->media,
1009 _("Codec negotiation failed. "
1010 "This problem might be resolved by installing "
1011 "more GStreamer codecs."),
1012 structure);
1013 error_emitted = TRUE;
1014 purple_media_end(priv->media, NULL, NULL);
1015 break;
1016 case FS_ERROR_NO_CODECS:
1017 purple_media_error(priv->media,
1018 _("No codecs found. "
1019 "Install some GStreamer codecs found "
1020 "in GStreamer plugins packages."));
1021 error_emitted = TRUE;
1022 purple_media_end(priv->media, NULL, NULL);
1023 break;
1024 default:
1025 purple_debug_error("backend-fs2",
1026 "farstream-error: %i: %s\n",
1027 error_no,
1028 gst_structure_get_string(structure, "error-msg"));
1029 break;
1032 if (FS_ERROR_IS_FATAL(error_no)) {
1033 if (!error_emitted)
1034 purple_media_error(priv->media,
1035 _("A non-recoverable Farstream error has occurred."));
1036 purple_media_end(priv->media, NULL, NULL);
1038 } else if (gst_structure_has_name(structure,
1039 "farstream-new-local-candidate")) {
1040 const GValue *value;
1041 FsStream *stream;
1042 FsCandidate *local_candidate;
1043 PurpleMediaCandidate *candidate;
1044 FsParticipant *participant;
1045 PurpleMediaBackendFs2Session *session;
1046 PurpleMediaBackendFs2Stream *media_stream;
1047 const gchar *name;
1049 value = gst_structure_get_value(structure, "stream");
1050 stream = g_value_get_object(value);
1051 value = gst_structure_get_value(structure, "candidate");
1052 local_candidate = g_value_get_boxed(value);
1054 session = get_session_from_fs_stream(self, stream);
1056 purple_debug_info("backend-fs2",
1057 "got new local candidate: %s\n",
1058 local_candidate->foundation);
1060 g_object_get(stream, "participant", &participant, NULL);
1061 name = g_object_get_data(G_OBJECT(participant), "purple-name");
1063 media_stream = get_stream(self, session->id, name);
1064 media_stream->local_candidates = g_list_append(
1065 media_stream->local_candidates,
1066 fs_candidate_copy(local_candidate));
1068 candidate = candidate_from_fs(local_candidate);
1069 g_signal_emit_by_name(self, "new-candidate",
1070 session->id, name, candidate);
1071 g_object_unref(candidate);
1072 g_object_unref(participant);
1073 } else if (gst_structure_has_name(structure,
1074 "farstream-local-candidates-prepared")) {
1075 const GValue *value;
1076 FsStream *stream;
1077 FsParticipant *participant;
1078 PurpleMediaBackendFs2Session *session;
1080 value = gst_structure_get_value(structure, "stream");
1081 stream = g_value_get_object(value);
1082 session = get_session_from_fs_stream(self, stream);
1084 g_object_get(stream, "participant", &participant, NULL);
1086 g_signal_emit_by_name(self, "candidates-prepared",
1087 session->id,
1088 g_object_get_data(G_OBJECT(participant), "purple-name"));
1090 g_object_unref(participant);
1091 } else if (gst_structure_has_name(structure,
1092 "farstream-new-active-candidate-pair")) {
1093 const GValue *value;
1094 FsStream *stream;
1095 FsCandidate *local_candidate;
1096 FsCandidate *remote_candidate;
1097 FsParticipant *participant;
1098 PurpleMediaBackendFs2Session *session;
1099 PurpleMediaCandidate *lcandidate, *rcandidate;
1101 value = gst_structure_get_value(structure, "stream");
1102 stream = g_value_get_object(value);
1103 value = gst_structure_get_value(structure, "local-candidate");
1104 local_candidate = g_value_get_boxed(value);
1105 value = gst_structure_get_value(structure, "remote-candidate");
1106 remote_candidate = g_value_get_boxed(value);
1108 g_object_get(stream, "participant", &participant, NULL);
1110 session = get_session_from_fs_stream(self, stream);
1112 lcandidate = candidate_from_fs(local_candidate);
1113 rcandidate = candidate_from_fs(remote_candidate);
1115 g_signal_emit_by_name(self, "active-candidate-pair",
1116 session->id,
1117 g_object_get_data(G_OBJECT(participant), "purple-name"),
1118 lcandidate, rcandidate);
1120 g_object_unref(participant);
1121 g_object_unref(lcandidate);
1122 g_object_unref(rcandidate);
1123 } else if (gst_structure_has_name(structure,
1124 "farstream-recv-codecs-changed")) {
1125 const GValue *value;
1126 GList *codecs;
1127 FsCodec *codec;
1129 value = gst_structure_get_value(structure, "codecs");
1130 codecs = g_value_get_boxed(value);
1131 codec = codecs->data;
1133 purple_debug_info("backend-fs2",
1134 "farstream-recv-codecs-changed: %s\n",
1135 codec->encoding_name);
1136 } else if (gst_structure_has_name(structure,
1137 "farstream-component-state-changed")) {
1138 const GValue *value;
1139 FsStreamState fsstate;
1140 guint component;
1141 const gchar *state;
1143 value = gst_structure_get_value(structure, "state");
1144 fsstate = g_value_get_enum(value);
1145 value = gst_structure_get_value(structure, "component");
1146 component = g_value_get_uint(value);
1148 switch (fsstate) {
1149 case FS_STREAM_STATE_FAILED:
1150 state = "FAILED";
1151 break;
1152 case FS_STREAM_STATE_DISCONNECTED:
1153 state = "DISCONNECTED";
1154 break;
1155 case FS_STREAM_STATE_GATHERING:
1156 state = "GATHERING";
1157 break;
1158 case FS_STREAM_STATE_CONNECTING:
1159 state = "CONNECTING";
1160 break;
1161 case FS_STREAM_STATE_CONNECTED:
1162 state = "CONNECTED";
1163 break;
1164 case FS_STREAM_STATE_READY:
1165 state = "READY";
1166 break;
1167 default:
1168 state = "UNKNOWN";
1169 break;
1172 purple_debug_info("backend-fs2",
1173 "farstream-component-state-changed: "
1174 "component: %u state: %s\n",
1175 component, state);
1176 } else if (gst_structure_has_name(structure,
1177 "farstream-send-codec-changed")) {
1178 const GValue *value;
1179 FsCodec *codec;
1180 gchar *codec_str;
1182 value = gst_structure_get_value(structure, "codec");
1183 codec = g_value_get_boxed(value);
1184 codec_str = fs_codec_to_string(codec);
1186 purple_debug_info("backend-fs2",
1187 "farstream-send-codec-changed: codec: %s\n",
1188 codec_str);
1190 g_free(codec_str);
1191 } else if (gst_structure_has_name(structure,
1192 "farstream-codecs-changed")) {
1193 const GValue *value;
1194 FsSession *fssession;
1195 GList *sessions;
1197 value = gst_structure_get_value(structure, "session");
1198 fssession = g_value_get_object(value);
1199 sessions = g_hash_table_get_values(priv->sessions);
1201 for (; sessions; sessions =
1202 g_list_delete_link(sessions, sessions)) {
1203 PurpleMediaBackendFs2Session *session = sessions->data;
1204 gchar *session_id;
1206 if (session->session != fssession)
1207 continue;
1209 session_id = g_strdup(session->id);
1210 g_signal_emit_by_name(self, "codecs-changed",
1211 session_id);
1212 g_free(session_id);
1213 g_list_free(sessions);
1214 break;
1219 static void
1220 gst_handle_message_error(GstBus *bus, GstMessage *msg,
1221 PurpleMediaBackendFs2 *self)
1223 PurpleMediaBackendFs2Private *priv =
1224 purple_media_backend_fs2_get_instance_private(self);
1225 GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg));
1226 GstElement *lastElement = NULL;
1227 GList *sessions;
1229 GError *error = NULL;
1230 gchar *debug_msg = NULL;
1232 gst_message_parse_error(msg, &error, &debug_msg);
1233 purple_debug_error("backend-fs2", "gst error %s\ndebugging: %s\n",
1234 error->message, debug_msg);
1236 g_error_free(error);
1237 g_free(debug_msg);
1239 while (element && !GST_IS_PIPELINE(element)) {
1240 if (element == priv->confbin)
1241 break;
1243 lastElement = element;
1244 element = GST_ELEMENT_PARENT(element);
1247 if (!element || !GST_IS_PIPELINE(element))
1248 return;
1250 sessions = purple_media_get_session_ids(priv->media);
1252 for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
1253 if (purple_media_get_src(priv->media, sessions->data)
1254 != lastElement)
1255 continue;
1257 if (purple_media_get_session_type(priv->media, sessions->data)
1258 & PURPLE_MEDIA_AUDIO)
1259 purple_media_error(priv->media,
1260 _("Error with your microphone"));
1261 else if (purple_media_get_session_type(priv->media,
1262 sessions->data) & PURPLE_MEDIA_VIDEO)
1263 purple_media_error(priv->media,
1264 _("Error with your webcam"));
1266 break;
1269 g_list_free(sessions);
1271 purple_media_error(priv->media, _("Conference error"));
1272 purple_media_end(priv->media, NULL, NULL);
1275 static gboolean
1276 gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self)
1278 switch(GST_MESSAGE_TYPE(msg)) {
1279 case GST_MESSAGE_ELEMENT:
1280 gst_handle_message_element(bus, msg, self);
1281 break;
1282 case GST_MESSAGE_ERROR:
1283 gst_handle_message_error(bus, msg, self);
1284 break;
1285 default:
1286 break;
1289 return TRUE;
1292 static void
1293 remove_element(GstElement *element)
1295 if (element) {
1296 gst_element_set_locked_state(element, TRUE);
1297 gst_element_set_state(element, GST_STATE_NULL);
1298 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
1302 static void
1303 state_changed_cb(PurpleMedia *media, PurpleMediaState state,
1304 gchar *sid, gchar *name, PurpleMediaBackendFs2 *self)
1306 if (state == PURPLE_MEDIA_STATE_END) {
1307 PurpleMediaBackendFs2Private *priv =
1308 purple_media_backend_fs2_get_instance_private(
1309 self);
1311 if (sid && name) {
1312 PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name);
1313 gst_object_unref(stream->stream);
1315 priv->streams = g_list_remove(priv->streams, stream);
1317 remove_element(stream->src);
1318 remove_element(stream->tee);
1319 remove_element(stream->volume);
1320 remove_element(stream->level);
1321 remove_element(stream->fakesink);
1322 remove_element(stream->queue);
1324 free_stream(stream);
1325 } else if (sid && !name) {
1326 PurpleMediaBackendFs2Session *session = get_session(self, sid);
1327 GstPad *pad;
1329 g_object_get(session->session, "sink-pad", &pad, NULL);
1330 gst_pad_unlink(GST_PAD_PEER(pad), pad);
1331 gst_object_unref(pad);
1333 gst_object_unref(session->session);
1334 g_hash_table_remove(priv->sessions, session->id);
1336 pad = gst_pad_get_peer(session->srcpad);
1337 gst_element_remove_pad(GST_ELEMENT_PARENT(pad), pad);
1338 gst_object_unref(pad);
1339 gst_object_unref(session->srcpad);
1341 remove_element(session->srcvalve);
1342 remove_element(session->tee);
1344 free_session(session);
1347 purple_media_manager_remove_output_windows(
1348 purple_media_get_manager(media), media, sid, name);
1352 static void
1353 stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
1354 gchar *sid, gchar *name, gboolean local,
1355 PurpleMediaBackendFs2 *self)
1357 if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) {
1358 PurpleMediaBackendFs2Stream *stream =
1359 get_stream(self, sid, name);
1360 GError *err = NULL;
1362 g_object_set(G_OBJECT(stream->stream), "direction",
1363 session_type_to_fs_stream_direction(
1364 stream->session->type), NULL);
1366 if (stream->remote_candidates == NULL ||
1367 purple_media_is_initiator(media, sid, name))
1368 return;
1370 if (stream->supports_add)
1371 fs_stream_add_remote_candidates(stream->stream,
1372 stream->remote_candidates, &err);
1373 else
1374 fs_stream_force_remote_candidates(stream->stream,
1375 stream->remote_candidates, &err);
1377 if (err == NULL)
1378 return;
1380 purple_debug_error("backend-fs2", "Error adding "
1381 "remote candidates: %s\n",
1382 err->message);
1383 g_error_free(err);
1384 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE ||
1385 type == PURPLE_MEDIA_INFO_UNMUTE)) {
1386 PurpleMediaBackendFs2Private *priv =
1387 purple_media_backend_fs2_get_instance_private(
1388 self);
1389 gboolean active = (type == PURPLE_MEDIA_INFO_MUTE);
1390 GList *sessions;
1392 if (sid == NULL)
1393 sessions = g_hash_table_get_values(priv->sessions);
1394 else
1395 sessions = g_list_prepend(NULL,
1396 get_session(self, sid));
1398 purple_debug_info("media", "Turning mute %s\n",
1399 active ? "on" : "off");
1401 for (; sessions; sessions = g_list_delete_link(
1402 sessions, sessions)) {
1403 PurpleMediaBackendFs2Session *session =
1404 sessions->data;
1406 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
1407 gchar *name = g_strdup_printf("volume_%s",
1408 session->id);
1409 GstElement *volume = gst_bin_get_by_name(
1410 GST_BIN(priv->confbin), name);
1411 g_free(name);
1412 g_object_set(volume, "mute", active, NULL);
1415 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
1416 type == PURPLE_MEDIA_INFO_UNHOLD)) {
1417 gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
1418 GList *streams = get_streams(self, sid, name);
1419 for (; streams; streams =
1420 g_list_delete_link(streams, streams)) {
1421 PurpleMediaBackendFs2Stream *stream = streams->data;
1422 if (stream->session->type & PURPLE_MEDIA_SEND_AUDIO) {
1423 g_object_set(stream->stream, "direction",
1424 session_type_to_fs_stream_direction(
1425 stream->session->type & ((active) ?
1426 ~PURPLE_MEDIA_SEND_AUDIO :
1427 PURPLE_MEDIA_AUDIO)), NULL);
1430 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE ||
1431 type == PURPLE_MEDIA_INFO_UNPAUSE)) {
1432 gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE);
1433 GList *streams = get_streams(self, sid, name);
1434 for (; streams; streams =
1435 g_list_delete_link(streams, streams)) {
1436 PurpleMediaBackendFs2Stream *stream = streams->data;
1437 if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
1438 g_object_set(stream->stream, "direction",
1439 session_type_to_fs_stream_direction(
1440 stream->session->type & ((active) ?
1441 ~PURPLE_MEDIA_SEND_VIDEO :
1442 PURPLE_MEDIA_VIDEO)), NULL);
1448 static gboolean
1449 init_conference(PurpleMediaBackendFs2 *self)
1451 PurpleMediaBackendFs2Private *priv =
1452 purple_media_backend_fs2_get_instance_private(self);
1453 GstElement *pipeline;
1454 GstBus *bus;
1455 gchar *name;
1456 GKeyFile *default_props;
1458 priv->conference = FS_CONFERENCE(
1459 gst_element_factory_make(priv->conference_type, NULL));
1461 if (priv->conference == NULL) {
1462 purple_debug_error("backend-fs2", "Conference == NULL\n");
1463 return FALSE;
1466 if (purple_account_get_silence_suppression(
1467 purple_media_get_account(priv->media)))
1468 priv->silence_threshold = purple_prefs_get_int(
1469 "/purple/media/audio/silence_threshold") / 100.0;
1470 else
1471 priv->silence_threshold = 0;
1473 pipeline = purple_media_manager_get_pipeline(
1474 purple_media_get_manager(priv->media));
1476 if (pipeline == NULL) {
1477 purple_debug_error("backend-fs2",
1478 "Couldn't retrieve pipeline.\n");
1479 return FALSE;
1482 name = g_strdup_printf("conf_%p", priv->conference);
1483 priv->confbin = gst_bin_new(name);
1484 if (priv->confbin == NULL) {
1485 purple_debug_error("backend-fs2",
1486 "Couldn't create confbin.\n");
1487 return FALSE;
1490 g_free(name);
1492 bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
1493 if (bus == NULL) {
1494 purple_debug_error("backend-fs2",
1495 "Couldn't get the pipeline's bus.\n");
1496 return FALSE;
1499 default_props = fs_utils_get_default_element_properties(GST_ELEMENT(priv->conference));
1500 if (default_props != NULL) {
1501 priv->notifier = fs_element_added_notifier_new();
1502 fs_element_added_notifier_add(priv->notifier,
1503 GST_BIN(priv->confbin));
1504 fs_element_added_notifier_set_properties_from_keyfile(priv->notifier, default_props);
1507 g_signal_connect(G_OBJECT(bus), "message",
1508 G_CALLBACK(gst_bus_cb), self);
1509 gst_object_unref(bus);
1511 if (!gst_bin_add(GST_BIN(pipeline),
1512 GST_ELEMENT(priv->confbin))) {
1513 purple_debug_error("backend-fs2", "Couldn't add confbin "
1514 "element to the pipeline\n");
1515 return FALSE;
1518 if (!gst_bin_add(GST_BIN(priv->confbin),
1519 GST_ELEMENT(priv->conference))) {
1520 purple_debug_error("backend-fs2", "Couldn't add conference "
1521 "element to the confbin\n");
1522 return FALSE;
1525 if (gst_element_set_state(GST_ELEMENT(priv->confbin),
1526 GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
1527 purple_debug_error("backend-fs2",
1528 "Failed to start conference.\n");
1529 return FALSE;
1532 return TRUE;
1535 static gboolean
1536 create_src(PurpleMediaBackendFs2 *self, const gchar *sess_id,
1537 PurpleMediaSessionType type)
1539 PurpleMediaBackendFs2Private *priv =
1540 purple_media_backend_fs2_get_instance_private(self);
1541 PurpleMediaBackendFs2Session *session;
1542 PurpleMediaSessionType session_type;
1543 FsMediaType media_type = session_type_to_fs_media_type(type);
1544 FsStreamDirection type_direction =
1545 session_type_to_fs_stream_direction(type);
1546 GstElement *src;
1547 GstPad *sinkpad, *srcpad;
1548 GstPad *ghost = NULL;
1550 if ((type_direction & FS_DIRECTION_SEND) == 0)
1551 return TRUE;
1553 session_type = session_type_from_fs(
1554 media_type, FS_DIRECTION_SEND);
1555 src = purple_media_manager_get_element(
1556 purple_media_get_manager(priv->media),
1557 session_type, priv->media, sess_id, NULL);
1559 if (!GST_IS_ELEMENT(src)) {
1560 purple_debug_error("backend-fs2",
1561 "Error creating src for session %s\n",
1562 sess_id);
1563 return FALSE;
1566 session = get_session(self, sess_id);
1568 if (session == NULL) {
1569 purple_debug_warning("backend-fs2",
1570 "purple_media_set_src: trying to set"
1571 " src on non-existent session\n");
1572 return FALSE;
1575 if (session->src)
1576 gst_object_unref(session->src);
1578 session->src = src;
1579 gst_element_set_locked_state(session->src, TRUE);
1581 session->tee = gst_element_factory_make("tee", NULL);
1582 gst_bin_add(GST_BIN(priv->confbin), session->tee);
1584 /* This supposedly isn't necessary, but it silences some warnings */
1585 if (GST_ELEMENT_PARENT(priv->confbin)
1586 == GST_ELEMENT_PARENT(session->src)) {
1587 GstPad *pad = gst_element_get_static_pad(session->tee, "sink");
1588 ghost = gst_ghost_pad_new(NULL, pad);
1589 gst_object_unref(pad);
1590 gst_pad_set_active(ghost, TRUE);
1591 gst_element_add_pad(priv->confbin, ghost);
1594 gst_element_set_state(session->tee, GST_STATE_PLAYING);
1595 gst_element_link(session->src, priv->confbin);
1596 if (ghost)
1597 session->srcpad = gst_pad_get_peer(ghost);
1599 g_object_get(session->session, "sink-pad", &sinkpad, NULL);
1600 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
1601 gchar *name = g_strdup_printf("volume_%s", session->id);
1602 GstElement *level;
1603 GstElement *volume = gst_element_factory_make("volume", name);
1604 double input_volume = purple_prefs_get_int(
1605 "/purple/media/audio/volume/input")/10.0;
1606 g_free(name);
1607 name = g_strdup_printf("sendlevel_%s", session->id);
1608 level = gst_element_factory_make("level", name);
1609 g_free(name);
1610 session->srcvalve = gst_element_factory_make("valve", NULL);
1611 gst_bin_add(GST_BIN(priv->confbin), volume);
1612 gst_bin_add(GST_BIN(priv->confbin), level);
1613 gst_bin_add(GST_BIN(priv->confbin), session->srcvalve);
1614 gst_element_set_state(level, GST_STATE_PLAYING);
1615 gst_element_set_state(volume, GST_STATE_PLAYING);
1616 gst_element_set_state(session->srcvalve, GST_STATE_PLAYING);
1617 gst_element_link(level, session->srcvalve);
1618 gst_element_link(volume, level);
1619 gst_element_link(session->tee, volume);
1620 srcpad = gst_element_get_static_pad(session->srcvalve, "src");
1621 g_object_set(volume, "volume", input_volume, NULL);
1622 } else {
1623 srcpad = gst_element_get_request_pad(session->tee, "src_%u");
1626 purple_debug_info("backend-fs2", "connecting pad: %s\n",
1627 gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
1628 ? "success" : "failure");
1629 gst_element_set_locked_state(session->src, FALSE);
1630 gst_object_unref(session->src);
1631 gst_object_unref(sinkpad);
1633 purple_media_manager_create_output_window(purple_media_get_manager(
1634 priv->media), priv->media, sess_id, NULL);
1636 purple_debug_info("backend-fs2", "create_src: setting source "
1637 "state to GST_STATE_PLAYING - it may hang here on win32\n");
1638 gst_element_set_state(session->src, GST_STATE_PLAYING);
1639 purple_debug_info("backend-fs2", "create_src: state set\n");
1641 return TRUE;
1644 static gboolean
1645 create_session(PurpleMediaBackendFs2 *self, const gchar *sess_id,
1646 PurpleMediaSessionType type, gboolean initiator,
1647 const gchar *transmitter)
1649 PurpleMediaBackendFs2Private *priv =
1650 purple_media_backend_fs2_get_instance_private(self);
1651 PurpleMediaBackendFs2Session *session;
1652 GError *err = NULL;
1653 GList *codec_conf = NULL;
1654 gchar *filename = NULL;
1656 session = g_new0(PurpleMediaBackendFs2Session, 1);
1658 session->session = fs_conference_new_session(priv->conference,
1659 session_type_to_fs_media_type(type), &err);
1661 #ifdef HAVE_MEDIA_APPLICATION
1662 if (type == PURPLE_MEDIA_APPLICATION) {
1663 GstCaps *caps;
1664 GObject *rtpsession = NULL;
1666 caps = gst_caps_new_empty_simple ("application/octet-stream");
1667 fs_session_set_allowed_caps (session->session, caps, caps, NULL);
1668 gst_caps_unref (caps);
1669 g_object_get (session->session, "internal-session", &rtpsession, NULL);
1670 if (rtpsession) {
1671 g_object_set (rtpsession, "probation", 0, NULL);
1672 g_object_unref (rtpsession);
1675 #endif
1676 if (err != NULL) {
1677 purple_media_error(priv->media,
1678 _("Error creating session: %s"),
1679 err->message);
1680 g_error_free(err);
1681 g_free(session);
1682 return FALSE;
1685 filename = g_build_filename(purple_config_dir(), "fs-codec.conf", NULL);
1686 codec_conf = fs_codec_list_from_keyfile(filename, &err);
1687 g_free(filename);
1689 if (err != NULL) {
1690 if (err->code == G_KEY_FILE_ERROR_NOT_FOUND)
1691 purple_debug_info("backend-fs2", "Couldn't read "
1692 "fs-codec.conf: %s\n",
1693 err->message);
1694 else
1695 purple_debug_error("backend-fs2", "Error reading "
1696 "fs-codec.conf: %s\n",
1697 err->message);
1698 g_error_free(err);
1700 purple_debug_info("backend-fs2",
1701 "Loading default codec conf instead\n");
1702 codec_conf = fs_utils_get_default_codec_preferences(
1703 GST_ELEMENT(priv->conference));
1706 fs_session_set_codec_preferences(session->session, codec_conf, NULL);
1707 fs_codec_list_destroy(codec_conf);
1710 * Removes a 5-7 second delay before
1711 * receiving the src-pad-added signal.
1712 * Only works for non-multicast FsRtpSessions.
1714 if (!purple_strequal(transmitter, "multicast"))
1715 g_object_set(G_OBJECT(session->session),
1716 "no-rtcp-timeout", 0, NULL);
1718 session->id = g_strdup(sess_id);
1719 session->backend = self;
1720 session->type = type;
1722 if (!priv->sessions) {
1723 purple_debug_info("backend-fs2",
1724 "Creating hash table for sessions\n");
1725 priv->sessions = g_hash_table_new_full(g_str_hash, g_str_equal,
1726 g_free, NULL);
1729 g_hash_table_insert(priv->sessions, g_strdup(session->id), session);
1731 if (!create_src(self, sess_id, type)) {
1732 purple_debug_info("backend-fs2", "Error creating the src\n");
1733 return FALSE;
1736 return TRUE;
1739 static void
1740 free_session(PurpleMediaBackendFs2Session *session)
1742 g_free(session->id);
1743 g_free(session);
1746 static gboolean
1747 create_participant(PurpleMediaBackendFs2 *self, const gchar *name)
1749 PurpleMediaBackendFs2Private *priv =
1750 purple_media_backend_fs2_get_instance_private(self);
1751 FsParticipant *participant;
1752 GError *err = NULL;
1754 participant = fs_conference_new_participant(
1755 priv->conference, &err);
1757 if (err) {
1758 purple_debug_error("backend-fs2",
1759 "Error creating participant: %s\n",
1760 err->message);
1761 g_error_free(err);
1762 return FALSE;
1765 g_object_set_data_full(G_OBJECT(participant), "purple-name",
1766 g_strdup(name), g_free);
1768 if (g_object_class_find_property(G_OBJECT_GET_CLASS(participant),
1769 "cname")) {
1770 g_object_set(participant, "cname", name, NULL);
1773 if (!priv->participants) {
1774 purple_debug_info("backend-fs2",
1775 "Creating hash table for participants\n");
1776 priv->participants = g_hash_table_new_full(g_str_hash,
1777 g_str_equal, g_free, g_object_unref);
1780 g_hash_table_insert(priv->participants, g_strdup(name), participant);
1782 return TRUE;
1785 static gboolean
1786 src_pad_added_cb_cb(PurpleMediaBackendFs2Stream *stream)
1788 PurpleMediaBackendFs2Private *priv;
1790 g_return_val_if_fail(stream != NULL, FALSE);
1792 priv = purple_media_backend_fs2_get_instance_private(
1793 stream->session->backend);
1794 stream->connected_cb_id = 0;
1796 purple_media_manager_create_output_window(
1797 purple_media_get_manager(priv->media), priv->media,
1798 stream->session->id, stream->participant);
1800 g_signal_emit_by_name(priv->media, "state-changed",
1801 PURPLE_MEDIA_STATE_CONNECTED,
1802 stream->session->id, stream->participant);
1803 return FALSE;
1806 static void
1807 src_pad_added_cb(FsStream *fsstream, GstPad *srcpad,
1808 FsCodec *codec, PurpleMediaBackendFs2Stream *stream)
1810 PurpleMediaBackendFs2Private *priv;
1811 GstPad *sinkpad;
1813 g_return_if_fail(FS_IS_STREAM(fsstream));
1814 g_return_if_fail(stream != NULL);
1816 priv = purple_media_backend_fs2_get_instance_private(
1817 stream->session->backend);
1819 if (stream->src == NULL) {
1820 GstElement *sink = NULL;
1822 if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
1823 double output_volume = purple_prefs_get_int(
1824 "/purple/media/audio/volume/output")/10.0;
1826 * Should this instead be:
1827 * audioconvert ! audioresample ! liveadder !
1828 * audioresample ! audioconvert ! realsink
1830 stream->queue = gst_element_factory_make("queue", NULL);
1831 stream->volume = gst_element_factory_make("volume", NULL);
1832 g_object_set(stream->volume, "volume", output_volume, NULL);
1833 stream->level = gst_element_factory_make("level", NULL);
1834 stream->src = gst_element_factory_make("liveadder", NULL);
1835 sink = purple_media_manager_get_element(
1836 purple_media_get_manager(priv->media),
1837 PURPLE_MEDIA_RECV_AUDIO, priv->media,
1838 stream->session->id,
1839 stream->participant);
1840 gst_bin_add(GST_BIN(priv->confbin), stream->queue);
1841 gst_bin_add(GST_BIN(priv->confbin), stream->volume);
1842 gst_bin_add(GST_BIN(priv->confbin), stream->level);
1843 gst_bin_add(GST_BIN(priv->confbin), sink);
1844 gst_element_set_state(sink, GST_STATE_PLAYING);
1845 gst_element_set_state(stream->level, GST_STATE_PLAYING);
1846 gst_element_set_state(stream->volume, GST_STATE_PLAYING);
1847 gst_element_set_state(stream->queue, GST_STATE_PLAYING);
1848 gst_element_link(stream->level, sink);
1849 gst_element_link(stream->volume, stream->level);
1850 gst_element_link(stream->queue, stream->volume);
1851 sink = stream->queue;
1852 } else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) {
1853 stream->src = gst_element_factory_make("funnel", NULL);
1854 sink = gst_element_factory_make("fakesink", NULL);
1855 g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
1856 gst_bin_add(GST_BIN(priv->confbin), sink);
1857 gst_element_set_state(sink, GST_STATE_PLAYING);
1858 stream->fakesink = sink;
1859 #ifdef HAVE_MEDIA_APPLICATION
1860 } else if (codec->media_type == FS_MEDIA_TYPE_APPLICATION) {
1861 stream->src = gst_element_factory_make("funnel", NULL);
1862 sink = purple_media_manager_get_element(
1863 purple_media_get_manager(priv->media),
1864 PURPLE_MEDIA_RECV_APPLICATION, priv->media,
1865 stream->session->id,
1866 stream->participant);
1867 gst_bin_add(GST_BIN(priv->confbin), sink);
1868 gst_element_set_state(sink, GST_STATE_PLAYING);
1869 #endif
1871 stream->tee = gst_element_factory_make("tee", NULL);
1872 gst_bin_add_many(GST_BIN(priv->confbin),
1873 stream->src, stream->tee, NULL);
1874 gst_element_set_state(stream->tee, GST_STATE_PLAYING);
1875 gst_element_set_state(stream->src, GST_STATE_PLAYING);
1876 gst_element_link_many(stream->src, stream->tee, sink, NULL);
1879 sinkpad = gst_element_get_request_pad(stream->src, "sink_%u");
1880 gst_pad_link(srcpad, sinkpad);
1881 gst_object_unref(sinkpad);
1883 stream->connected_cb_id = g_timeout_add(0,
1884 (GSourceFunc)src_pad_added_cb_cb, stream);
1887 static gboolean
1888 create_stream(PurpleMediaBackendFs2 *self,
1889 const gchar *sess_id, const gchar *who,
1890 PurpleMediaSessionType type, gboolean initiator,
1891 const gchar *transmitter,
1892 guint num_params, GParameter *params)
1894 PurpleMediaBackendFs2Private *priv =
1895 purple_media_backend_fs2_get_instance_private(self);
1896 GError *err = NULL;
1897 FsStream *fsstream = NULL;
1898 const gchar *stun_ip = purple_network_get_stun_ip();
1899 const gchar *turn_ip = purple_network_get_turn_ip();
1900 guint _num_params = num_params;
1901 GParameter *_params;
1902 FsStreamDirection type_direction =
1903 session_type_to_fs_stream_direction(type);
1904 PurpleMediaBackendFs2Session *session;
1905 PurpleMediaBackendFs2Stream *stream;
1906 FsParticipant *participant;
1907 /* check if the protocol has already specified a relay-info
1908 we need to do this to allow them to override when using non-standard
1909 TURN modes, like Google f.ex. */
1910 gboolean got_turn_from_protocol = FALSE;
1911 guint i;
1912 GPtrArray *relay_info = g_ptr_array_new_full (1, (GDestroyNotify) gst_structure_free);
1913 gboolean ret;
1915 session = get_session(self, sess_id);
1917 if (session == NULL) {
1918 purple_debug_error("backend-fs2",
1919 "Couldn't find session to create stream.\n");
1920 return FALSE;
1923 participant = get_participant(self, who);
1925 if (participant == NULL) {
1926 purple_debug_error("backend-fs2", "Couldn't find "
1927 "participant to create stream.\n");
1928 return FALSE;
1931 fsstream = fs_session_new_stream(session->session, participant,
1932 initiator == TRUE ? type_direction :
1933 (type_direction & FS_DIRECTION_RECV), &err);
1935 if (fsstream == NULL) {
1936 if (err) {
1937 purple_debug_error("backend-fs2", "Error creating stream: %s\n",
1938 err->message ? err->message : "NULL");
1939 g_error_free(err);
1940 } else
1941 purple_debug_error("backend-fs2",
1942 "Error creating stream\n");
1943 return FALSE;
1946 for (i = 0 ; i < num_params ; i++) {
1947 if (purple_strequal(params[i].name, "relay-info")) {
1948 got_turn_from_protocol = TRUE;
1949 break;
1953 _params = g_new0(GParameter, num_params + 3);
1954 memcpy(_params, params, sizeof(GParameter) * num_params);
1956 /* set the controlling mode parameter */
1957 _params[_num_params].name = "controlling-mode";
1958 g_value_init(&_params[_num_params].value, G_TYPE_BOOLEAN);
1959 g_value_set_boolean(&_params[_num_params].value, initiator);
1960 ++_num_params;
1962 if (stun_ip) {
1963 purple_debug_info("backend-fs2",
1964 "Setting stun-ip on new stream: %s\n", stun_ip);
1966 _params[_num_params].name = "stun-ip";
1967 g_value_init(&_params[_num_params].value, G_TYPE_STRING);
1968 g_value_set_string(&_params[_num_params].value, stun_ip);
1969 ++_num_params;
1972 if (turn_ip && purple_strequal("nice", transmitter) && !got_turn_from_protocol) {
1973 gint port;
1974 const gchar *username = purple_prefs_get_string(
1975 "/purple/network/turn_username");
1976 const gchar *password = purple_prefs_get_string(
1977 "/purple/network/turn_password");
1979 /* UDP */
1980 port = purple_prefs_get_int("/purple/network/turn_port");
1981 if (port > 0) {
1982 g_ptr_array_add (relay_info,
1983 gst_structure_new ("relay-info",
1984 "ip", G_TYPE_STRING, turn_ip,
1985 "port", G_TYPE_UINT, port,
1986 "username", G_TYPE_STRING, username,
1987 "password", G_TYPE_STRING, password,
1988 "relay-type", G_TYPE_STRING, "udp",
1989 NULL));
1992 /* TCP */
1993 port = purple_prefs_get_int("/purple/network/turn_port_tcp");
1994 if (port > 0) {
1995 g_ptr_array_add (relay_info,
1996 gst_structure_new ("relay-info",
1997 "ip", G_TYPE_STRING, turn_ip,
1998 "port", G_TYPE_UINT, port,
1999 "username", G_TYPE_STRING, username,
2000 "password", G_TYPE_STRING, password,
2001 "relay-type", G_TYPE_STRING, "tcp",
2002 NULL));
2005 /* TURN over SSL is only supported by libnice for Google's "psuedo" SSL mode
2006 at this time */
2008 purple_debug_info("backend-fs2",
2009 "Setting relay-info on new stream\n");
2010 _params[_num_params].name = "relay-info";
2011 g_value_init(&_params[_num_params].value, G_TYPE_PTR_ARRAY);
2012 g_value_set_boxed(&_params[_num_params].value, relay_info);
2013 _num_params++;
2016 ret = fs_stream_set_transmitter(fsstream, transmitter,
2017 _params, _num_params, &err);
2018 for (i = 0 ; i < _num_params ; i++)
2019 g_value_unset (&_params[i].value);
2020 g_free(_params);
2021 if (relay_info)
2022 g_ptr_array_unref (relay_info);
2023 if (ret == FALSE) {
2024 purple_debug_error("backend-fs2",
2025 "Could not set transmitter %s: %s.\n",
2026 transmitter, err ? err->message : NULL);
2027 g_clear_error(&err);
2028 return FALSE;
2031 stream = g_new0(PurpleMediaBackendFs2Stream, 1);
2032 stream->participant = g_strdup(who);
2033 stream->session = session;
2034 stream->stream = fsstream;
2035 stream->supports_add = purple_strequal(transmitter, "nice");
2037 priv->streams = g_list_append(priv->streams, stream);
2039 g_signal_connect(G_OBJECT(fsstream), "src-pad-added",
2040 G_CALLBACK(src_pad_added_cb), stream);
2042 return TRUE;
2045 static void
2046 free_stream(PurpleMediaBackendFs2Stream *stream)
2048 /* Remove the connected_cb timeout */
2049 if (stream->connected_cb_id != 0)
2050 g_source_remove(stream->connected_cb_id);
2052 g_free(stream->participant);
2054 if (stream->local_candidates)
2055 fs_candidate_list_destroy(stream->local_candidates);
2057 if (stream->remote_candidates)
2058 fs_candidate_list_destroy(stream->remote_candidates);
2060 g_free(stream);
2063 static gboolean
2064 purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
2065 const gchar *sess_id, const gchar *who,
2066 PurpleMediaSessionType type, gboolean initiator,
2067 const gchar *transmitter,
2068 guint num_params, GParameter *params)
2070 PurpleMediaBackendFs2 *backend = PURPLE_MEDIA_BACKEND_FS2(self);
2071 PurpleMediaBackendFs2Private *priv =
2072 purple_media_backend_fs2_get_instance_private(backend);
2073 PurpleMediaBackendFs2Stream *stream;
2075 if (priv->conference == NULL && !init_conference(backend)) {
2076 purple_debug_error("backend-fs2",
2077 "Error initializing the conference.\n");
2078 return FALSE;
2081 if (get_session(backend, sess_id) == NULL &&
2082 !create_session(backend, sess_id, type,
2083 initiator, transmitter)) {
2084 purple_debug_error("backend-fs2",
2085 "Error creating the session.\n");
2086 return FALSE;
2089 if (get_participant(backend, who) == NULL &&
2090 !create_participant(backend, who)) {
2091 purple_debug_error("backend-fs2",
2092 "Error creating the participant.\n");
2093 return FALSE;
2096 stream = get_stream(backend, sess_id, who);
2098 if (stream != NULL) {
2099 FsStreamDirection type_direction =
2100 session_type_to_fs_stream_direction(type);
2102 if (session_type_to_fs_stream_direction(
2103 stream->session->type) != type_direction) {
2104 /* change direction */
2105 g_object_set(stream->stream, "direction",
2106 type_direction, NULL);
2108 } else if (!create_stream(backend, sess_id, who, type,
2109 initiator, transmitter, num_params, params)) {
2110 purple_debug_error("backend-fs2",
2111 "Error creating the stream.\n");
2112 return FALSE;
2115 return TRUE;
2118 static void
2119 purple_media_backend_fs2_add_remote_candidates(PurpleMediaBackend *self,
2120 const gchar *sess_id, const gchar *participant,
2121 GList *remote_candidates)
2123 PurpleMediaBackendFs2Private *priv;
2124 PurpleMediaBackendFs2Stream *stream;
2125 GError *err = NULL;
2127 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2129 priv = purple_media_backend_fs2_get_instance_private(
2130 PURPLE_MEDIA_BACKEND_FS2(self));
2131 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2132 sess_id, participant);
2134 if (stream == NULL) {
2135 purple_debug_error("backend-fs2",
2136 "purple_media_add_remote_candidates: "
2137 "couldn't find stream %s %s.\n",
2138 sess_id ? sess_id : "(null)",
2139 participant ? participant : "(null)");
2140 return;
2143 stream->remote_candidates = g_list_concat(stream->remote_candidates,
2144 candidate_list_to_fs(remote_candidates));
2146 if (purple_media_is_initiator(priv->media, sess_id, participant) ||
2147 purple_media_accepted(
2148 priv->media, sess_id, participant)) {
2150 if (stream->supports_add)
2151 fs_stream_add_remote_candidates(stream->stream,
2152 stream->remote_candidates, &err);
2153 else
2154 fs_stream_force_remote_candidates(stream->stream,
2155 stream->remote_candidates, &err);
2157 if (err) {
2158 purple_debug_error("backend-fs2", "Error adding remote"
2159 " candidates: %s\n", err->message);
2160 g_error_free(err);
2165 static gboolean
2166 purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
2167 const gchar *sess_id)
2169 PurpleMediaBackendFs2Private *priv;
2170 gboolean ret = FALSE;
2172 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2174 priv = purple_media_backend_fs2_get_instance_private(
2175 PURPLE_MEDIA_BACKEND_FS2(self));
2177 if (sess_id != NULL) {
2178 PurpleMediaBackendFs2Session *session = get_session(
2179 PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2181 if (session == NULL)
2182 return FALSE;
2184 if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
2185 #ifdef HAVE_MEDIA_APPLICATION
2186 PURPLE_MEDIA_SEND_APPLICATION |
2187 #endif
2188 PURPLE_MEDIA_SEND_VIDEO)) {
2190 GList *codecs = NULL;
2192 g_object_get(session->session,
2193 "codecs", &codecs, NULL);
2194 if (codecs) {
2195 fs_codec_list_destroy (codecs);
2196 ret = TRUE;
2198 } else
2199 ret = TRUE;
2200 } else {
2201 GList *values = g_hash_table_get_values(priv->sessions);
2203 for (; values; values = g_list_delete_link(values, values)) {
2204 PurpleMediaBackendFs2Session *session = values->data;
2206 if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
2207 #ifdef HAVE_MEDIA_APPLICATION
2208 PURPLE_MEDIA_SEND_APPLICATION |
2209 #endif
2210 PURPLE_MEDIA_SEND_VIDEO)) {
2212 GList *codecs = NULL;
2214 g_object_get(session->session,
2215 "codecs", &codecs, NULL);
2216 if (codecs) {
2217 fs_codec_list_destroy (codecs);
2218 ret = TRUE;
2219 } else {
2220 ret = FALSE;
2221 break;
2223 } else
2224 ret = TRUE;
2227 if (values != NULL)
2228 g_list_free(values);
2231 return ret;
2234 static GList *
2235 purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
2236 const gchar *sess_id)
2238 PurpleMediaBackendFs2Session *session;
2239 GList *fscodecs;
2240 GList *codecs;
2242 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
2244 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2246 if (session == NULL)
2247 return NULL;
2249 g_object_get(G_OBJECT(session->session),
2250 "codecs", &fscodecs, NULL);
2251 codecs = codec_list_from_fs(fscodecs);
2252 fs_codec_list_destroy(fscodecs);
2254 return codecs;
2257 static GList *
2258 purple_media_backend_fs2_get_local_candidates(PurpleMediaBackend *self,
2259 const gchar *sess_id, const gchar *participant)
2261 PurpleMediaBackendFs2Stream *stream;
2262 GList *candidates = NULL;
2264 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
2266 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2267 sess_id, participant);
2269 if (stream != NULL)
2270 candidates = candidate_list_from_fs(
2271 stream->local_candidates);
2272 return candidates;
2275 static gboolean
2276 purple_media_backend_fs2_set_remote_codecs(PurpleMediaBackend *self,
2277 const gchar *sess_id, const gchar *participant,
2278 GList *codecs)
2280 PurpleMediaBackendFs2Stream *stream;
2281 GList *fscodecs;
2282 GError *err = NULL;
2284 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2285 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2286 sess_id, participant);
2288 if (stream == NULL)
2289 return FALSE;
2291 fscodecs = codec_list_to_fs(codecs);
2292 fs_stream_set_remote_codecs(stream->stream, fscodecs, &err);
2293 fs_codec_list_destroy(fscodecs);
2295 if (err) {
2296 purple_debug_error("backend-fs2",
2297 "Error setting remote codecs: %s\n",
2298 err->message);
2299 g_error_free(err);
2300 return FALSE;
2303 return TRUE;
2306 static GstStructure *
2307 create_fs2_srtp_structure(const gchar *cipher, const gchar *auth,
2308 const gchar *key, gsize key_len)
2310 GstStructure *result;
2311 GstBuffer *buffer;
2312 GstMapInfo info;
2314 buffer = gst_buffer_new_allocate(NULL, key_len, NULL);
2315 gst_buffer_map(buffer, &info, GST_MAP_WRITE);
2316 memcpy(info.data, key, key_len);
2317 gst_buffer_unmap(buffer, &info);
2319 result = gst_structure_new("FarstreamSRTP",
2320 "cipher", G_TYPE_STRING, cipher,
2321 "auth", G_TYPE_STRING, auth,
2322 "key", GST_TYPE_BUFFER, buffer,
2323 NULL);
2324 gst_buffer_unref(buffer);
2326 return result;
2329 static gboolean
2330 purple_media_backend_fs2_set_encryption_parameters (PurpleMediaBackend *self,
2331 const gchar *sess_id, const gchar *cipher, const gchar *auth,
2332 const gchar *key, gsize key_len)
2334 PurpleMediaBackendFs2Session *session;
2335 GstStructure *srtp;
2336 GError *err = NULL;
2337 gboolean result;
2339 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2341 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2342 if (!session)
2343 return FALSE;
2345 srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
2346 if (!srtp)
2347 return FALSE;
2349 result = fs_session_set_encryption_parameters(session->session, srtp,
2350 &err);
2351 if (!result) {
2352 purple_debug_error("backend-fs2",
2353 "Error setting encryption parameters: %s\n", err->message);
2354 g_error_free(err);
2357 gst_structure_free(srtp);
2358 return result;
2361 static gboolean
2362 purple_media_backend_fs2_set_decryption_parameters (PurpleMediaBackend *self,
2363 const gchar *sess_id, const gchar *participant,
2364 const gchar *cipher, const gchar *auth,
2365 const gchar *key, gsize key_len)
2367 PurpleMediaBackendFs2Stream *stream;
2368 GstStructure *srtp;
2369 GError *err = NULL;
2370 gboolean result;
2372 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2374 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), sess_id,
2375 participant);
2376 if (!stream)
2377 return FALSE;
2379 srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
2380 if (!srtp)
2381 return FALSE;
2383 result = fs_stream_set_decryption_parameters(stream->stream, srtp,
2384 &err);
2385 if (!result) {
2386 purple_debug_error("backend-fs2",
2387 "Error setting decryption parameters: %s\n", err->message);
2388 g_error_free(err);
2391 gst_structure_free(srtp);
2392 return result;
2395 static gboolean
2396 purple_media_backend_fs2_set_send_codec(PurpleMediaBackend *self,
2397 const gchar *sess_id, PurpleMediaCodec *codec)
2399 PurpleMediaBackendFs2Session *session;
2400 FsCodec *fscodec;
2401 GError *err = NULL;
2403 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2405 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2407 if (session == NULL)
2408 return FALSE;
2410 fscodec = codec_to_fs(codec);
2411 fs_session_set_send_codec(session->session, fscodec, &err);
2412 fs_codec_destroy(fscodec);
2414 if (err) {
2415 purple_debug_error("media", "Error setting send codec\n");
2416 g_error_free(err);
2417 return FALSE;
2420 return TRUE;
2423 static const gchar **
2424 purple_media_backend_fs2_get_available_params(void)
2426 static const gchar *supported_params[] = {
2427 "sdes-cname", "sdes-email", "sdes-location", "sdes-name", "sdes-note",
2428 "sdes-phone", "sdes-tool", NULL
2431 return supported_params;
2434 static const gchar*
2435 param_to_sdes_type(const gchar *param)
2437 const gchar **supported = purple_media_backend_fs2_get_available_params();
2438 static const gchar *sdes_types[] = {
2439 "cname", "email", "location", "name", "note", "phone", "tool", NULL
2441 guint i;
2443 for (i = 0; supported[i] != NULL; ++i) {
2444 if (purple_strequal(param, supported[i])) {
2445 return sdes_types[i];
2449 return NULL;
2452 static void
2453 purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
2454 guint num_params, GParameter *params)
2456 PurpleMediaBackendFs2Private *priv;
2457 guint i;
2458 GstStructure *sdes;
2460 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2462 priv = purple_media_backend_fs2_get_instance_private(
2463 PURPLE_MEDIA_BACKEND_FS2(self));
2465 if (priv->conference == NULL &&
2466 !init_conference(PURPLE_MEDIA_BACKEND_FS2(self))) {
2467 purple_debug_error("backend-fs2",
2468 "Error initializing the conference.\n");
2469 return;
2472 g_object_get(G_OBJECT(priv->conference), "sdes", &sdes, NULL);
2474 for (i = 0; i != num_params; ++i) {
2475 const gchar *sdes_type = param_to_sdes_type(params[i].name);
2476 if (!sdes_type)
2477 continue;
2479 gst_structure_set(sdes, sdes_type,
2480 G_TYPE_STRING, g_value_get_string(&params[i].value),
2481 NULL);
2484 g_object_set(G_OBJECT(priv->conference), "sdes", sdes, NULL);
2485 gst_structure_free(sdes);
2487 static gboolean
2488 send_dtmf_callback(gpointer userdata)
2490 FsSession *session = userdata;
2492 fs_session_stop_telephony_event(session);
2494 return FALSE;
2496 static gboolean
2497 purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
2498 const gchar *sess_id, gchar dtmf, guint8 volume,
2499 guint16 duration)
2501 PurpleMediaBackendFs2Session *session;
2502 FsDTMFEvent event;
2504 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2506 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2507 if (session == NULL)
2508 return FALSE;
2510 /* Convert DTMF char into FsDTMFEvent enum */
2511 switch(dtmf) {
2512 case '0': event = FS_DTMF_EVENT_0; break;
2513 case '1': event = FS_DTMF_EVENT_1; break;
2514 case '2': event = FS_DTMF_EVENT_2; break;
2515 case '3': event = FS_DTMF_EVENT_3; break;
2516 case '4': event = FS_DTMF_EVENT_4; break;
2517 case '5': event = FS_DTMF_EVENT_5; break;
2518 case '6': event = FS_DTMF_EVENT_6; break;
2519 case '7': event = FS_DTMF_EVENT_7; break;
2520 case '8': event = FS_DTMF_EVENT_8; break;
2521 case '9': event = FS_DTMF_EVENT_9; break;
2522 case '*': event = FS_DTMF_EVENT_STAR; break;
2523 case '#': event = FS_DTMF_EVENT_POUND; break;
2524 case 'A': event = FS_DTMF_EVENT_A; break;
2525 case 'B': event = FS_DTMF_EVENT_B; break;
2526 case 'C': event = FS_DTMF_EVENT_C; break;
2527 case 'D': event = FS_DTMF_EVENT_D; break;
2528 default:
2529 return FALSE;
2532 if (!fs_session_start_telephony_event(session->session,
2533 event, volume)) {
2534 return FALSE;
2537 if (duration <= 50) {
2538 fs_session_stop_telephony_event(session->session);
2539 } else {
2540 g_timeout_add(duration, send_dtmf_callback,
2541 session->session);
2544 return TRUE;
2546 #else
2547 GType
2548 purple_media_backend_fs2_get_type(void)
2550 return G_TYPE_NONE;
2552 #endif /* USE_VV */
2554 GstElement *
2555 purple_media_backend_fs2_get_src(PurpleMediaBackendFs2 *self,
2556 const gchar *sess_id)
2558 #ifdef USE_VV
2559 PurpleMediaBackendFs2Session *session = get_session(self, sess_id);
2560 return session != NULL ? session->src : NULL;
2561 #else
2562 return NULL;
2563 #endif
2566 GstElement *
2567 purple_media_backend_fs2_get_tee(PurpleMediaBackendFs2 *self,
2568 const gchar *sess_id, const gchar *who)
2570 #ifdef USE_VV
2571 if (sess_id != NULL && who == NULL) {
2572 PurpleMediaBackendFs2Session *session =
2573 get_session(self, sess_id);
2574 return (session != NULL) ? session->tee : NULL;
2575 } else if (sess_id != NULL && who != NULL) {
2576 PurpleMediaBackendFs2Stream *stream =
2577 get_stream(self, sess_id, who);
2578 return (stream != NULL) ? stream->tee : NULL;
2581 #endif /* USE_VV */
2582 g_return_val_if_reached(NULL);
2585 void
2586 purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self,
2587 const gchar *sess_id, double level)
2589 #ifdef USE_VV
2590 PurpleMediaBackendFs2Private *priv;
2591 GList *sessions;
2593 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2595 priv = purple_media_backend_fs2_get_instance_private(self);
2597 purple_prefs_set_int("/purple/media/audio/volume/input", level);
2599 if (sess_id == NULL)
2600 sessions = g_hash_table_get_values(priv->sessions);
2601 else
2602 sessions = g_list_append(NULL, get_session(self, sess_id));
2604 for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
2605 PurpleMediaBackendFs2Session *session = sessions->data;
2607 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
2608 gchar *name = g_strdup_printf("volume_%s",
2609 session->id);
2610 GstElement *volume = gst_bin_get_by_name(
2611 GST_BIN(priv->confbin), name);
2612 g_free(name);
2613 g_object_set(volume, "volume", level/10.0, NULL);
2616 #endif /* USE_VV */
2619 void
2620 purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
2621 const gchar *sess_id, const gchar *who, double level)
2623 #ifdef USE_VV
2624 GList *streams;
2626 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2628 purple_prefs_set_int("/purple/media/audio/volume/output", level);
2630 streams = get_streams(self, sess_id, who);
2632 for (; streams; streams = g_list_delete_link(streams, streams)) {
2633 PurpleMediaBackendFs2Stream *stream = streams->data;
2635 if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO
2636 && GST_IS_ELEMENT(stream->volume)) {
2637 g_object_set(stream->volume, "volume",
2638 level/10.0, NULL);
2641 #endif /* USE_VV */
2644 #ifdef USE_VV
2645 static gboolean
2646 purple_media_backend_fs2_set_send_rtcp_mux(PurpleMediaBackend *self,
2647 const gchar *sess_id, const gchar *participant,
2648 gboolean send_rtcp_mux)
2650 PurpleMediaBackendFs2Stream *stream;
2652 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2653 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2654 sess_id, participant);
2656 if (stream != NULL &&
2657 g_object_class_find_property (G_OBJECT_GET_CLASS (stream->stream),
2658 "send-rtcp-mux") != NULL) {
2659 g_object_set (stream->stream, "send-rtcp-mux", send_rtcp_mux, NULL);
2660 return TRUE;
2663 return FALSE;
2665 #endif /* USE_VV */