]> git.0d.be Git - empathy.git/blob - src/empathy-call-handler.c
Updated Belarusian translation.
[empathy.git] / src / empathy-call-handler.c
1 /*
2  * empathy-call-handler.c - Source for EmpathyCallHandler
3  * Copyright (C) 2008-2009 Collabora Ltd.
4  * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 #include <telepathy-glib/telepathy-glib.h>
27
28 #include <telepathy-farstream/telepathy-farstream.h>
29
30 #include <libempathy/empathy-utils.h>
31
32 #include <libempathy-gtk/empathy-call-utils.h>
33
34 #include "empathy-call-handler.h"
35
36 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
37 #include <libempathy/empathy-debug.h>
38
39 G_DEFINE_TYPE(EmpathyCallHandler, empathy_call_handler, G_TYPE_OBJECT)
40
41 /* signal enum */
42 enum {
43   CONFERENCE_ADDED,
44   CONFERENCE_REMOVED,
45   SRC_PAD_ADDED,
46   CONTENT_ADDED,
47   CONTENT_REMOVED,
48   CLOSED,
49   CANDIDATES_CHANGED,
50   STATE_CHANGED,
51   FRAMERATE_CHANGED,
52   RESOLUTION_CHANGED,
53   LAST_SIGNAL
54 };
55
56 static guint signals[LAST_SIGNAL] = {0};
57
58 enum {
59   PROP_CALL_CHANNEL = 1,
60   PROP_GST_BUS,
61   PROP_CONTACT,
62   PROP_INITIAL_AUDIO,
63   PROP_INITIAL_VIDEO,
64   PROP_SEND_AUDIO_CODEC,
65   PROP_SEND_VIDEO_CODEC,
66   PROP_RECV_AUDIO_CODECS,
67   PROP_RECV_VIDEO_CODECS,
68   PROP_AUDIO_REMOTE_CANDIDATE,
69   PROP_VIDEO_REMOTE_CANDIDATE,
70   PROP_AUDIO_LOCAL_CANDIDATE,
71   PROP_VIDEO_LOCAL_CANDIDATE,
72 };
73
74 /* private structure */
75
76 struct _EmpathyCallHandlerPriv {
77   TpCallChannel *call;
78
79   EmpathyContact *contact;
80   TfChannel *tfchannel;
81   gboolean initial_audio;
82   gboolean initial_video;
83
84   FsCodec *send_audio_codec;
85   FsCodec *send_video_codec;
86   GList *recv_audio_codecs;
87   GList *recv_video_codecs;
88   FsCandidate *audio_remote_candidate;
89   FsCandidate *video_remote_candidate;
90   FsCandidate *audio_local_candidate;
91   FsCandidate *video_local_candidate;
92   gboolean accept_when_initialised;
93 };
94
95 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyCallHandler)
96
97 static void
98 empathy_call_handler_dispose (GObject *object)
99 {
100   EmpathyCallHandlerPriv *priv = GET_PRIV (object);
101
102   tp_clear_object (&priv->tfchannel);
103   tp_clear_object (&priv->call);
104   tp_clear_object (&priv->contact);
105
106   G_OBJECT_CLASS (empathy_call_handler_parent_class)->dispose (object);
107 }
108
109 static void
110 empathy_call_handler_finalize (GObject *object)
111 {
112   EmpathyCallHandlerPriv *priv = GET_PRIV (object);
113
114   fs_codec_destroy (priv->send_audio_codec);
115   fs_codec_destroy (priv->send_video_codec);
116   fs_codec_list_destroy (priv->recv_audio_codecs);
117   fs_codec_list_destroy (priv->recv_video_codecs);
118   fs_candidate_destroy (priv->audio_remote_candidate);
119   fs_candidate_destroy (priv->video_remote_candidate);
120   fs_candidate_destroy (priv->audio_local_candidate);
121   fs_candidate_destroy (priv->video_local_candidate);
122
123   G_OBJECT_CLASS (empathy_call_handler_parent_class)->finalize (object);
124 }
125
126 static void
127 empathy_call_handler_init (EmpathyCallHandler *obj)
128 {
129   EmpathyCallHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj,
130     EMPATHY_TYPE_CALL_HANDLER, EmpathyCallHandlerPriv);
131
132   obj->priv = priv;
133 }
134
135 static void
136 on_call_accepted_cb (GObject *source_object,
137     GAsyncResult *res,
138     gpointer user_data)
139 {
140   TpCallChannel *call = TP_CALL_CHANNEL (source_object);
141   GError *error = NULL;
142
143   if (!tp_call_channel_accept_finish (call, res, &error))
144     {
145       g_warning ("could not accept Call: %s", error->message);
146       g_error_free (error);
147     }
148 }
149
150 static void
151 on_call_invalidated_cb (TpCallChannel *call,
152     guint domain,
153     gint code,
154     gchar *message,
155     EmpathyCallHandler *self)
156 {
157   EmpathyCallHandlerPriv *priv = self->priv;
158
159   if (priv->call == call)
160     {
161       /* Invalidated unexpectedly? Fake call ending */
162       g_signal_emit (self, signals[STATE_CHANGED], 0,
163           TP_CALL_STATE_ENDED, NULL);
164       priv->accept_when_initialised = FALSE;
165       tp_clear_object (&priv->call);
166       tp_clear_object (&priv->tfchannel);
167     }
168 }
169
170 static void
171 on_call_state_changed_cb (TpCallChannel *call,
172   TpCallState state,
173   TpCallFlags flags,
174   TpCallStateReason *reason,
175   GHashTable *details,
176   EmpathyCallHandler *handler)
177 {
178   EmpathyCallHandlerPriv *priv = handler->priv;
179
180   /* Clean up the TfChannel before bubbling the state-change signal
181    * further up. This ensures that the conference-removed signal is
182    * emitted before state-changed so that the client gets a chance
183    * to remove the conference from the pipeline before resetting the
184    * pipeline itself.
185    */
186   if (state == TP_CALL_STATE_ENDED)
187     {
188       tp_channel_close_async (TP_CHANNEL (call), NULL, NULL);
189       priv->accept_when_initialised = FALSE;
190       tp_clear_object (&priv->call);
191       tp_clear_object (&priv->tfchannel);
192     }
193
194   g_signal_emit (handler, signals[STATE_CHANGED], 0, state,
195       reason->dbus_reason);
196
197   if (state == TP_CALL_STATE_INITIALISED &&
198       priv->accept_when_initialised)
199     {
200       tp_call_channel_accept_async (priv->call, on_call_accepted_cb, NULL);
201       priv->accept_when_initialised = FALSE;
202     }
203 }
204
205 static void
206 empathy_call_handler_set_property (GObject *object,
207   guint property_id, const GValue *value, GParamSpec *pspec)
208 {
209   EmpathyCallHandlerPriv *priv = GET_PRIV (object);
210
211   switch (property_id)
212     {
213       case PROP_CONTACT:
214         priv->contact = g_value_dup_object (value);
215         break;
216       case PROP_CALL_CHANNEL:
217         g_return_if_fail (priv->call == NULL);
218
219         priv->call = g_value_dup_object (value);
220
221         tp_g_signal_connect_object (priv->call, "state-changed",
222           G_CALLBACK (on_call_state_changed_cb), object, 0);
223         tp_g_signal_connect_object (priv->call, "invalidated",
224           G_CALLBACK (on_call_invalidated_cb), object, 0);
225         break;
226       case PROP_INITIAL_AUDIO:
227         priv->initial_audio = g_value_get_boolean (value);
228         break;
229       case PROP_INITIAL_VIDEO:
230         priv->initial_video = g_value_get_boolean (value);
231         break;
232       default:
233         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
234     }
235 }
236
237 static void
238 empathy_call_handler_get_property (GObject *object,
239   guint property_id, GValue *value, GParamSpec *pspec)
240 {
241   EmpathyCallHandlerPriv *priv = GET_PRIV (object);
242
243   switch (property_id)
244     {
245       case PROP_CONTACT:
246         g_value_set_object (value, priv->contact);
247         break;
248       case PROP_CALL_CHANNEL:
249         g_value_set_object (value, priv->call);
250         break;
251       case PROP_INITIAL_AUDIO:
252         g_value_set_boolean (value, priv->initial_audio);
253         break;
254       case PROP_INITIAL_VIDEO:
255         g_value_set_boolean (value, priv->initial_video);
256         break;
257       case PROP_SEND_AUDIO_CODEC:
258         g_value_set_boxed (value, priv->send_audio_codec);
259         break;
260       case PROP_SEND_VIDEO_CODEC:
261         g_value_set_boxed (value, priv->send_video_codec);
262         break;
263       case PROP_RECV_AUDIO_CODECS:
264         g_value_set_boxed (value, priv->recv_audio_codecs);
265         break;
266       case PROP_RECV_VIDEO_CODECS:
267         g_value_set_boxed (value, priv->recv_video_codecs);
268         break;
269       case PROP_AUDIO_REMOTE_CANDIDATE:
270         g_value_set_boxed (value, priv->audio_remote_candidate);
271         break;
272       case PROP_VIDEO_REMOTE_CANDIDATE:
273         g_value_set_boxed (value, priv->video_remote_candidate);
274         break;
275       case PROP_AUDIO_LOCAL_CANDIDATE:
276         g_value_set_boxed (value, priv->audio_local_candidate);
277         break;
278       case PROP_VIDEO_LOCAL_CANDIDATE:
279         g_value_set_boxed (value, priv->video_local_candidate);
280         break;
281       default:
282         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
283     }
284 }
285
286
287 static void
288 empathy_call_handler_class_init (EmpathyCallHandlerClass *klass)
289 {
290   GObjectClass *object_class = G_OBJECT_CLASS (klass);
291   GParamSpec *param_spec;
292
293   g_type_class_add_private (klass, sizeof (EmpathyCallHandlerPriv));
294
295   object_class->set_property = empathy_call_handler_set_property;
296   object_class->get_property = empathy_call_handler_get_property;
297   object_class->dispose = empathy_call_handler_dispose;
298   object_class->finalize = empathy_call_handler_finalize;
299
300   param_spec = g_param_spec_object ("target-contact",
301     "TargetContact", "The contact",
302     EMPATHY_TYPE_CONTACT,
303     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
304   g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
305
306   param_spec = g_param_spec_object ("call-channel",
307     "call channel", "The call channel",
308     TP_TYPE_CALL_CHANNEL,
309     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
310   g_object_class_install_property (object_class, PROP_CALL_CHANNEL, param_spec);
311
312   param_spec = g_param_spec_boolean ("initial-audio",
313     "initial-audio", "Whether the call should start with audio",
314     TRUE,
315     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
316   g_object_class_install_property (object_class, PROP_INITIAL_AUDIO,
317       param_spec);
318
319   param_spec = g_param_spec_boolean ("initial-video",
320     "initial-video", "Whether the call should start with video",
321     FALSE,
322     G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
323   g_object_class_install_property (object_class, PROP_INITIAL_VIDEO,
324     param_spec);
325
326   param_spec = g_param_spec_boxed ("send-audio-codec",
327     "send audio codec", "Codec used to encode the outgoing video stream",
328     FS_TYPE_CODEC,
329     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
330   g_object_class_install_property (object_class, PROP_SEND_AUDIO_CODEC,
331     param_spec);
332
333   param_spec = g_param_spec_boxed ("send-video-codec",
334     "send video codec", "Codec used to encode the outgoing video stream",
335     FS_TYPE_CODEC,
336     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
337   g_object_class_install_property (object_class, PROP_SEND_VIDEO_CODEC,
338     param_spec);
339
340   param_spec = g_param_spec_boxed ("recv-audio-codecs",
341     "recvs audio codec", "Codecs used to decode the incoming audio stream",
342     FS_TYPE_CODEC_LIST,
343     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
344   g_object_class_install_property (object_class, PROP_RECV_AUDIO_CODECS,
345     param_spec);
346
347   param_spec = g_param_spec_boxed ("recv-video-codecs",
348     "recvs video codec", "Codecs used to decode the incoming video stream",
349     FS_TYPE_CODEC_LIST,
350     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
351   g_object_class_install_property (object_class, PROP_RECV_VIDEO_CODECS,
352     param_spec);
353
354   param_spec = g_param_spec_boxed ("audio-remote-candidate",
355     "audio remote candidate",
356     "Remote candidate used for the audio stream",
357     FS_TYPE_CANDIDATE,
358     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
359   g_object_class_install_property (object_class,
360       PROP_AUDIO_REMOTE_CANDIDATE, param_spec);
361
362   param_spec = g_param_spec_boxed ("video-remote-candidate",
363     "video remote candidate",
364     "Remote candidate used for the video stream",
365     FS_TYPE_CANDIDATE,
366     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
367   g_object_class_install_property (object_class,
368       PROP_VIDEO_REMOTE_CANDIDATE, param_spec);
369
370   param_spec = g_param_spec_boxed ("audio-local-candidate",
371     "audio local candidate",
372     "Local candidate used for the audio stream",
373     FS_TYPE_CANDIDATE,
374     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
375   g_object_class_install_property (object_class,
376       PROP_AUDIO_REMOTE_CANDIDATE, param_spec);
377
378   param_spec = g_param_spec_boxed ("video-local-candidate",
379     "video local candidate",
380     "Local candidate used for the video stream",
381     FS_TYPE_CANDIDATE,
382     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
383   g_object_class_install_property (object_class,
384       PROP_VIDEO_REMOTE_CANDIDATE, param_spec);
385
386   signals[CONFERENCE_ADDED] =
387     g_signal_new ("conference-added", G_TYPE_FROM_CLASS (klass),
388       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
389       g_cclosure_marshal_generic,
390       G_TYPE_NONE,
391       1, FS_TYPE_CONFERENCE);
392
393   signals[CONFERENCE_REMOVED] =
394     g_signal_new ("conference-removed", G_TYPE_FROM_CLASS (klass),
395       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
396       g_cclosure_marshal_generic,
397       G_TYPE_NONE,
398       1, FS_TYPE_CONFERENCE);
399
400   signals[SRC_PAD_ADDED] =
401     g_signal_new ("src-pad-added", G_TYPE_FROM_CLASS (klass),
402       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
403       g_cclosure_marshal_generic,
404       G_TYPE_BOOLEAN,
405       2, TF_TYPE_CONTENT, GST_TYPE_PAD);
406
407   signals[CONTENT_ADDED] =
408     g_signal_new ("content-added", G_TYPE_FROM_CLASS (klass),
409       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
410       g_cclosure_marshal_generic,
411       G_TYPE_BOOLEAN,
412       1, TF_TYPE_CONTENT);
413
414   signals[CONTENT_REMOVED] =
415     g_signal_new ("content-removed", G_TYPE_FROM_CLASS (klass),
416       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
417       g_cclosure_marshal_generic,
418       G_TYPE_BOOLEAN,
419       1, TF_TYPE_CONTENT);
420
421   signals[CLOSED] =
422     g_signal_new ("closed", G_TYPE_FROM_CLASS (klass),
423       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
424       g_cclosure_marshal_generic,
425       G_TYPE_NONE,
426       0);
427
428   signals[CANDIDATES_CHANGED] =
429     g_signal_new ("candidates-changed", G_TYPE_FROM_CLASS (klass),
430       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
431       g_cclosure_marshal_generic,
432       G_TYPE_NONE, 1, G_TYPE_UINT);
433
434   signals[STATE_CHANGED] =
435     g_signal_new ("state-changed", G_TYPE_FROM_CLASS (klass),
436       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
437       g_cclosure_marshal_generic,
438       G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
439
440   signals[FRAMERATE_CHANGED] =
441     g_signal_new ("framerate-changed", G_TYPE_FROM_CLASS (klass),
442       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
443       g_cclosure_marshal_generic,
444       G_TYPE_NONE, 1, G_TYPE_UINT);
445
446   signals[RESOLUTION_CHANGED] =
447     g_signal_new ("resolution-changed", G_TYPE_FROM_CLASS (klass),
448       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
449       g_cclosure_marshal_generic,
450       G_TYPE_NONE,
451       2, G_TYPE_UINT, G_TYPE_UINT);
452 }
453
454 EmpathyCallHandler *
455 empathy_call_handler_new_for_channel (TpCallChannel *call,
456   EmpathyContact *contact)
457 {
458   return EMPATHY_CALL_HANDLER (g_object_new (EMPATHY_TYPE_CALL_HANDLER,
459     "call-channel", call,
460     "initial-video", tp_call_channel_has_initial_video (call, NULL),
461     "target-contact", contact,
462     NULL));
463 }
464
465 static void
466 update_sending_codec (EmpathyCallHandler *self,
467     FsCodec *codec,
468     FsSession *session)
469 {
470   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
471   FsMediaType type;
472
473   if (codec == NULL || session == NULL)
474     return;
475
476   g_object_get (session, "media-type", &type, NULL);
477
478   if (type == FS_MEDIA_TYPE_AUDIO)
479     {
480       priv->send_audio_codec = fs_codec_copy (codec);
481       g_object_notify (G_OBJECT (self), "send-audio-codec");
482     }
483   else if (type == FS_MEDIA_TYPE_VIDEO)
484     {
485       priv->send_video_codec = fs_codec_copy (codec);
486       g_object_notify (G_OBJECT (self), "send-video-codec");
487     }
488 }
489
490 static void
491 update_receiving_codec (EmpathyCallHandler *self,
492     GList *codecs,
493     FsStream *stream)
494 {
495   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
496   FsSession *session;
497   FsMediaType type;
498
499   if (codecs == NULL || stream == NULL)
500     return;
501
502   g_object_get (stream, "session", &session, NULL);
503   if (session == NULL)
504     return;
505
506   g_object_get (session, "media-type", &type, NULL);
507
508   if (type == FS_MEDIA_TYPE_AUDIO)
509     {
510       priv->recv_audio_codecs = fs_codec_list_copy (codecs);
511       g_object_notify (G_OBJECT (self), "recv-audio-codecs");
512     }
513   else if (type == FS_MEDIA_TYPE_VIDEO)
514     {
515       priv->recv_video_codecs = fs_codec_list_copy (codecs);
516       g_object_notify (G_OBJECT (self), "recv-video-codecs");
517     }
518
519   g_object_unref (session);
520 }
521
522 static void
523 update_candidates (EmpathyCallHandler *self,
524     FsCandidate *remote_candidate,
525     FsCandidate *local_candidate,
526     FsStream *stream)
527 {
528   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
529   FsSession *session;
530   FsMediaType type;
531
532   if (stream == NULL)
533     return;
534
535   g_object_get (stream, "session", &session, NULL);
536   if (session == NULL)
537     return;
538
539   g_object_get (session, "media-type", &type, NULL);
540
541   if (type == FS_MEDIA_TYPE_AUDIO)
542     {
543       if (remote_candidate != NULL)
544         {
545           fs_candidate_destroy (priv->audio_remote_candidate);
546           priv->audio_remote_candidate = fs_candidate_copy (remote_candidate);
547           g_object_notify (G_OBJECT (self), "audio-remote-candidate");
548         }
549
550       if (local_candidate != NULL)
551         {
552           fs_candidate_destroy (priv->audio_local_candidate);
553           priv->audio_local_candidate = fs_candidate_copy (local_candidate);
554           g_object_notify (G_OBJECT (self), "audio-local-candidate");
555         }
556
557       g_signal_emit (G_OBJECT (self), signals[CANDIDATES_CHANGED], 0,
558           FS_MEDIA_TYPE_AUDIO);
559     }
560   else if (type == FS_MEDIA_TYPE_VIDEO)
561     {
562       if (remote_candidate != NULL)
563         {
564           fs_candidate_destroy (priv->video_remote_candidate);
565           priv->video_remote_candidate = fs_candidate_copy (remote_candidate);
566           g_object_notify (G_OBJECT (self), "video-remote-candidate");
567         }
568
569       if (local_candidate != NULL)
570         {
571           fs_candidate_destroy (priv->video_local_candidate);
572           priv->video_local_candidate = fs_candidate_copy (local_candidate);
573           g_object_notify (G_OBJECT (self), "video-local-candidate");
574         }
575
576       g_signal_emit (G_OBJECT (self), signals[CANDIDATES_CHANGED], 0,
577           FS_MEDIA_TYPE_VIDEO);
578     }
579
580   g_object_unref (session);
581 }
582
583 void
584 empathy_call_handler_bus_message (EmpathyCallHandler *handler,
585   GstBus *bus, GstMessage *message)
586 {
587   EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
588   const GstStructure *s = gst_message_get_structure (message);
589
590   if (priv->tfchannel == NULL)
591     return;
592
593   if (s != NULL &&
594       gst_structure_has_name (s, "farsight-send-codec-changed"))
595     {
596       const GValue *val;
597       FsCodec *codec;
598       FsSession *session;
599
600       DEBUG ("farsight-send-codec-changed");
601
602       val = gst_structure_get_value (s, "codec");
603       codec = g_value_get_boxed (val);
604
605       val = gst_structure_get_value (s, "session");
606       session = g_value_get_object (val);
607
608       update_sending_codec (handler, codec, session);
609     }
610   else if (s != NULL &&
611       gst_structure_has_name (s, "farsight-recv-codecs-changed"))
612     {
613       const GValue *val;
614       GList *codecs;
615       FsStream *stream;
616
617       DEBUG ("farsight-recv-codecs-changed");
618
619       val = gst_structure_get_value (s, "codecs");
620       codecs = g_value_get_boxed (val);
621
622       val = gst_structure_get_value (s, "stream");
623       stream = g_value_get_object (val);
624
625       update_receiving_codec (handler, codecs, stream);
626     }
627   else if (s != NULL &&
628       gst_structure_has_name (s, "farsight-new-active-candidate-pair"))
629     {
630       const GValue *val;
631       FsCandidate *remote_candidate, *local_candidate;
632       FsStream *stream;
633
634       DEBUG ("farsight-new-active-candidate-pair");
635
636       val = gst_structure_get_value (s, "remote-candidate");
637       remote_candidate = g_value_get_boxed (val);
638
639       val = gst_structure_get_value (s, "local-candidate");
640       local_candidate = g_value_get_boxed (val);
641
642       val = gst_structure_get_value (s, "stream");
643       stream = g_value_get_object (val);
644
645       update_candidates (handler, remote_candidate, local_candidate, stream);
646     }
647
648   tf_channel_bus_message (priv->tfchannel, message);
649 }
650
651 static void
652 on_tf_channel_conference_added_cb (TfChannel *tfchannel,
653   GstElement *conference,
654   EmpathyCallHandler *self)
655 {
656   g_signal_emit (G_OBJECT (self), signals[CONFERENCE_ADDED], 0,
657     conference);
658 }
659
660 static void
661 on_tf_channel_conference_removed_cb (TfChannel *tfchannel,
662   FsConference *conference,
663   EmpathyCallHandler *self)
664 {
665   g_signal_emit (G_OBJECT (self), signals[CONFERENCE_REMOVED], 0,
666     GST_ELEMENT (conference));
667 }
668
669 static gboolean
670 src_pad_added_error_idle (gpointer data)
671 {
672   TfContent *content = data;
673
674   tf_content_error_literal (content, "Could not link sink");
675   g_object_unref (content);
676
677   return FALSE;
678 }
679
680 static void
681 on_tf_content_src_pad_added_cb (TfContent *content,
682   guint handle,
683   FsStream *stream,
684   GstPad *pad,
685   FsCodec *codec,
686   EmpathyCallHandler *handler)
687 {
688   gboolean retval;
689
690   g_signal_emit (G_OBJECT (handler), signals[SRC_PAD_ADDED], 0,
691       content, pad, &retval);
692
693   if (!retval)
694     g_idle_add (src_pad_added_error_idle, g_object_ref (content));
695 }
696
697 static void
698 on_tf_content_framerate_changed (TfContent *content,
699   GParamSpec *spec,
700   EmpathyCallHandler *handler)
701 {
702   guint framerate;
703
704   g_object_get (content, "framerate", &framerate, NULL);
705
706   if (framerate != 0)
707     g_signal_emit (G_OBJECT (handler), signals[FRAMERATE_CHANGED], 0,
708         framerate);
709 }
710
711 static void
712 on_tf_content_resolution_changed (TfContent *content,
713    guint width,
714    guint height,
715    EmpathyCallHandler *handler)
716 {
717   if (width > 0 && height > 0)
718     g_signal_emit (G_OBJECT (handler), signals[RESOLUTION_CHANGED], 0,
719         width, height);
720 }
721
722 static void
723 on_tf_channel_content_added_cb (TfChannel *tfchannel,
724   TfContent *content,
725   EmpathyCallHandler *handler)
726 {
727   FsMediaType mtype;
728   FsSession *session;
729 //  FsStream *fs_stream;
730   FsCodec *codec;
731 //  GList *codecs;
732   gboolean retval;
733
734   g_signal_connect (content, "src-pad-added",
735       G_CALLBACK (on_tf_content_src_pad_added_cb), handler);
736 #if 0
737   g_signal_connect (content, "start-sending",
738       G_CALLBACK (on_tf_content_start_sending_cb), handler);
739   g_signal_connect (content, "stop-sending",
740       G_CALLBACK (on_tf_content_stop_sending_cb), handler);
741 #endif
742
743   g_signal_emit (G_OBJECT (handler), signals[CONTENT_ADDED], 0,
744     content, &retval);
745
746  if (!retval)
747       tf_content_error_literal (content, "Could not link source");
748
749  /* Get sending codec */
750  g_object_get (content, "fs-session", &session, NULL);
751  g_object_get (session, "current-send-codec", &codec, NULL);
752
753  update_sending_codec (handler, codec, session);
754
755  tp_clear_object (&session);
756  tp_clear_object (&codec);
757
758  /* Get receiving codec */
759 /* FIXME
760  g_object_get (content, "fs-stream", &fs_stream, NULL);
761  g_object_get (fs_stream, "current-recv-codecs", &codecs, NULL);
762
763  update_receiving_codec (handler, codecs, fs_stream);
764
765  fs_codec_list_destroy (codecs);
766  tp_clear_object (&fs_stream);
767 */
768
769   g_object_get (content, "media-type", &mtype, NULL);
770
771  if (mtype == FS_MEDIA_TYPE_VIDEO)
772    {
773      guint framerate, width, height;
774
775      g_signal_connect (content, "notify::framerate",
776          G_CALLBACK (on_tf_content_framerate_changed),
777          handler);
778
779      g_signal_connect (content, "resolution-changed",
780          G_CALLBACK (on_tf_content_resolution_changed),
781          handler);
782
783      g_object_get (content,
784          "framerate", &framerate,
785          "width", &width,
786          "height", &height,
787          NULL);
788
789      if (framerate > 0)
790        g_signal_emit (G_OBJECT (handler), signals[FRAMERATE_CHANGED], 0,
791            framerate);
792
793      if (width > 0 && height > 0)
794        g_signal_emit (G_OBJECT (handler), signals[RESOLUTION_CHANGED], 0,
795            width, height);
796    }
797 }
798
799 static void
800 on_tf_channel_content_removed_cb (TfChannel *tfchannel,
801   TfContent *content,
802   EmpathyCallHandler *handler)
803 {
804   gboolean retval;
805
806   DEBUG ("removing content");
807
808   g_signal_emit (G_OBJECT (handler), signals[CONTENT_REMOVED], 0,
809       content, &retval);
810
811   if (!retval)
812     {
813       g_warning ("Could not remove content!");
814
815       tf_content_error_literal (content, "Could not link source");
816     }
817 }
818
819 static void
820 on_tf_channel_closed_cb (TfChannel *tfchannel,
821     EmpathyCallHandler *handler)
822 {
823   g_signal_emit (G_OBJECT (handler), signals[CLOSED], 0);
824 }
825
826 static void
827 on_tf_channel_ready (GObject *source,
828     GAsyncResult *result,
829     gpointer user_data)
830 {
831   EmpathyCallHandler *self = EMPATHY_CALL_HANDLER (user_data);
832   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
833   GError *error = NULL;
834
835   priv->tfchannel = TF_CHANNEL (g_async_initable_new_finish (
836       G_ASYNC_INITABLE (source), result, NULL));
837
838   if (priv->tfchannel == NULL)
839     {
840       g_warning ("Failed to create Farstream channel: %s", error->message);
841       g_error_free (error);
842       return;
843     }
844
845   /* Set up the telepathy farstream channel */
846   g_signal_connect (priv->tfchannel, "closed",
847       G_CALLBACK (on_tf_channel_closed_cb), self);
848   g_signal_connect (priv->tfchannel, "fs-conference-added",
849       G_CALLBACK (on_tf_channel_conference_added_cb), self);
850   g_signal_connect (priv->tfchannel, "fs-conference-removed",
851       G_CALLBACK (on_tf_channel_conference_removed_cb), self);
852   g_signal_connect (priv->tfchannel, "content-added",
853       G_CALLBACK (on_tf_channel_content_added_cb), self);
854   g_signal_connect (priv->tfchannel, "content-removed",
855       G_CALLBACK (on_tf_channel_content_removed_cb), self);
856 }
857
858 static void
859 empathy_call_handler_start_tpfs (EmpathyCallHandler *self)
860 {
861   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
862
863   tf_channel_new_async (TP_CHANNEL (priv->call),
864       on_tf_channel_ready, self);
865 }
866
867 static void
868 empathy_call_handler_request_cb (GObject *source,
869     GAsyncResult *result,
870     gpointer user_data)
871 {
872   EmpathyCallHandler *self = EMPATHY_CALL_HANDLER (user_data);
873   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
874   TpChannel *channel;
875   GError *error = NULL;
876   TpAccountChannelRequest *req = TP_ACCOUNT_CHANNEL_REQUEST (source);
877
878   channel = tp_account_channel_request_create_and_handle_channel_finish (req,
879       result, NULL, &error);
880   if (channel == NULL)
881     {
882       DEBUG ("Failed to create the channel: %s", error->message);
883       g_error_free (error);
884       return;
885     }
886
887   if (!TP_IS_CALL_CHANNEL (channel))
888     {
889       DEBUG ("The channel is not a Call channel!");
890       return;
891     }
892
893   priv->call = TP_CALL_CHANNEL (channel);
894   tp_g_signal_connect_object (priv->call, "state-changed",
895     G_CALLBACK (on_call_state_changed_cb), self, 0);
896   tp_g_signal_connect_object (priv->call, "invalidated",
897     G_CALLBACK (on_call_invalidated_cb), self, 0);
898
899   g_object_notify (G_OBJECT (self), "call-channel");
900
901   empathy_call_handler_start_tpfs (self);
902   tp_call_channel_accept_async (priv->call, on_call_accepted_cb, NULL);
903 }
904
905 void
906 empathy_call_handler_start_call (EmpathyCallHandler *handler,
907     gint64 timestamp)
908 {
909   EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
910   TpAccountChannelRequest *req;
911   TpAccount *account;
912   GHashTable *request;
913
914   if (priv->call != NULL)
915     {
916       empathy_call_handler_start_tpfs (handler);
917
918       if (tp_channel_get_requested (TP_CHANNEL (priv->call)))
919         {
920           /* accept outgoing channels immediately */
921           tp_call_channel_accept_async (priv->call,
922               on_call_accepted_cb, NULL);
923         }
924       else
925         {
926           /* accepting incoming channels when they are INITIALISED */
927           if (tp_call_channel_get_state (priv->call, NULL, NULL, NULL) ==
928               TP_CALL_STATE_INITIALISED)
929             tp_call_channel_accept_async (priv->call,
930                 on_call_accepted_cb, NULL);
931           else
932             priv->accept_when_initialised = TRUE;
933         }
934
935       return;
936     }
937
938   /* No TpCallChannel (we are redialing). Request a new call channel */
939   g_assert (priv->contact != NULL);
940
941   account = empathy_contact_get_account (priv->contact);
942   request = empathy_call_create_call_request (
943       empathy_contact_get_id (priv->contact),
944       priv->initial_audio, priv->initial_video);
945
946   req = tp_account_channel_request_new (account, request, timestamp);
947
948   tp_account_channel_request_create_and_handle_channel_async (req, NULL,
949       empathy_call_handler_request_cb, handler);
950
951   g_object_unref (req);
952   g_hash_table_unref (request);
953 }
954
955 /**
956  * empathy_call_handler_stop_call:
957  * @handler: an #EmpathyCallHandler
958  *
959  * Closes the #EmpathyCallHandler's call and frees its resources.
960  */
961 void
962 empathy_call_handler_stop_call (EmpathyCallHandler *handler)
963 {
964   EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
965
966   if (priv->call != NULL)
967     {
968       tp_call_channel_hangup_async (priv->call,
969           TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
970           "", "", NULL, NULL);
971     }
972 }
973
974 /**
975  * empathy_call_handler_has_initial_video:
976  * @handler: an #EmpathyCallHandler
977  *
978  * Return %TRUE if the call managed by this #EmpathyCallHandler was
979  * created with video enabled
980  *
981  * Return value: %TRUE if the call was created as a video conversation.
982  */
983 gboolean
984 empathy_call_handler_has_initial_video (EmpathyCallHandler *handler)
985 {
986   EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
987
988   return priv->initial_video;
989 }
990
991 FsCodec *
992 empathy_call_handler_get_send_audio_codec (EmpathyCallHandler *self)
993 {
994   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
995
996   return priv->send_audio_codec;
997 }
998
999 FsCodec *
1000 empathy_call_handler_get_send_video_codec (EmpathyCallHandler *self)
1001 {
1002   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
1003
1004   return priv->send_video_codec;
1005 }
1006
1007 GList *
1008 empathy_call_handler_get_recv_audio_codecs (EmpathyCallHandler *self)
1009 {
1010   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
1011
1012   return priv->recv_audio_codecs;
1013 }
1014
1015 GList *
1016 empathy_call_handler_get_recv_video_codecs (EmpathyCallHandler *self)
1017 {
1018   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
1019
1020   return priv->recv_video_codecs;
1021 }
1022
1023 FsCandidate *
1024 empathy_call_handler_get_audio_remote_candidate (
1025     EmpathyCallHandler *self)
1026 {
1027   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
1028
1029   return priv->audio_remote_candidate;
1030 }
1031
1032 FsCandidate *
1033 empathy_call_handler_get_audio_local_candidate (
1034     EmpathyCallHandler *self)
1035 {
1036   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
1037
1038   return priv->audio_local_candidate;
1039 }
1040
1041 FsCandidate *
1042 empathy_call_handler_get_video_remote_candidate (
1043     EmpathyCallHandler *self)
1044 {
1045   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
1046
1047   return priv->video_remote_candidate;
1048 }
1049
1050 FsCandidate *
1051 empathy_call_handler_get_video_local_candidate (
1052     EmpathyCallHandler *self)
1053 {
1054   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
1055
1056   return priv->video_local_candidate;
1057 }
1058
1059 EmpathyContact *
1060 empathy_call_handler_get_contact (EmpathyCallHandler *self)
1061 {
1062   return self->priv->contact;
1063 }