audio: depurple candidate
[siplcs.git] / src / core / sipe-media.c
blobc3b781b2093fdef833e36ea58451f8251abd5914
1 /**
2 * @file sipe-media.c
4 * pidgin-sipe
6 * Copyright (C) 2010 Jakub Adam <jakub.adam@tieto.com>
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 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <glib.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <libpurple/mediamanager.h>
31 #include <nice/agent.h>
33 #include "sipe-core.h"
34 #include "sipe.h"
35 #include "sipmsg.h"
36 #include "sipe-session.h"
37 #include "sipe-media.h"
38 #include "sipe-dialog.h"
39 #include "sipe-utils.h"
40 #include "sipe-common.h"
42 gchar *
43 sipe_media_get_callid(sipe_media_call *call)
45 return call->dialog->callid;
48 void sipe_media_codec_list_free(GList *codecs)
50 for (; codecs; codecs = g_list_delete_link(codecs, codecs)) {
51 sipe_backend_codec_free(codecs->data);
55 void sipe_media_candidate_list_free(GList *candidates)
57 for (; candidates; candidates = g_list_delete_link(candidates, candidates)) {
58 sipe_backend_candidate_free(candidates->data);
62 static void
63 sipe_media_call_free(sipe_media_call *call)
65 if (call) {
66 sipe_utils_nameval_free(call->sdp_attrs);
67 if (call->invitation)
68 sipmsg_free(call->invitation);
69 sipe_media_codec_list_free(call->remote_codecs);
70 sipe_media_candidate_list_free(call->remote_candidates);
71 g_free(call);
75 static GList *
76 sipe_media_parse_remote_codecs(const sipe_media_call *call)
78 int i = 0;
79 const gchar *attr;
80 GList *codecs = NULL;
82 while ((attr = sipe_utils_nameval_find_instance(call->sdp_attrs, "rtpmap", i++))) {
83 gchar **tokens = g_strsplit_set(attr, " /", 3);
85 int id = atoi(tokens[0]);
86 gchar *name = tokens[1];
87 int clock_rate = atoi(tokens[2]);
88 SipeMediaType type = SIPE_MEDIA_AUDIO;
90 sipe_codec *codec = sipe_backend_codec_new(id, name, clock_rate, type);
92 codecs = g_list_append(codecs, codec);
93 g_strfreev(tokens);
96 return codecs;
99 static gint
100 codec_name_compare(sipe_codec* codec1, sipe_codec* codec2)
102 gchar *name1 = sipe_backend_codec_get_name(codec1);
103 gchar *name2 = sipe_backend_codec_get_name(codec2);
105 return g_strcmp0(name1, name2);
108 static GList *
109 sipe_media_prune_remote_codecs(sipe_media_call *call, GList *codecs)
111 GList *remote_codecs = codecs;
112 GList *local_codecs = sipe_backend_get_local_codecs(call);
113 GList *pruned_codecs = NULL;
115 while (remote_codecs) {
116 sipe_codec *c = remote_codecs->data;
118 if (g_list_find_custom(local_codecs, c, (GCompareFunc)codec_name_compare)) {
119 pruned_codecs = g_list_append(pruned_codecs, c);
120 remote_codecs->data = NULL;
122 remote_codecs = remote_codecs->next;
125 sipe_media_codec_list_free(codecs);
127 return pruned_codecs;
130 static GList *
131 sipe_media_parse_remote_candidates(sipe_media_call *call)
133 GSList *sdp_attrs = call->sdp_attrs;
134 sipe_candidate *candidate;
135 GList *candidates = NULL;
136 const gchar *attr;
137 int i = 0;
139 const gchar* username = sipe_utils_nameval_find(sdp_attrs, "ice-ufrag");
140 const gchar* password = sipe_utils_nameval_find(sdp_attrs, "ice-pwd");
142 while ((attr = sipe_utils_nameval_find_instance(sdp_attrs, "candidate", i++))) {
143 gchar **tokens;
144 gchar *foundation;
145 SipeComponentType component;
146 SipeNetworkProtocol protocol;
147 guint32 priority;
148 gchar* ip;
149 guint16 port;
150 SipeCandidateType type;
152 tokens = g_strsplit_set(attr, " ", 0);
154 foundation = tokens[0];
156 switch (atoi(tokens[1])) {
157 case 1:
158 component = SIPE_COMPONENT_RTP;
159 break;
160 case 2:
161 component = SIPE_COMPONENT_RTCP;
162 break;
163 default:
164 component = SIPE_COMPONENT_NONE;
167 if (sipe_strequal(tokens[2], "UDP"))
168 protocol = SIPE_NETWORK_PROTOCOL_UDP;
169 else {
170 // Ignore TCP candidates, at least for now...
171 g_strfreev(tokens);
172 continue;
175 priority = atoi(tokens[3]);
176 ip = tokens[4];
177 port = atoi(tokens[5]);
179 if (sipe_strequal(tokens[7], "host"))
180 type = SIPE_CANDIDATE_TYPE_HOST;
181 else if (sipe_strequal(tokens[7], "relay"))
182 type = SIPE_CANDIDATE_TYPE_RELAY;
183 else if (sipe_strequal(tokens[7], "srflx"))
184 type = SIPE_CANDIDATE_TYPE_SRFLX;
185 else {
186 g_strfreev(tokens);
187 continue;
190 candidate = sipe_backend_candidate_new(foundation, component,
191 type, protocol, ip, port);
192 g_object_set(candidate, "priority", priority, NULL);
193 candidates = g_list_append(candidates, candidate);
195 g_strfreev(tokens);
198 if (!candidates) {
199 // No a=candidate in SDP message, revert to OC2005 behaviour
200 candidate = sipe_backend_candidate_new("foundation",
201 SIPE_COMPONENT_RTP,
202 SIPE_CANDIDATE_TYPE_HOST,
203 SIPE_NETWORK_PROTOCOL_UDP,
204 call->remote_ip, call->remote_port);
205 candidates = g_list_append(candidates, candidate);
207 candidate = sipe_backend_candidate_new("foundation",
208 SIPE_COMPONENT_RTCP,
209 SIPE_CANDIDATE_TYPE_HOST,
210 SIPE_NETWORK_PROTOCOL_UDP,
211 call->remote_ip, call->remote_port + 1);
212 candidates = g_list_append(candidates, candidate);
214 // This seems to be pre-OC2007 R2 UAC
215 call->legacy_mode = TRUE;
218 if (username) {
219 GList *it = candidates;
220 while (it) {
221 sipe_backend_candidate_set_username_and_pwd(it->data, username, password);
222 it = it->next;
226 return candidates;
229 static gchar *
230 sipe_media_sdp_codec_ids_format(GList *codecs)
232 GString *result = g_string_new(NULL);
234 while (codecs) {
235 sipe_codec *c = codecs->data;
237 gchar *tmp = g_strdup_printf(" %d", sipe_backend_codec_get_id(c));
238 g_string_append(result,tmp);
239 g_free(tmp);
241 codecs = codecs->next;
244 return g_string_free(result, FALSE);
247 static gchar *
248 sipe_media_sdp_codecs_format(GList *codecs)
250 GString *result = g_string_new(NULL);
252 while (codecs) {
253 sipe_codec *c = codecs->data;
254 GList *params = NULL;
256 gchar *tmp = g_strdup_printf("a=rtpmap:%d %s/%d\r\n",
257 sipe_backend_codec_get_id(c),
258 sipe_backend_codec_get_name(c),
259 sipe_backend_codec_get_clock_rate(c));
261 g_string_append(result, tmp);
262 g_free(tmp);
264 if ((params = sipe_backend_codec_get_optional_parameters(c))) {
265 tmp = g_strdup_printf("a=fmtp:%d",sipe_backend_codec_get_id(c));
266 g_string_append(result, tmp);
267 g_free(tmp);
269 while (params) {
270 struct sipnameval* par = params->data;
271 tmp = g_strdup_printf(" %s=%s", par->name, par->value);
272 g_string_append(result, tmp);
273 g_free(tmp);
274 params = params->next;
276 g_string_append(result, "\r\n");
279 codecs = codecs->next;
282 return g_string_free(result, FALSE);
285 static gchar *
286 sipe_media_sdp_candidates_format(GList *candidates, sipe_media_call* call, gboolean remote_candidate)
288 GString *result = g_string_new("");
289 gchar *tmp;
290 gchar *username = sipe_backend_candidate_get_username(candidates->data);
291 gchar *password = sipe_backend_candidate_get_password(candidates->data);
292 guint16 rtcp_port = 0;
294 if (call->legacy_mode)
295 return g_string_free(result, FALSE);
297 tmp = g_strdup_printf("a=ice-ufrag:%s\r\na=ice-pwd:%s\r\n",username, password);
298 g_string_append(result, tmp);
299 g_free(tmp);
301 while (candidates) {
302 sipe_candidate *c = candidates->data;
304 guint16 port;
305 guint16 component;
306 gchar *protocol;
307 gchar *type;
309 port = sipe_backend_candidate_get_port(c);
311 switch (sipe_backend_candidate_get_component_type(c)) {
312 case SIPE_COMPONENT_RTP:
313 component = 1;
314 break;
315 case SIPE_COMPONENT_RTCP:
316 component = 2;
317 if (rtcp_port == 0)
318 rtcp_port = port;
319 break;
320 case SIPE_COMPONENT_NONE:
321 component = 0;
324 switch (sipe_backend_candidate_get_protocol(c)) {
325 case SIPE_NETWORK_PROTOCOL_TCP:
326 protocol = "TCP";
327 break;
328 case SIPE_NETWORK_PROTOCOL_UDP:
329 protocol = "UDP";
330 break;
333 switch (sipe_backend_candidate_get_type(c)) {
334 case SIPE_CANDIDATE_TYPE_HOST:
335 type = "host";
336 break;
337 case SIPE_CANDIDATE_TYPE_RELAY:
338 type = "relay";
339 break;
340 case SIPE_CANDIDATE_TYPE_SRFLX:
341 type = "srflx";
342 break;
343 default:
344 // TODO: error unknown/unsupported type
345 break;
348 tmp = g_strdup_printf("a=candidate:%s %u %s %u %s %d typ %s \r\n",
349 sipe_backend_candidate_get_foundation(c),
350 component,
351 protocol,
352 sipe_backend_candidate_get_priority(c),
353 sipe_backend_candidate_get_ip(c),
354 port,
355 type);
357 g_string_append(result, tmp);
358 g_free(tmp);
360 candidates = candidates->next;
363 if (remote_candidate) {
364 PurpleMediaCandidate *first = call->remote_candidates->data;
365 PurpleMediaCandidate *second = call->remote_candidates->next->data;
366 tmp = g_strdup_printf("a=remote-candidates:1 %s %u 2 %s %u\r\n",
367 purple_media_candidate_get_ip(first), purple_media_candidate_get_port(first),
368 purple_media_candidate_get_ip(second), purple_media_candidate_get_port(second));
370 g_string_append(result, tmp);
371 g_free(tmp);
375 if (rtcp_port != 0) {
376 tmp = g_strdup_printf("a=maxptime:200\r\na=rtcp:%u\r\n", rtcp_port);
377 g_string_append(result, tmp);
378 g_free(tmp);
381 return g_string_free(result, FALSE);
384 static gchar*
385 sipe_media_create_sdp(sipe_media_call *call, gboolean remote_candidate) {
386 PurpleMedia *media = call->media;
387 GList *local_codecs = purple_media_get_codecs(media, "sipe-voice");
388 GList *local_candidates = purple_media_get_local_candidates(media, "sipe-voice", call->dialog->with);
390 // TODO: more sophisticated
391 guint16 local_port = purple_media_candidate_get_port(local_candidates->data);
392 const char *ip = sipe_utils_get_suitable_local_ip(-1);
394 gchar *sdp_codecs = sipe_media_sdp_codecs_format(local_codecs);
395 gchar *sdp_codec_ids = sipe_media_sdp_codec_ids_format(local_codecs);
396 gchar *sdp_candidates = sipe_media_sdp_candidates_format(local_candidates, call, remote_candidate);
397 gchar *inactive = call->state == SIPE_CALL_HELD ? "a=inactive\r\n" : "";
399 gchar *body = g_strdup_printf(
400 "v=0\r\n"
401 "o=- 0 0 IN IP4 %s\r\n"
402 "s=session\r\n"
403 "c=IN IP4 %s\r\n"
404 "b=CT:99980\r\n"
405 "t=0 0\r\n"
406 "m=audio %d RTP/AVP%s\r\n"
407 "%s"
408 "%s"
409 "%s"
410 "a=encryption:rejected\r\n"
411 ,ip, ip, local_port, sdp_codec_ids, sdp_candidates, inactive, sdp_codecs);
413 g_free(sdp_codecs);
414 g_free(sdp_codec_ids);
415 g_free(sdp_candidates);
417 return body;
420 static void
421 sipe_media_session_ready_cb(sipe_media_call *call)
423 PurpleMedia *media = call->media;
424 PurpleAccount *account = purple_media_get_account(media);
426 if (!purple_media_candidates_prepared(media, NULL, NULL))
427 return;
429 if (!call->sdp_response)
430 call->sdp_response = sipe_media_create_sdp(call, FALSE);
432 if (!purple_media_accepted(media, NULL, NULL)) {
433 if (!call->legacy_mode)
434 send_sip_response(account->gc, call->invitation, 183, "Session Progress", call->sdp_response);
435 } else {
436 send_sip_response(account->gc, call->invitation, 200, "OK", call->sdp_response);
437 call->state = SIPE_CALL_RUNNING;
441 static void
442 sipe_invite_call(struct sipe_account_data *sip)
444 gchar *hdr;
445 gchar *contact;
446 gchar *body;
447 sipe_media_call *call = sip->media_call;
448 struct sip_dialog *dialog = call->dialog;
450 contact = get_contact(sip);
451 hdr = g_strdup_printf(
452 "Supported: ms-sender\r\n"
453 "ms-keep-alive: UAC;hop-hop=yes\r\n"
454 "Contact: %s%s\r\n"
455 "Supported: Replaces\r\n"
456 "Content-Type: application/sdp\r\n",
457 contact,
458 call->state == SIPE_CALL_HELD ? ";+sip.rendering=\"no\"" : "");
459 g_free(contact);
461 body = sipe_media_create_sdp(call, TRUE);
463 send_sip_request(sip->gc, "INVITE", dialog->with, dialog->with, hdr, body,
464 dialog, NULL);
466 g_free(body);
467 g_free(hdr);
470 static void
471 notify_state_change(struct sipe_account_data *sip, gboolean local) {
472 if (local) {
473 sipe_invite_call(sip);
474 } else {
475 gchar* body = sipe_media_create_sdp(sip->media_call, TRUE);
476 send_sip_response(sip->gc, sip->media_call->invitation, 200, "OK", body);
477 g_free(body);
481 static void
482 sipe_media_stream_info_cb(PurpleMedia *media,
483 PurpleMediaInfoType type,
484 SIPE_UNUSED_PARAMETER gchar *sid,
485 SIPE_UNUSED_PARAMETER gchar *name,
486 gboolean local, struct sipe_account_data *sip)
488 sipe_media_call *call = sip->media_call;
490 if (type == PURPLE_MEDIA_INFO_ACCEPT)
491 sipe_media_session_ready_cb(call);
492 else if (type == PURPLE_MEDIA_INFO_REJECT) {
493 PurpleAccount *account = purple_media_get_account(media);
494 send_sip_response(account->gc, call->invitation, 603, "Decline", NULL);
495 sipe_media_call_free(call);
496 sip->media_call = NULL;
497 } else if (type == PURPLE_MEDIA_INFO_HOLD) {
498 if (call->state == SIPE_CALL_HELD)
499 return;
501 call->state = SIPE_CALL_HELD;
502 notify_state_change(sip, local);
503 purple_media_stream_info(media, PURPLE_MEDIA_INFO_HOLD, NULL, NULL, TRUE);
505 } else if (type == PURPLE_MEDIA_INFO_UNHOLD) {
506 if (call->state == SIPE_CALL_RUNNING)
507 return;
509 call->state = SIPE_CALL_RUNNING;
510 notify_state_change(sip, local);
512 purple_media_stream_info(media, PURPLE_MEDIA_INFO_UNHOLD, NULL, NULL, TRUE);
513 } else if (type == PURPLE_MEDIA_INFO_HANGUP) {
514 call->state = SIPE_CALL_FINISHED;
515 if (local)
516 send_sip_request(sip->gc, "BYE", call->dialog->with, call->dialog->with,
517 NULL, NULL, call->dialog, NULL);
518 sipe_media_call_free(call);
519 sip->media_call = NULL;
523 static gboolean
524 sipe_media_parse_sdp_frame(sipe_media_call* call, gchar *frame) {
525 gchar **lines = g_strsplit(frame, "\r\n", 0);
526 GSList *sdp_attrs = NULL;
527 gchar *remote_ip = NULL;
528 guint16 remote_port = 0;
529 gchar **ptr;
530 gboolean no_error = TRUE;
532 for (ptr = lines; *ptr != NULL; ++ptr) {
533 if (g_str_has_prefix(*ptr, "a=")) {
534 gchar **parts = g_strsplit(*ptr + 2, ":", 2);
535 if(!parts[0]) {
536 g_strfreev(parts);
537 sipe_utils_nameval_free(sdp_attrs);
538 sdp_attrs = NULL;
539 no_error = FALSE;
540 break;
542 sdp_attrs = sipe_utils_nameval_add(sdp_attrs, parts[0], parts[1]);
543 g_strfreev(parts);
545 } else if (g_str_has_prefix(*ptr, "o=")) {
546 gchar **parts = g_strsplit(*ptr + 2, " ", 6);
547 remote_ip = g_strdup(parts[5]);
548 g_strfreev(parts);
549 } else if (g_str_has_prefix(*ptr, "m=")) {
550 gchar **parts = g_strsplit(*ptr + 2, " ", 3);
551 remote_port = atoi(parts[1]);
552 g_strfreev(parts);
556 g_strfreev(lines);
558 if (no_error) {
559 sipe_utils_nameval_free(call->sdp_attrs);
560 call->sdp_attrs = sdp_attrs;
561 call->remote_ip = remote_ip;
562 call->remote_port = remote_port;
565 return no_error;
568 static struct sip_dialog *
569 sipe_media_dialog_init(struct sip_session* session, struct sipmsg *msg)
571 gchar *newTag = gentag();
572 const gchar *oldHeader;
573 gchar *newHeader;
574 struct sip_dialog *dialog;
576 oldHeader = sipmsg_find_header(msg, "To");
577 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
578 sipmsg_remove_header_now(msg, "To");
579 sipmsg_add_header_now(msg, "To", newHeader);
580 g_free(newHeader);
582 dialog = sipe_dialog_add(session);
583 dialog->callid = g_strdup(session->callid);
584 dialog->with = parse_from(sipmsg_find_header(msg, "From"));
585 sipe_dialog_parse(dialog, msg, FALSE);
587 return dialog;
590 static sipe_media_call *
591 sipe_media_call_init(struct sipmsg *msg)
593 sipe_media_call *call;
595 call = g_new0(sipe_media_call, 1);
597 if (sipe_media_parse_sdp_frame(call, msg->body) == FALSE) {
598 g_free(call);
599 return NULL;
602 call->invitation = msg;
603 call->legacy_mode = FALSE;
604 call->state = SIPE_CALL_CONNECTING;
605 call->remote_candidates = sipe_media_parse_remote_candidates(call);
606 return call;
609 void sipe_media_hold(struct sipe_account_data *sip) {
610 if (sip->media_call) {
611 purple_media_stream_info(sip->media_call->media, PURPLE_MEDIA_INFO_HOLD,
612 NULL, NULL, FALSE);
616 void sipe_media_unhold(struct sipe_account_data *sip) {
617 if (sip->media_call) {
618 purple_media_stream_info(sip->media_call->media, PURPLE_MEDIA_INFO_UNHOLD,
619 NULL, NULL, FALSE);
623 void sipe_media_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
625 PurpleMediaManager *manager = purple_media_manager_get();
626 PurpleMedia *media;
628 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
630 sipe_media_call *call;
631 struct sip_session *session;
632 struct sip_dialog *dialog;
634 GParameter *params;
636 if (sip->media_call) {
637 if (sipe_strequal(sip->media_call->dialog->callid, callid)) {
638 gchar *rsp;
640 call = sip->media_call;
642 sipmsg_free(call->invitation);
643 msg->dont_free = TRUE;
644 call->invitation = msg;
646 sipmsg_add_header(msg, "Supported", "Replaces");
648 sipe_utils_nameval_free(call->sdp_attrs);
649 call->sdp_attrs = NULL;
650 if (!sipe_media_parse_sdp_frame(call, msg->body)) {
651 // TODO: handle error
654 if (call->legacy_mode && call->state == SIPE_CALL_RUNNING) {
655 sipe_media_hold(sip);
656 return;
659 if (sipe_utils_nameval_find(call->sdp_attrs, "inactive")) {
660 sipe_media_hold(sip);
661 return;
664 if (call->state == SIPE_CALL_HELD) {
665 sipe_media_unhold(sip);
666 return;
669 call->remote_codecs = sipe_media_parse_remote_codecs(call);
670 call->remote_codecs = sipe_media_prune_remote_codecs(call->media, call->remote_codecs);
671 if (!call->remote_codecs) {
672 // TODO: error no remote codecs
674 if (sipe_backend_set_remote_codecs(call, call->dialog->with) == FALSE)
675 printf("ERROR SET REMOTE CODECS"); // TODO
677 rsp = sipe_media_create_sdp(sip->media_call, TRUE);
678 send_sip_response(sip->gc, msg, 200, "OK", rsp);
679 g_free(rsp);
680 } else {
681 // TODO: send Busy Here
682 printf("MEDIA SESSION ALREADY IN PROGRESS");
684 return;
687 call = sipe_media_call_init(msg);
689 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
690 dialog = sipe_media_dialog_init(session, msg);
692 media = purple_media_manager_create_media(manager, sip->account,
693 "fsrtpconference", dialog->with, FALSE);
695 g_signal_connect(G_OBJECT(media), "stream-info",
696 G_CALLBACK(sipe_media_stream_info_cb), sip);
697 g_signal_connect_swapped(G_OBJECT(media), "candidates-prepared",
698 G_CALLBACK(sipe_media_session_ready_cb), call);
701 call->session = session;
702 call->dialog = dialog;
703 call->media = media;
705 if (call->legacy_mode) {
706 purple_media_add_stream(media, "sipe-voice", dialog->with,
707 PURPLE_MEDIA_AUDIO, FALSE, "rawudp", 0, NULL);
708 } else {
709 params = g_new0(GParameter, 2);
710 params[0].name = "controlling-mode";
711 g_value_init(&params[0].value, G_TYPE_BOOLEAN);
712 g_value_set_boolean(&params[0].value, FALSE);
713 params[1].name = "compatibility-mode";
714 g_value_init(&params[1].value, G_TYPE_UINT);
715 g_value_set_uint(&params[1].value, NICE_COMPATIBILITY_OC2007R2);
718 purple_media_add_stream(media, "sipe-voice", dialog->with,
719 PURPLE_MEDIA_AUDIO, FALSE, "nice", 2, params);
722 purple_media_add_remote_candidates(media, "sipe-voice", dialog->with,
723 call->remote_candidates);
725 call->remote_codecs = sipe_media_parse_remote_codecs(call);
726 call->remote_codecs = sipe_media_prune_remote_codecs(call, call->remote_codecs);
727 if (!call->remote_candidates || !call->remote_codecs) {
728 sipe_media_call_free(call);
729 sip->media_call = NULL;
730 printf("ERROR NO CANDIDATES OR CODECS");
731 return;
733 if (sipe_backend_set_remote_codecs(call, dialog->with) == FALSE)
734 printf("ERROR SET REMOTE CODECS"); // TODO
736 sip->media_call = call;
738 // TODO: copy message instead of this don't free thing
739 msg->dont_free = TRUE;
740 send_sip_response(sip->gc, msg, 180, "Ringing", NULL);
743 void sipe_media_hangup(struct sipe_account_data *sip)
745 if (sip->media_call) {
746 purple_media_stream_info(sip->media_call->media, PURPLE_MEDIA_INFO_HANGUP,
747 NULL, NULL, FALSE);
752 Local Variables:
753 mode: c
754 c-file-style: "bsd"
755 indent-tabs-mode: t
756 tab-width: 8
757 End: