]> git.0d.be Git - empathy.git/blobdiff - src/empathy-call-window.c
use avatar-default instead of the deprecated stock_person icon
[empathy.git] / src / empathy-call-window.c
index 98707462a74231d3d335eb0f6ca86cb9d536c59a..598f483ab460756f5af559a43daec381862c76c0 100644 (file)
@@ -29,7 +29,9 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
+#include <telepathy-glib/util.h>
 #include <telepathy-farsight/channel.h>
+#include <telepathy-glib/util.h>
 
 #include <gst/farsight/fs-element-added-notifier.h>
 
@@ -43,6 +45,8 @@
 #include <libempathy-gtk/empathy-video-src.h>
 #include <libempathy-gtk/empathy-ui-utils.h>
 #include <libempathy-gtk/empathy-sound.h>
+#include <libempathy-gtk/empathy-geometry.h>
+#include <libempathy-gtk/empathy-images.h>
 
 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
 #include <libempathy/empathy-debug.h>
@@ -95,6 +99,12 @@ typedef enum {
   REDIALING
 } CallState;
 
+typedef enum {
+  CAMERA_STATE_OFF = 0,
+  CAMERA_STATE_PREVIEW,
+  CAMERA_STATE_ON,
+} CameraState;
+
 /* private structure */
 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
 
@@ -109,6 +119,8 @@ struct _EmpathyCallWindowPriv
 
   GtkUIManager *ui_manager;
   GtkWidget *errors_vbox;
+  /* widget displays the video received from the remote user. This widget is
+   * alive only during call. */
   GtkWidget *video_output;
   GtkWidget *video_preview;
   GtkWidget *remote_user_avatar_widget;
@@ -119,13 +131,15 @@ struct _EmpathyCallWindowPriv
   GtkWidget *volume_button;
   GtkWidget *redial_button;
   GtkWidget *mic_button;
-  GtkWidget *camera_button;
   GtkWidget *toolbar;
   GtkWidget *pane;
-  GtkAction *always_show_preview;
-  GtkAction *send_video;
   GtkAction *redial;
   GtkAction *menu_fullscreen;
+  GtkAction *action_camera;
+  GtkAction *action_camera_preview;
+  GtkWidget *tool_button_camera_off;
+  GtkWidget *tool_button_camera_preview;
+  GtkWidget *tool_button_camera_on;
 
   /* The frames and boxes that contain self and remote avatar and video
      input/output. When we redial, we destroy and re-create the boxes */
@@ -176,6 +190,7 @@ struct _EmpathyCallWindowPriv
   GMutex *lock;
   gboolean call_started;
   gboolean sending_video;
+  CameraState camera_state;
 
   EmpathyCallWindowFullscreen *fullscreen;
   gboolean is_fullscreen;
@@ -185,6 +200,11 @@ struct _EmpathyCallWindowPriv
   gboolean sidebar_was_visible_before_fs;
   gint original_width_before_fs;
   gint original_height_before_fs;
+
+  /* TRUE if the call should be started when the pipeline is playing */
+  gboolean start_call_when_playing;
+  /* TRUE if we requested to set the pipeline in the playing state */
+  gboolean pipeline_playing;
 };
 
 #define GET_PRIV(o) \
@@ -203,18 +223,9 @@ static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
   EmpathyCallWindow *window);
 
-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);
-
-static void empathy_call_window_always_show_preview_toggled_cb (
-  GtkToggleAction *toggle, EmpathyCallWindow *window);
-
 static void empathy_call_window_mic_toggled_cb (
   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
 
@@ -262,11 +273,32 @@ static void
 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
   gdouble value, EmpathyCallWindow *window);
 
+static void block_camera_control_signals (EmpathyCallWindow *self);
+static void unblock_camera_control_signals (EmpathyCallWindow *self);
+
 static void
 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GtkToolItem *tool_item;
+  GtkWidget *camera_off_icon;
+  GdkPixbuf *pixbuf, *modded_pixbuf;
+
+  /* set the icon of the 'camera off' button by greying off the webcam icon */
+  pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
+      GTK_ICON_SIZE_SMALL_TOOLBAR);
+
+  modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+      gdk_pixbuf_get_width (pixbuf),
+      gdk_pixbuf_get_height (pixbuf));
+
+  gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
+  g_object_unref (pixbuf);
+
+  camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
+  g_object_unref (modded_pixbuf);
+  gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
+        priv->tool_button_camera_off), camera_off_icon);
 
   /* Add an empty expanded GtkToolItem so the volume button is at the end of
    * the toolbar. */
@@ -496,9 +528,6 @@ empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   gdouble volume;
 
-  if (priv->audio_input == NULL)
-    return;
-
   volume = gtk_adjustment_get_value (adj)/100.0;
 
   /* Don't store the volume because of muting */
@@ -570,19 +599,17 @@ empathy_call_window_create_audio_input (EmpathyCallWindow *self)
 }
 
 static void
-empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
+create_video_output_widget (EmpathyCallWindow *self)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
 
-  /* 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);
+  g_assert (priv->video_output == NULL);
+  g_assert (priv->pipeline != NULL);
 
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
   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);
 
@@ -591,84 +618,141 @@ empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
   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);
+  g_object_unref (bus);
+}
 
+static void
+create_audio_output (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_assert (priv->audio_output == NULL);
   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)
+create_video_input (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);
-
+  g_assert (priv->video_input == NULL);
   priv->video_input = empathy_video_src_new ();
   gst_object_ref (priv->video_input);
   gst_object_sink (priv->video_input);
+}
+
+static void
+create_audio_input (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
+  g_assert (priv->audio_input == NULL);
   priv->audio_input = empathy_audio_src_new ();
   gst_object_ref (priv->audio_input);
   gst_object_sink (priv->audio_input);
 
-  empathy_signal_connect_weak (priv->audio_input, "peak-level-changed",
+  tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
-    G_OBJECT (self));
+    self, 0);
 }
 
 static void
-empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
+add_video_preview_to_pipeline (EmpathyCallWindow *self)
 {
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GstElement *preview;
-  GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
 
-  if (priv->video_preview != NULL)
+  g_assert (priv->video_preview != NULL);
+  g_assert (priv->pipeline != NULL);
+  g_assert (priv->video_input != NULL);
+  g_assert (priv->video_tee != NULL);
+
+  preview = empathy_video_widget_get_element (
+      EMPATHY_VIDEO_WIDGET (priv->video_preview));
+
+  if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
     {
-      /* 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);
+      g_warning ("Could not add video input to pipeline");
       return;
     }
 
+  if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee))
+    {
+      g_warning ("Could not add video tee to pipeline");
+      return;
+    }
+
+  if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
+    {
+      g_warning ("Could not add video preview to pipeline");
+      return;
+    }
+
+  if (!gst_element_link (priv->video_input, priv->video_tee))
+    {
+      g_warning ("Could not link video input to video tee");
+      return;
+    }
+
+  if (!gst_element_link (priv->video_tee, preview))
+    {
+      g_warning ("Could not link video tee to video preview");
+      return;
+    }
+}
+
+static void
+create_video_preview (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
+
+  g_assert (priv->video_preview == NULL);
   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);
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
 
   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);
+  priv->video_tee = gst_element_factory_make ("tee", NULL);
+  gst_object_ref (priv->video_tee);
+  gst_object_sink (priv->video_tee);
 
   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
+play_camera (EmpathyCallWindow *window,
+    gboolean play)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  GstElement *preview;
+  GstState state;
+
+  if (priv->video_preview == NULL)
+    {
+      create_video_preview (window);
+      add_video_preview_to_pipeline (window);
+    }
+
+  if (play)
+    state = GST_STATE_PLAYING;
+  else
+    state = GST_STATE_NULL;
+
+  preview = empathy_video_widget_get_element (
+      EMPATHY_VIDEO_WIDGET (priv->video_preview));
+
+  gst_element_set_state (preview, state);
+  gst_element_set_state (priv->video_input, state);
+  gst_element_set_state (priv->video_tee, state);
 }
 
 static void
@@ -682,8 +766,7 @@ display_video_preview (EmpathyCallWindow *self,
       /* Display the preview and hide the self avatar */
       DEBUG ("Show video preview");
 
-      if (priv->video_preview == NULL)
-        empathy_call_window_setup_video_preview (self);
+      play_camera (self, TRUE);
       gtk_widget_show (priv->video_preview);
       gtk_widget_hide (priv->self_user_avatar_widget);
     }
@@ -693,7 +776,10 @@ display_video_preview (EmpathyCallWindow *self,
       DEBUG ("Show self avatar");
 
       if (priv->video_preview != NULL)
-        gtk_widget_hide (priv->video_preview);
+        {
+          gtk_widget_hide (priv->video_preview);
+          play_camera (self, FALSE);
+        }
       gtk_widget_show (priv->self_user_avatar_widget);
     }
 }
@@ -703,7 +789,7 @@ empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
-  empathy_call_window_status_message (window, _("Connecting..."));
+  empathy_call_window_status_message (window, _("Connecting"));
   priv->call_state = CONNECTING;
 
   if (priv->outgoing)
@@ -711,6 +797,214 @@ empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
         EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
 }
 
+static void
+disable_camera (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_OFF)
+    return;
+
+  DEBUG ("Disable camera");
+
+  display_video_preview (self, FALSE);
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    empathy_call_window_set_send_video (self, FALSE);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_on), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_preview), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
+      CAMERA_STATE_OFF);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_OFF;
+}
+
+static void
+tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_OFF)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  disable_camera (self);
+}
+
+static void
+enable_preview (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_PREVIEW)
+    return;
+
+  DEBUG ("Enable preview");
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    /* preview is already displayed so we just have to stop sending */
+    empathy_call_window_set_send_video (self, FALSE);
+
+  display_video_preview (self, TRUE);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_on), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_preview), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
+      CAMERA_STATE_PREVIEW);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_PREVIEW;
+}
+
+static void
+tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_PREVIEW)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  enable_preview (self);
+}
+
+static void
+enable_camera (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    return;
+
+  if (priv->video_input == NULL)
+    {
+      DEBUG ("Can't enable camera, no input");
+      return;
+    }
+
+
+  DEBUG ("Enable camera");
+
+  empathy_call_window_set_send_video (self, TRUE);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_preview), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_on), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
+      CAMERA_STATE_ON);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_ON;
+}
+
+static void
+tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_ON)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  enable_camera (self);
+}
+
+static void
+action_camera_change_cb (GtkRadioAction *action,
+    GtkRadioAction *current,
+    EmpathyCallWindow *self)
+{
+  CameraState state;
+
+  state = gtk_radio_action_get_current_value (current);
+
+  switch (state)
+    {
+      case CAMERA_STATE_OFF:
+        disable_camera (self);
+        break;
+
+      case CAMERA_STATE_PREVIEW:
+        enable_preview (self);
+        break;
+
+      case CAMERA_STATE_ON:
+        enable_camera (self);
+        break;
+
+      default:
+        g_assert_not_reached ();
+    }
+}
+
+static void
+create_pipeline (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
+
+  g_assert (priv->pipeline == NULL);
+
+  priv->pipeline = gst_pipeline_new (NULL);
+  priv->pipeline_playing = FALSE;
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+  priv->bus_message_source_id = gst_bus_add_watch (bus,
+      empathy_call_window_bus_message, self);
+
+  g_object_unref (bus);
+}
+
+
 static void
 empathy_call_window_init (EmpathyCallWindow *self)
 {
@@ -720,7 +1014,6 @@ empathy_call_window_init (EmpathyCallWindow *self)
   GtkWidget *h;
   GtkWidget *arrow;
   GtkWidget *page;
-  GstBus *bus;
   gchar *filename;
   GKeyFile *keyfile;
   GError *error = NULL;
@@ -733,13 +1026,15 @@ empathy_call_window_init (EmpathyCallWindow *self)
     "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,
-    "always_show_preview", &priv->always_show_preview,
     "ui_manager", &priv->ui_manager,
     "menufullscreen", &priv->menu_fullscreen,
+    "camera_off", &priv->tool_button_camera_off,
+    "camera_preview", &priv->tool_button_camera_preview,
+    "camera_on", &priv->tool_button_camera_on,
+    "action_camera_off",  &priv->action_camera,
+    "action_camera_preview",  &priv->action_camera_preview,
     NULL);
   g_free (filename);
 
@@ -749,11 +1044,11 @@ empathy_call_window_init (EmpathyCallWindow *self)
     "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,
-    "always_show_preview", "toggled",
-        empathy_call_window_always_show_preview_toggled_cb,
     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
+    "camera_off", "toggled", tool_button_camera_off_toggled_cb,
+    "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
+    "camera_on", "toggled", tool_button_camera_on_toggled_cb,
+    "action_camera_off", "changed", action_camera_change_cb,
     NULL);
 
   priv->lock = g_mutex_new ();
@@ -765,14 +1060,50 @@ empathy_call_window_init (EmpathyCallWindow *self)
                                   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));
-  priv->bus_message_source_id = gst_bus_add_watch (bus,
-      empathy_call_window_bus_message, self);
+  /* 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);
+  gtk_box_pack_start (GTK_BOX (priv->content_hbox),
+      priv->remote_user_output_frame, TRUE, TRUE,
+      CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+
+  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);
+
+  gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
+      priv->remote_user_output_hbox);
+
+  /* self user output frame */
+  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->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);
+
+  create_pipeline (self);
+  create_video_output_widget (self);
+  create_audio_input (self);
+  create_audio_output (self);
+  create_video_input (self);
 
   priv->fsnotifier = fs_element_added_notifier_new ();
   fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
 
+  /* The call will be started as soon the pipeline is playing */
+  priv->start_call_when_playing = TRUE;
+
   keyfile = g_key_file_new ();
   filename = empathy_file_lookup ("element-properties", "data");
   if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
@@ -788,30 +1119,14 @@ empathy_call_window_init (EmpathyCallWindow *self)
     }
   g_free (filename);
 
-
-  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);
-  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->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);
   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
       FALSE, FALSE, 0);
-  empathy_call_window_setup_self_frame (bus, self);
 
   empathy_call_window_setup_toolbar (self);
 
-  g_object_unref (bus);
-
   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
@@ -826,15 +1141,9 @@ empathy_call_window_init (EmpathyCallWindow *self)
   priv->sidebar = empathy_sidebar_new ();
   g_signal_connect (G_OBJECT (priv->sidebar),
     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
-  g_signal_connect (G_OBJECT (priv->sidebar),
-    "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
-  gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
-
-  priv->dtmf_panel = empathy_call_window_create_dtmf (self);
-  empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
-    priv->dtmf_panel);
-
-  gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
+  g_signal_connect (G_OBJECT (priv->sidebar),
+    "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
+  gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
 
   page = empathy_call_window_create_audio_input (self);
   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
@@ -844,6 +1153,13 @@ empathy_call_window_init (EmpathyCallWindow *self)
   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
     page);
 
+  priv->dtmf_panel = empathy_call_window_create_dtmf (self);
+  empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
+    priv->dtmf_panel);
+
+  gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
+
+
   gtk_widget_show_all (top_vbox);
 
   gtk_widget_hide (priv->sidebar);
@@ -870,6 +1186,8 @@ empathy_call_window_init (EmpathyCallWindow *self)
 
   g_object_ref (priv->ui_manager);
   g_object_unref (gui);
+
+  empathy_geometry_bind (GTK_WINDOW (self), "call-window");
 }
 
 /* Instead of specifying a width and a height, we specify only one size. That's
@@ -889,11 +1207,14 @@ init_contact_avatar_with_size (EmpathyContact *contact,
 
   if (pixbuf_avatar == NULL)
     {
-      pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
-          size);
+      pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
+          EMPATHY_IMAGE_AVATAR_DEFAULT, size);
     }
 
   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
+
+  if (pixbuf_avatar != NULL)
+    g_object_unref (pixbuf_avatar);
 }
 
 static void
@@ -1021,6 +1342,14 @@ empathy_call_window_constructed (GObject *object)
 
   empathy_call_window_setup_avatars (self, priv->handler);
   empathy_call_window_set_state_connecting (self);
+
+  if (!empathy_call_handler_has_initial_video (priv->handler))
+    {
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
+    }
+  /* If call has InitialVideo, the preview will be started once the call has
+   * been started (start_call()). */
 }
 
 static void empathy_call_window_dispose (GObject *object);
@@ -1087,6 +1416,7 @@ static void
 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
     GParamSpec *property, EmpathyCallWindow *self)
 {
+  DEBUG ("video stream changed");
   empathy_call_window_update_avatars_visibility (call, self);
 }
 
@@ -1106,15 +1436,22 @@ empathy_call_window_dispose (GObject *object)
 
   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);
+    {
+      empathy_call_handler_stop_call (priv->handler);
+      g_object_unref (priv->handler);
+    }
   priv->handler = NULL;
 
+  if (priv->bus_message_source_id != 0)
+    {
+      g_source_remove (priv->bus_message_source_id);
+      priv->bus_message_source_id = 0;
+    }
+
   if (priv->pipeline != NULL)
     g_object_unref (priv->pipeline);
   priv->pipeline = NULL;
@@ -1135,6 +1472,10 @@ empathy_call_window_dispose (GObject *object)
     g_object_unref (priv->video_tee);
   priv->video_tee = NULL;
 
+  if (priv->liveadder != NULL)
+    gst_object_unref (priv->liveadder);
+  priv->liveadder = NULL;
+
   if (priv->fsnotifier != NULL)
     g_object_unref (priv->fsnotifier);
   priv->fsnotifier = NULL;
@@ -1147,6 +1488,10 @@ empathy_call_window_dispose (GObject *object)
     g_object_unref (priv->ui_manager);
   priv->ui_manager = NULL;
 
+  if (priv->fullscreen != NULL)
+    g_object_unref (priv->fullscreen);
+  priv->fullscreen = NULL;
+
   if (priv->contact != NULL)
     {
       g_signal_handlers_disconnect_by_func (priv->contact,
@@ -1173,12 +1518,6 @@ empathy_call_window_finalize (GObject *object)
       priv->video_output_motion_handler_id = 0;
     }
 
-  if (priv->bus_message_source_id != 0)
-    {
-      g_source_remove (priv->bus_message_source_id);
-      priv->bus_message_source_id = 0;
-    }
-
   /* free any data held directly by the object here */
   g_mutex_free (priv->lock);
 
@@ -1214,7 +1553,7 @@ empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
-  if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
+  if (type != FS_MEDIA_TYPE_VIDEO)
     return TRUE;
 
   if (direction == FS_DIRECTION_RECV)
@@ -1248,21 +1587,9 @@ empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
         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;
-
       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
           empathy_call_window_mic_volume_changed_cb, self);
 
-      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;
@@ -1274,6 +1601,11 @@ empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
       priv->liveadder = NULL;
       priv->funnel = NULL;
 
+      create_pipeline (self);
+      /* Call will be started when user will hit the 'redial' button */
+      priv->start_call_when_playing = FALSE;
+      gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+
       return TRUE;
     }
   else
@@ -1286,11 +1618,14 @@ empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
 }
 
 static gboolean
-empathy_call_window_disconnected (EmpathyCallWindow *self)
+empathy_call_window_disconnected (EmpathyCallWindow *self,
+    gboolean restart)
 {
   gboolean could_disconnect = FALSE;
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
+  gboolean could_reset_pipeline;
+
+  could_reset_pipeline = empathy_call_window_reset_pipeline (self);
 
   if (priv->call_state == CONNECTING)
       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
@@ -1300,8 +1635,6 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
 
   if (could_reset_pipeline)
     {
-      gboolean initial_video = empathy_call_handler_has_initial_video (
-          priv->handler);
       g_mutex_lock (priv->lock);
 
       g_timer_stop (priv->timer);
@@ -1312,33 +1645,52 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
 
       g_mutex_unlock (priv->lock);
 
+      if (!restart)
+        /* We are about to destroy the window, no need to update it or create
+         * a video preview */
+        return TRUE;
+
       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);
+      /* Unsensitive the camera and mic button */
+      gtk_widget_set_sensitive (priv->tool_button_camera_on, 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);
+
+      /* Be sure that the mic button is enabled */
       gtk_toggle_tool_button_set_active (
           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
 
+      if (priv->camera_state == CAMERA_STATE_ON)
+        {
+          /* Enable the 'preview' button as we are not sending atm. */
+          gtk_toggle_tool_button_set_active (
+              GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_preview), TRUE);
+        }
+      else if (priv->camera_state == CAMERA_STATE_PREVIEW)
+        {
+          /* Restart the preview with the new pipeline. */
+          display_video_preview (self, TRUE);
+        }
+
       gtk_progress_bar_set_fraction (
           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
 
-      gtk_widget_hide (priv->video_output);
+      /* destroy the video output; it will be recreated when we'll redial */
+      gtk_widget_destroy (priv->video_output);
+      priv->video_output = NULL;
+
       gtk_widget_show (priv->remote_user_avatar_widget);
 
       priv->sending_video = FALSE;
       priv->call_started = FALSE;
 
       could_disconnect = TRUE;
+
+      /* TODO: display the self avatar of the preview (depends if the "Always
+       * Show Video Preview" is enabled or not) */
     }
 
   return could_disconnect;
@@ -1352,7 +1704,8 @@ empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
-  if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
+  if (empathy_call_window_disconnected (self, TRUE) &&
+      priv->call_state == REDIALING)
       empathy_call_window_restart_call (self);
 }
 
@@ -1408,28 +1761,79 @@ empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GstPad *pad;
+  GstElement *output;
 
   if (priv->funnel == NULL)
     {
-      GstElement *output;
-
       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
         (priv->video_output));
 
       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
 
-      gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
-      gst_bin_add (GST_BIN (priv->pipeline), output);
+      if (!priv->funnel)
+        {
+          g_warning ("Could not create fsfunnel");
+          return NULL;
+        }
+
+      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
+        {
+          gst_object_unref (priv->funnel);
+          priv->funnel = NULL;
+          g_warning ("Could  not add funnel to pipeline");
+          return NULL;
+        }
+
+      if (!gst_bin_add (GST_BIN (priv->pipeline), output))
+        {
+          g_warning ("Could not add the video output widget to the pipeline");
+          goto error;
+        }
+
+      if (!gst_element_link (priv->funnel, output))
+        {
+          g_warning ("Could not link output sink to funnel");
+          goto error_output_added;
+        }
 
-      gst_element_link (priv->funnel, output);
+      if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start video sink");
+          goto error_output_added;
+        }
 
-      gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
-      gst_element_set_state (output, GST_STATE_PLAYING);
+      if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start funnel");
+          goto error_output_added;
+        }
     }
 
   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
 
+  if (!pad)
+    g_warning ("Could not get request pad from funnel");
+
   return pad;
+
+
+ error_output_added:
+
+  gst_element_set_locked_state (priv->funnel, TRUE);
+  gst_element_set_locked_state (output, TRUE);
+
+  gst_element_set_state (priv->funnel, GST_STATE_NULL);
+  gst_element_set_state (output, GST_STATE_NULL);
+
+  gst_bin_remove (GST_BIN (priv->pipeline), output);
+  gst_element_set_locked_state (output, FALSE);
+
+ error:
+
+  gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
+  priv->funnel = NULL;
+
+  return NULL;
 }
 
 /* Called with global lock held */
@@ -1438,23 +1842,114 @@ empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GstPad *pad;
+  GstElement *filter;
+  GError *gerror = NULL;
 
   if (priv->liveadder == NULL)
     {
       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
 
-      gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
-      gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
+      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
+        {
+          g_warning ("Could not add liveadder to the pipeline");
+          goto error_add_liveadder;
+        }
+      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
+        {
+          g_warning ("Could not add audio sink to pipeline");
+          goto error_add_output;
+        }
+
+      if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start liveadder");
+          goto error;
+        }
+
+      if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start audio sink");
+          goto error;
+        }
+
+      if (GST_PAD_LINK_FAILED (
+              gst_element_link (priv->liveadder, priv->audio_output)))
+        {
+          g_warning ("Could not link liveadder to audio output");
+          goto error;
+        }
+    }
+
+  filter = gst_parse_bin_from_description (
+      "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
+  if (filter == NULL)
+    {
+      g_warning ("Could not make audio conversion filter: %s", gerror->message);
+      g_clear_error (&gerror);
+      goto error;
+    }
 
-      gst_element_link (priv->liveadder, priv->audio_output);
+  if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
+    {
+      g_warning ("Could not add audio conversion filter to pipeline");
+      gst_object_unref (filter);
+      goto error;
+    }
+
+  if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+    {
+      g_warning ("Could not start audio conversion filter");
+      goto error_filter;
+    }
 
-      gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
-      gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
+  if (!gst_element_link (filter, priv->liveadder))
+    {
+      g_warning ("Could not link audio conversion filter to liveadder");
+      goto error_filter;
     }
 
-  pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
+  pad = gst_element_get_static_pad (filter, "sink");
+
+  if (pad == NULL)
+    {
+      g_warning ("Could not get sink pad from filter");
+      goto error_filter;
+    }
 
   return pad;
+
+ error_filter:
+
+  gst_element_set_locked_state (filter, TRUE);
+  gst_element_set_state (filter, GST_STATE_NULL);
+  gst_bin_remove (GST_BIN (priv->pipeline), filter);
+
+ error:
+
+  gst_element_set_locked_state (priv->liveadder, TRUE);
+  gst_element_set_locked_state (priv->audio_output, TRUE);
+
+  gst_element_set_state (priv->liveadder, GST_STATE_NULL);
+  gst_element_set_state (priv->audio_output, GST_STATE_NULL);
+
+  gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
+
+ error_add_output:
+
+  gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
+
+  gst_element_set_locked_state (priv->liveadder, FALSE);
+  gst_element_set_locked_state (priv->audio_output, FALSE);
+
+ error_add_liveadder:
+
+  if (priv->liveadder != NULL)
+    {
+      gst_object_unref (priv->liveadder);
+      priv->liveadder = NULL;
+    }
+
+  return NULL;
 }
 
 static gboolean
@@ -1601,7 +2096,7 @@ media_stream_error_to_txt (EmpathyCallWindow *self,
             "product=Telepathy&amp;component=%s", cm);
 
         result = g_strdup_printf (
-            _("Something not expected happened in a Telepathy component. "
+            _("Something unexpected happened in a Telepathy component. "
               "Please <a href=\"%s\">report this bug</a> and attach "
               "logs gathered from the 'Debug' window in the Help menu."), url);
 
@@ -1676,8 +2171,9 @@ empathy_call_window_connected (gpointer user_data)
 
   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);
+  tp_g_signal_connect_object (call, "notify::video-stream",
+    G_CALLBACK (empathy_call_window_video_stream_changed_cb),
+    self, 0);
 
   if (empathy_tp_call_has_dtmf (call))
     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
@@ -1688,13 +2184,10 @@ empathy_call_window_connected (gpointer user_data)
   priv->sending_video = can_send_video ?
     empathy_tp_call_is_sending_video (call) : FALSE;
 
-  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),
+      GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
       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_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
 
   gtk_action_set_sensitive (priv->redial, FALSE);
   gtk_widget_set_sensitive (priv->redial_button, FALSE);
@@ -1719,12 +2212,13 @@ empathy_call_window_connected (gpointer user_data)
 
 
 /* Called from the streaming thread */
-static void
+static gboolean
 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
   GstPad *src, guint media_type, gpointer user_data)
 {
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  gboolean retval = FALSE;
 
   GstPad *pad;
 
@@ -1751,55 +2245,119 @@ empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
         g_assert_not_reached ();
     }
 
-  gst_pad_link (src, pad);
+  if (pad == NULL)
+    goto out;
+
+  if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
+      g_warning ("Could not link %s sink pad",
+          media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
+  else
+      retval = TRUE;
+
   gst_object_unref (pad);
 
+ out:
+
+  /* If no sink could be linked, try to add fakesink to prevent the whole call
+   * aborting */
+
+  if (!retval)
+    {
+      GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
+
+      if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
+        {
+          GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
+          if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
+              GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
+            {
+              gst_element_set_locked_state (fakesink, TRUE);
+              gst_element_set_state (fakesink, GST_STATE_NULL);
+              gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
+            }
+          else
+            {
+              g_debug ("Could not link real sink, linked fakesink instead");
+            }
+          gst_object_unref (sinkpad);
+        }
+      else
+        {
+          gst_object_unref (fakesink);
+        }
+    }
+
+
   g_mutex_unlock (priv->lock);
+
+  return TRUE;
 }
 
-/* Called from the streaming thread */
-static void
+static gboolean
 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
   GstPad *sink, guint media_type, gpointer user_data)
 {
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GstPad *pad;
+  gboolean retval = FALSE;
 
   switch (media_type)
     {
       case TP_MEDIA_STREAM_TYPE_AUDIO:
-        gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
+        if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
+          {
+            g_warning ("Could not add audio source to pipeline");
+            break;
+          }
 
         pad = gst_element_get_static_pad (priv->audio_input, "src");
-        gst_pad_link (pad, sink);
+        if (!pad)
+          {
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            g_warning ("Could not get source pad from audio source");
+            break;
+          }
+
+        if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
+          {
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            g_warning ("Could not link audio source to farsight");
+            break;
+          }
+
+        if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+          {
+            g_warning ("Could not start audio source");
+            gst_element_set_state (priv->audio_input, GST_STATE_NULL);
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            break;
+          }
 
-        gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
+        retval = TRUE;
         break;
       case TP_MEDIA_STREAM_TYPE_VIDEO:
         if (priv->video_input != NULL)
           {
-            EmpathyTpCall *call;
-            g_object_get (priv->handler, "tp-call", &call, NULL);
-
-            if (empathy_tp_call_is_sending_video (call))
-              {
-                display_video_preview (self, TRUE);
-              }
-
-            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);
+                if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
+                  {
+                    g_warning ("Could not link videp soure input pipeline");
+                    break;
+                  }
+                gst_object_unref (pad);
               }
+
+            retval = TRUE;
           }
         break;
       default:
         g_assert_not_reached ();
     }
 
+  return retval;
 }
 
 static void
@@ -1808,6 +2366,9 @@ empathy_call_window_remove_video_input (EmpathyCallWindow *self)
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GstElement *preview;
 
+  disable_camera (self);
+
+  DEBUG ("remove video input");
   preview = empathy_video_widget_get_element (
     EMPATHY_VIDEO_WIDGET (priv->video_preview));
 
@@ -1825,15 +2386,26 @@ empathy_call_window_remove_video_input (EmpathyCallWindow *self)
   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_show (priv->self_user_avatar_widget);
+  gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
+  gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
 }
 
+static void
+start_call (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  priv->call_started = TRUE;
+  empathy_call_handler_start_call (priv->handler,
+      gtk_get_current_event_time ());
+
+  if (empathy_call_handler_has_initial_video (priv->handler))
+    {
+      /* Enable 'send video' buttons and display the preview */
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
+    }
+}
 
 static gboolean
 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
