]> git.0d.be Git - empathy.git/blobdiff - src/empathy-call-window.c
Merge commit 'jtellier/video-call-button-sensitivity'
[empathy.git] / src / empathy-call-window.c
index 08c0e7ea203bc0ffda8df7031d51939297b281fa..520a928215a6707bde1648d1e3771a5fca852195 100644 (file)
@@ -32,6 +32,7 @@
 #include <telepathy-farsight/channel.h>
 
 #include <libempathy/empathy-tp-contact-factory.h>
+#include <libempathy/empathy-call-factory.h>
 #include <libempathy/empathy-utils.h>
 #include <libempathy-gtk/empathy-avatar-image.h>
 #include <libempathy-gtk/empathy-video-widget.h>
@@ -39,9 +40,9 @@
 #include <libempathy-gtk/empathy-audio-sink.h>
 #include <libempathy-gtk/empathy-video-src.h>
 #include <libempathy-gtk/empathy-ui-utils.h>
+#include <libempathy-gtk/empathy-sound.h>
 
 #include "empathy-call-window.h"
-
 #include "empathy-call-window-fullscreen.h"
 #include "empathy-sidebar.h"
 
 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
 
+/* If an video input error occurs, the error message will start with "v4l" */
+#define VIDEO_INPUT_ERROR_PREFIX "v4l"
+
+/* The time interval in milliseconds between 2 outgoing rings */
+#define MS_BETWEEN_RING 500
+
 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
 
 /* signal enum */
@@ -75,6 +82,13 @@ enum {
   PROP_CALL_HANDLER = 1,
 };
 
+typedef enum {
+  CONNECTING,
+  CONNECTED,
+  DISCONNECTED,
+  REDIALING
+} CallState;
+
 /* private structure */
 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
 
@@ -84,7 +98,8 @@ struct _EmpathyCallWindowPriv
   EmpathyCallHandler *handler;
   EmpathyContact *contact;
 
-  gboolean connected;
+  guint call_state;
+  gboolean outgoing;
 
   GtkUIManager *ui_manager;
   GtkWidget *video_output;
@@ -95,24 +110,36 @@ struct _EmpathyCallWindowPriv
   GtkWidget *sidebar_button;
   GtkWidget *statusbar;
   GtkWidget *volume_button;
+  GtkWidget *redial_button;
   GtkWidget *mic_button;
   GtkWidget *camera_button;
   GtkWidget *toolbar;
   GtkWidget *pane;
+  GtkAction *show_preview;
   GtkAction *send_video;
+  GtkAction *redial;
   GtkAction *menu_fullscreen;
 
+  /* The frames and boxes that contain self and remote avatar and video
+     input/output. When we redial, we destroy and re-create the boxes */
+  GtkWidget *remote_user_output_frame;
+  GtkWidget *self_user_output_frame;
+  GtkWidget *remote_user_output_hbox;
+  GtkWidget *self_user_output_hbox;
+
   /* We keep a reference on the hbox which contains the main content so we can
      easilly repack everything when toggling fullscreen */
   GtkWidget *content_hbox;
 
-  /* This vbox is contained in the content_hbox. When toggling fullscreen,
+  /* This vbox is contained in the content_hbox and it contains the
+     self_user_output_frame and the sidebar button. When toggling fullscreen,
      it needs to be repacked. We keep a reference on it for easier access. */
   GtkWidget *vbox;
 
   gulong video_output_motion_handler_id;
 
   gdouble volume;
+  GtkWidget *volume_progress_bar;
   GtkAdjustment *audio_input_adj;
 
   GtkWidget *dtmf_panel;
@@ -168,6 +195,9 @@ static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
   EmpathyCallWindow *window);
 
+static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
+  gboolean send);
+
 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
   EmpathyCallWindow *window);
 
@@ -203,9 +233,17 @@ static gboolean empathy_call_window_video_output_motion_notify (GtkWidget *widge
 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
   guint button);
 
+static void empathy_call_window_redial_cb (gpointer object,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_restart_call (EmpathyCallWindow *window);
+
 static void empathy_call_window_status_message (EmpathyCallWindow *window,
   gchar *message);
 
+static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
+  EmpathyCallWindow *window);
+
 static gboolean empathy_call_window_bus_message (GstBus *bus,
   GstMessage *message, gpointer user_data);
 
@@ -455,7 +493,7 @@ empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
     priv->volume = volume;
 
   /* Ensure that the toggle button is active if the volume is > 0 and inactive
-   * if it's smaller then 0 */
+   * if it's smaller than 0 */
   if ((volume > 0) != gtk_toggle_tool_button_get_active (
         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
     gtk_toggle_tool_button_set_active (
@@ -467,19 +505,20 @@ empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
 
 static void
 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
-  gdouble level, GtkProgressBar *bar)
+  gdouble level, EmpathyCallWindow *window)
 {
   gdouble value;
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), value);
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar), value);
 }
 
 static GtkWidget *
 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GtkWidget *hbox, *vbox, *scale, *progress, *label;
+  GtkWidget *hbox, *vbox, *scale, *label;
   GtkAdjustment *adj;
 
   hbox = gtk_hbox_new (TRUE, 3);
@@ -502,17 +541,130 @@ empathy_call_window_create_audio_input (EmpathyCallWindow *self)
   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 3);
   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
 
-  progress = gtk_progress_bar_new ();
-  gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (progress),
-    GTK_PROGRESS_BOTTOM_TO_TOP);
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), 0);
+  priv->volume_progress_bar = gtk_progress_bar_new ();
+  gtk_progress_bar_set_orientation (
+      GTK_PROGRESS_BAR (priv->volume_progress_bar), GTK_PROGRESS_BOTTOM_TO_TOP);
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
+      0);
+
+  gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
+      3);
+
+  return hbox;
+}
+
+static void
+empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  /* Initializing all the content (UI and output gst elements) related to the
+     remote contact */
+  priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
+
+  priv->remote_user_avatar_widget = gtk_image_new ();
+  gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
+      priv->remote_user_avatar_widget, TRUE, TRUE, 0);
+
+  priv->video_output = empathy_video_widget_new (bus);
+  gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
+      priv->video_output, TRUE, TRUE, 0);
+
+  gtk_widget_add_events (priv->video_output,
+      GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
+  g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
+      G_CALLBACK (empathy_call_window_video_button_press_cb), self);
+
+  gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
+      priv->remote_user_output_hbox);
+
+  priv->audio_output = empathy_audio_sink_new ();
+  gst_object_ref (priv->audio_output);
+  gst_object_sink (priv->audio_output);
+}
+
+static void
+empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  /* Initializing all the content (UI and input gst elements) related to the
+     self contact, except for the video preview widget. This widget is only
+     initialized when the "show video preview" option is activated */
+  priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
+
+  priv->self_user_avatar_widget = gtk_image_new ();
+  gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
+      priv->self_user_avatar_widget, TRUE, TRUE, 0);
+
+  gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
+      priv->self_user_output_hbox);
+
+  priv->video_input = empathy_video_src_new ();
+  gst_object_ref (priv->video_input);
+  gst_object_sink (priv->video_input);
+
+  priv->audio_input = empathy_audio_src_new ();
+  gst_object_ref (priv->audio_input);
+  gst_object_sink (priv->audio_input);
 
   g_signal_connect (priv->audio_input, "peak-level-changed",
-    G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), progress);
+    G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), self);
+}
 
-  gtk_box_pack_start (GTK_BOX (hbox), progress, FALSE, FALSE, 3);
+static void
+empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  GstElement *preview;
+  GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
 
-  return hbox;
+  if (priv->video_preview != NULL)
+    {
+      /* Since the video preview and the video tee are initialized and freed
+         at the same time, if one is initialized, then the other one should
+         be too. */
+      g_assert (priv->video_tee != NULL);
+      return;
+    }
+
+  g_assert (priv->video_tee == NULL);
+
+  priv->video_tee = gst_element_factory_make ("tee", NULL);
+  gst_object_ref (priv->video_tee);
+  gst_object_sink (priv->video_tee);
+
+  priv->video_preview = empathy_video_widget_new_with_size (bus,
+      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
+  g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
+  gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
+      priv->video_preview, TRUE, TRUE, 0);
+
+  preview = empathy_video_widget_get_element (
+      EMPATHY_VIDEO_WIDGET (priv->video_preview));
+  gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
+      priv->video_tee, preview, NULL);
+  gst_element_link_many (priv->video_input, priv->video_tee,
+      preview, NULL);
+
+  g_object_unref (bus);
+
+  gst_element_set_state (preview, GST_STATE_PLAYING);
+  gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
+  gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
+}
+
+static void
+empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  empathy_call_window_status_message (window, _("Connecting..."));
+  priv->call_state = CONNECTING;
+
+  if (priv->outgoing)
+    empathy_sound_start_playing (GTK_WIDGET (window),
+        EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
 }
 
 static void
@@ -524,8 +676,6 @@ empathy_call_window_init (EmpathyCallWindow *self)
   GtkWidget *h;
   GtkWidget *arrow;
   GtkWidget *page;
-  GtkWidget *remote_user_output_frame, *self_user_output_frame;
-  GtkWidget *remote_user_output_hbox, *self_user_output_hbox;
   GstBus *bus;
   gchar *filename;
 
@@ -534,10 +684,13 @@ empathy_call_window_init (EmpathyCallWindow *self)
     "call_window_vbox", &top_vbox,
     "pane", &priv->pane,
     "statusbar", &priv->statusbar,
+    "redial", &priv->redial_button,
     "microphone", &priv->mic_button,
     "camera", &priv->camera_button,
     "toolbar", &priv->toolbar,
     "send_video", &priv->send_video,
+    "menuredial", &priv->redial,
+    "show_preview", &priv->show_preview,
     "ui_manager", &priv->ui_manager,
     "menufullscreen", &priv->menu_fullscreen,
     NULL);
@@ -545,6 +698,8 @@ empathy_call_window_init (EmpathyCallWindow *self)
   empathy_builder_connect (gui, self,
     "menuhangup", "activate", empathy_call_window_hangup_cb,
     "hangup", "clicked", empathy_call_window_hangup_cb,
+    "menuredial", "activate", empathy_call_window_redial_cb,
+    "redial", "clicked", empathy_call_window_redial_cb,
     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
     "camera", "toggled", empathy_call_window_camera_toggled_cb,
     "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
@@ -558,80 +713,33 @@ empathy_call_window_init (EmpathyCallWindow *self)
 
   empathy_call_window_setup_toolbar (self);
 
-  priv->pipeline = gst_pipeline_new (NULL);
-
   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
                                   CONTENT_HBOX_BORDER_WIDTH);
   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
 
+  priv->pipeline = gst_pipeline_new (NULL);
   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
-
   gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
 
-  remote_user_output_frame = gtk_frame_new (NULL);
-  gtk_widget_set_size_request (remote_user_output_frame,
+  priv->remote_user_output_frame = gtk_frame_new (NULL);
+  gtk_widget_set_size_request (priv->remote_user_output_frame,
       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
-  remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
-
-  priv->remote_user_avatar_widget = gtk_image_new ();
-  gtk_box_pack_start (GTK_BOX (remote_user_output_hbox),
-      priv->remote_user_avatar_widget, TRUE, TRUE, 0);
-
-  priv->video_output = empathy_video_widget_new (bus);
-  gtk_box_pack_start (GTK_BOX (remote_user_output_hbox),
-      priv->video_output, TRUE, TRUE, 0);
-
-  gtk_container_add (GTK_CONTAINER (remote_user_output_frame),
-      remote_user_output_hbox);
-
-  gtk_widget_add_events (priv->video_output,
-      GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
-  g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
-      G_CALLBACK (empathy_call_window_video_button_press_cb), self);
-  gtk_box_pack_start (GTK_BOX (priv->content_hbox), remote_user_output_frame,
-      TRUE, TRUE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+  gtk_box_pack_start (GTK_BOX (priv->content_hbox),
+      priv->remote_user_output_frame, TRUE, TRUE,
+      CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+  empathy_call_window_setup_remote_frame (bus, self);
 
-  priv->video_tee = gst_element_factory_make ("tee", NULL);
-  gst_object_ref (priv->video_tee);
-  gst_object_sink (priv->video_tee);
+  priv->self_user_output_frame = gtk_frame_new (NULL);
+  gtk_widget_set_size_request (priv->self_user_output_frame,
+      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
 
   priv->vbox = gtk_vbox_new (FALSE, 3);
   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
-                      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
-
-  self_user_output_frame = gtk_frame_new (NULL);
-  gtk_widget_set_size_request (self_user_output_frame, SELF_VIDEO_SECTION_WIDTH,
-      SELF_VIDEO_SECTION_HEIGTH);
-  self_user_output_hbox = gtk_hbox_new (FALSE, 0);
-
-  priv->self_user_avatar_widget = gtk_image_new ();
-  gtk_box_pack_start (GTK_BOX (self_user_output_hbox),
-      priv->self_user_avatar_widget, TRUE, TRUE, 0);
-
-  priv->video_preview = empathy_video_widget_new_with_size (bus,
-      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
-  g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
-  gtk_box_pack_start (GTK_BOX (self_user_output_hbox), priv->video_preview,
-      TRUE, TRUE, 0);
-
-  gtk_container_add (GTK_CONTAINER (self_user_output_frame),
-      self_user_output_hbox);
-
-  gtk_box_pack_start (GTK_BOX (priv->vbox), self_user_output_frame, FALSE,
+      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+  gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame, FALSE,
       FALSE, 0);
-
-  priv->video_input = empathy_video_src_new ();
-  gst_object_ref (priv->video_input);
-  gst_object_sink (priv->video_input);
-
-  priv->audio_input = empathy_audio_src_new ();
-  gst_object_ref (priv->audio_input);
-  gst_object_sink (priv->audio_input);
-
-  priv->audio_output = empathy_audio_sink_new ();
-  gst_object_ref (priv->audio_output);
-  gst_object_sink (priv->audio_output);
+  empathy_call_window_setup_self_frame (bus, self);
 
   g_object_unref (bus);
 
@@ -688,8 +796,6 @@ empathy_call_window_init (EmpathyCallWindow *self)
   g_signal_connect (G_OBJECT (self), "key-press-event",
       G_CALLBACK (empathy_call_window_key_press_cb), self);
 
-  empathy_call_window_status_message (self, _("Connecting..."));
-
   priv->timer = g_timer_new ();
 
   g_object_ref (priv->ui_manager);
@@ -703,7 +809,6 @@ static void
 init_contact_avatar_with_size (EmpathyContact *contact, GtkWidget *image_widget,
     gint size)
 {
-
   GdkPixbuf *pixbuf_avatar = NULL;
 
   if (contact != NULL)
@@ -764,14 +869,12 @@ empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
 }
 
 static void
-empathy_call_window_constructed (GObject *object)
+empathy_call_window_setup_avatars (EmpathyCallWindow *self,
+    EmpathyCallHandler *handler)
 {
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
-  g_assert (priv->handler != NULL);
-
-  g_object_get (priv->handler, "contact", &(priv->contact), NULL);
+  g_object_get (handler, "contact", &(priv->contact), NULL);
 
   if (priv->contact != NULL)
     {
@@ -811,15 +914,42 @@ empathy_call_window_constructed (GObject *object)
       priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
       REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
 
-  /* We hide the self avatar. It will be shown if a problem is
-     encountered when we try to send video. As for the remote avatar, it
-     is shown by default and will be hidden when we receive video from
-     the remote side. */
-  gtk_widget_hide (priv->self_user_avatar_widget);
+  /* The remote avatar is shown by default and will be hidden when we receive
+     video from the remote side. */
   gtk_widget_hide (priv->video_output);
   gtk_widget_show (priv->remote_user_avatar_widget);
 }
 
+static void
+empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
+    EmpathyCallHandler *handler)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  gboolean initial_video = empathy_call_handler_has_initial_video (priv->handler);
+
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
+      initial_video);
+}
+
+static void
+empathy_call_window_constructed (GObject *object)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  EmpathyTpCall *call;
+
+  g_assert (priv->handler != NULL);
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+  priv->outgoing = (call == NULL);
+  if (call != NULL)
+    g_object_unref (call);
+
+  empathy_call_window_setup_avatars (self, priv->handler);
+  empathy_call_window_setup_video_preview_visibility (self, priv->handler);
+  empathy_call_window_set_state_connecting (self);
+}
+
 static void empathy_call_window_dispose (GObject *object);
 static void empathy_call_window_finalize (GObject *object);
 
@@ -878,12 +1008,19 @@ empathy_call_window_class_init (
     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
   g_object_class_install_property (object_class,
     PROP_CALL_HANDLER, param_spec);
+}
 
+static void
+empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
+    GParamSpec *property, EmpathyCallWindow *self)
+{
+  empathy_call_window_update_avatars_visibility (call, self);
 }
 
 void
 empathy_call_window_dispose (GObject *object)
 {
+  EmpathyTpCall *call;
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
@@ -892,6 +1029,16 @@ empathy_call_window_dispose (GObject *object)
 
   priv->dispose_has_run = TRUE;
 
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+
+  if (call != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (call,
+        empathy_call_window_video_stream_changed_cb, object);
+    }
+
+  g_object_unref (call);
+
   if (priv->handler != NULL)
     g_object_unref (priv->handler);
 
@@ -996,26 +1143,116 @@ empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
   return priv->video_input != NULL;
 }
 
-static void
+static gboolean
+empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
+{
+  GstStateChangeReturn state_change_return;
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->pipeline == NULL)
+    return TRUE;
+
+  state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+
+  if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
+        state_change_return == GST_STATE_CHANGE_NO_PREROLL)
+    {
+      if (priv->pipeline != NULL)
+        g_object_unref (priv->pipeline);
+      priv->pipeline = NULL;
+
+      if (priv->video_input != NULL)
+        g_object_unref (priv->video_input);
+      priv->video_input = NULL;
+
+      if (priv->audio_input != NULL)
+        g_object_unref (priv->audio_input);
+      priv->audio_input = NULL;
+
+      if (priv->audio_output != NULL)
+        g_object_unref (priv->audio_output);
+      priv->audio_output = NULL;
+
+      if (priv->video_tee != NULL)
+        g_object_unref (priv->video_tee);
+      priv->video_tee = NULL;
+
+      if (priv->video_preview != NULL)
+        gtk_widget_destroy (priv->video_preview);
+      priv->video_preview = NULL;
+
+      priv->liveadder = NULL;
+      priv->funnel = NULL;
+
+      return TRUE;
+    }
+  else
+    {
+      g_message ("Error: could not destroy pipeline. Closing call window");
+      gtk_widget_destroy (GTK_WIDGET (self));
+
+      return FALSE;
+    }
+}
+
+static gboolean
 empathy_call_window_disconnected (EmpathyCallWindow *self)
 {
+  gboolean could_disconnect = FALSE;
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
 
-  g_mutex_lock (priv->lock);
+  if (priv->call_state == CONNECTING)
+      empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
 
-  g_timer_stop (priv->timer);
+  if (priv->call_state != REDIALING)
+    priv->call_state = DISCONNECTED;
 
-  if (priv->timer_id != 0)
-    g_source_remove (priv->timer_id);
-  priv->timer_id = 0;
+  if (could_reset_pipeline)
+    {
+      gboolean initial_video = empathy_call_handler_has_initial_video (
+          priv->handler);
+      g_mutex_lock (priv->lock);
 
-  g_mutex_unlock (priv->lock);
+      g_timer_stop (priv->timer);
 
-  empathy_call_window_status_message (self, _("Disconnected"));
+      if (priv->timer_id != 0)
+        g_source_remove (priv->timer_id);
+      priv->timer_id = 0;
 
-  gtk_widget_set_sensitive (priv->camera_button, FALSE);
-  gtk_action_set_sensitive (priv->send_video, FALSE);
-  priv->sending_video = FALSE;
+      g_mutex_unlock (priv->lock);
+
+      empathy_call_window_status_message (self, _("Disconnected"));
+
+      gtk_action_set_sensitive (priv->redial, TRUE);
+      gtk_widget_set_sensitive (priv->redial_button, TRUE);
+
+      /* Reseting the send_video, camera_buton and mic_button to their
+         initial state */
+      gtk_widget_set_sensitive (priv->camera_button, FALSE);
+      gtk_widget_set_sensitive (priv->mic_button, FALSE);
+      gtk_action_set_sensitive (priv->send_video, FALSE);
+      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
+          initial_video);
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
+
+      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
+          FALSE);
+      gtk_action_set_sensitive (priv->show_preview, FALSE);
+
+      gtk_widget_hide (priv->video_output);
+      gtk_widget_show (priv->remote_user_avatar_widget);
+
+      priv->sending_video = FALSE;
+      priv->call_started = FALSE;
+
+      could_disconnect = TRUE;
+    }
+
+  return could_disconnect;
 }
 
 
@@ -1023,8 +1260,10 @@ static void
 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
 {
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
-  empathy_call_window_disconnected (self);
+  if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
+      empathy_call_window_restart_call (self);
 }
 
 /* Called with global lock held */
@@ -1107,17 +1346,41 @@ empathy_call_window_connected (gpointer user_data)
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   EmpathyTpCall *call;
+  gboolean can_send_video = priv->video_input != NULL && priv->contact != NULL
+    && empathy_contact_can_voip_video (priv->contact);
 
   g_object_get (priv->handler, "tp-call", &call, NULL);
 
+  g_signal_connect (call, "notify::video-stream",
+    G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
+
   if (empathy_tp_call_has_dtmf (call))
     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
 
-  if (priv->video_input != NULL)
-    {
-      gtk_widget_set_sensitive (priv->camera_button, TRUE);
-      gtk_action_set_sensitive (priv->send_video, TRUE);
-    }
+  if (priv->video_input == NULL)
+    empathy_call_window_set_send_video (self, FALSE);
+
+  priv->sending_video = can_send_video ?
+    empathy_tp_call_is_sending_video (call) : FALSE;
+
+  gtk_action_set_sensitive (priv->show_preview, TRUE);
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
+      priv->sending_video
+      || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
+      priv->sending_video && priv->video_input != NULL);
+  gtk_toggle_tool_button_set_active (
+      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
+      priv->sending_video && priv->video_input != NULL);
+  gtk_widget_set_sensitive (priv->camera_button, can_send_video);
+  gtk_action_set_sensitive (priv->send_video, can_send_video);
+
+  gtk_action_set_sensitive (priv->redial, FALSE);
+  gtk_widget_set_sensitive (priv->redial_button, FALSE);
+
+  gtk_widget_set_sensitive (priv->mic_button, TRUE);
+
+  empathy_call_window_update_avatars_visibility (call, self);
 
   g_object_unref (call);
 
@@ -1146,11 +1409,12 @@ empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
 
   g_mutex_lock (priv->lock);
 
-  if (priv->connected == FALSE)
+  if (priv->call_state != CONNECTED)
     {
       g_timer_start (priv->timer);
       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
-      priv->connected = TRUE;
+      priv->call_state = CONNECTED;
+      empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
     }
 
   switch (media_type)
@@ -1195,53 +1459,34 @@ empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
       case TP_MEDIA_STREAM_TYPE_VIDEO:
         if (priv->video_input != NULL)
           {
-            pad = gst_element_get_request_pad (priv->video_tee, "src%d");
-            gst_pad_link (pad, sink);
-          }
-        break;
-      default:
-        g_assert_not_reached ();
-    }
+            EmpathyTpCall *call;
+            g_object_get (priv->handler, "tp-call", &call, NULL);
 
-}
+            if (empathy_tp_call_is_sending_video (call))
+              {
+                empathy_call_window_setup_video_preview (self);
 
-static gboolean
-empathy_gst_bin_has_child (GstBin *bin, GstElement *element)
-{
-  GstIterator *it;
-  gboolean ret = FALSE;
-  GstElement *item;
+                gtk_toggle_action_set_active (
+                    GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
 
-  it = gst_bin_iterate_recurse (bin);
+                if (priv->video_preview != NULL)
+                  gtk_widget_show (priv->video_preview);
+                gtk_widget_hide (priv->self_user_avatar_widget);
+              }
 
-  for (;;)
-    {
-      switch (gst_iterator_next (it, (gpointer *)&item))
-       {
-         case GST_ITERATOR_OK:
-           if (item == element)
-            {
-              gst_object_unref (GST_OBJECT (item));
-              ret = TRUE;
-              goto out;
-            }
-           gst_object_unref (GST_OBJECT (item));
-           break;
-         case GST_ITERATOR_RESYNC:
-           gst_iterator_resync (it);
-           break;
-        case GST_ITERATOR_ERROR:
-           g_assert_not_reached ();
-           /* fallthrough */
-        case GST_ITERATOR_DONE:
-           goto out;
-           break;
-      }
+            g_object_unref (call);
+
+            if (priv->video_tee != NULL)
+              {
+                pad = gst_element_get_request_pad (priv->video_tee, "src%d");
+                gst_pad_link (pad, sink);
+              }
+          }
+        break;
+      default:
+        g_assert_not_reached ();
     }
-    gst_iterator_free (it);
 
-out:
-  return ret;
 }
 
 static void
@@ -1264,8 +1509,15 @@ empathy_call_window_remove_video_input (EmpathyCallWindow *self)
   priv->video_input = NULL;
   g_object_unref (priv->video_tee);
   priv->video_tee = NULL;
+  gtk_widget_destroy (priv->video_preview);
+  priv->video_preview = NULL;
+
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
+  gtk_toggle_tool_button_set_active (
+      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
+  gtk_widget_set_sensitive (priv->camera_button, FALSE);
+  gtk_action_set_sensitive (priv->send_video, FALSE);
 
-  gtk_widget_hide (priv->video_preview);
   gtk_widget_show (priv->self_user_avatar_widget);
 }
 
@@ -1304,23 +1556,24 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
       case GST_MESSAGE_ERROR:
         {
           GError *error = NULL;
+          GstElement *gst_error;
           gchar *debug;
 
           gst_message_parse_error (message, &error, &debug);
+          gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
 
           g_message ("Element error: %s -- %s\n", error->message, debug);
 
-          if (priv->video_input != NULL &&
-              empathy_gst_bin_has_child (GST_BIN (priv->video_input),
-                GST_ELEMENT (GST_MESSAGE_SRC (message))))
+          if (g_str_has_prefix (gst_element_get_name (gst_error),
+                VIDEO_INPUT_ERROR_PREFIX))
             {
               /* Remove the video input and continue */
-              empathy_call_window_remove_video_input (self);
-              gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+              if (priv->video_input != NULL)
+                empathy_call_window_remove_video_input (self);
+              gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
             }
           else
             {
-              gst_element_set_state (priv->pipeline, GST_STATE_NULL);
               empathy_call_window_disconnected (self);
             }
           g_error_free (error);
@@ -1333,11 +1586,52 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
   return TRUE;
 }
 
+static void
+empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
+    {
+      if (priv->video_preview != NULL)
+        {
+          gtk_widget_hide (priv->self_user_avatar_widget);
+          gtk_widget_show (priv->video_preview);
+        }
+      else
+        {
+          if (priv->video_preview != NULL)
+            gtk_widget_hide (priv->video_preview);
+
+          gtk_widget_show (priv->self_user_avatar_widget);
+        }
+    }
+}
+
+static void
+empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
+    EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (empathy_tp_call_is_receiving_video (call))
+    {
+      gtk_widget_hide (priv->remote_user_avatar_widget);
+      gtk_widget_show (priv->video_output);
+    }
+  else
+    {
+      gtk_widget_hide (priv->video_output);
+      gtk_widget_show (priv->remote_user_avatar_widget);
+    }
+
+  empathy_call_window_update_self_avatar_visibility (window);
+}
+
 static void
 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  GstElement *preview;
 
   g_signal_connect (priv->handler, "conference-added",
     G_CALLBACK (empathy_call_window_conference_added_cb), window);
@@ -1350,15 +1644,6 @@ empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
   g_signal_connect (priv->handler, "sink-pad-added",
     G_CALLBACK (empathy_call_window_sink_added_cb), window);
 
-
-  preview = empathy_video_widget_get_element (
-    EMPATHY_VIDEO_WIDGET (priv->video_preview));
-
-  gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
-    priv->video_tee, preview, NULL);
-  gst_element_link_many (priv->video_input, priv->video_tee,
-    preview, NULL);
-
   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
 }
 
@@ -1368,7 +1653,11 @@ empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
-  gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+  if (priv->pipeline != NULL)
+    gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+
+  if (priv->call_state == CONNECTING)
+    empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
 
   return FALSE;
 }
@@ -1514,6 +1803,17 @@ empathy_call_window_set_send_video (EmpathyCallWindow *window,
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   EmpathyTpCall *call;
 
+  priv->sending_video = send;
+
+  /* When we start sending video, we want to show the video preview by
+     default. */
+  if (send)
+    {
+      empathy_call_window_setup_video_preview (window);
+      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
+          TRUE);
+    }
+
   g_object_get (priv->handler, "tp-call", &call, NULL);
   empathy_tp_call_request_video_stream_direction (call, send);
   g_object_unref (call);
@@ -1526,11 +1826,13 @@ empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   gboolean active;
 
+  if (priv->call_state != CONNECTED)
+    return;
+
   active = (gtk_toggle_tool_button_get_active (toggle));
 
   if (priv->sending_video == active)
     return;
-  priv->sending_video = active;
 
   empathy_call_window_set_send_video (window, active);
   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
@@ -1543,11 +1845,13 @@ empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   gboolean active;
 
+  if (priv->call_state != CONNECTED)
+    return;
+
   active = (gtk_toggle_action_get_active (toggle));
 
   if (priv->sending_video == active)
     return;
-  priv->sending_video = active;
 
   empathy_call_window_set_send_video (window, active);
   gtk_toggle_tool_button_set_active (
@@ -1558,7 +1862,21 @@ static void
 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
   EmpathyCallWindow *window)
 {
-  /* FIXME: Not implemented */
+  gboolean show_preview_toggled;
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  show_preview_toggled = gtk_toggle_action_get_active (toggle);
+
+  if (show_preview_toggled)
+    {
+      empathy_call_window_setup_video_preview (window);
+      gtk_widget_show (priv->self_user_output_frame);
+      empathy_call_window_update_self_avatar_visibility (window);
+    }
+  else
+    {
+      gtk_widget_hide (priv->self_user_output_frame);
+    }
 }
 
 static void
@@ -1568,6 +1886,9 @@ empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   gboolean active;
 
+  if (priv->audio_input == NULL)
+    return;
+
   active = (gtk_toggle_tool_button_get_active (toggle));
 
   if (active)
@@ -1612,11 +1933,59 @@ empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
 static void
 empathy_call_window_hangup_cb (gpointer object,
                                EmpathyCallWindow *window)
+{
+  if (empathy_call_window_disconnected (window))
+    gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+static void
+empathy_call_window_restart_call (EmpathyCallWindow *window)
+{
+  GstBus *bus;
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  gtk_widget_destroy (priv->remote_user_output_hbox);
+  gtk_widget_destroy (priv->self_user_output_hbox);
+
+  priv->pipeline = gst_pipeline_new (NULL);
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+  gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
+
+  empathy_call_window_setup_remote_frame (bus, window);
+  empathy_call_window_setup_self_frame (bus, window);
+
+  g_object_unref (bus);
+
+  gtk_widget_show_all (priv->content_hbox);
+
+  if (!empathy_call_handler_has_initial_video (priv->handler))
+    gtk_widget_hide (priv->self_user_output_frame);
+
+  priv->outgoing = TRUE;
+  empathy_call_window_set_state_connecting (window);
+
+  priv->call_started = TRUE;
+  empathy_call_handler_start_call (priv->handler);
+  empathy_call_window_setup_avatars (window, priv->handler);
+  gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+
+  gtk_action_set_sensitive (priv->redial, FALSE);
+  gtk_widget_set_sensitive (priv->redial_button, FALSE);
+}
+
+static void
+empathy_call_window_redial_cb (gpointer object,
+    EmpathyCallWindow *window)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
-  gst_element_set_state (priv->pipeline, GST_STATE_NULL);
-  gtk_widget_destroy (GTK_WIDGET (window));
+  if (priv->call_state == CONNECTED)
+    priv->call_state = REDIALING;
+
+  empathy_call_handler_stop_call (priv->handler);
+
+  if (priv->call_state != CONNECTED)
+    empathy_call_window_restart_call (window);
 }
 
 static void
@@ -1678,7 +2047,6 @@ empathy_call_window_video_output_motion_notify (GtkWidget *widget,
       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
       return TRUE;
     }
-
   return FALSE;
 }