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