@@ -1860,9 +2432,11 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
             if (newstate == GST_STATE_PAUSED)
               {
-                priv->call_started = TRUE;
-                empathy_call_handler_start_call (priv->handler);
                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+                priv->pipeline_playing = TRUE;
+
+                if (priv->start_call_when_playing)
+                  start_call (self);
               }
           }
         break;
@@ -1887,7 +2461,7 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
             }
           else
             {
-              empathy_call_window_disconnected (self);
+              empathy_call_window_disconnected (self, TRUE);
             }
           g_error_free (error);
           g_free (debug);
@@ -1929,10 +2503,10 @@ call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
   if (call == NULL)
     return;
 
-  empathy_signal_connect_weak (call, "audio-stream-error",
-      G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
-  empathy_signal_connect_weak (call, "video-stream-error",
-      G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
+  tp_g_signal_connect_object (call, "audio-stream-error",
+      G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
+  tp_g_signal_connect_object (call, "video-stream-error",
+      G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
 
   g_object_unref (call);
 }
@@ -1959,10 +2533,12 @@ empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
   g_object_get (priv->handler, "tp-call", &call, NULL);
   if (call != NULL)
     {
-      empathy_signal_connect_weak (call, "audio-stream-error",
-        G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
-      empathy_signal_connect_weak (call, "video-stream-error",
-        G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
+      tp_g_signal_connect_object (call, "audio-stream-error",
+        G_CALLBACK (empathy_call_window_audio_stream_error), window,
+        0);
+      tp_g_signal_connect_object (call, "video-stream-error",
+        G_CALLBACK (empathy_call_window_video_stream_error), window,
+        0);
 
       g_object_unref (call);
     }
@@ -2154,71 +2730,15 @@ empathy_call_window_set_send_video (EmpathyCallWindow *window,
 
   /* When we start sending video, we want to show the video preview by
      default. */
-  if (send)
-    {
-      display_video_preview (window, TRUE);
-    }
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-  empathy_tp_call_request_video_stream_direction (call, send);
-  g_object_unref (call);
-}
-
-static void
-empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *window)
-{
-  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;
-
-  empathy_call_window_set_send_video (window, active);
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
-}
-
-static void
-empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  gboolean active;
+  display_video_preview (window, send);
 
   if (priv->call_state != CONNECTED)
     return;
 
-  active = (gtk_toggle_action_get_active (toggle));
-
-  if (priv->sending_video == active)
-    return;
-
-  empathy_call_window_set_send_video (window, active);
-  gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
-}
-
-static void
-empathy_call_window_always_show_preview_toggled_cb (GtkToggleAction *toggle,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (gtk_toggle_action_get_active (toggle))
-    {
-      display_video_preview (window, TRUE);
-    }
-  else
-    {
-      /* disable preview if we are not sending */
-      if (!priv->sending_video)
-        display_video_preview (window, FALSE);
-    }
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+  DEBUG ("%s sending video", send ? "start": "stop");
+  empathy_tp_call_request_video_stream_direction (call, send);
+  g_object_unref (call);
 }
 
 static void
@@ -2228,9 +2748,6 @@ 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)
@@ -2276,26 +2793,20 @@ static void
 empathy_call_window_hangup_cb (gpointer object,
                                EmpathyCallWindow *window)
 {
-  if (empathy_call_window_disconnected (window))
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  empathy_call_handler_stop_call (priv->handler);
+
+  if (empathy_call_window_disconnected (window, FALSE))
     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));
-  priv->bus_message_source_id = 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);
+  create_video_output_widget (window);
 
   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
@@ -2305,20 +2816,17 @@ empathy_call_window_restart_call (EmpathyCallWindow *window)
    * been updated during that time. That's why we manually update it here */
   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, 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);
+  if (priv->pipeline_playing)
+    start_call (window);
+  else
+    /* call will be started when the pipeline is ready */
+    priv->start_call_when_playing = TRUE;
+
+
   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);
@@ -2447,3 +2955,36 @@ empathy_call_window_volume_changed_cb (GtkScaleButton *button,
   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
     value);
 }
+
+/* block all the signals related to camera control widgets. This is useful
+ * when we are manually updating the UI and so don't want to fire the
+ * callbacks */
+static void
+block_camera_control_signals (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_block_by_func (priv->tool_button_camera_off,
+      tool_button_camera_off_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
+      tool_button_camera_preview_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->tool_button_camera_on,
+      tool_button_camera_on_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->action_camera,
+      action_camera_change_cb, self);
+}
+
+static void
+unblock_camera_control_signals (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
+      tool_button_camera_off_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
+      tool_button_camera_preview_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
+      tool_button_camera_on_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->action_camera,
+      action_camera_change_cb, self);
+}