audio: handle special case when going offline during call
[siplcs.git] / src / purple / purple-media.c
blob434489196fc7d07bb35ee53286b96ecb4c9c1b59
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"
25 #include "sipe-common.h"
27 #include "mediamanager.h"
28 #include "request.h"
29 #include "agent.h"
31 #include "sipe-backend.h"
32 #include "sipe-core.h"
34 #include "purple-private.h"
36 struct sipe_backend_media {
37 PurpleMedia *m;
38 // Prevent infinite recursion in on_stream_info_cb
39 gboolean in_recursion;
40 GSList *streams;
43 struct sipe_backend_stream {
44 gchar *sessionid;
45 gchar *participant;
48 static void
49 backend_stream_free(struct sipe_backend_stream *stream)
51 if (stream) {
52 g_free(stream->participant);
53 g_free(stream);
57 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type);
58 static PurpleMediaCandidateType sipe_candidate_type_to_purple(SipeCandidateType type);
59 static SipeCandidateType purple_candidate_type_to_sipe(PurpleMediaCandidateType type);
60 static PurpleMediaNetworkProtocol sipe_network_protocol_to_purple(SipeNetworkProtocol proto);
61 static SipeNetworkProtocol purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto);
63 static void
64 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
65 SIPE_UNUSED_PARAMETER gchar *sessionid,
66 SIPE_UNUSED_PARAMETER gchar *participant,
67 struct sipe_media_call *call)
69 if (call->candidates_prepared_cb)
70 call->candidates_prepared_cb(call);
73 static void
74 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
75 PurpleMediaState state,
76 gchar *sessionid,
77 gchar *participant,
78 struct sipe_media_call *call)
80 SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state, sessionid, participant);
81 if (state == PURPLE_MEDIA_STATE_CONNECTED && call->media_connected_cb)
82 call->media_connected_cb(call);
85 static struct sipe_backend_stream *
86 stream_find(struct sipe_backend_media *media,
87 const gchar *sessionid,
88 const gchar *participant)
90 GSList *streams = media->streams;
92 while (streams) {
93 struct sipe_backend_stream *s = streams->data;
94 if ( sipe_strequal(s->sessionid, sessionid)
95 && sipe_strequal(s->participant, participant))
96 return s;
98 streams = streams->next;
101 return NULL;
104 static void
105 on_stream_info_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
106 PurpleMediaInfoType type,
107 gchar *sessionid,
108 gchar *participant,
109 gboolean local,
110 struct sipe_media_call *call)
112 struct sipe_backend_media *m = call->backend_private;
113 if (m->in_recursion) {
114 m->in_recursion = FALSE;
115 return;
118 if (type == PURPLE_MEDIA_INFO_ACCEPT && call->call_accept_cb
119 && !sessionid && !participant)
120 call->call_accept_cb(call, local);
121 else if (type == PURPLE_MEDIA_INFO_HOLD && call->call_hold_cb) {
122 call->call_hold_cb(call, local, TRUE);
123 if (!local) {
124 m->in_recursion = TRUE;
125 purple_media_stream_info(m->m, PURPLE_MEDIA_INFO_HOLD, NULL, NULL, TRUE);
127 } else if (type == PURPLE_MEDIA_INFO_UNHOLD && call->call_hold_cb) {
128 call->call_hold_cb(call, local, FALSE);
129 m->in_recursion = TRUE;
130 if (!call->local_on_hold && !call->remote_on_hold) {
131 purple_media_stream_info(m->m, PURPLE_MEDIA_INFO_UNHOLD, NULL, NULL, TRUE);
132 } else {
133 /* Remote side is still on hold, keep local also held to prevent sending
134 * unnecessary media over network */
135 purple_media_stream_info(m->m, PURPLE_MEDIA_INFO_HOLD, NULL, NULL, TRUE);
137 } else if (type == PURPLE_MEDIA_INFO_HANGUP || type == PURPLE_MEDIA_INFO_REJECT) {
138 if (!sessionid && !participant) {
139 if (type == PURPLE_MEDIA_INFO_HANGUP && call->call_hangup_cb)
140 call->call_hangup_cb(call, local);
141 else if (type == PURPLE_MEDIA_INFO_REJECT && call->call_reject_cb)
142 call->call_reject_cb(call, local);
143 } else if (sessionid && participant) {
144 struct sipe_backend_stream *stream;
145 stream = stream_find(m, sessionid, participant);
146 if (stream) {
147 m->streams = g_slist_remove(m->streams, stream);
148 backend_stream_free(stream);
154 struct sipe_backend_media *
155 sipe_backend_media_new(struct sipe_core_public *sipe_public,
156 struct sipe_media_call *call,
157 const gchar *participant,
158 gboolean initiator)
160 struct sipe_backend_media *media = g_new0(struct sipe_backend_media, 1);
161 struct sipe_backend_private *purple_private = sipe_public->backend_private;
162 PurpleMediaManager *manager = purple_media_manager_get();
164 media->m = purple_media_manager_create_media(manager,
165 purple_private->account,
166 "fsrtpconference",
167 participant, initiator);
169 g_signal_connect(G_OBJECT(media->m), "candidates-prepared",
170 G_CALLBACK(on_candidates_prepared_cb), call);
171 g_signal_connect(G_OBJECT(media->m), "stream-info",
172 G_CALLBACK(on_stream_info_cb), call);
173 g_signal_connect(G_OBJECT(media->m), "state-changed",
174 G_CALLBACK(on_state_changed_cb), call);
176 return media;
179 void
180 sipe_backend_media_free(struct sipe_backend_media *media)
182 GSList *stream = media->streams;
183 g_object_unref(media->m);
185 for (; stream; stream = g_slist_delete_link(stream, stream))
186 backend_stream_free(stream->data);
188 g_free(media);
191 struct sipe_backend_stream *
192 sipe_backend_media_add_stream(struct sipe_backend_media *media,
193 const gchar* participant,
194 SipeMediaType type,
195 gboolean use_nice,
196 gboolean initiator)
198 struct sipe_backend_stream *stream = NULL;
199 PurpleMediaSessionType prpl_type = sipe_media_to_purple(type);
200 GParameter *params = NULL;
201 guint params_cnt = 0;
202 gchar *transmitter;
203 gchar *sessionid;
205 if (use_nice) {
206 transmitter = "nice";
207 params_cnt = 2;
209 params = g_new0(GParameter, params_cnt);
210 params[0].name = "controlling-mode";
211 g_value_init(&params[0].value, G_TYPE_BOOLEAN);
212 g_value_set_boolean(&params[0].value, initiator);
213 params[1].name = "compatibility-mode";
214 g_value_init(&params[1].value, G_TYPE_UINT);
215 g_value_set_uint(&params[1].value, NICE_COMPATIBILITY_OC2007R2);
217 sessionid = "sipe-voice-nice";
218 } else {
219 transmitter = "rawudp";
220 sessionid = "sipe-voice-rawudp";
223 if (purple_media_add_stream(media->m, sessionid, participant, prpl_type,
224 initiator, transmitter, params_cnt, params)) {
225 stream = g_new0(struct sipe_backend_stream, 1);
226 stream->sessionid = sessionid;
227 stream->participant = g_strdup(participant);
229 media->streams = g_slist_append(media->streams, stream);
232 g_free(params);
234 return stream;
237 void
238 sipe_backend_media_remove_stream(struct sipe_backend_media *media, struct sipe_backend_stream *stream)
240 purple_media_end(media->m, stream->sessionid, stream->participant);
243 void
244 sipe_backend_media_add_remote_candidates(struct sipe_backend_media *media,
245 struct sipe_backend_stream *stream,
246 GList *candidates)
248 purple_media_add_remote_candidates(media->m, stream->sessionid,
249 stream->participant, candidates);
252 gboolean sipe_backend_media_is_initiator(struct sipe_backend_media *media,
253 struct sipe_backend_stream *stream)
255 return purple_media_is_initiator(media->m,
256 stream ? stream->sessionid : NULL,
257 stream ? stream->participant : NULL);
260 gboolean sipe_backend_media_accepted(struct sipe_backend_media *media)
262 return purple_media_accepted(media->m, NULL, NULL);
265 GList *
266 sipe_backend_media_get_active_local_candidates(struct sipe_backend_media *media,
267 struct sipe_backend_stream *stream)
269 return purple_media_get_active_local_candidates(media->m,
270 stream->sessionid,
271 stream->participant);
274 GList *
275 sipe_backend_media_get_active_remote_candidates(struct sipe_backend_media *media,
276 struct sipe_backend_stream *stream)
278 return purple_media_get_active_remote_candidates(media->m,
279 stream->sessionid,
280 stream->participant);
283 struct sipe_backend_codec *
284 sipe_backend_codec_new(int id, const char *name, SipeMediaType type, guint clock_rate)
286 return (struct sipe_backend_codec *)purple_media_codec_new(id, name,
287 sipe_media_to_purple(type),
288 clock_rate);
291 void
292 sipe_backend_codec_free(struct sipe_backend_codec *codec)
294 if (codec)
295 g_object_unref(codec);
299 sipe_backend_codec_get_id(struct sipe_backend_codec *codec)
301 return purple_media_codec_get_id((PurpleMediaCodec *)codec);
304 gchar *
305 sipe_backend_codec_get_name(struct sipe_backend_codec *codec)
307 /* Not explicitly documented, but return value must be g_free()'d */
308 return purple_media_codec_get_encoding_name((PurpleMediaCodec *)codec);
311 guint
312 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec *codec)
314 return purple_media_codec_get_clock_rate((PurpleMediaCodec *)codec);
317 void
318 sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec *codec,
319 const gchar *name, const gchar *value)
321 purple_media_codec_add_optional_parameter((PurpleMediaCodec *)codec, name, value);
324 GList *
325 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec *codec)
327 return purple_media_codec_get_optional_parameters((PurpleMediaCodec *)codec);
330 gboolean
331 sipe_backend_set_remote_codecs(struct sipe_backend_media *media,
332 struct sipe_backend_stream *stream,
333 GList *codecs)
335 return purple_media_set_remote_codecs(media->m,
336 stream->sessionid,
337 stream->participant,
338 codecs);
341 GList*
342 sipe_backend_get_local_codecs(struct sipe_media_call *call,
343 struct sipe_backend_stream *stream)
345 return purple_media_get_codecs(call->backend_private->m, stream->sessionid);
348 struct sipe_backend_candidate *
349 sipe_backend_candidate_new(const gchar *foundation,
350 SipeComponentType component,
351 SipeCandidateType type, SipeNetworkProtocol proto,
352 const gchar *ip, guint port)
354 return (struct sipe_backend_candidate *)purple_media_candidate_new(
355 foundation,
356 component,
357 sipe_candidate_type_to_purple(type),
358 sipe_network_protocol_to_purple(proto),
360 port);
363 void
364 sipe_backend_candidate_free(struct sipe_backend_candidate *candidate)
366 if (candidate)
367 g_object_unref(candidate);
370 gchar *
371 sipe_backend_candidate_get_username(struct sipe_backend_candidate *candidate)
373 /* Not explicitly documented, but return value must be g_free()'d */
374 return purple_media_candidate_get_username((PurpleMediaCandidate*)candidate);
377 gchar *
378 sipe_backend_candidate_get_password(struct sipe_backend_candidate *candidate)
380 /* Not explicitly documented, but return value must be g_free()'d */
381 return purple_media_candidate_get_password((PurpleMediaCandidate*)candidate);
384 gchar *
385 sipe_backend_candidate_get_foundation(struct sipe_backend_candidate *candidate)
387 /* Not explicitly documented, but return value must be g_free()'d */
388 return purple_media_candidate_get_foundation((PurpleMediaCandidate*)candidate);
391 gchar *
392 sipe_backend_candidate_get_ip(struct sipe_backend_candidate *candidate)
394 /* Not explicitly documented, but return value must be g_free()'d */
395 return purple_media_candidate_get_ip((PurpleMediaCandidate*)candidate);
398 guint
399 sipe_backend_candidate_get_port(struct sipe_backend_candidate *candidate)
401 return purple_media_candidate_get_port((PurpleMediaCandidate*)candidate);
404 gchar *
405 sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate *candidate)
407 /* Not explicitly documented, but return value must be g_free()'d */
408 return purple_media_candidate_get_base_ip((PurpleMediaCandidate*)candidate);
411 guint
412 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate *candidate)
414 return purple_media_candidate_get_base_port((PurpleMediaCandidate*)candidate);
417 guint32
418 sipe_backend_candidate_get_priority(struct sipe_backend_candidate *candidate)
420 return purple_media_candidate_get_priority((PurpleMediaCandidate*)candidate);
423 void
424 sipe_backend_candidate_set_priority(struct sipe_backend_candidate *candidate, guint32 priority)
426 g_object_set(candidate, "priority", priority, NULL);
429 SipeComponentType
430 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate *candidate)
432 return purple_media_candidate_get_component_id((PurpleMediaCandidate*)candidate);
435 SipeCandidateType
436 sipe_backend_candidate_get_type(struct sipe_backend_candidate *candidate)
438 PurpleMediaCandidateType type =
439 purple_media_candidate_get_candidate_type((PurpleMediaCandidate*)candidate);
440 return purple_candidate_type_to_sipe(type);
443 SipeNetworkProtocol
444 sipe_backend_candidate_get_protocol(struct sipe_backend_candidate *candidate)
446 PurpleMediaNetworkProtocol proto =
447 purple_media_candidate_get_protocol((PurpleMediaCandidate*)candidate);
448 return purple_network_protocol_to_sipe(proto);
451 void
452 sipe_backend_candidate_set_username_and_pwd(struct sipe_backend_candidate *candidate,
453 const gchar *username,
454 const gchar *password)
456 g_object_set(candidate, "username", username, "password", password, NULL);
459 static void
460 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key,
461 gpointer value,
462 gpointer user_data)
464 GList *entry = value;
465 GList **candidates = user_data;
467 g_object_unref(entry->data);
468 *candidates = g_list_delete_link(*candidates, entry);
471 static GList *
472 ensure_candidate_pairs(GList *candidates)
474 GHashTable *lone_cand_links;
475 GList *i;
477 lone_cand_links = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
479 for (i = candidates; i; i = i->next) {
480 PurpleMediaCandidate *c = i->data;
481 gchar *foundation = purple_media_candidate_get_foundation(c);
483 if (g_hash_table_lookup(lone_cand_links, foundation)) {
484 g_hash_table_remove(lone_cand_links, foundation);
485 g_free(foundation);
486 } else {
487 g_hash_table_insert(lone_cand_links, foundation, i);
491 g_hash_table_foreach(lone_cand_links, remove_lone_candidate_cb, &candidates);
492 g_hash_table_destroy(lone_cand_links);
494 return candidates;
497 GList *
498 sipe_backend_get_local_candidates(struct sipe_backend_media *media,
499 struct sipe_backend_stream *stream)
501 GList *candidates = purple_media_get_local_candidates(media->m,
502 stream->sessionid,
503 stream->participant);
505 * Sometimes purple will not return complete list of candidates, even
506 * after "candidates-prepared" signal is emitted. This is a feature of
507 * libnice, namely affecting candidates discovered via UPnP. Nice does
508 * not wait until discovery is finished and can signal end of candidate
509 * gathering before all responses from UPnP enabled gateways are received.
511 * Remove any incomplete RTP+RTCP candidate pairs from the list.
513 candidates = ensure_candidate_pairs(candidates);
514 return candidates;
517 void
518 sipe_backend_media_hold(struct sipe_backend_media *media, gboolean local)
520 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HOLD,
521 NULL, NULL, local);
524 void
525 sipe_backend_media_unhold(struct sipe_backend_media *media, gboolean local)
527 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_UNHOLD,
528 NULL, NULL, local);
531 void
532 sipe_backend_media_hangup(struct sipe_backend_media *media, gboolean local)
534 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HANGUP,
535 NULL, NULL, local);
538 void
539 sipe_backend_media_reject(struct sipe_backend_media *media, gboolean local)
541 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_REJECT,
542 NULL, NULL, local);
545 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type)
547 switch (type) {
548 case SIPE_MEDIA_AUDIO: return PURPLE_MEDIA_AUDIO;
549 case SIPE_MEDIA_VIDEO: return PURPLE_MEDIA_VIDEO;
550 default: return PURPLE_MEDIA_NONE;
554 /*SipeMediaType purple_media_to_sipe(PurpleMediaSessionType type)
556 switch (type) {
557 case PURPLE_MEDIA_AUDIO: return SIPE_MEDIA_AUDIO;
558 case PURPLE_MEDIA_VIDEO: return SIPE_MEDIA_VIDEO;
559 default: return SIPE_MEDIA_AUDIO;
563 static PurpleMediaCandidateType
564 sipe_candidate_type_to_purple(SipeCandidateType type)
566 switch (type) {
567 case SIPE_CANDIDATE_TYPE_HOST: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
568 case SIPE_CANDIDATE_TYPE_RELAY: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
569 case SIPE_CANDIDATE_TYPE_SRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
570 case SIPE_CANDIDATE_TYPE_PRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
571 default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
575 static SipeCandidateType
576 purple_candidate_type_to_sipe(PurpleMediaCandidateType type)
578 switch (type) {
579 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST: return SIPE_CANDIDATE_TYPE_HOST;
580 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY: return SIPE_CANDIDATE_TYPE_RELAY;
581 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX: return SIPE_CANDIDATE_TYPE_SRFLX;
582 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX: return SIPE_CANDIDATE_TYPE_PRFLX;
583 default: return SIPE_CANDIDATE_TYPE_HOST;
587 static PurpleMediaNetworkProtocol
588 sipe_network_protocol_to_purple(SipeNetworkProtocol proto)
590 switch (proto) {
591 case SIPE_NETWORK_PROTOCOL_TCP: return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
592 case SIPE_NETWORK_PROTOCOL_UDP: return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
593 default: return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
597 static SipeNetworkProtocol
598 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto)
600 switch (proto) {
601 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP: return SIPE_NETWORK_PROTOCOL_TCP;
602 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP: return SIPE_NETWORK_PROTOCOL_UDP;
603 default: return SIPE_NETWORK_PROTOCOL_UDP;
608 Local Variables:
609 mode: c
610 c-file-style: "bsd"
611 indent-tabs-mode: t
612 tab-width: 8
613 End: