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