Use g_list_free_full instead of manual iterations
[pidgin-git.git] / libpurple / media / backend-fs2.c
blob560a634d4a9187d096ed8cfaf8eb369a4421b033
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);
397 g_list_free_full(priv->streams, (GDestroyNotify)free_stream);
399 if (priv->sessions) {
400 GList *sessions = g_hash_table_get_values(priv->sessions);
402 g_list_free_full(sessions, (GDestroyNotify)free_session);
403 g_hash_table_destroy(priv->sessions);
406 G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->finalize(obj);
409 static void
410 purple_media_backend_fs2_set_property(GObject *object, guint prop_id,
411 const GValue *value, GParamSpec *pspec)
413 PurpleMediaBackendFs2Private *priv;
414 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object));
416 priv = purple_media_backend_fs2_get_instance_private(
417 PURPLE_MEDIA_BACKEND_FS2(object));
419 switch (prop_id) {
420 case PROP_CONFERENCE_TYPE:
421 priv->conference_type = g_value_dup_string(value);
422 break;
423 case PROP_MEDIA:
424 priv->media = g_value_get_object(value);
426 if (priv->media == NULL)
427 break;
429 g_object_add_weak_pointer(G_OBJECT(priv->media),
430 (gpointer*)&priv->media);
432 g_signal_connect(G_OBJECT(priv->media),
433 "state-changed",
434 G_CALLBACK(state_changed_cb),
435 PURPLE_MEDIA_BACKEND_FS2(object));
436 g_signal_connect(G_OBJECT(priv->media), "stream-info",
437 G_CALLBACK(stream_info_cb),
438 PURPLE_MEDIA_BACKEND_FS2(object));
439 break;
440 default:
441 G_OBJECT_WARN_INVALID_PROPERTY_ID(
442 object, prop_id, pspec);
443 break;
447 static void
448 purple_media_backend_fs2_get_property(GObject *object, guint prop_id,
449 GValue *value, GParamSpec *pspec)
451 PurpleMediaBackendFs2Private *priv;
452 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object));
454 priv = purple_media_backend_fs2_get_instance_private(
455 PURPLE_MEDIA_BACKEND_FS2(object));
457 switch (prop_id) {
458 case PROP_CONFERENCE_TYPE:
459 g_value_set_string(value, priv->conference_type);
460 break;
461 case PROP_MEDIA:
462 g_value_set_object(value, priv->media);
463 break;
464 default:
465 G_OBJECT_WARN_INVALID_PROPERTY_ID(
466 object, prop_id, pspec);
467 break;
471 static void
472 purple_media_backend_fs2_class_init(PurpleMediaBackendFs2Class *klass)
474 GObjectClass *gobject_class = (GObjectClass*)klass;
475 GList *features;
476 GList *it;
478 gobject_class->dispose = purple_media_backend_fs2_dispose;
479 gobject_class->finalize = purple_media_backend_fs2_finalize;
480 gobject_class->set_property = purple_media_backend_fs2_set_property;
481 gobject_class->get_property = purple_media_backend_fs2_get_property;
483 g_object_class_override_property(gobject_class, PROP_CONFERENCE_TYPE,
484 "conference-type");
485 g_object_class_override_property(gobject_class, PROP_MEDIA, "media");
487 /* VA-API elements aren't well supported in Farstream. Ignore them. */
488 features = gst_registry_get_feature_list_by_plugin(gst_registry_get(),
489 "vaapi");
490 g_list_foreach(features, (GFunc)gst_plugin_feature_set_rank, GINT_TO_POINTER(GST_RANK_NONE));
491 gst_plugin_feature_list_free(features);
494 static void
495 purple_media_backend_iface_init(PurpleMediaBackendInterface *iface)
497 iface->add_stream = purple_media_backend_fs2_add_stream;
498 iface->add_remote_candidates =
499 purple_media_backend_fs2_add_remote_candidates;
500 iface->codecs_ready = purple_media_backend_fs2_codecs_ready;
501 iface->get_codecs = purple_media_backend_fs2_get_codecs;
502 iface->get_local_candidates =
503 purple_media_backend_fs2_get_local_candidates;
504 iface->set_remote_codecs = purple_media_backend_fs2_set_remote_codecs;
505 iface->set_send_codec = purple_media_backend_fs2_set_send_codec;
506 iface->set_encryption_parameters =
507 purple_media_backend_fs2_set_encryption_parameters;
508 iface->set_decryption_parameters =
509 purple_media_backend_fs2_set_decryption_parameters;
510 iface->set_params = purple_media_backend_fs2_set_params;
511 iface->get_available_params = purple_media_backend_fs2_get_available_params;
512 iface->send_dtmf = purple_media_backend_fs2_send_dtmf;
513 iface->set_send_rtcp_mux = purple_media_backend_fs2_set_send_rtcp_mux;
516 static FsMediaType
517 session_type_to_fs_media_type(PurpleMediaSessionType type)
519 if (type & PURPLE_MEDIA_AUDIO)
520 return FS_MEDIA_TYPE_AUDIO;
521 else if (type & PURPLE_MEDIA_VIDEO)
522 return FS_MEDIA_TYPE_VIDEO;
523 #ifdef HAVE_MEDIA_APPLICATION
524 else if (type & PURPLE_MEDIA_APPLICATION)
525 return FS_MEDIA_TYPE_APPLICATION;
526 #endif
527 else
528 return 0;
531 static FsStreamDirection
532 session_type_to_fs_stream_direction(PurpleMediaSessionType type)
534 if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO ||
535 (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
536 return FS_DIRECTION_BOTH;
537 else if ((type & PURPLE_MEDIA_SEND_AUDIO) ||
538 (type & PURPLE_MEDIA_SEND_VIDEO))
539 return FS_DIRECTION_SEND;
540 else if ((type & PURPLE_MEDIA_RECV_AUDIO) ||
541 (type & PURPLE_MEDIA_RECV_VIDEO))
542 return FS_DIRECTION_RECV;
543 #ifdef HAVE_MEDIA_APPLICATION
544 else if ((type & PURPLE_MEDIA_APPLICATION) == PURPLE_MEDIA_APPLICATION)
545 return FS_DIRECTION_BOTH;
546 else if (type & PURPLE_MEDIA_SEND_APPLICATION)
547 return FS_DIRECTION_SEND;
548 else if (type & PURPLE_MEDIA_RECV_APPLICATION)
549 return FS_DIRECTION_RECV;
550 #endif
551 else
552 return FS_DIRECTION_NONE;
555 static PurpleMediaSessionType
556 session_type_from_fs(FsMediaType type, FsStreamDirection direction)
558 PurpleMediaSessionType result = PURPLE_MEDIA_NONE;
559 if (type == FS_MEDIA_TYPE_AUDIO) {
560 if (direction & FS_DIRECTION_SEND)
561 result |= PURPLE_MEDIA_SEND_AUDIO;
562 if (direction & FS_DIRECTION_RECV)
563 result |= PURPLE_MEDIA_RECV_AUDIO;
564 } else if (type == FS_MEDIA_TYPE_VIDEO) {
565 if (direction & FS_DIRECTION_SEND)
566 result |= PURPLE_MEDIA_SEND_VIDEO;
567 if (direction & FS_DIRECTION_RECV)
568 result |= PURPLE_MEDIA_RECV_VIDEO;
569 #ifdef HAVE_MEDIA_APPLICATION
570 } else if (type == FS_MEDIA_TYPE_APPLICATION) {
571 if (direction & FS_DIRECTION_SEND)
572 result |= PURPLE_MEDIA_SEND_APPLICATION;
573 if (direction & FS_DIRECTION_RECV)
574 result |= PURPLE_MEDIA_RECV_APPLICATION;
575 #endif
577 return result;
580 static FsCandidate *
581 candidate_to_fs(PurpleMediaCandidate *candidate)
583 FsCandidate *fscandidate;
584 gchar *foundation;
585 guint component_id;
586 gchar *ip;
587 guint port;
588 gchar *base_ip;
589 guint base_port;
590 PurpleMediaNetworkProtocol proto;
591 guint32 priority;
592 PurpleMediaCandidateType type;
593 gchar *username;
594 gchar *password;
595 guint ttl;
597 if (candidate == NULL)
598 return NULL;
600 g_object_get(G_OBJECT(candidate),
601 "foundation", &foundation,
602 "component-id", &component_id,
603 "ip", &ip,
604 "port", &port,
605 "base-ip", &base_ip,
606 "base-port", &base_port,
607 "protocol", &proto,
608 "priority", &priority,
609 "type", &type,
610 "username", &username,
611 "password", &password,
612 "ttl", &ttl,
613 NULL);
615 fscandidate = fs_candidate_new(foundation,
616 component_id, purple_media_candidate_type_to_fs(type),
617 purple_media_network_protocol_to_fs(proto), ip, port);
619 fscandidate->base_ip = base_ip;
620 fscandidate->base_port = base_port;
621 fscandidate->priority = priority;
622 fscandidate->username = username;
623 fscandidate->password = password;
624 fscandidate->ttl = ttl;
626 g_free(foundation);
627 g_free(ip);
628 return fscandidate;
631 static GList *
632 candidate_list_to_fs(GList *candidates)
634 GList *new_list = NULL;
636 for (; candidates; candidates = g_list_next(candidates)) {
637 new_list = g_list_prepend(new_list,
638 candidate_to_fs(candidates->data));
641 new_list = g_list_reverse(new_list);
642 return new_list;
645 static PurpleMediaCandidate *
646 candidate_from_fs(FsCandidate *fscandidate)
648 PurpleMediaCandidate *candidate;
650 if (fscandidate == NULL)
651 return NULL;
653 candidate = purple_media_candidate_new(fscandidate->foundation,
654 fscandidate->component_id,
655 purple_media_candidate_type_from_fs(fscandidate->type),
656 purple_media_network_protocol_from_fs(fscandidate->proto),
657 fscandidate->ip, fscandidate->port);
658 g_object_set(candidate,
659 "base-ip", fscandidate->base_ip,
660 "base-port", fscandidate->base_port,
661 "priority", fscandidate->priority,
662 "username", fscandidate->username,
663 "password", fscandidate->password,
664 "ttl", fscandidate->ttl, NULL);
665 return candidate;
668 static GList *
669 candidate_list_from_fs(GList *candidates)
671 GList *new_list = NULL;
673 for (; candidates; candidates = g_list_next(candidates)) {
674 new_list = g_list_prepend(new_list,
675 candidate_from_fs(candidates->data));
678 new_list = g_list_reverse(new_list);
679 return new_list;
682 static FsCodec *
683 codec_to_fs(const PurpleMediaCodec *codec)
685 FsCodec *new_codec;
686 gint id;
687 char *encoding_name;
688 PurpleMediaSessionType media_type;
689 guint clock_rate;
690 guint channels;
691 GList *iter;
693 if (codec == NULL)
694 return NULL;
696 g_object_get(G_OBJECT(codec),
697 "id", &id,
698 "encoding-name", &encoding_name,
699 "media-type", &media_type,
700 "clock-rate", &clock_rate,
701 "channels", &channels,
702 "optional-params", &iter,
703 NULL);
705 new_codec = fs_codec_new(id, encoding_name,
706 session_type_to_fs_media_type(media_type),
707 clock_rate);
708 new_codec->channels = channels;
710 for (; iter; iter = g_list_next(iter)) {
711 PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data;
712 fs_codec_add_optional_parameter(new_codec,
713 param->key, param->value);
716 g_free(encoding_name);
717 return new_codec;
720 static PurpleMediaCodec *
721 codec_from_fs(const FsCodec *codec)
723 PurpleMediaCodec *new_codec;
724 GList *iter;
726 if (codec == NULL)
727 return NULL;
729 new_codec = purple_media_codec_new(codec->id, codec->encoding_name,
730 session_type_from_fs(codec->media_type,
731 FS_DIRECTION_BOTH), codec->clock_rate);
732 g_object_set(new_codec, "channels", codec->channels, NULL);
734 for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
735 FsCodecParameter *param = (FsCodecParameter*)iter->data;
736 purple_media_codec_add_optional_parameter(new_codec,
737 param->name, param->value);
740 return new_codec;
743 static GList *
744 codec_list_from_fs(GList *codecs)
746 GList *new_list = NULL;
748 for (; codecs; codecs = g_list_next(codecs)) {
749 new_list = g_list_prepend(new_list,
750 codec_from_fs(codecs->data));
753 new_list = g_list_reverse(new_list);
754 return new_list;
757 static GList *
758 codec_list_to_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_to_fs(codecs->data));
767 new_list = g_list_reverse(new_list);
768 return new_list;
771 static PurpleMediaBackendFs2Session *
772 get_session(PurpleMediaBackendFs2 *self, const gchar *sess_id)
774 PurpleMediaBackendFs2Private *priv;
775 PurpleMediaBackendFs2Session *session = NULL;
777 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
779 priv = purple_media_backend_fs2_get_instance_private(self);
781 if (priv->sessions != NULL)
782 session = g_hash_table_lookup(priv->sessions, sess_id);
784 return session;
787 static FsParticipant *
788 get_participant(PurpleMediaBackendFs2 *self, const gchar *name)
790 PurpleMediaBackendFs2Private *priv;
791 FsParticipant *participant = NULL;
793 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
795 priv = purple_media_backend_fs2_get_instance_private(self);
797 if (priv->participants != NULL)
798 participant = g_hash_table_lookup(priv->participants, name);
800 return participant;
803 static PurpleMediaBackendFs2Stream *
804 get_stream(PurpleMediaBackendFs2 *self,
805 const gchar *sess_id, const gchar *name)
807 PurpleMediaBackendFs2Private *priv;
808 GList *streams;
810 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
812 priv = purple_media_backend_fs2_get_instance_private(self);
813 streams = priv->streams;
815 for (; streams; streams = g_list_next(streams)) {
816 PurpleMediaBackendFs2Stream *stream = streams->data;
817 if (purple_strequal(stream->session->id, sess_id) &&
818 purple_strequal(stream->participant, name))
819 return stream;
822 return NULL;
825 static GList *
826 get_streams(PurpleMediaBackendFs2 *self,
827 const gchar *sess_id, const gchar *name)
829 PurpleMediaBackendFs2Private *priv;
830 GList *streams, *ret = NULL;
832 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
834 priv = purple_media_backend_fs2_get_instance_private(self);
835 streams = priv->streams;
837 for (; streams; streams = g_list_next(streams)) {
838 PurpleMediaBackendFs2Stream *stream = streams->data;
840 if (sess_id != NULL && !purple_strequal(stream->session->id, sess_id))
841 continue;
842 else if (name != NULL && !purple_strequal(stream->participant, name))
843 continue;
844 else
845 ret = g_list_prepend(ret, stream);
848 ret = g_list_reverse(ret);
849 return ret;
852 static PurpleMediaBackendFs2Session *
853 get_session_from_fs_stream(PurpleMediaBackendFs2 *self, FsStream *stream)
855 PurpleMediaBackendFs2Private *priv =
856 purple_media_backend_fs2_get_instance_private(self);
857 FsSession *fssession;
858 GList *values;
860 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
861 g_return_val_if_fail(FS_IS_STREAM(stream), NULL);
863 g_object_get(stream, "session", &fssession, NULL);
865 values = g_hash_table_get_values(priv->sessions);
867 for (; values; values = g_list_delete_link(values, values)) {
868 PurpleMediaBackendFs2Session *session = values->data;
870 if (session->session == fssession) {
871 g_list_free(values);
872 g_object_unref(fssession);
873 return session;
877 g_object_unref(fssession);
878 return NULL;
881 static gdouble
882 gst_msg_db_to_percent(GstMessage *msg, gchar *value_name)
884 const GValue *list;
885 const GValue *value;
886 gdouble value_db;
887 gdouble percent;
889 list = gst_structure_get_value(gst_message_get_structure(msg), value_name);
890 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
891 value = g_value_array_get_nth(g_value_get_boxed(list), 0);
892 G_GNUC_END_IGNORE_DEPRECATIONS
893 value_db = g_value_get_double(value);
894 percent = pow(10, value_db / 20);
895 return (percent > 1.0) ? 1.0 : percent;
898 static void
899 purple_media_error_fs(PurpleMedia *media, const gchar *error,
900 const GstStructure *fs_error)
902 const gchar *error_msg = gst_structure_get_string(fs_error, "error-msg");
904 purple_media_error(media, "%s%s%s", error,
905 error_msg ? _("\n\nMessage from Farstream: ") : "",
906 error_msg ? error_msg : "");
909 static void
910 gst_handle_message_element(GstBus *bus, GstMessage *msg,
911 PurpleMediaBackendFs2 *self)
913 PurpleMediaBackendFs2Private *priv =
914 purple_media_backend_fs2_get_instance_private(self);
915 GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
916 static guint level_id = 0;
917 const GstStructure *structure = gst_message_get_structure(msg);
919 if (level_id == 0)
920 level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA);
922 if (gst_structure_has_name(structure, "level")) {
923 GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
924 gchar *name;
925 gchar *participant = NULL;
926 PurpleMediaBackendFs2Session *session = NULL;
927 gdouble percent;
929 if (!PURPLE_IS_MEDIA(priv->media) ||
930 GST_ELEMENT_PARENT(src) != priv->confbin)
931 return;
933 name = gst_element_get_name(src);
935 if (!strncmp(name, "sendlevel_", 10)) {
936 session = get_session(self, name+10);
937 if (priv->silence_threshold > 0) {
938 percent = gst_msg_db_to_percent(msg, "decay");
939 g_object_set(session->srcvalve,
940 "drop", (percent < priv->silence_threshold), NULL);
944 g_free(name);
946 if (!g_signal_has_handler_pending(priv->media, level_id, 0, FALSE))
947 return;
949 if (!session) {
950 GList *iter = priv->streams;
951 PurpleMediaBackendFs2Stream *stream;
952 for (; iter; iter = g_list_next(iter)) {
953 stream = iter->data;
954 if (stream->level == src) {
955 session = stream->session;
956 participant = stream->participant;
957 break;
962 if (!session)
963 return;
965 percent = gst_msg_db_to_percent(msg, "rms");
967 g_signal_emit(priv->media, level_id, 0,
968 session->id, participant, percent);
969 return;
972 if (!FS_IS_CONFERENCE(src) || !PURPLE_IS_MEDIA_BACKEND(self) ||
973 priv->conference != FS_CONFERENCE(src))
974 return;
976 if (gst_structure_has_name(structure, "farstream-error")) {
977 FsError error_no;
978 gboolean error_emitted = FALSE;
979 gst_structure_get_enum(structure, "error-no",
980 FS_TYPE_ERROR, (gint*)&error_no);
981 switch (error_no) {
982 case FS_ERROR_CONSTRUCTION:
983 purple_media_error_fs(priv->media,
984 _("Error initializing the call. "
985 "This probably denotes problem in "
986 "installation of GStreamer or Farstream."),
987 structure);
988 error_emitted = TRUE;
989 break;
990 case FS_ERROR_NETWORK:
991 purple_media_error_fs(priv->media, _("Network error."),
992 structure);
993 error_emitted = TRUE;
994 purple_media_end(priv->media, NULL, NULL);
995 break;
996 case FS_ERROR_NEGOTIATION_FAILED:
997 purple_media_error_fs(priv->media,
998 _("Codec negotiation failed. "
999 "This problem might be resolved by installing "
1000 "more GStreamer codecs."),
1001 structure);
1002 error_emitted = TRUE;
1003 purple_media_end(priv->media, NULL, NULL);
1004 break;
1005 case FS_ERROR_NO_CODECS:
1006 purple_media_error(priv->media,
1007 _("No codecs found. "
1008 "Install some GStreamer codecs found "
1009 "in GStreamer plugins packages."));
1010 error_emitted = TRUE;
1011 purple_media_end(priv->media, NULL, NULL);
1012 break;
1013 default:
1014 purple_debug_error("backend-fs2",
1015 "farstream-error: %i: %s\n",
1016 error_no,
1017 gst_structure_get_string(structure, "error-msg"));
1018 break;
1021 if (FS_ERROR_IS_FATAL(error_no)) {
1022 if (!error_emitted)
1023 purple_media_error(priv->media,
1024 _("A non-recoverable Farstream error has occurred."));
1025 purple_media_end(priv->media, NULL, NULL);
1027 } else if (gst_structure_has_name(structure,
1028 "farstream-new-local-candidate")) {
1029 const GValue *value;
1030 FsStream *stream;
1031 FsCandidate *local_candidate;
1032 PurpleMediaCandidate *candidate;
1033 FsParticipant *participant;
1034 PurpleMediaBackendFs2Session *session;
1035 PurpleMediaBackendFs2Stream *media_stream;
1036 const gchar *name;
1038 value = gst_structure_get_value(structure, "stream");
1039 stream = g_value_get_object(value);
1040 value = gst_structure_get_value(structure, "candidate");
1041 local_candidate = g_value_get_boxed(value);
1043 session = get_session_from_fs_stream(self, stream);
1045 purple_debug_info("backend-fs2",
1046 "got new local candidate: %s\n",
1047 local_candidate->foundation);
1049 g_object_get(stream, "participant", &participant, NULL);
1050 name = g_object_get_data(G_OBJECT(participant), "purple-name");
1052 media_stream = get_stream(self, session->id, name);
1053 media_stream->local_candidates = g_list_append(
1054 media_stream->local_candidates,
1055 fs_candidate_copy(local_candidate));
1057 candidate = candidate_from_fs(local_candidate);
1058 g_signal_emit_by_name(self, "new-candidate",
1059 session->id, name, candidate);
1060 g_object_unref(candidate);
1061 g_object_unref(participant);
1062 } else if (gst_structure_has_name(structure,
1063 "farstream-local-candidates-prepared")) {
1064 const GValue *value;
1065 FsStream *stream;
1066 FsParticipant *participant;
1067 PurpleMediaBackendFs2Session *session;
1069 value = gst_structure_get_value(structure, "stream");
1070 stream = g_value_get_object(value);
1071 session = get_session_from_fs_stream(self, stream);
1073 g_object_get(stream, "participant", &participant, NULL);
1075 g_signal_emit_by_name(self, "candidates-prepared",
1076 session->id,
1077 g_object_get_data(G_OBJECT(participant), "purple-name"));
1079 g_object_unref(participant);
1080 } else if (gst_structure_has_name(structure,
1081 "farstream-new-active-candidate-pair")) {
1082 const GValue *value;
1083 FsStream *stream;
1084 FsCandidate *local_candidate;
1085 FsCandidate *remote_candidate;
1086 FsParticipant *participant;
1087 PurpleMediaBackendFs2Session *session;
1088 PurpleMediaCandidate *lcandidate, *rcandidate;
1090 value = gst_structure_get_value(structure, "stream");
1091 stream = g_value_get_object(value);
1092 value = gst_structure_get_value(structure, "local-candidate");
1093 local_candidate = g_value_get_boxed(value);
1094 value = gst_structure_get_value(structure, "remote-candidate");
1095 remote_candidate = g_value_get_boxed(value);
1097 g_object_get(stream, "participant", &participant, NULL);
1099 session = get_session_from_fs_stream(self, stream);
1101 lcandidate = candidate_from_fs(local_candidate);
1102 rcandidate = candidate_from_fs(remote_candidate);
1104 g_signal_emit_by_name(self, "active-candidate-pair",
1105 session->id,
1106 g_object_get_data(G_OBJECT(participant), "purple-name"),
1107 lcandidate, rcandidate);
1109 g_object_unref(participant);
1110 g_object_unref(lcandidate);
1111 g_object_unref(rcandidate);
1112 } else if (gst_structure_has_name(structure,
1113 "farstream-recv-codecs-changed")) {
1114 const GValue *value;
1115 GList *codecs;
1116 FsCodec *codec;
1118 value = gst_structure_get_value(structure, "codecs");
1119 codecs = g_value_get_boxed(value);
1120 codec = codecs->data;
1122 purple_debug_info("backend-fs2",
1123 "farstream-recv-codecs-changed: %s\n",
1124 codec->encoding_name);
1125 } else if (gst_structure_has_name(structure,
1126 "farstream-component-state-changed")) {
1127 const GValue *value;
1128 FsStreamState fsstate;
1129 guint component;
1130 const gchar *state;
1132 value = gst_structure_get_value(structure, "state");
1133 fsstate = g_value_get_enum(value);
1134 value = gst_structure_get_value(structure, "component");
1135 component = g_value_get_uint(value);
1137 switch (fsstate) {
1138 case FS_STREAM_STATE_FAILED:
1139 state = "FAILED";
1140 break;
1141 case FS_STREAM_STATE_DISCONNECTED:
1142 state = "DISCONNECTED";
1143 break;
1144 case FS_STREAM_STATE_GATHERING:
1145 state = "GATHERING";
1146 break;
1147 case FS_STREAM_STATE_CONNECTING:
1148 state = "CONNECTING";
1149 break;
1150 case FS_STREAM_STATE_CONNECTED:
1151 state = "CONNECTED";
1152 break;
1153 case FS_STREAM_STATE_READY:
1154 state = "READY";
1155 break;
1156 default:
1157 state = "UNKNOWN";
1158 break;
1161 purple_debug_info("backend-fs2",
1162 "farstream-component-state-changed: "
1163 "component: %u state: %s\n",
1164 component, state);
1165 } else if (gst_structure_has_name(structure,
1166 "farstream-send-codec-changed")) {
1167 const GValue *value;
1168 FsCodec *codec;
1169 gchar *codec_str;
1171 value = gst_structure_get_value(structure, "codec");
1172 codec = g_value_get_boxed(value);
1173 codec_str = fs_codec_to_string(codec);
1175 purple_debug_info("backend-fs2",
1176 "farstream-send-codec-changed: codec: %s\n",
1177 codec_str);
1179 g_free(codec_str);
1180 } else if (gst_structure_has_name(structure,
1181 "farstream-codecs-changed")) {
1182 const GValue *value;
1183 FsSession *fssession;
1184 GList *sessions;
1186 value = gst_structure_get_value(structure, "session");
1187 fssession = g_value_get_object(value);
1188 sessions = g_hash_table_get_values(priv->sessions);
1190 for (; sessions; sessions =
1191 g_list_delete_link(sessions, sessions)) {
1192 PurpleMediaBackendFs2Session *session = sessions->data;
1193 gchar *session_id;
1195 if (session->session != fssession)
1196 continue;
1198 session_id = g_strdup(session->id);
1199 g_signal_emit_by_name(self, "codecs-changed",
1200 session_id);
1201 g_free(session_id);
1202 g_list_free(sessions);
1203 break;
1208 static void
1209 gst_handle_message_error(GstBus *bus, GstMessage *msg,
1210 PurpleMediaBackendFs2 *self)
1212 PurpleMediaBackendFs2Private *priv =
1213 purple_media_backend_fs2_get_instance_private(self);
1214 GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg));
1215 GstElement *lastElement = NULL;
1216 GList *sessions;
1218 GError *error = NULL;
1219 gchar *debug_msg = NULL;
1221 gst_message_parse_error(msg, &error, &debug_msg);
1222 purple_debug_error("backend-fs2", "gst error %s\ndebugging: %s\n",
1223 error->message, debug_msg);
1225 g_error_free(error);
1226 g_free(debug_msg);
1228 while (element && !GST_IS_PIPELINE(element)) {
1229 if (element == priv->confbin)
1230 break;
1232 lastElement = element;
1233 element = GST_ELEMENT_PARENT(element);
1236 if (!element || !GST_IS_PIPELINE(element))
1237 return;
1239 sessions = purple_media_get_session_ids(priv->media);
1241 for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
1242 if (purple_media_get_src(priv->media, sessions->data)
1243 != lastElement)
1244 continue;
1246 if (purple_media_get_session_type(priv->media, sessions->data)
1247 & PURPLE_MEDIA_AUDIO)
1248 purple_media_error(priv->media,
1249 _("Error with your microphone"));
1250 else if (purple_media_get_session_type(priv->media,
1251 sessions->data) & PURPLE_MEDIA_VIDEO)
1252 purple_media_error(priv->media,
1253 _("Error with your webcam"));
1255 break;
1258 g_list_free(sessions);
1260 purple_media_error(priv->media, _("Conference error"));
1261 purple_media_end(priv->media, NULL, NULL);
1264 static gboolean
1265 gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self)
1267 switch(GST_MESSAGE_TYPE(msg)) {
1268 case GST_MESSAGE_ELEMENT:
1269 gst_handle_message_element(bus, msg, self);
1270 break;
1271 case GST_MESSAGE_ERROR:
1272 gst_handle_message_error(bus, msg, self);
1273 break;
1274 default:
1275 break;
1278 return TRUE;
1281 static void
1282 remove_element(GstElement *element)
1284 if (element) {
1285 gst_element_set_locked_state(element, TRUE);
1286 gst_element_set_state(element, GST_STATE_NULL);
1287 gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
1291 static void
1292 state_changed_cb(PurpleMedia *media, PurpleMediaState state,
1293 gchar *sid, gchar *name, PurpleMediaBackendFs2 *self)
1295 if (state == PURPLE_MEDIA_STATE_END) {
1296 PurpleMediaBackendFs2Private *priv =
1297 purple_media_backend_fs2_get_instance_private(
1298 self);
1300 if (sid && name) {
1301 PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name);
1302 gst_object_unref(stream->stream);
1304 priv->streams = g_list_remove(priv->streams, stream);
1306 remove_element(stream->src);
1307 remove_element(stream->tee);
1308 remove_element(stream->volume);
1309 remove_element(stream->level);
1310 remove_element(stream->fakesink);
1311 remove_element(stream->queue);
1313 free_stream(stream);
1314 } else if (sid && !name) {
1315 PurpleMediaBackendFs2Session *session = get_session(self, sid);
1316 GstPad *pad;
1318 g_object_get(session->session, "sink-pad", &pad, NULL);
1319 gst_pad_unlink(GST_PAD_PEER(pad), pad);
1320 gst_object_unref(pad);
1322 gst_object_unref(session->session);
1323 g_hash_table_remove(priv->sessions, session->id);
1325 pad = gst_pad_get_peer(session->srcpad);
1326 gst_element_remove_pad(GST_ELEMENT_PARENT(pad), pad);
1327 gst_object_unref(pad);
1328 gst_object_unref(session->srcpad);
1330 remove_element(session->srcvalve);
1331 remove_element(session->tee);
1333 free_session(session);
1336 purple_media_manager_remove_output_windows(
1337 purple_media_get_manager(media), media, sid, name);
1341 static void
1342 stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
1343 gchar *sid, gchar *name, gboolean local,
1344 PurpleMediaBackendFs2 *self)
1346 if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) {
1347 PurpleMediaBackendFs2Stream *stream =
1348 get_stream(self, sid, name);
1349 GError *err = NULL;
1351 g_object_set(G_OBJECT(stream->stream), "direction",
1352 session_type_to_fs_stream_direction(
1353 stream->session->type), NULL);
1355 if (stream->remote_candidates == NULL ||
1356 purple_media_is_initiator(media, sid, name))
1357 return;
1359 if (stream->supports_add)
1360 fs_stream_add_remote_candidates(stream->stream,
1361 stream->remote_candidates, &err);
1362 else
1363 fs_stream_force_remote_candidates(stream->stream,
1364 stream->remote_candidates, &err);
1366 if (err == NULL)
1367 return;
1369 purple_debug_error("backend-fs2", "Error adding "
1370 "remote candidates: %s\n",
1371 err->message);
1372 g_error_free(err);
1373 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE ||
1374 type == PURPLE_MEDIA_INFO_UNMUTE)) {
1375 PurpleMediaBackendFs2Private *priv =
1376 purple_media_backend_fs2_get_instance_private(
1377 self);
1378 gboolean active = (type == PURPLE_MEDIA_INFO_MUTE);
1379 GList *sessions;
1381 if (sid == NULL)
1382 sessions = g_hash_table_get_values(priv->sessions);
1383 else
1384 sessions = g_list_prepend(NULL,
1385 get_session(self, sid));
1387 purple_debug_info("media", "Turning mute %s\n",
1388 active ? "on" : "off");
1390 for (; sessions; sessions = g_list_delete_link(
1391 sessions, sessions)) {
1392 PurpleMediaBackendFs2Session *session =
1393 sessions->data;
1395 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
1396 gchar *name = g_strdup_printf("volume_%s",
1397 session->id);
1398 GstElement *volume = gst_bin_get_by_name(
1399 GST_BIN(priv->confbin), name);
1400 g_free(name);
1401 g_object_set(volume, "mute", active, NULL);
1404 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
1405 type == PURPLE_MEDIA_INFO_UNHOLD)) {
1406 gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
1407 GList *streams = get_streams(self, sid, name);
1408 for (; streams; streams =
1409 g_list_delete_link(streams, streams)) {
1410 PurpleMediaBackendFs2Stream *stream = streams->data;
1411 if (stream->session->type & PURPLE_MEDIA_SEND_AUDIO) {
1412 g_object_set(stream->stream, "direction",
1413 session_type_to_fs_stream_direction(
1414 stream->session->type & ((active) ?
1415 ~PURPLE_MEDIA_SEND_AUDIO :
1416 PURPLE_MEDIA_AUDIO)), NULL);
1419 } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE ||
1420 type == PURPLE_MEDIA_INFO_UNPAUSE)) {
1421 gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE);
1422 GList *streams = get_streams(self, sid, name);
1423 for (; streams; streams =
1424 g_list_delete_link(streams, streams)) {
1425 PurpleMediaBackendFs2Stream *stream = streams->data;
1426 if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
1427 g_object_set(stream->stream, "direction",
1428 session_type_to_fs_stream_direction(
1429 stream->session->type & ((active) ?
1430 ~PURPLE_MEDIA_SEND_VIDEO :
1431 PURPLE_MEDIA_VIDEO)), NULL);
1437 static gboolean
1438 init_conference(PurpleMediaBackendFs2 *self)
1440 PurpleMediaBackendFs2Private *priv =
1441 purple_media_backend_fs2_get_instance_private(self);
1442 GstElement *pipeline;
1443 GstBus *bus;
1444 gchar *name;
1445 GKeyFile *default_props;
1447 priv->conference = FS_CONFERENCE(
1448 gst_element_factory_make(priv->conference_type, NULL));
1450 if (priv->conference == NULL) {
1451 purple_debug_error("backend-fs2", "Conference == NULL\n");
1452 return FALSE;
1455 if (purple_account_get_silence_suppression(
1456 purple_media_get_account(priv->media)))
1457 priv->silence_threshold = purple_prefs_get_int(
1458 "/purple/media/audio/silence_threshold") / 100.0;
1459 else
1460 priv->silence_threshold = 0;
1462 pipeline = purple_media_manager_get_pipeline(
1463 purple_media_get_manager(priv->media));
1465 if (pipeline == NULL) {
1466 purple_debug_error("backend-fs2",
1467 "Couldn't retrieve pipeline.\n");
1468 return FALSE;
1471 name = g_strdup_printf("conf_%p", priv->conference);
1472 priv->confbin = gst_bin_new(name);
1473 if (priv->confbin == NULL) {
1474 purple_debug_error("backend-fs2",
1475 "Couldn't create confbin.\n");
1476 return FALSE;
1479 g_free(name);
1481 bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
1482 if (bus == NULL) {
1483 purple_debug_error("backend-fs2",
1484 "Couldn't get the pipeline's bus.\n");
1485 return FALSE;
1488 default_props = fs_utils_get_default_element_properties(GST_ELEMENT(priv->conference));
1489 if (default_props != NULL) {
1490 priv->notifier = fs_element_added_notifier_new();
1491 fs_element_added_notifier_add(priv->notifier,
1492 GST_BIN(priv->confbin));
1493 fs_element_added_notifier_set_properties_from_keyfile(priv->notifier, default_props);
1496 g_signal_connect(G_OBJECT(bus), "message",
1497 G_CALLBACK(gst_bus_cb), self);
1498 gst_object_unref(bus);
1500 if (!gst_bin_add(GST_BIN(pipeline),
1501 GST_ELEMENT(priv->confbin))) {
1502 purple_debug_error("backend-fs2", "Couldn't add confbin "
1503 "element to the pipeline\n");
1504 return FALSE;
1507 if (!gst_bin_add(GST_BIN(priv->confbin),
1508 GST_ELEMENT(priv->conference))) {
1509 purple_debug_error("backend-fs2", "Couldn't add conference "
1510 "element to the confbin\n");
1511 return FALSE;
1514 if (gst_element_set_state(GST_ELEMENT(priv->confbin),
1515 GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
1516 purple_debug_error("backend-fs2",
1517 "Failed to start conference.\n");
1518 return FALSE;
1521 return TRUE;
1524 static gboolean
1525 create_src(PurpleMediaBackendFs2 *self, const gchar *sess_id,
1526 PurpleMediaSessionType type)
1528 PurpleMediaBackendFs2Private *priv =
1529 purple_media_backend_fs2_get_instance_private(self);
1530 PurpleMediaBackendFs2Session *session;
1531 PurpleMediaSessionType session_type;
1532 FsMediaType media_type = session_type_to_fs_media_type(type);
1533 FsStreamDirection type_direction =
1534 session_type_to_fs_stream_direction(type);
1535 GstElement *src;
1536 GstPad *sinkpad, *srcpad;
1537 GstPad *ghost = NULL;
1539 if ((type_direction & FS_DIRECTION_SEND) == 0)
1540 return TRUE;
1542 session_type = session_type_from_fs(
1543 media_type, FS_DIRECTION_SEND);
1544 src = purple_media_manager_get_element(
1545 purple_media_get_manager(priv->media),
1546 session_type, priv->media, sess_id, NULL);
1548 if (!GST_IS_ELEMENT(src)) {
1549 purple_debug_error("backend-fs2",
1550 "Error creating src for session %s\n",
1551 sess_id);
1552 return FALSE;
1555 session = get_session(self, sess_id);
1557 if (session == NULL) {
1558 purple_debug_warning("backend-fs2",
1559 "purple_media_set_src: trying to set"
1560 " src on non-existent session\n");
1561 return FALSE;
1564 if (session->src)
1565 gst_object_unref(session->src);
1567 session->src = src;
1568 gst_element_set_locked_state(session->src, TRUE);
1570 session->tee = gst_element_factory_make("tee", NULL);
1571 gst_bin_add(GST_BIN(priv->confbin), session->tee);
1573 /* This supposedly isn't necessary, but it silences some warnings */
1574 if (GST_ELEMENT_PARENT(priv->confbin)
1575 == GST_ELEMENT_PARENT(session->src)) {
1576 GstPad *pad = gst_element_get_static_pad(session->tee, "sink");
1577 ghost = gst_ghost_pad_new(NULL, pad);
1578 gst_object_unref(pad);
1579 gst_pad_set_active(ghost, TRUE);
1580 gst_element_add_pad(priv->confbin, ghost);
1583 gst_element_set_state(session->tee, GST_STATE_PLAYING);
1584 gst_element_link(session->src, priv->confbin);
1585 if (ghost)
1586 session->srcpad = gst_pad_get_peer(ghost);
1588 g_object_get(session->session, "sink-pad", &sinkpad, NULL);
1589 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
1590 gchar *name = g_strdup_printf("volume_%s", session->id);
1591 GstElement *level;
1592 GstElement *volume = gst_element_factory_make("volume", name);
1593 double input_volume = purple_prefs_get_int(
1594 "/purple/media/audio/volume/input")/10.0;
1595 g_free(name);
1596 name = g_strdup_printf("sendlevel_%s", session->id);
1597 level = gst_element_factory_make("level", name);
1598 g_free(name);
1599 session->srcvalve = gst_element_factory_make("valve", NULL);
1600 gst_bin_add(GST_BIN(priv->confbin), volume);
1601 gst_bin_add(GST_BIN(priv->confbin), level);
1602 gst_bin_add(GST_BIN(priv->confbin), session->srcvalve);
1603 gst_element_set_state(level, GST_STATE_PLAYING);
1604 gst_element_set_state(volume, GST_STATE_PLAYING);
1605 gst_element_set_state(session->srcvalve, GST_STATE_PLAYING);
1606 gst_element_link(level, session->srcvalve);
1607 gst_element_link(volume, level);
1608 gst_element_link(session->tee, volume);
1609 srcpad = gst_element_get_static_pad(session->srcvalve, "src");
1610 g_object_set(volume, "volume", input_volume, NULL);
1611 } else {
1612 srcpad = gst_element_get_request_pad(session->tee, "src_%u");
1615 purple_debug_info("backend-fs2", "connecting pad: %s\n",
1616 gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
1617 ? "success" : "failure");
1618 gst_element_set_locked_state(session->src, FALSE);
1619 gst_object_unref(session->src);
1620 gst_object_unref(sinkpad);
1622 purple_media_manager_create_output_window(purple_media_get_manager(
1623 priv->media), priv->media, sess_id, NULL);
1625 purple_debug_info("backend-fs2", "create_src: setting source "
1626 "state to GST_STATE_PLAYING - it may hang here on win32\n");
1627 gst_element_set_state(session->src, GST_STATE_PLAYING);
1628 purple_debug_info("backend-fs2", "create_src: state set\n");
1630 return TRUE;
1633 static gboolean
1634 create_session(PurpleMediaBackendFs2 *self, const gchar *sess_id,
1635 PurpleMediaSessionType type, gboolean initiator,
1636 const gchar *transmitter)
1638 PurpleMediaBackendFs2Private *priv =
1639 purple_media_backend_fs2_get_instance_private(self);
1640 PurpleMediaBackendFs2Session *session;
1641 GError *err = NULL;
1642 GList *codec_conf = NULL;
1643 gchar *filename = NULL;
1645 session = g_new0(PurpleMediaBackendFs2Session, 1);
1647 session->session = fs_conference_new_session(priv->conference,
1648 session_type_to_fs_media_type(type), &err);
1650 #ifdef HAVE_MEDIA_APPLICATION
1651 if (type == PURPLE_MEDIA_APPLICATION) {
1652 GstCaps *caps;
1653 GObject *rtpsession = NULL;
1655 caps = gst_caps_new_empty_simple ("application/octet-stream");
1656 fs_session_set_allowed_caps (session->session, caps, caps, NULL);
1657 gst_caps_unref (caps);
1658 g_object_get (session->session, "internal-session", &rtpsession, NULL);
1659 if (rtpsession) {
1660 g_object_set (rtpsession, "probation", 0, NULL);
1661 g_object_unref (rtpsession);
1664 #endif
1665 if (err != NULL) {
1666 purple_media_error(priv->media,
1667 _("Error creating session: %s"),
1668 err->message);
1669 g_error_free(err);
1670 g_free(session);
1671 return FALSE;
1674 filename = g_build_filename(purple_config_dir(), "fs-codec.conf", NULL);
1675 codec_conf = fs_codec_list_from_keyfile(filename, &err);
1676 g_free(filename);
1678 if (err != NULL) {
1679 if (err->code == G_KEY_FILE_ERROR_NOT_FOUND)
1680 purple_debug_info("backend-fs2", "Couldn't read "
1681 "fs-codec.conf: %s\n",
1682 err->message);
1683 else
1684 purple_debug_error("backend-fs2", "Error reading "
1685 "fs-codec.conf: %s\n",
1686 err->message);
1687 g_error_free(err);
1689 purple_debug_info("backend-fs2",
1690 "Loading default codec conf instead\n");
1691 codec_conf = fs_utils_get_default_codec_preferences(
1692 GST_ELEMENT(priv->conference));
1695 fs_session_set_codec_preferences(session->session, codec_conf, NULL);
1696 fs_codec_list_destroy(codec_conf);
1699 * Removes a 5-7 second delay before
1700 * receiving the src-pad-added signal.
1701 * Only works for non-multicast FsRtpSessions.
1703 if (!purple_strequal(transmitter, "multicast"))
1704 g_object_set(G_OBJECT(session->session),
1705 "no-rtcp-timeout", 0, NULL);
1707 session->id = g_strdup(sess_id);
1708 session->backend = self;
1709 session->type = type;
1711 if (!priv->sessions) {
1712 purple_debug_info("backend-fs2",
1713 "Creating hash table for sessions\n");
1714 priv->sessions = g_hash_table_new_full(g_str_hash, g_str_equal,
1715 g_free, NULL);
1718 g_hash_table_insert(priv->sessions, g_strdup(session->id), session);
1720 if (!create_src(self, sess_id, type)) {
1721 purple_debug_info("backend-fs2", "Error creating the src\n");
1722 return FALSE;
1725 return TRUE;
1728 static void
1729 free_session(PurpleMediaBackendFs2Session *session)
1731 g_free(session->id);
1732 g_free(session);
1735 static gboolean
1736 create_participant(PurpleMediaBackendFs2 *self, const gchar *name)
1738 PurpleMediaBackendFs2Private *priv =
1739 purple_media_backend_fs2_get_instance_private(self);
1740 FsParticipant *participant;
1741 GError *err = NULL;
1743 participant = fs_conference_new_participant(
1744 priv->conference, &err);
1746 if (err) {
1747 purple_debug_error("backend-fs2",
1748 "Error creating participant: %s\n",
1749 err->message);
1750 g_error_free(err);
1751 return FALSE;
1754 g_object_set_data_full(G_OBJECT(participant), "purple-name",
1755 g_strdup(name), g_free);
1757 if (g_object_class_find_property(G_OBJECT_GET_CLASS(participant),
1758 "cname")) {
1759 g_object_set(participant, "cname", name, NULL);
1762 if (!priv->participants) {
1763 purple_debug_info("backend-fs2",
1764 "Creating hash table for participants\n");
1765 priv->participants = g_hash_table_new_full(g_str_hash,
1766 g_str_equal, g_free, g_object_unref);
1769 g_hash_table_insert(priv->participants, g_strdup(name), participant);
1771 return TRUE;
1774 static gboolean
1775 src_pad_added_cb_cb(PurpleMediaBackendFs2Stream *stream)
1777 PurpleMediaBackendFs2Private *priv;
1779 g_return_val_if_fail(stream != NULL, FALSE);
1781 priv = purple_media_backend_fs2_get_instance_private(
1782 stream->session->backend);
1783 stream->connected_cb_id = 0;
1785 purple_media_manager_create_output_window(
1786 purple_media_get_manager(priv->media), priv->media,
1787 stream->session->id, stream->participant);
1789 g_signal_emit_by_name(priv->media, "state-changed",
1790 PURPLE_MEDIA_STATE_CONNECTED,
1791 stream->session->id, stream->participant);
1792 return FALSE;
1795 static void
1796 src_pad_added_cb(FsStream *fsstream, GstPad *srcpad,
1797 FsCodec *codec, PurpleMediaBackendFs2Stream *stream)
1799 PurpleMediaBackendFs2Private *priv;
1800 GstPad *sinkpad;
1802 g_return_if_fail(FS_IS_STREAM(fsstream));
1803 g_return_if_fail(stream != NULL);
1805 priv = purple_media_backend_fs2_get_instance_private(
1806 stream->session->backend);
1808 if (stream->src == NULL) {
1809 GstElement *sink = NULL;
1811 if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
1812 double output_volume = purple_prefs_get_int(
1813 "/purple/media/audio/volume/output")/10.0;
1815 * Should this instead be:
1816 * audioconvert ! audioresample ! liveadder !
1817 * audioresample ! audioconvert ! realsink
1819 stream->queue = gst_element_factory_make("queue", NULL);
1820 stream->volume = gst_element_factory_make("volume", NULL);
1821 g_object_set(stream->volume, "volume", output_volume, NULL);
1822 stream->level = gst_element_factory_make("level", NULL);
1823 stream->src = gst_element_factory_make("liveadder", NULL);
1824 sink = purple_media_manager_get_element(
1825 purple_media_get_manager(priv->media),
1826 PURPLE_MEDIA_RECV_AUDIO, priv->media,
1827 stream->session->id,
1828 stream->participant);
1829 gst_bin_add(GST_BIN(priv->confbin), stream->queue);
1830 gst_bin_add(GST_BIN(priv->confbin), stream->volume);
1831 gst_bin_add(GST_BIN(priv->confbin), stream->level);
1832 gst_bin_add(GST_BIN(priv->confbin), sink);
1833 gst_element_set_state(sink, GST_STATE_PLAYING);
1834 gst_element_set_state(stream->level, GST_STATE_PLAYING);
1835 gst_element_set_state(stream->volume, GST_STATE_PLAYING);
1836 gst_element_set_state(stream->queue, GST_STATE_PLAYING);
1837 gst_element_link(stream->level, sink);
1838 gst_element_link(stream->volume, stream->level);
1839 gst_element_link(stream->queue, stream->volume);
1840 sink = stream->queue;
1841 } else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) {
1842 stream->src = gst_element_factory_make("funnel", NULL);
1843 sink = gst_element_factory_make("fakesink", NULL);
1844 g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
1845 gst_bin_add(GST_BIN(priv->confbin), sink);
1846 gst_element_set_state(sink, GST_STATE_PLAYING);
1847 stream->fakesink = sink;
1848 #ifdef HAVE_MEDIA_APPLICATION
1849 } else if (codec->media_type == FS_MEDIA_TYPE_APPLICATION) {
1850 stream->src = gst_element_factory_make("funnel", NULL);
1851 sink = purple_media_manager_get_element(
1852 purple_media_get_manager(priv->media),
1853 PURPLE_MEDIA_RECV_APPLICATION, priv->media,
1854 stream->session->id,
1855 stream->participant);
1856 gst_bin_add(GST_BIN(priv->confbin), sink);
1857 gst_element_set_state(sink, GST_STATE_PLAYING);
1858 #endif
1860 stream->tee = gst_element_factory_make("tee", NULL);
1861 gst_bin_add_many(GST_BIN(priv->confbin),
1862 stream->src, stream->tee, NULL);
1863 gst_element_set_state(stream->tee, GST_STATE_PLAYING);
1864 gst_element_set_state(stream->src, GST_STATE_PLAYING);
1865 gst_element_link_many(stream->src, stream->tee, sink, NULL);
1868 sinkpad = gst_element_get_request_pad(stream->src, "sink_%u");
1869 gst_pad_link(srcpad, sinkpad);
1870 gst_object_unref(sinkpad);
1872 stream->connected_cb_id = g_timeout_add(0,
1873 (GSourceFunc)src_pad_added_cb_cb, stream);
1876 static gboolean
1877 create_stream(PurpleMediaBackendFs2 *self,
1878 const gchar *sess_id, const gchar *who,
1879 PurpleMediaSessionType type, gboolean initiator,
1880 const gchar *transmitter,
1881 guint num_params, GParameter *params)
1883 PurpleMediaBackendFs2Private *priv =
1884 purple_media_backend_fs2_get_instance_private(self);
1885 GError *err = NULL;
1886 FsStream *fsstream = NULL;
1887 const gchar *stun_ip = purple_network_get_stun_ip();
1888 const gchar *turn_ip = purple_network_get_turn_ip();
1889 guint _num_params = num_params;
1890 GParameter *_params;
1891 FsStreamDirection type_direction =
1892 session_type_to_fs_stream_direction(type);
1893 PurpleMediaBackendFs2Session *session;
1894 PurpleMediaBackendFs2Stream *stream;
1895 FsParticipant *participant;
1896 /* check if the protocol has already specified a relay-info
1897 we need to do this to allow them to override when using non-standard
1898 TURN modes, like Google f.ex. */
1899 gboolean got_turn_from_protocol = FALSE;
1900 guint i;
1901 GPtrArray *relay_info = g_ptr_array_new_full (1, (GDestroyNotify) gst_structure_free);
1902 gboolean ret;
1904 session = get_session(self, sess_id);
1906 if (session == NULL) {
1907 purple_debug_error("backend-fs2",
1908 "Couldn't find session to create stream.\n");
1909 return FALSE;
1912 participant = get_participant(self, who);
1914 if (participant == NULL) {
1915 purple_debug_error("backend-fs2", "Couldn't find "
1916 "participant to create stream.\n");
1917 return FALSE;
1920 fsstream = fs_session_new_stream(session->session, participant,
1921 initiator == TRUE ? type_direction :
1922 (type_direction & FS_DIRECTION_RECV), &err);
1924 if (fsstream == NULL) {
1925 if (err) {
1926 purple_debug_error("backend-fs2", "Error creating stream: %s\n",
1927 err->message ? err->message : "NULL");
1928 g_error_free(err);
1929 } else
1930 purple_debug_error("backend-fs2",
1931 "Error creating stream\n");
1932 return FALSE;
1935 for (i = 0 ; i < num_params ; i++) {
1936 if (purple_strequal(params[i].name, "relay-info")) {
1937 got_turn_from_protocol = TRUE;
1938 break;
1942 _params = g_new0(GParameter, num_params + 3);
1943 memcpy(_params, params, sizeof(GParameter) * num_params);
1945 /* set the controlling mode parameter */
1946 _params[_num_params].name = "controlling-mode";
1947 g_value_init(&_params[_num_params].value, G_TYPE_BOOLEAN);
1948 g_value_set_boolean(&_params[_num_params].value, initiator);
1949 ++_num_params;
1951 if (stun_ip) {
1952 purple_debug_info("backend-fs2",
1953 "Setting stun-ip on new stream: %s\n", stun_ip);
1955 _params[_num_params].name = "stun-ip";
1956 g_value_init(&_params[_num_params].value, G_TYPE_STRING);
1957 g_value_set_string(&_params[_num_params].value, stun_ip);
1958 ++_num_params;
1961 if (turn_ip && purple_strequal("nice", transmitter) && !got_turn_from_protocol) {
1962 gint port;
1963 const gchar *username = purple_prefs_get_string(
1964 "/purple/network/turn_username");
1965 const gchar *password = purple_prefs_get_string(
1966 "/purple/network/turn_password");
1968 /* UDP */
1969 port = purple_prefs_get_int("/purple/network/turn_port");
1970 if (port > 0) {
1971 g_ptr_array_add (relay_info,
1972 gst_structure_new ("relay-info",
1973 "ip", G_TYPE_STRING, turn_ip,
1974 "port", G_TYPE_UINT, port,
1975 "username", G_TYPE_STRING, username,
1976 "password", G_TYPE_STRING, password,
1977 "relay-type", G_TYPE_STRING, "udp",
1978 NULL));
1981 /* TCP */
1982 port = purple_prefs_get_int("/purple/network/turn_port_tcp");
1983 if (port > 0) {
1984 g_ptr_array_add (relay_info,
1985 gst_structure_new ("relay-info",
1986 "ip", G_TYPE_STRING, turn_ip,
1987 "port", G_TYPE_UINT, port,
1988 "username", G_TYPE_STRING, username,
1989 "password", G_TYPE_STRING, password,
1990 "relay-type", G_TYPE_STRING, "tcp",
1991 NULL));
1994 /* TURN over SSL is only supported by libnice for Google's "psuedo" SSL mode
1995 at this time */
1997 purple_debug_info("backend-fs2",
1998 "Setting relay-info on new stream\n");
1999 _params[_num_params].name = "relay-info";
2000 g_value_init(&_params[_num_params].value, G_TYPE_PTR_ARRAY);
2001 g_value_set_boxed(&_params[_num_params].value, relay_info);
2002 _num_params++;
2005 ret = fs_stream_set_transmitter(fsstream, transmitter,
2006 _params, _num_params, &err);
2007 for (i = 0 ; i < _num_params ; i++)
2008 g_value_unset (&_params[i].value);
2009 g_free(_params);
2010 if (relay_info)
2011 g_ptr_array_unref (relay_info);
2012 if (ret == FALSE) {
2013 purple_debug_error("backend-fs2",
2014 "Could not set transmitter %s: %s.\n",
2015 transmitter, err ? err->message : NULL);
2016 g_clear_error(&err);
2017 return FALSE;
2020 stream = g_new0(PurpleMediaBackendFs2Stream, 1);
2021 stream->participant = g_strdup(who);
2022 stream->session = session;
2023 stream->stream = fsstream;
2024 stream->supports_add = purple_strequal(transmitter, "nice");
2026 priv->streams = g_list_append(priv->streams, stream);
2028 g_signal_connect(G_OBJECT(fsstream), "src-pad-added",
2029 G_CALLBACK(src_pad_added_cb), stream);
2031 return TRUE;
2034 static void
2035 free_stream(PurpleMediaBackendFs2Stream *stream)
2037 /* Remove the connected_cb timeout */
2038 if (stream->connected_cb_id != 0)
2039 g_source_remove(stream->connected_cb_id);
2041 g_free(stream->participant);
2043 if (stream->local_candidates)
2044 fs_candidate_list_destroy(stream->local_candidates);
2046 if (stream->remote_candidates)
2047 fs_candidate_list_destroy(stream->remote_candidates);
2049 g_free(stream);
2052 static gboolean
2053 purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
2054 const gchar *sess_id, const gchar *who,
2055 PurpleMediaSessionType type, gboolean initiator,
2056 const gchar *transmitter,
2057 guint num_params, GParameter *params)
2059 PurpleMediaBackendFs2 *backend = PURPLE_MEDIA_BACKEND_FS2(self);
2060 PurpleMediaBackendFs2Private *priv =
2061 purple_media_backend_fs2_get_instance_private(backend);
2062 PurpleMediaBackendFs2Stream *stream;
2064 if (priv->conference == NULL && !init_conference(backend)) {
2065 purple_debug_error("backend-fs2",
2066 "Error initializing the conference.\n");
2067 return FALSE;
2070 if (get_session(backend, sess_id) == NULL &&
2071 !create_session(backend, sess_id, type,
2072 initiator, transmitter)) {
2073 purple_debug_error("backend-fs2",
2074 "Error creating the session.\n");
2075 return FALSE;
2078 if (get_participant(backend, who) == NULL &&
2079 !create_participant(backend, who)) {
2080 purple_debug_error("backend-fs2",
2081 "Error creating the participant.\n");
2082 return FALSE;
2085 stream = get_stream(backend, sess_id, who);
2087 if (stream != NULL) {
2088 FsStreamDirection type_direction =
2089 session_type_to_fs_stream_direction(type);
2091 if (session_type_to_fs_stream_direction(
2092 stream->session->type) != type_direction) {
2093 /* change direction */
2094 g_object_set(stream->stream, "direction",
2095 type_direction, NULL);
2097 } else if (!create_stream(backend, sess_id, who, type,
2098 initiator, transmitter, num_params, params)) {
2099 purple_debug_error("backend-fs2",
2100 "Error creating the stream.\n");
2101 return FALSE;
2104 return TRUE;
2107 static void
2108 purple_media_backend_fs2_add_remote_candidates(PurpleMediaBackend *self,
2109 const gchar *sess_id, const gchar *participant,
2110 GList *remote_candidates)
2112 PurpleMediaBackendFs2Private *priv;
2113 PurpleMediaBackendFs2Stream *stream;
2114 GError *err = NULL;
2116 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2118 priv = purple_media_backend_fs2_get_instance_private(
2119 PURPLE_MEDIA_BACKEND_FS2(self));
2120 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2121 sess_id, participant);
2123 if (stream == NULL) {
2124 purple_debug_error("backend-fs2",
2125 "purple_media_add_remote_candidates: "
2126 "couldn't find stream %s %s.\n",
2127 sess_id ? sess_id : "(null)",
2128 participant ? participant : "(null)");
2129 return;
2132 stream->remote_candidates = g_list_concat(stream->remote_candidates,
2133 candidate_list_to_fs(remote_candidates));
2135 if (purple_media_is_initiator(priv->media, sess_id, participant) ||
2136 purple_media_accepted(
2137 priv->media, sess_id, participant)) {
2139 if (stream->supports_add)
2140 fs_stream_add_remote_candidates(stream->stream,
2141 stream->remote_candidates, &err);
2142 else
2143 fs_stream_force_remote_candidates(stream->stream,
2144 stream->remote_candidates, &err);
2146 if (err) {
2147 purple_debug_error("backend-fs2", "Error adding remote"
2148 " candidates: %s\n", err->message);
2149 g_error_free(err);
2154 static gboolean
2155 purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
2156 const gchar *sess_id)
2158 PurpleMediaBackendFs2Private *priv;
2159 gboolean ret = FALSE;
2161 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2163 priv = purple_media_backend_fs2_get_instance_private(
2164 PURPLE_MEDIA_BACKEND_FS2(self));
2166 if (sess_id != NULL) {
2167 PurpleMediaBackendFs2Session *session = get_session(
2168 PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2170 if (session == NULL)
2171 return FALSE;
2173 if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
2174 #ifdef HAVE_MEDIA_APPLICATION
2175 PURPLE_MEDIA_SEND_APPLICATION |
2176 #endif
2177 PURPLE_MEDIA_SEND_VIDEO)) {
2179 GList *codecs = NULL;
2181 g_object_get(session->session,
2182 "codecs", &codecs, NULL);
2183 if (codecs) {
2184 fs_codec_list_destroy (codecs);
2185 ret = TRUE;
2187 } else
2188 ret = TRUE;
2189 } else {
2190 GList *values = g_hash_table_get_values(priv->sessions);
2192 for (; values; values = g_list_delete_link(values, values)) {
2193 PurpleMediaBackendFs2Session *session = values->data;
2195 if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
2196 #ifdef HAVE_MEDIA_APPLICATION
2197 PURPLE_MEDIA_SEND_APPLICATION |
2198 #endif
2199 PURPLE_MEDIA_SEND_VIDEO)) {
2201 GList *codecs = NULL;
2203 g_object_get(session->session,
2204 "codecs", &codecs, NULL);
2205 if (codecs) {
2206 fs_codec_list_destroy (codecs);
2207 ret = TRUE;
2208 } else {
2209 ret = FALSE;
2210 break;
2212 } else
2213 ret = TRUE;
2216 if (values != NULL)
2217 g_list_free(values);
2220 return ret;
2223 static GList *
2224 purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
2225 const gchar *sess_id)
2227 PurpleMediaBackendFs2Session *session;
2228 GList *fscodecs;
2229 GList *codecs;
2231 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
2233 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2235 if (session == NULL)
2236 return NULL;
2238 g_object_get(G_OBJECT(session->session),
2239 "codecs", &fscodecs, NULL);
2240 codecs = codec_list_from_fs(fscodecs);
2241 fs_codec_list_destroy(fscodecs);
2243 return codecs;
2246 static GList *
2247 purple_media_backend_fs2_get_local_candidates(PurpleMediaBackend *self,
2248 const gchar *sess_id, const gchar *participant)
2250 PurpleMediaBackendFs2Stream *stream;
2251 GList *candidates = NULL;
2253 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
2255 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2256 sess_id, participant);
2258 if (stream != NULL)
2259 candidates = candidate_list_from_fs(
2260 stream->local_candidates);
2261 return candidates;
2264 static gboolean
2265 purple_media_backend_fs2_set_remote_codecs(PurpleMediaBackend *self,
2266 const gchar *sess_id, const gchar *participant,
2267 GList *codecs)
2269 PurpleMediaBackendFs2Stream *stream;
2270 GList *fscodecs;
2271 GError *err = NULL;
2273 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2274 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2275 sess_id, participant);
2277 if (stream == NULL)
2278 return FALSE;
2280 fscodecs = codec_list_to_fs(codecs);
2281 fs_stream_set_remote_codecs(stream->stream, fscodecs, &err);
2282 fs_codec_list_destroy(fscodecs);
2284 if (err) {
2285 purple_debug_error("backend-fs2",
2286 "Error setting remote codecs: %s\n",
2287 err->message);
2288 g_error_free(err);
2289 return FALSE;
2292 return TRUE;
2295 static GstStructure *
2296 create_fs2_srtp_structure(const gchar *cipher, const gchar *auth,
2297 const gchar *key, gsize key_len)
2299 GstStructure *result;
2300 GstBuffer *buffer;
2301 GstMapInfo info;
2303 buffer = gst_buffer_new_allocate(NULL, key_len, NULL);
2304 gst_buffer_map(buffer, &info, GST_MAP_WRITE);
2305 memcpy(info.data, key, key_len);
2306 gst_buffer_unmap(buffer, &info);
2308 result = gst_structure_new("FarstreamSRTP",
2309 "cipher", G_TYPE_STRING, cipher,
2310 "auth", G_TYPE_STRING, auth,
2311 "key", GST_TYPE_BUFFER, buffer,
2312 NULL);
2313 gst_buffer_unref(buffer);
2315 return result;
2318 static gboolean
2319 purple_media_backend_fs2_set_encryption_parameters (PurpleMediaBackend *self,
2320 const gchar *sess_id, const gchar *cipher, const gchar *auth,
2321 const gchar *key, gsize key_len)
2323 PurpleMediaBackendFs2Session *session;
2324 GstStructure *srtp;
2325 GError *err = NULL;
2326 gboolean result;
2328 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2330 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2331 if (!session)
2332 return FALSE;
2334 srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
2335 if (!srtp)
2336 return FALSE;
2338 result = fs_session_set_encryption_parameters(session->session, srtp,
2339 &err);
2340 if (!result) {
2341 purple_debug_error("backend-fs2",
2342 "Error setting encryption parameters: %s\n", err->message);
2343 g_error_free(err);
2346 gst_structure_free(srtp);
2347 return result;
2350 static gboolean
2351 purple_media_backend_fs2_set_decryption_parameters (PurpleMediaBackend *self,
2352 const gchar *sess_id, const gchar *participant,
2353 const gchar *cipher, const gchar *auth,
2354 const gchar *key, gsize key_len)
2356 PurpleMediaBackendFs2Stream *stream;
2357 GstStructure *srtp;
2358 GError *err = NULL;
2359 gboolean result;
2361 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2363 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), sess_id,
2364 participant);
2365 if (!stream)
2366 return FALSE;
2368 srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
2369 if (!srtp)
2370 return FALSE;
2372 result = fs_stream_set_decryption_parameters(stream->stream, srtp,
2373 &err);
2374 if (!result) {
2375 purple_debug_error("backend-fs2",
2376 "Error setting decryption parameters: %s\n", err->message);
2377 g_error_free(err);
2380 gst_structure_free(srtp);
2381 return result;
2384 static gboolean
2385 purple_media_backend_fs2_set_send_codec(PurpleMediaBackend *self,
2386 const gchar *sess_id, PurpleMediaCodec *codec)
2388 PurpleMediaBackendFs2Session *session;
2389 FsCodec *fscodec;
2390 GError *err = NULL;
2392 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2394 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2396 if (session == NULL)
2397 return FALSE;
2399 fscodec = codec_to_fs(codec);
2400 fs_session_set_send_codec(session->session, fscodec, &err);
2401 fs_codec_destroy(fscodec);
2403 if (err) {
2404 purple_debug_error("media", "Error setting send codec\n");
2405 g_error_free(err);
2406 return FALSE;
2409 return TRUE;
2412 static const gchar **
2413 purple_media_backend_fs2_get_available_params(void)
2415 static const gchar *supported_params[] = {
2416 "sdes-cname", "sdes-email", "sdes-location", "sdes-name", "sdes-note",
2417 "sdes-phone", "sdes-tool", NULL
2420 return supported_params;
2423 static const gchar*
2424 param_to_sdes_type(const gchar *param)
2426 const gchar **supported = purple_media_backend_fs2_get_available_params();
2427 static const gchar *sdes_types[] = {
2428 "cname", "email", "location", "name", "note", "phone", "tool", NULL
2430 guint i;
2432 for (i = 0; supported[i] != NULL; ++i) {
2433 if (purple_strequal(param, supported[i])) {
2434 return sdes_types[i];
2438 return NULL;
2441 static void
2442 purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
2443 guint num_params, GParameter *params)
2445 PurpleMediaBackendFs2Private *priv;
2446 guint i;
2447 GstStructure *sdes;
2449 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2451 priv = purple_media_backend_fs2_get_instance_private(
2452 PURPLE_MEDIA_BACKEND_FS2(self));
2454 if (priv->conference == NULL &&
2455 !init_conference(PURPLE_MEDIA_BACKEND_FS2(self))) {
2456 purple_debug_error("backend-fs2",
2457 "Error initializing the conference.\n");
2458 return;
2461 g_object_get(G_OBJECT(priv->conference), "sdes", &sdes, NULL);
2463 for (i = 0; i != num_params; ++i) {
2464 const gchar *sdes_type = param_to_sdes_type(params[i].name);
2465 if (!sdes_type)
2466 continue;
2468 gst_structure_set(sdes, sdes_type,
2469 G_TYPE_STRING, g_value_get_string(&params[i].value),
2470 NULL);
2473 g_object_set(G_OBJECT(priv->conference), "sdes", sdes, NULL);
2474 gst_structure_free(sdes);
2476 static gboolean
2477 send_dtmf_callback(gpointer userdata)
2479 FsSession *session = userdata;
2481 fs_session_stop_telephony_event(session);
2483 return FALSE;
2485 static gboolean
2486 purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
2487 const gchar *sess_id, gchar dtmf, guint8 volume,
2488 guint16 duration)
2490 PurpleMediaBackendFs2Session *session;
2491 FsDTMFEvent event;
2493 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2495 session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
2496 if (session == NULL)
2497 return FALSE;
2499 /* Convert DTMF char into FsDTMFEvent enum */
2500 switch(dtmf) {
2501 case '0': event = FS_DTMF_EVENT_0; break;
2502 case '1': event = FS_DTMF_EVENT_1; break;
2503 case '2': event = FS_DTMF_EVENT_2; break;
2504 case '3': event = FS_DTMF_EVENT_3; break;
2505 case '4': event = FS_DTMF_EVENT_4; break;
2506 case '5': event = FS_DTMF_EVENT_5; break;
2507 case '6': event = FS_DTMF_EVENT_6; break;
2508 case '7': event = FS_DTMF_EVENT_7; break;
2509 case '8': event = FS_DTMF_EVENT_8; break;
2510 case '9': event = FS_DTMF_EVENT_9; break;
2511 case '*': event = FS_DTMF_EVENT_STAR; break;
2512 case '#': event = FS_DTMF_EVENT_POUND; break;
2513 case 'A': event = FS_DTMF_EVENT_A; break;
2514 case 'B': event = FS_DTMF_EVENT_B; break;
2515 case 'C': event = FS_DTMF_EVENT_C; break;
2516 case 'D': event = FS_DTMF_EVENT_D; break;
2517 default:
2518 return FALSE;
2521 if (!fs_session_start_telephony_event(session->session,
2522 event, volume)) {
2523 return FALSE;
2526 if (duration <= 50) {
2527 fs_session_stop_telephony_event(session->session);
2528 } else {
2529 g_timeout_add(duration, send_dtmf_callback,
2530 session->session);
2533 return TRUE;
2535 #else
2536 GType
2537 purple_media_backend_fs2_get_type(void)
2539 return G_TYPE_NONE;
2541 #endif /* USE_VV */
2543 GstElement *
2544 purple_media_backend_fs2_get_src(PurpleMediaBackendFs2 *self,
2545 const gchar *sess_id)
2547 #ifdef USE_VV
2548 PurpleMediaBackendFs2Session *session = get_session(self, sess_id);
2549 return session != NULL ? session->src : NULL;
2550 #else
2551 return NULL;
2552 #endif
2555 GstElement *
2556 purple_media_backend_fs2_get_tee(PurpleMediaBackendFs2 *self,
2557 const gchar *sess_id, const gchar *who)
2559 #ifdef USE_VV
2560 if (sess_id != NULL && who == NULL) {
2561 PurpleMediaBackendFs2Session *session =
2562 get_session(self, sess_id);
2563 return (session != NULL) ? session->tee : NULL;
2564 } else if (sess_id != NULL && who != NULL) {
2565 PurpleMediaBackendFs2Stream *stream =
2566 get_stream(self, sess_id, who);
2567 return (stream != NULL) ? stream->tee : NULL;
2570 #endif /* USE_VV */
2571 g_return_val_if_reached(NULL);
2574 void
2575 purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self,
2576 const gchar *sess_id, double level)
2578 #ifdef USE_VV
2579 PurpleMediaBackendFs2Private *priv;
2580 GList *sessions;
2582 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2584 priv = purple_media_backend_fs2_get_instance_private(self);
2586 purple_prefs_set_int("/purple/media/audio/volume/input", level);
2588 if (sess_id == NULL)
2589 sessions = g_hash_table_get_values(priv->sessions);
2590 else
2591 sessions = g_list_append(NULL, get_session(self, sess_id));
2593 for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
2594 PurpleMediaBackendFs2Session *session = sessions->data;
2596 if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
2597 gchar *name = g_strdup_printf("volume_%s",
2598 session->id);
2599 GstElement *volume = gst_bin_get_by_name(
2600 GST_BIN(priv->confbin), name);
2601 g_free(name);
2602 g_object_set(volume, "volume", level/10.0, NULL);
2605 #endif /* USE_VV */
2608 void
2609 purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
2610 const gchar *sess_id, const gchar *who, double level)
2612 #ifdef USE_VV
2613 GList *streams;
2615 g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
2617 purple_prefs_set_int("/purple/media/audio/volume/output", level);
2619 streams = get_streams(self, sess_id, who);
2621 for (; streams; streams = g_list_delete_link(streams, streams)) {
2622 PurpleMediaBackendFs2Stream *stream = streams->data;
2624 if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO
2625 && GST_IS_ELEMENT(stream->volume)) {
2626 g_object_set(stream->volume, "volume",
2627 level/10.0, NULL);
2630 #endif /* USE_VV */
2633 #ifdef USE_VV
2634 static gboolean
2635 purple_media_backend_fs2_set_send_rtcp_mux(PurpleMediaBackend *self,
2636 const gchar *sess_id, const gchar *participant,
2637 gboolean send_rtcp_mux)
2639 PurpleMediaBackendFs2Stream *stream;
2641 g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
2642 stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
2643 sess_id, participant);
2645 if (stream != NULL &&
2646 g_object_class_find_property (G_OBJECT_GET_CLASS (stream->stream),
2647 "send-rtcp-mux") != NULL) {
2648 g_object_set (stream->stream, "send-rtcp-mux", send_rtcp_mux, NULL);
2649 return TRUE;
2652 return FALSE;
2654 #endif /* USE_VV */