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