media: fix unconfirmed stream counting
[siplcs.git] / src / purple / purple-media.c
blobcfac8c69b4ba7337f93bdcab91cbbf44e6cac565
1 /**
2 * @file purple-media.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include "glib.h"
24 #include "glib/gstdio.h"
25 #include <fcntl.h>
26 #include <string.h>
27 #include <unistd.h>
29 #include "sipe-common.h"
31 #include "mediamanager.h"
32 #include "media-gst.h"
33 #include "request.h"
34 #include "agent.h"
36 #include "sipe-backend.h"
37 #include "sipe-core.h"
39 #include "purple-private.h"
41 struct sipe_backend_media {
42 PurpleMedia *m;
43 GSList *streams;
44 /**
45 * Number of media streams that were not yet locally accepted or rejected.
47 guint unconfirmed_streams;
50 struct sipe_backend_stream {
51 gchar *sessionid;
52 gchar *participant;
53 gboolean candidates_prepared;
54 gboolean local_on_hold;
55 gboolean remote_on_hold;
56 gboolean accepted;
59 static void
60 backend_stream_free(struct sipe_backend_stream *stream)
62 if (stream) {
63 g_free(stream->sessionid);
64 g_free(stream->participant);
65 g_free(stream);
69 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type);
70 static PurpleMediaCandidateType sipe_candidate_type_to_purple(SipeCandidateType type);
71 static SipeCandidateType purple_candidate_type_to_sipe(PurpleMediaCandidateType type);
72 static PurpleMediaNetworkProtocol sipe_network_protocol_to_purple(SipeNetworkProtocol proto);
73 static SipeNetworkProtocol purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto);
75 static void
76 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
77 gchar *sessionid,
78 SIPE_UNUSED_PARAMETER gchar *participant,
79 struct sipe_media_call *call)
81 struct sipe_backend_stream *stream;
82 stream = sipe_backend_media_get_stream_by_id(call->backend_private, sessionid);
84 stream->candidates_prepared = TRUE;
86 if (call->candidates_prepared_cb &&
87 sipe_backend_candidates_prepared(call->backend_private)) {
88 call->candidates_prepared_cb(call, stream);
92 static void
93 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
94 PurpleMediaState state,
95 gchar *sessionid,
96 gchar *participant,
97 struct sipe_media_call *call)
99 SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state, sessionid, participant);
100 if (state == PURPLE_MEDIA_STATE_END &&
101 !sessionid && !participant && call->media_end_cb)
102 call->media_end_cb(call);
105 /* Used externally in purple-plugin.c. This declaration stops the compiler
106 * complaining about missing prototype. */
107 void capture_pipeline(gchar *label);
109 void
110 capture_pipeline(gchar *label) {
111 PurpleMediaManager *manager = purple_media_manager_get();
112 GstElement *pipeline = purple_media_manager_get_pipeline(manager);
113 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, label);
116 static void
117 on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media, gchar *message,
118 struct sipe_media_call *call)
120 capture_pipeline("ERROR");
122 if (call->error_cb)
123 call->error_cb(call, message);
126 static void
127 on_stream_info_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
128 PurpleMediaInfoType type,
129 gchar *sessionid,
130 gchar *participant,
131 gboolean local,
132 struct sipe_media_call *call)
134 if (type == PURPLE_MEDIA_INFO_ACCEPT) {
135 if (call->call_accept_cb && !sessionid && !participant)
136 call->call_accept_cb(call, local);
137 else if (sessionid && participant) {
138 struct sipe_backend_stream *stream;
139 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
140 sessionid);
141 if (stream) {
142 if (!stream->accepted && local)
143 --call->backend_private->unconfirmed_streams;
144 stream->accepted = TRUE;
147 } else if (type == PURPLE_MEDIA_INFO_HOLD || type == PURPLE_MEDIA_INFO_UNHOLD) {
149 gboolean state = (type == PURPLE_MEDIA_INFO_HOLD);
151 if (sessionid) {
152 // Hold specific stream
153 struct sipe_backend_stream *stream;
154 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
155 sessionid);
157 if (local)
158 stream->local_on_hold = state;
159 else
160 stream->remote_on_hold = state;
161 } else {
162 // Hold all streams
163 GSList *i = sipe_backend_media_get_streams(call->backend_private);
164 for (; i; i = i->next) {
165 struct sipe_backend_stream *stream = i->data;
167 if (local)
168 stream->local_on_hold = state;
169 else
170 stream->remote_on_hold = state;
174 if (call->call_hold_cb)
175 call->call_hold_cb(call, local, state);
176 } else if (type == PURPLE_MEDIA_INFO_HANGUP || type == PURPLE_MEDIA_INFO_REJECT) {
177 if (!sessionid && !participant) {
178 if (type == PURPLE_MEDIA_INFO_HANGUP && call->call_hangup_cb)
179 call->call_hangup_cb(call, local);
180 else if (type == PURPLE_MEDIA_INFO_REJECT && call->call_reject_cb && !local)
181 call->call_reject_cb(call, local);
182 } else if (sessionid && participant) {
183 struct sipe_backend_stream *stream;
184 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
185 sessionid);
187 if (stream) {
188 call->backend_private->streams = g_slist_remove(call->backend_private->streams, stream);
189 backend_stream_free(stream);
190 if (local && --call->backend_private->unconfirmed_streams == 0 &&
191 call->call_reject_cb)
192 call->call_reject_cb(call, local);
198 struct sipe_backend_media *
199 sipe_backend_media_new(struct sipe_core_public *sipe_public,
200 struct sipe_media_call *call,
201 const gchar *participant,
202 gboolean initiator)
204 struct sipe_backend_media *media = g_new0(struct sipe_backend_media, 1);
205 struct sipe_backend_private *purple_private = sipe_public->backend_private;
206 PurpleMediaManager *manager = purple_media_manager_get();
207 GstElement *pipeline;
209 media->m = purple_media_manager_create_media(manager,
210 purple_private->account,
211 "fsrtpconference",
212 participant, initiator);
214 g_signal_connect(G_OBJECT(media->m), "candidates-prepared",
215 G_CALLBACK(on_candidates_prepared_cb), call);
216 g_signal_connect(G_OBJECT(media->m), "stream-info",
217 G_CALLBACK(on_stream_info_cb), call);
218 g_signal_connect(G_OBJECT(media->m), "error",
219 G_CALLBACK(on_error_cb), call);
220 g_signal_connect(G_OBJECT(media->m), "state-changed",
221 G_CALLBACK(on_state_changed_cb), call);
223 /* On error, the pipeline is no longer in PLAYING state and libpurple
224 * will not switch it back to PLAYING, preventing any more calls until
225 * application restart. We switch the state ourselves here to negate
226 * effect of any error in previous call (if any). */
227 pipeline = purple_media_manager_get_pipeline(manager);
228 gst_element_set_state(pipeline, GST_STATE_PLAYING);
230 return media;
233 void
234 sipe_backend_media_free(struct sipe_backend_media *media)
236 if (media) {
237 GSList *stream = media->streams;
239 for (; stream; stream = g_slist_delete_link(stream, stream))
240 backend_stream_free(stream->data);
242 g_free(media);
246 void
247 sipe_backend_media_set_cname(struct sipe_backend_media *media, gchar *cname)
249 if (media) {
250 guint num_params = 3;
251 GParameter *params = g_new0(GParameter, num_params);
252 params[0].name = "sdes-cname";
253 g_value_init(&params[0].value, G_TYPE_STRING);
254 g_value_set_string(&params[0].value, cname);
255 params[1].name = "sdes-name";
256 g_value_init(&params[1].value, G_TYPE_STRING);
257 params[2].name = "sdes-tool";
258 g_value_init(&params[2].value, G_TYPE_STRING);
260 purple_media_set_params(media->m, num_params, params);
262 g_value_unset(&params[0].value);
263 g_free(params);
267 #define FS_CODECS_CONF \
268 "# Automatically created by SIPE plugin\n" \
269 "[video/H263]\n" \
270 "farsight-send-profile=videoscale ! ffmpegcolorspace ! fsvideoanyrate ! ffenc_h263 rtp-payload-size=30 ! rtph263pay\n" \
271 "\n" \
272 "[audio/PCMA]\n" \
273 "farsight-send-profile=audioconvert ! audioresample ! audioconvert ! alawenc ! rtppcmapay min-ptime=20000000 max-ptime=20000000\n" \
274 "\n" \
275 "[audio/PCMU]\n" \
276 "farsight-send-profile=audioconvert ! audioresample ! audioconvert ! mulawenc ! rtppcmupay min-ptime=20000000 max-ptime=20000000\n";
278 static void
279 ensure_codecs_conf()
281 gchar *filename;
282 filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
284 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
285 int fd = g_open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
286 gchar *fs_codecs_conf = FS_CODECS_CONF;
287 if (!fd || write(fd, fs_codecs_conf, strlen(fs_codecs_conf)) == -1)
288 SIPE_DEBUG_ERROR_NOFORMAT("Can not create fs-codec.conf!");
289 close(fd);
292 g_free(filename);
295 struct sipe_backend_media_relays *
296 sipe_backend_media_relays_convert(GSList *media_relays, gchar *username, gchar *password)
298 GValueArray *relay_info = g_value_array_new(0);
300 for (; media_relays; media_relays = media_relays->next) {
301 struct sipe_media_relay *relay = media_relays->data;
302 GValue value;
303 GstStructure *gst_relay_info;
305 /* Skip relays where IP could not be resolved. */
306 if (!relay->hostname)
307 continue;
309 gst_relay_info = gst_structure_new("relay-info",
310 "ip", G_TYPE_STRING, relay->hostname,
311 "port", G_TYPE_UINT, relay->udp_port,
312 "username", G_TYPE_STRING, username,
313 "password", G_TYPE_STRING, password,
314 NULL);
316 if (!gst_relay_info) {
317 g_value_array_free(relay_info);
318 return NULL;
321 memset(&value, 0, sizeof(GValue));
322 g_value_init(&value, GST_TYPE_STRUCTURE);
323 gst_value_set_structure(&value, gst_relay_info);
325 relay_info = g_value_array_append(relay_info, &value);
326 gst_structure_free(gst_relay_info);
329 return (struct sipe_backend_media_relays *)relay_info;
332 void
333 sipe_backend_media_relays_free(struct sipe_backend_media_relays *media_relays)
335 g_value_array_free((GValueArray *)media_relays);
338 struct sipe_backend_stream *
339 sipe_backend_media_add_stream(struct sipe_backend_media *media,
340 const gchar *id,
341 const gchar *participant,
342 SipeMediaType type,
343 SipeIceVersion ice_version,
344 gboolean initiator,
345 struct sipe_backend_media_relays *media_relays)
347 struct sipe_backend_stream *stream = NULL;
348 PurpleMediaSessionType prpl_type = sipe_media_to_purple(type);
349 GParameter *params = NULL;
350 guint params_cnt = 0;
351 gchar *transmitter;
353 if (ice_version != SIPE_ICE_NO_ICE) {
354 transmitter = "nice";
355 params_cnt = 2;
357 params = g_new0(GParameter, params_cnt);
359 params[0].name = "compatibility-mode";
360 g_value_init(&params[0].value, G_TYPE_UINT);
361 g_value_set_uint(&params[0].value,
362 ice_version == SIPE_ICE_DRAFT_6 ?
363 NICE_COMPATIBILITY_OC2007 :
364 NICE_COMPATIBILITY_OC2007R2);
366 if (media_relays) {
367 params[1].name = "relay-info";
368 g_value_init(&params[1].value, G_TYPE_VALUE_ARRAY);
369 g_value_set_boxed(&params[1].value, media_relays);
370 } else
371 --params_cnt;
372 } else {
373 // TODO: session naming here, Communicator needs audio/video
374 transmitter = "rawudp";
375 //sessionid = "sipe-voice-rawudp";
378 ensure_codecs_conf();
380 if (purple_media_add_stream(media->m, id, participant, prpl_type,
381 initiator, transmitter, params_cnt,
382 params)) {
383 stream = g_new0(struct sipe_backend_stream, 1);
384 stream->sessionid = g_strdup(id);
385 stream->participant = g_strdup(participant);
386 stream->candidates_prepared = FALSE;
388 media->streams = g_slist_append(media->streams, stream);
389 if (!initiator)
390 ++media->unconfirmed_streams;
393 if (media_relays)
394 g_value_unset(&params[1].value);
396 g_free(params);
398 return stream;
401 void
402 sipe_backend_media_remove_stream(struct sipe_backend_media *media,
403 struct sipe_backend_stream *stream)
405 g_return_if_fail(media && stream);
407 purple_media_end(media->m, stream->sessionid, NULL);
408 media->streams = g_slist_remove(media->streams, stream);
409 backend_stream_free(stream);
412 GSList *sipe_backend_media_get_streams(struct sipe_backend_media *media)
414 return media->streams;
417 struct sipe_backend_stream *
418 sipe_backend_media_get_stream_by_id(struct sipe_backend_media *media,
419 const gchar *id)
421 GSList *i;
422 for (i = media->streams; i; i = i->next) {
423 struct sipe_backend_stream *stream = i->data;
424 if (sipe_strequal(stream->sessionid, id))
425 return stream;
427 return NULL;
430 void
431 sipe_backend_media_add_remote_candidates(struct sipe_backend_media *media,
432 struct sipe_backend_stream *stream,
433 GList *candidates)
435 purple_media_add_remote_candidates(media->m, stream->sessionid,
436 stream->participant, candidates);
439 gboolean sipe_backend_media_is_initiator(struct sipe_backend_media *media,
440 struct sipe_backend_stream *stream)
442 return purple_media_is_initiator(media->m,
443 stream ? stream->sessionid : NULL,
444 stream ? stream->participant : NULL);
447 gboolean sipe_backend_media_accepted(struct sipe_backend_media *media)
449 return purple_media_accepted(media->m, NULL, NULL);
452 gboolean
453 sipe_backend_candidates_prepared(struct sipe_backend_media *media)
455 GSList *streams = media->streams;
456 for (; streams; streams = streams->next) {
457 struct sipe_backend_stream *s = streams->data;
458 if (!s->candidates_prepared)
459 return FALSE;
461 return TRUE;
464 GList *
465 sipe_backend_media_get_active_local_candidates(struct sipe_backend_media *media,
466 struct sipe_backend_stream *stream)
468 return purple_media_get_active_local_candidates(media->m,
469 stream->sessionid,
470 stream->participant);
473 GList *
474 sipe_backend_media_get_active_remote_candidates(struct sipe_backend_media *media,
475 struct sipe_backend_stream *stream)
477 return purple_media_get_active_remote_candidates(media->m,
478 stream->sessionid,
479 stream->participant);
482 gchar *
483 sipe_backend_stream_get_id(struct sipe_backend_stream *stream)
485 return stream->sessionid;
488 void sipe_backend_stream_hold(struct sipe_backend_media *media,
489 struct sipe_backend_stream *stream,
490 gboolean local)
492 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HOLD,
493 stream->sessionid, stream->participant,
494 local);
497 void sipe_backend_stream_unhold(struct sipe_backend_media *media,
498 struct sipe_backend_stream *stream,
499 gboolean local)
501 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_UNHOLD,
502 stream->sessionid, stream->participant,
503 local);
506 gboolean sipe_backend_stream_is_held(struct sipe_backend_stream *stream)
508 g_return_val_if_fail(stream, FALSE);
510 return stream->local_on_hold || stream->remote_on_hold;
513 struct sipe_backend_codec *
514 sipe_backend_codec_new(int id, const char *name, SipeMediaType type, guint clock_rate)
516 return (struct sipe_backend_codec *)purple_media_codec_new(id, name,
517 sipe_media_to_purple(type),
518 clock_rate);
521 void
522 sipe_backend_codec_free(struct sipe_backend_codec *codec)
524 if (codec)
525 g_object_unref(codec);
529 sipe_backend_codec_get_id(struct sipe_backend_codec *codec)
531 return purple_media_codec_get_id((PurpleMediaCodec *)codec);
534 gchar *
535 sipe_backend_codec_get_name(struct sipe_backend_codec *codec)
537 /* Not explicitly documented, but return value must be g_free()'d */
538 return purple_media_codec_get_encoding_name((PurpleMediaCodec *)codec);
541 guint
542 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec *codec)
544 return purple_media_codec_get_clock_rate((PurpleMediaCodec *)codec);
547 void
548 sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec *codec,
549 const gchar *name, const gchar *value)
551 purple_media_codec_add_optional_parameter((PurpleMediaCodec *)codec, name, value);
554 GList *
555 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec *codec)
557 return purple_media_codec_get_optional_parameters((PurpleMediaCodec *)codec);
560 gboolean
561 sipe_backend_set_remote_codecs(struct sipe_backend_media *media,
562 struct sipe_backend_stream *stream,
563 GList *codecs)
565 return purple_media_set_remote_codecs(media->m,
566 stream->sessionid,
567 stream->participant,
568 codecs);
571 GList*
572 sipe_backend_get_local_codecs(struct sipe_backend_media *media,
573 struct sipe_backend_stream *stream)
575 GList *codecs = purple_media_get_codecs(media->m,
576 stream->sessionid);
577 GList *i = codecs;
578 gboolean is_conference = (g_strstr_len(stream->participant,
579 strlen(stream->participant),
580 "app:conf:audio-video:") != NULL);
583 * Do not announce Theora. Its optional parameters are too long,
584 * Communicator rejects such SDP message and does not support the codec
585 * anyway.
587 * For some yet unknown reason, A/V conferencing server does not accept
588 * voice stream sent by SIPE when SIREN codec is in use. Nevertheless,
589 * we are able to decode incoming SIREN from server and with MSOC
590 * client, bidirectional call using the codec works. Until resolved,
591 * do not try to negotiate SIREN usage when conferencing. PCMA or PCMU
592 * seems to work properly in this scenario.
594 while (i) {
595 PurpleMediaCodec *codec = i->data;
596 gchar *encoding_name = purple_media_codec_get_encoding_name(codec);
598 if (sipe_strequal(encoding_name,"THEORA") ||
599 (is_conference && sipe_strequal(encoding_name,"SIREN"))) {
600 GList *tmp;
601 g_object_unref(codec);
602 tmp = i->next;
603 codecs = g_list_delete_link(codecs, i);
604 i = tmp;
605 } else
606 i = i->next;
608 g_free(encoding_name);
611 return codecs;
614 struct sipe_backend_candidate *
615 sipe_backend_candidate_new(const gchar *foundation,
616 SipeComponentType component,
617 SipeCandidateType type, SipeNetworkProtocol proto,
618 const gchar *ip, guint port,
619 const gchar *username,
620 const gchar *password)
622 PurpleMediaCandidate *c = purple_media_candidate_new(
623 /* Libnice and Farsight rely on non-NULL foundation to
624 * distinguish between candidates of a component. When NULL
625 * foundation is passed (ie. ICE draft 6 does not use foudation),
626 * use username instead. If no foundation is provided, Farsight
627 * may signal an active candidate different from the one actually
628 * in use. See Farsight's agent_new_selected_pair() in
629 * fs-nice-stream-transmitter.h where first candidate in the
630 * remote list is always selected when no foundation. */
631 foundation ? foundation : username,
632 component,
633 sipe_candidate_type_to_purple(type),
634 sipe_network_protocol_to_purple(proto),
636 port);
637 g_object_set(c, "username", username, "password", password, NULL);
638 return (struct sipe_backend_candidate *)c;
641 void
642 sipe_backend_candidate_free(struct sipe_backend_candidate *candidate)
644 if (candidate)
645 g_object_unref(candidate);
648 gchar *
649 sipe_backend_candidate_get_username(struct sipe_backend_candidate *candidate)
651 /* Not explicitly documented, but return value must be g_free()'d */
652 return purple_media_candidate_get_username((PurpleMediaCandidate*)candidate);
655 gchar *
656 sipe_backend_candidate_get_password(struct sipe_backend_candidate *candidate)
658 /* Not explicitly documented, but return value must be g_free()'d */
659 return purple_media_candidate_get_password((PurpleMediaCandidate*)candidate);
662 gchar *
663 sipe_backend_candidate_get_foundation(struct sipe_backend_candidate *candidate)
665 /* Not explicitly documented, but return value must be g_free()'d */
666 return purple_media_candidate_get_foundation((PurpleMediaCandidate*)candidate);
669 gchar *
670 sipe_backend_candidate_get_ip(struct sipe_backend_candidate *candidate)
672 /* Not explicitly documented, but return value must be g_free()'d */
673 return purple_media_candidate_get_ip((PurpleMediaCandidate*)candidate);
676 guint
677 sipe_backend_candidate_get_port(struct sipe_backend_candidate *candidate)
679 return purple_media_candidate_get_port((PurpleMediaCandidate*)candidate);
682 gchar *
683 sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate *candidate)
685 /* Not explicitly documented, but return value must be g_free()'d */
686 return purple_media_candidate_get_base_ip((PurpleMediaCandidate*)candidate);
689 guint
690 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate *candidate)
692 return purple_media_candidate_get_base_port((PurpleMediaCandidate*)candidate);
695 guint32
696 sipe_backend_candidate_get_priority(struct sipe_backend_candidate *candidate)
698 return purple_media_candidate_get_priority((PurpleMediaCandidate*)candidate);
701 void
702 sipe_backend_candidate_set_priority(struct sipe_backend_candidate *candidate, guint32 priority)
704 g_object_set(candidate, "priority", priority, NULL);
707 SipeComponentType
708 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate *candidate)
710 return purple_media_candidate_get_component_id((PurpleMediaCandidate*)candidate);
713 SipeCandidateType
714 sipe_backend_candidate_get_type(struct sipe_backend_candidate *candidate)
716 PurpleMediaCandidateType type =
717 purple_media_candidate_get_candidate_type((PurpleMediaCandidate*)candidate);
718 return purple_candidate_type_to_sipe(type);
721 SipeNetworkProtocol
722 sipe_backend_candidate_get_protocol(struct sipe_backend_candidate *candidate)
724 PurpleMediaNetworkProtocol proto =
725 purple_media_candidate_get_protocol((PurpleMediaCandidate*)candidate);
726 return purple_network_protocol_to_sipe(proto);
729 static void
730 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key,
731 gpointer value,
732 gpointer user_data)
734 GList *entry = value;
735 GList **candidates = user_data;
737 g_object_unref(entry->data);
738 *candidates = g_list_delete_link(*candidates, entry);
741 static GList *
742 ensure_candidate_pairs(GList *candidates)
744 GHashTable *lone_cand_links;
745 GList *i;
747 lone_cand_links = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
749 for (i = candidates; i; i = i->next) {
750 PurpleMediaCandidate *c = i->data;
751 gchar *foundation = purple_media_candidate_get_foundation(c);
753 if (g_hash_table_lookup(lone_cand_links, foundation)) {
754 g_hash_table_remove(lone_cand_links, foundation);
755 g_free(foundation);
756 } else {
757 g_hash_table_insert(lone_cand_links, foundation, i);
761 g_hash_table_foreach(lone_cand_links, remove_lone_candidate_cb, &candidates);
762 g_hash_table_destroy(lone_cand_links);
764 return candidates;
767 GList *
768 sipe_backend_get_local_candidates(struct sipe_backend_media *media,
769 struct sipe_backend_stream *stream)
771 GList *candidates = purple_media_get_local_candidates(media->m,
772 stream->sessionid,
773 stream->participant);
775 * Sometimes purple will not return complete list of candidates, even
776 * after "candidates-prepared" signal is emitted. This is a feature of
777 * libnice, namely affecting candidates discovered via UPnP. Nice does
778 * not wait until discovery is finished and can signal end of candidate
779 * gathering before all responses from UPnP enabled gateways are received.
781 * Remove any incomplete RTP+RTCP candidate pairs from the list.
783 candidates = ensure_candidate_pairs(candidates);
784 return candidates;
787 void
788 sipe_backend_media_accept(struct sipe_backend_media *media, gboolean local)
790 if (media)
791 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_ACCEPT,
792 NULL, NULL, local);
795 void
796 sipe_backend_media_hangup(struct sipe_backend_media *media, gboolean local)
798 if (media)
799 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HANGUP,
800 NULL, NULL, local);
803 void
804 sipe_backend_media_reject(struct sipe_backend_media *media, gboolean local)
806 if (media)
807 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_REJECT,
808 NULL, NULL, local);
811 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type)
813 switch (type) {
814 case SIPE_MEDIA_AUDIO: return PURPLE_MEDIA_AUDIO;
815 case SIPE_MEDIA_VIDEO: return PURPLE_MEDIA_VIDEO;
816 default: return PURPLE_MEDIA_NONE;
820 /*SipeMediaType purple_media_to_sipe(PurpleMediaSessionType type)
822 switch (type) {
823 case PURPLE_MEDIA_AUDIO: return SIPE_MEDIA_AUDIO;
824 case PURPLE_MEDIA_VIDEO: return SIPE_MEDIA_VIDEO;
825 default: return SIPE_MEDIA_AUDIO;
829 static PurpleMediaCandidateType
830 sipe_candidate_type_to_purple(SipeCandidateType type)
832 switch (type) {
833 case SIPE_CANDIDATE_TYPE_HOST: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
834 case SIPE_CANDIDATE_TYPE_RELAY: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
835 case SIPE_CANDIDATE_TYPE_SRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
836 case SIPE_CANDIDATE_TYPE_PRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
837 default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
841 static SipeCandidateType
842 purple_candidate_type_to_sipe(PurpleMediaCandidateType type)
844 switch (type) {
845 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST: return SIPE_CANDIDATE_TYPE_HOST;
846 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY: return SIPE_CANDIDATE_TYPE_RELAY;
847 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX: return SIPE_CANDIDATE_TYPE_SRFLX;
848 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX: return SIPE_CANDIDATE_TYPE_PRFLX;
849 default: return SIPE_CANDIDATE_TYPE_HOST;
853 static PurpleMediaNetworkProtocol
854 sipe_network_protocol_to_purple(SipeNetworkProtocol proto)
856 switch (proto) {
857 case SIPE_NETWORK_PROTOCOL_TCP: return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
858 case SIPE_NETWORK_PROTOCOL_UDP: return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
859 default: return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
863 static SipeNetworkProtocol
864 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto)
866 switch (proto) {
867 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP: return SIPE_NETWORK_PROTOCOL_TCP;
868 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP: return SIPE_NETWORK_PROTOCOL_UDP;
869 default: return SIPE_NETWORK_PROTOCOL_UDP;
874 Local Variables:
875 mode: c
876 c-file-style: "bsd"
877 indent-tabs-mode: t
878 tab-width: 8
879 End: