diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am
index 2a4438e..3b997b0 100644
--- a/libempathy-gtk/Makefile.am
+++ b/libempathy-gtk/Makefile.am
@@ -54,6 +54,7 @@ libempathy_gtk_handwritten_source =            	\
 	empathy-presence-chooser.c		\
 	empathy-profile-chooser.c		\
 	empathy-smiley-manager.c		\
+	empathy-sound.c				\
 	empathy-spell.c				\
 	empathy-status-preset-dialog.c		\
 	empathy-theme-boxes.c			\
@@ -113,6 +114,7 @@ libempathy_gtk_headers =			\
 	empathy-presence-chooser.h		\
 	empathy-profile-chooser.h		\
 	empathy-smiley-manager.h		\
+	empathy-sound.h				\
 	empathy-spell.h				\
 	empathy-status-preset-dialog.h		\
 	empathy-theme-boxes.h			\
diff --git a/libempathy-gtk/empathy-sound.c b/libempathy-gtk/empathy-sound.c
new file mode 100644
index 0000000..f5afd57
--- /dev/null
+++ b/libempathy-gtk/empathy-sound.c
@@ -0,0 +1,306 @@
+/*
+ * empathy-sound.c - Various sound related utility functions.
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <config.h>
+
+#include "empathy-sound.h"
+
+#include <canberra-gtk.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
+#include <libempathy/empathy-debug.h>
+#include <libempathy/empathy-utils.h>
+
+#include "empathy-conf.h"
+
+typedef struct {
+  EmpathySound sound_id;
+  const char * event_ca_id;
+  const char * event_ca_description;
+  const char * gconf_key;
+} EmpathySoundEntry;
+
+typedef struct {
+  GtkWidget *widget;
+  gint sound_id;
+  guint play_interval;
+  guint event_soure_id;
+} EmpathyRepeatableSound;
+
+/* NOTE: these entries MUST be in the same order than EmpathySound enum */
+static EmpathySoundEntry sound_entries[LAST_EMPATHY_SOUND] = {
+  { EMPATHY_SOUND_MESSAGE_INCOMING, "message-new-instant",
+    N_("Received an instant message"), EMPATHY_PREFS_SOUNDS_INCOMING_MESSAGE } ,
+  { EMPATHY_SOUND_MESSAGE_OUTGOING, "message-sent-instant",
+    N_("Sent an instant message"), EMPATHY_PREFS_SOUNDS_OUTGOING_MESSAGE } ,
+  { EMPATHY_SOUND_CONVERSATION_NEW, "message-new-instant",
+    N_("Incoming chat request"), EMPATHY_PREFS_SOUNDS_NEW_CONVERSATION },
+  { EMPATHY_SOUND_CONTACT_CONNECTED, "service-login",
+    N_("Contact connected"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGIN },
+  { EMPATHY_SOUND_CONTACT_DISCONNECTED, "service-logout",
+    N_("Contact disconnected"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGOUT },
+  { EMPATHY_SOUND_ACCOUNT_CONNECTED, "service-login",
+    N_("Connected to server"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGIN },
+  { EMPATHY_SOUND_ACCOUNT_DISCONNECTED, "service-logout",
+    N_("Disconnected from server"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGOUT },
+  { EMPATHY_SOUND_PHONE_INCOMING, "phone-incoming-call",
+    N_("Incoming voice call"), NULL },
+  { EMPATHY_SOUND_PHONE_OUTGOING, "phone-outgoing-calling",
+    N_("Outgoing voice call"), NULL },
+  { EMPATHY_SOUND_PHONE_HANGUP, "phone-hangup",
+    N_("Voice call ended"), NULL },
+};
+
+/* An hash table containing currently repeating sounds. The format is the
+   following:
+   Key: An EmpathySound
+   Value : The EmpathyRepeatableSound associated with that EmpathySound. */
+static GHashTable *repeating_sounds;
+
+static void playing_finished_cb (ca_context *c, guint id, int error_code,
+  gpointer user_data);
+
+static gboolean
+empathy_sound_pref_is_enabled (const char *key)
+{
+  EmpathyConf *conf;
+  gboolean res;
+
+  conf = empathy_conf_get ();
+  res = FALSE;
+
+  empathy_conf_get_bool (conf, EMPATHY_PREFS_SOUNDS_ENABLED, &res);
+
+  if (!res)
+    return FALSE;
+
+  if (!empathy_check_available_state ())
+    {
+      empathy_conf_get_bool (conf, EMPATHY_PREFS_SOUNDS_DISABLED_AWAY, &res);
+
+      if (res)
+        return FALSE;
+    }
+
+  empathy_conf_get_bool (conf, key, &res);
+
+  return res;
+}
+
+/**
+ * empathy_sound_stop:
+ * @sound_id: The #EmpathySound to stop playing.
+ * @timeout_before_replay: The amount of time, in milliseconds, between two
+ *                         consecutive play.
+ *
+ * Stop playing a sound. If it has been stated in loop with
+ * empathy_sound_start_playing (), it will also stop replaying.
+ */
+void
+empathy_sound_stop (EmpathySound sound_id)
+{
+  EmpathySoundEntry *entry;
+  gpointer data;
+  gpointer repeatable_sound_key;
+
+  g_return_if_fail (sound_id < LAST_EMPATHY_SOUND);
+
+  entry = &(sound_entries[sound_id]);
+  g_return_if_fail (entry->sound_id == sound_id);
+
+  ca_context_cancel (ca_gtk_context_get (), entry->sound_id);
+
+  /* If the sound to stop is a repeating sound, we remove the GSource to make
+     sure that it does not play again */
+  repeatable_sound_key = GINT_TO_POINTER (sound_id);
+
+  if (g_hash_table_lookup_extended (repeating_sounds, repeatable_sound_key,
+          &repeatable_sound_key, &data))
+    {
+      EmpathyRepeatableSound *repeatable_sound = data;
+
+      if (repeatable_sound->event_soure_id != 0)
+        g_source_remove (repeatable_sound->event_soure_id);
+
+      g_hash_table_remove (repeating_sounds, repeatable_sound_key);
+      g_slice_free (EmpathyRepeatableSound, repeatable_sound);
+
+      if (g_hash_table_size (repeating_sounds) == 0)
+        {
+          g_hash_table_unref (repeating_sounds);
+          repeating_sounds = NULL;
+        }
+    }
+}
+
+/**
+ * empathy_sound_play:
+ * @widget: The #GtkWidget from which the sound is originating.
+ * @sound_id: The #EmpathySound to play.
+ * @callback: The #ca_finish_callback_t function that will be called when the
+ *            sound has stopped playing.
+ * @user_data: user data to pass to the function.
+ *
+ * Plays a sound.
+ */
+gboolean
+empathy_sound_play_full (GtkWidget *widget, EmpathySound sound_id,
+  ca_finish_callback_t callback, gpointer user_data)
+{
+  EmpathySoundEntry *entry;
+  gboolean should_play = TRUE;
+  ca_proplist *p = NULL;
+  ca_context *c;
+  gpointer repeatable_sound_key;
+  gpointer repeatable_sound_data;
+
+  g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
+
+  entry = &(sound_entries[sound_id]);
+  g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
+
+  if (entry->gconf_key != NULL)
+    should_play = empathy_sound_pref_is_enabled (entry->gconf_key);
+
+  if (!should_play)
+    return FALSE;
+
+  /* The sound might already be playing repeatedly. If it's the case, we
+     immediadely return. */
+  repeatable_sound_key = GINT_TO_POINTER (sound_id);
+
+  if (repeating_sounds != NULL
+       && g_hash_table_lookup_extended (repeating_sounds, repeatable_sound_key,
+              NULL, &repeatable_sound_data))
+    {
+      EmpathyRepeatableSound *repeatable_sound = repeatable_sound_data;
+
+      /* If we came in that function from the timeout callback, then the
+         repeatable_sound's event_soure_id will be 0. In that case, we do want
+         to go on and actualy play the sound. */
+      if (repeatable_sound->event_soure_id != 0)
+        return TRUE;
+    }
+
+  c = ca_gtk_context_get ();
+  ca_context_cancel (c, entry->sound_id);
+
+  DEBUG ("Play sound \"%s\" (%s)",
+         entry->event_ca_id,
+         entry->event_ca_description);
+
+  if (ca_proplist_create (&p) < 0)
+    goto failed;
+
+  if (ca_proplist_sets (p, CA_PROP_EVENT_ID, entry->event_ca_id) < 0)
+    goto failed;
+
+  if (ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION,
+      gettext (entry->event_ca_id)) < 0)
+    goto failed;
+
+  if (ca_gtk_proplist_set_for_widget (p, widget) < 0)
+    goto failed;
+
+  ca_context_play_full (ca_gtk_context_get (), entry->sound_id,
+    p, callback, user_data);
+
+  ca_proplist_destroy (p);
+
+  return TRUE;
+
+failed:
+  if (p != NULL)
+    ca_proplist_destroy (p);
+
+  return FALSE;
+}
+
+/**
+ * empathy_sound_play:
+ * @widget: The #GtkWidget from which the sound is originating.
+ * @sound_id: The #EmpathySound to play.
+ *
+ * Plays a sound.
+ */
+void
+empathy_sound_play (GtkWidget *widget, EmpathySound sound_id)
+{
+  empathy_sound_play_full (widget, sound_id, NULL, NULL);
+}
+
+static gboolean
+playing_timeout_cb (gpointer data)
+{
+  EmpathyRepeatableSound *repeatable_sound = data;
+
+  repeatable_sound->event_soure_id = 0;
+
+  empathy_sound_play_full (repeatable_sound->widget, repeatable_sound->sound_id,
+      playing_finished_cb, data);
+
+  return FALSE;
+}
+
+static void
+playing_finished_cb (ca_context *c, guint id, int error_code,
+  gpointer user_data)
+{
+  EmpathyRepeatableSound *repeatable_sound = user_data;
+
+  repeatable_sound->event_soure_id = g_timeout_add (
+      repeatable_sound->play_interval, playing_timeout_cb, user_data);
+}
+
+/**
+ * empathy_sound_start_playing:
+ * @widget: The #GtkWidget from which the sound is originating.
+ * @sound_id: The #EmpathySound to play.
+ * @timeout_before_replay: The amount of time, in milliseconds, between two
+ *                         consecutive play.
+ *
+ * Start playing a sound in loop. To stop the sound, call empathy_call_stop ()
+ * by passing it the same @sound_id. Note that it is not possible to play the
+ * same sound multiple times simultaneously.
+ */
+void
+empathy_sound_start_playing (GtkWidget *widget, EmpathySound sound_id,
+    guint timeout_before_replay)
+{
+  EmpathyRepeatableSound *repeatable_sound =
+    g_slice_new (EmpathyRepeatableSound);
+  gpointer repeatable_sound_key;
+
+  repeatable_sound->widget = widget;
+  repeatable_sound->sound_id = sound_id;
+  repeatable_sound->play_interval = timeout_before_replay;
+
+  empathy_sound_play_full (widget, sound_id, playing_finished_cb,
+        repeatable_sound);
+
+  if (repeating_sounds == NULL)
+    repeating_sounds = g_hash_table_new (NULL, NULL);
+
+  repeatable_sound_key = GINT_TO_POINTER (sound_id);
+
+  g_hash_table_insert (repeating_sounds, repeatable_sound_key,
+      repeatable_sound);
+}
diff --git a/libempathy-gtk/empathy-sound.h b/libempathy-gtk/empathy-sound.h
new file mode 100644
index 0000000..25692a5
--- /dev/null
+++ b/libempathy-gtk/empathy-sound.h
@@ -0,0 +1,56 @@
+/*
+ * empathy-sound.h - Various sound related utility functions.
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#ifndef __EMPATHY_SOUND_H__
+#define __EMPATHY_SOUND_H__
+
+#include <gtk/gtk.h>
+
+#include <canberra-gtk.h>
+
+G_BEGIN_DECLS
+
+/* NOTE: Keep this sync with sound_entries in empathy-sound.c */
+typedef enum {
+	EMPATHY_SOUND_MESSAGE_INCOMING = 0,
+	EMPATHY_SOUND_MESSAGE_OUTGOING,
+	EMPATHY_SOUND_CONVERSATION_NEW,
+	EMPATHY_SOUND_CONTACT_CONNECTED,
+	EMPATHY_SOUND_CONTACT_DISCONNECTED,
+	EMPATHY_SOUND_ACCOUNT_CONNECTED,
+	EMPATHY_SOUND_ACCOUNT_DISCONNECTED,
+	EMPATHY_SOUND_PHONE_INCOMING,
+	EMPATHY_SOUND_PHONE_OUTGOING,
+	EMPATHY_SOUND_PHONE_HANGUP,
+	LAST_EMPATHY_SOUND,
+} EmpathySound;
+
+void empathy_sound_play (GtkWidget *widget, EmpathySound sound_id);
+void empathy_sound_stop (EmpathySound sound_id);
+
+void empathy_sound_start_playing (GtkWidget *widget, EmpathySound sound_id,
+    guint timeout_before_replay);
+
+gboolean empathy_sound_play_full (GtkWidget *widget, EmpathySound sound_id,
+    ca_finish_callback_t callback, gpointer user_data);
+
+G_END_DECLS
+
+#endif /* #ifndef __EMPATHY_SOUND_H__*/
\ No newline at end of file
diff --git a/libempathy-gtk/empathy-ui-utils.c b/libempathy-gtk/empathy-ui-utils.c
index cf423de..227e821 100644
--- a/libempathy-gtk/empathy-ui-utils.c
+++ b/libempathy-gtk/empathy-ui-utils.c
@@ -37,7 +37,6 @@
 #include <glib/gi18n-lib.h>
 #include <gtk/gtk.h>
 #include <gio/gio.h>
-#include <canberra-gtk.h>
 
 #include <libmissioncontrol/mc-profile.h>
 
@@ -1540,140 +1539,4 @@ empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
 		G_CALLBACK (file_manager_receive_file_response_cb), handler);
 
 	gtk_widget_show (widget);
-}
-
-typedef struct {
-	EmpathySound sound_id;
-	const char * event_ca_id;
-	const char * event_ca_description;
-	const char * gconf_key;
-} EmpathySoundEntry;
-
-/* NOTE: these entries MUST be in the same order than EmpathySound enum */
-static EmpathySoundEntry sound_entries[LAST_EMPATHY_SOUND] = {
-	{ EMPATHY_SOUND_MESSAGE_INCOMING, "message-new-instant",
-	  N_("Received an instant message"), EMPATHY_PREFS_SOUNDS_INCOMING_MESSAGE } ,
-	{ EMPATHY_SOUND_MESSAGE_OUTGOING, "message-sent-instant",
-	  N_("Sent an instant message"), EMPATHY_PREFS_SOUNDS_OUTGOING_MESSAGE } ,
-	{ EMPATHY_SOUND_CONVERSATION_NEW, "message-new-instant",
-	  N_("Incoming chat request"), EMPATHY_PREFS_SOUNDS_NEW_CONVERSATION },
-	{ EMPATHY_SOUND_CONTACT_CONNECTED, "service-login",
-	  N_("Contact connected"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGIN },
-	{ EMPATHY_SOUND_CONTACT_DISCONNECTED, "service-logout",
-	  N_("Contact disconnected"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGOUT },
-	{ EMPATHY_SOUND_ACCOUNT_CONNECTED, "service-login",
-	  N_("Connected to server"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGIN },
-	{ EMPATHY_SOUND_ACCOUNT_DISCONNECTED, "service-logout",
-	  N_("Disconnected from server"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGOUT },
-	{ EMPATHY_SOUND_PHONE_INCOMING, "phone-incoming-call",
-	  N_("Incoming voice call"), NULL },
-	{ EMPATHY_SOUND_PHONE_OUTGOING, "phone-outgoing-calling",
-	  N_("Outgoing voice call"), NULL },
-	{ EMPATHY_SOUND_PHONE_HANGUP, "phone-hangup",
-	  N_("Voice call ended"), NULL },
-};
-
-
-static gboolean
-empathy_sound_pref_is_enabled (const char *key)
-{
-	EmpathyConf *conf;
-	gboolean res;
-
-	conf = empathy_conf_get ();
-	res = FALSE;
-
-	empathy_conf_get_bool (conf, EMPATHY_PREFS_SOUNDS_ENABLED, &res);
-
-	if (!res) {
-		return FALSE;
-	}
-
-	if (!empathy_check_available_state ()) {
-		empathy_conf_get_bool (conf, EMPATHY_PREFS_SOUNDS_DISABLED_AWAY,
-				       &res);
-		if (res) {
-			return FALSE;
-		}
-	}
-
-	empathy_conf_get_bool (conf, key, &res);
-
-	return res;
-}
-
-void
-empathy_sound_stop (EmpathySound sound_id)
-{
-	EmpathySoundEntry *entry;
-
-	g_return_if_fail (sound_id < LAST_EMPATHY_SOUND);
-
-	entry = &(sound_entries[sound_id]);
-	g_return_if_fail (entry->sound_id == sound_id);
-
-	ca_context_cancel (ca_gtk_context_get (), entry->sound_id);
-}
-
-
-gboolean
-empathy_sound_play_full (GtkWidget *widget, EmpathySound sound_id,
-	ca_finish_callback_t callback, gpointer user_data)
-{
-	EmpathySoundEntry *entry;
-	gboolean should_play = TRUE;
-	ca_proplist *p = NULL;
-	ca_context *c;
-
-	g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
-
-	entry = &(sound_entries[sound_id]);
-	g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
-
-	if (entry->gconf_key != NULL) {
-		should_play = empathy_sound_pref_is_enabled (entry->gconf_key);
-	}
-
-	if (!should_play)
-		return FALSE;
-
-	c = ca_gtk_context_get ();
-	ca_context_cancel (c, entry->sound_id);
-
-	DEBUG ("Play sound \"%s\" (%s)",
-	       entry->event_ca_id,
-	       entry->event_ca_description);
-
-	if (ca_proplist_create (&p) < 0)
-		goto failed;
-
-	if (ca_proplist_sets (p, CA_PROP_EVENT_ID, entry->event_ca_id) < 0)
-		goto failed;
-
-	if (ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION,
-			gettext (entry->event_ca_id)) < 0)
-		goto failed;
-
-	if (ca_gtk_proplist_set_for_widget (p, widget) < 0)
-		goto failed;
-
-	ca_context_play_full (ca_gtk_context_get (), entry->sound_id,
-		p, callback, user_data);
-
-	ca_proplist_destroy (p);
-
-	return TRUE;
-
-failed:
-	if (p != NULL)
-		ca_proplist_destroy (p);
-
-	return FALSE;
-}
-
-void
-empathy_sound_play (GtkWidget *widget, EmpathySound sound_id)
-{
-	empathy_sound_play_full (widget, sound_id, NULL, NULL);
-}
-
+}
\ No newline at end of file
diff --git a/libempathy-gtk/empathy-ui-utils.h b/libempathy-gtk/empathy-ui-utils.h
index 60d48f3..4c13846 100644
--- a/libempathy-gtk/empathy-ui-utils.h
+++ b/libempathy-gtk/empathy-ui-utils.h
@@ -34,8 +34,6 @@
 
 #include <gtk/gtk.h>
 
-#include <canberra-gtk.h>
-
 #include <libmissioncontrol/mc-account.h>
 #include <libmissioncontrol/mc-profile.h>
 
@@ -46,21 +44,6 @@
 
 G_BEGIN_DECLS
 
-/* NOTE: Keep this sync with sound_entries in empathy-ui-utils.c */
-typedef enum {
-	EMPATHY_SOUND_MESSAGE_INCOMING = 0,
-	EMPATHY_SOUND_MESSAGE_OUTGOING,
-	EMPATHY_SOUND_CONVERSATION_NEW,
-	EMPATHY_SOUND_CONTACT_CONNECTED,
-	EMPATHY_SOUND_CONTACT_DISCONNECTED,
-	EMPATHY_SOUND_ACCOUNT_CONNECTED,
-	EMPATHY_SOUND_ACCOUNT_DISCONNECTED,
-	EMPATHY_SOUND_PHONE_INCOMING,
-	EMPATHY_SOUND_PHONE_OUTGOING,
-	EMPATHY_SOUND_PHONE_HANGUP,
-	LAST_EMPATHY_SOUND,
-} EmpathySound;
-
 void            empathy_gtk_init                        (void);
 GRegex *        empathy_uri_regex_dup_singleton         (void);
 
@@ -129,15 +112,6 @@ GtkWidget * empathy_link_button_new                     (const gchar      *url,
 void        empathy_send_file_with_file_chooser         (EmpathyContact   *contact);
 void        empathy_receive_file_with_file_chooser      (EmpathyFTHandler *handler);
 
-/* Sounds */
-void        empathy_sound_play                          (GtkWidget        *widget,
-							 EmpathySound      sound_id);
-gboolean    empathy_sound_play_full                     (GtkWidget *widget,
-							 EmpathySound sound_id,
-							 ca_finish_callback_t callback,
-							 gpointer user_data);
-void        empathy_sound_stop                          (EmpathySound sound_id);
-
 G_END_DECLS
 
 #endif /*  __EMPATHY_UI_UTILS_H__ */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e80a29c..cfd9215 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -40,6 +40,7 @@ libempathy-gtk/empathy-log-window.c
 [type: gettext/glade]libempathy-gtk/empathy-log-window.ui
 [type: gettext/glade]libempathy-gtk/empathy-new-message-dialog.ui
 libempathy-gtk/empathy-presence-chooser.c
+libempathy-gtk/empathy-sound.c
 libempathy-gtk/empathy-status-preset-dialog.c
 [type: gettext/glade]libempathy-gtk/empathy-status-preset-dialog.ui
 libempathy-gtk/empathy-theme-boxes.c
diff --git a/src/empathy-call-window.c b/src/empathy-call-window.c
index 4ad1b26..5dfd7c8 100644
--- a/src/empathy-call-window.c
+++ b/src/empathy-call-window.c
@@ -44,6 +44,7 @@
 #include "empathy-call-window.h"
 
 #include "empathy-call-window-fullscreen.h"
+#include "empathy-event-manager.h"
 #include "empathy-sidebar.h"
 
 #define BUTTON_ID "empathy-call-dtmf-button-id"
@@ -60,8 +61,6 @@
 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
 
-#define CONNECTING_STATUS_TEXT _("Connecting...")
-
 /* If an video input error occurs, the error message will start with "v4l" */
 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
 
@@ -79,8 +78,16 @@ static guint signals[LAST_SIGNAL] = {0};
 
 enum {
   PROP_CALL_HANDLER = 1,
+  PROP_OUTGOING
 };
 
+typedef enum {
+  CONNECTING,
+  CONNECTED,
+  DISCONNECTED,
+  REDIALING
+} CallState;
+
 /* private structure */
 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
 
@@ -90,7 +97,10 @@ struct _EmpathyCallWindowPriv
   EmpathyCallHandler *handler;
   EmpathyContact *contact;
 
-  gboolean connected;
+  guint call_state;
+
+  /* Whether the call is outgoing or not */
+  gboolean outgoing;
 
   GtkUIManager *ui_manager;
   GtkWidget *video_output;
@@ -127,6 +137,9 @@ struct _EmpathyCallWindowPriv
      it needs to be repacked. We keep a reference on it for easier access. */
   GtkWidget *vbox;
 
+  /* Handles the outgoing ringing.  */
+  EmpathyEventManager *event_manager;
+
   gulong video_output_motion_handler_id;
 
   gdouble volume;
@@ -165,10 +178,6 @@ struct _EmpathyCallWindowPriv
   gboolean sidebar_was_visible_before_fs;
   gint original_width_before_fs;
   gint original_height_before_fs;
-
-  /* Used to indicate if we are currently redialing. If we are, as soon as the
-     channel is closed, the call is automatically re-initiated.*/
-  gboolean redialing;
 };
 
 #define GET_PRIV(o) \
@@ -650,6 +659,18 @@ empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
 }
 
 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_event_manager_start_outgoing_ring (priv->event_manager);
+}
+
+static void
 empathy_call_window_init (EmpathyCallWindow *self)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
@@ -661,6 +682,8 @@ empathy_call_window_init (EmpathyCallWindow *self)
   GstBus *bus;
   gchar *filename;
 
+  priv->event_manager = empathy_event_manager_dup_singleton ();
+
   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
   gui = empathy_builder_get_file (filename,
     "call_window_vbox", &top_vbox,
@@ -778,8 +801,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_STATUS_TEXT);
-
   priv->timer = g_timer_new ();
 
   g_object_ref (priv->ui_manager);
@@ -925,6 +946,7 @@ empathy_call_window_constructed (GObject *object)
   g_assert (priv->handler != NULL);
   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);
@@ -941,6 +963,9 @@ empathy_call_window_set_property (GObject *object,
       case PROP_CALL_HANDLER:
         priv->handler = g_value_dup_object (value);
         break;
+      case PROP_OUTGOING:
+        priv->outgoing = g_value_get_boolean (value);
+        break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -957,6 +982,9 @@ empathy_call_window_get_property (GObject *object,
       case PROP_CALL_HANDLER:
         g_value_set_object (value, priv->handler);
         break;
+      case PROP_OUTGOING:
+        g_value_set_boolean (value, priv->outgoing);
+        break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -986,6 +1014,12 @@ empathy_call_window_class_init (
   g_object_class_install_property (object_class,
     PROP_CALL_HANDLER, param_spec);
 
+  param_spec = g_param_spec_boolean ("outgoing",
+    "outgoing", "Wheter the call is outgoing or not",
+    FALSE,
+    G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+    PROP_OUTGOING, param_spec);
 }
 
 static void
@@ -1086,10 +1120,11 @@ empathy_call_window_finalize (GObject *object)
 
 
 EmpathyCallWindow *
-empathy_call_window_new (EmpathyCallHandler *handler)
+empathy_call_window_new (EmpathyCallHandler *handler, gboolean outgoing)
 {
   return EMPATHY_CALL_WINDOW (
-    g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
+    g_object_new (EMPATHY_TYPE_CALL_WINDOW,
+      "handler", handler, "outgoing", outgoing, NULL));
 }
 
 static void
@@ -1180,7 +1215,11 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
 
-  priv->connected = FALSE;
+  if (priv->call_state == CONNECTING)
+      empathy_event_manager_stop_outgoing_ring (priv->event_manager);
+
+  if (priv->call_state != REDIALING)
+    priv->call_state = DISCONNECTED;
 
   if (could_reset_pipeline)
     {
@@ -1232,11 +1271,8 @@ empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
-  if (empathy_call_window_disconnected (self) && priv->redialing)
-    {
+  if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
       empathy_call_window_restart_call (self);
-      priv->redialing = FALSE;
-    }
 }
 
 /* Called with global lock held */
@@ -1377,11 +1413,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_event_manager_stop_outgoing_ring (priv->event_manager);
     }
 
   switch (media_type)
@@ -1623,6 +1660,9 @@ empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
   if (priv->pipeline != NULL)
     gst_element_set_state (priv->pipeline, GST_STATE_NULL);
 
+  if (priv->call_state == CONNECTING)
+      empathy_event_manager_stop_outgoing_ring (priv->event_manager);
+
   return FALSE;
 }
 
@@ -1790,7 +1830,7 @@ empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   gboolean active;
 
-  if (!priv->connected)
+  if (priv->call_state != CONNECTED)
     return;
 
   active = (gtk_toggle_tool_button_get_active (toggle));
@@ -1809,7 +1849,7 @@ empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   gboolean active;
 
-  if (!priv->connected)
+  if (priv->call_state != CONNECTED)
     return;
 
   active = (gtk_toggle_action_get_active (toggle));
@@ -1922,7 +1962,8 @@ empathy_call_window_restart_call (EmpathyCallWindow *window)
   if (!empathy_call_handler_has_initial_video (priv->handler))
     gtk_widget_hide (priv->self_user_output_frame);
 
-  empathy_call_window_status_message (window, CONNECTING_STATUS_TEXT);
+  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);
@@ -1938,12 +1979,12 @@ empathy_call_window_redial_cb (gpointer object,
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
-  if (priv->connected)
-    priv->redialing = TRUE;
+  if (priv->call_state == CONNECTED)
+    priv->call_state = REDIALING;
 
   empathy_call_handler_stop_call (priv->handler);
 
-  if (!priv->connected)
+  if (priv->call_state != CONNECTED)
     empathy_call_window_restart_call (window);
 }
 
diff --git a/src/empathy-call-window.h b/src/empathy-call-window.h
index 01ea5de..f507a7b 100644
--- a/src/empathy-call-window.h
+++ b/src/empathy-call-window.h
@@ -57,7 +57,8 @@ GType empathy_call_window_get_type (void);
   (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_CALL_WINDOW, \
     EmpathyCallWindowClass))
 
-EmpathyCallWindow *empathy_call_window_new (EmpathyCallHandler *handler);
+EmpathyCallWindow *empathy_call_window_new (EmpathyCallHandler *handler,
+    gboolean outgoing);
 
 G_END_DECLS
 
diff --git a/src/empathy-chat-window.c b/src/empathy-chat-window.c
index 09f945f..3b0388c 100644
--- a/src/empathy-chat-window.c
+++ b/src/empathy-chat-window.c
@@ -50,6 +50,7 @@
 #include <libempathy-gtk/empathy-log-window.h>
 #include <libempathy-gtk/empathy-geometry.h>
 #include <libempathy-gtk/empathy-smiley-manager.h>
+#include <libempathy-gtk/empathy-sound.h>
 #include <libempathy-gtk/empathy-ui-utils.h>
 
 #include "empathy-chat-window.h"
diff --git a/src/empathy-event-manager.c b/src/empathy-event-manager.c
index 1ee619e..1883676 100644
--- a/src/empathy-event-manager.c
+++ b/src/empathy-event-manager.c
@@ -39,7 +39,7 @@
 
 #include <libempathy-gtk/empathy-images.h>
 #include <libempathy-gtk/empathy-contact-dialogs.h>
-#include <libempathy-gtk/empathy-ui-utils.h>
+#include <libempathy-gtk/empathy-sound.h>
 
 #include "empathy-event-manager.h"
 #include "empathy-main-window.h"
@@ -50,6 +50,9 @@
 
 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyEventManager)
 
+/* The time interval in milliseconds between 2 incoming and outgoing rings */
+#define MS_BETWEEN_RING 500
+
 typedef struct {
   EmpathyEventManager *manager;
   EmpathyDispatchOperation *operation;
@@ -74,9 +77,12 @@ typedef struct {
   /* Ongoing approvals */
   GSList *approvals;
 
-  /* voip ringing sound */
-  guint voip_timeout;
-  gint ringing;
+  /* We only allow one ringing of a specific type to play at any given moment.
+     Those members are used to determine if there any widgets still ringing.
+     When there are none, we stop the sound. */
+  gint incoming_ringing_nb;
+  gint outgoing_ringing_nb;
+
 } EmpathyEventManagerPriv;
 
 typedef struct _EventPriv EventPriv;
@@ -91,6 +97,13 @@ struct _EventPriv {
   gpointer user_data;
 };
 
+typedef struct _RingingInfoPriv RingingInfoPriv;
+
+struct _RingingInfoPriv {
+  gint ringing_sound;
+  gint *ringing_nb;
+};
+
 enum {
   EVENT_ADDED,
   EVENT_REMOVED,
@@ -161,79 +174,54 @@ event_free (EventPriv *event)
   g_slice_free (EventPriv, event);
 }
 
-static void event_manager_ringing_finished_cb (ca_context *c, guint id,
-  int error_code, gpointer user_data);
-
-static gboolean
-event_manager_ringing_timeout_cb (gpointer data)
-{
-  EmpathyEventManager *manager = EMPATHY_EVENT_MANAGER (data);
-  EmpathyEventManagerPriv *priv = GET_PRIV (manager);
-
-  priv->voip_timeout = 0;
-
-  empathy_sound_play_full (empathy_main_window_get (),
-      EMPATHY_SOUND_PHONE_INCOMING, event_manager_ringing_finished_cb,
-      manager);
-
-  return FALSE;
-}
-
-static gboolean
-event_manager_ringing_idle_cb (gpointer data)
+static void
+event_manager_start_ringing (RingingInfoPriv *ringing_info)
 {
-  EmpathyEventManager *manager = EMPATHY_EVENT_MANAGER (data);
-  EmpathyEventManagerPriv *priv = GET_PRIV (manager);
-
-  if (priv->ringing > 0)
-    priv->voip_timeout = g_timeout_add (500, event_manager_ringing_timeout_cb,
-      data);
+  (*(ringing_info->ringing_nb))++;
 
-  return FALSE;
+  if (*(ringing_info->ringing_nb) == 1)
+    {
+      empathy_sound_start_playing (empathy_main_window_get (),
+          ringing_info->ringing_sound, MS_BETWEEN_RING);
+    }
 }
 
 static void
-event_manager_ringing_finished_cb (ca_context *c, guint id, int error_code,
-  gpointer user_data)
+event_manager_stop_ringing (RingingInfoPriv *ringing_info)
 {
-  if (error_code == CA_ERROR_CANCELED)
-    return;
+  if (*(ringing_info->ringing_nb) > 0)
+    {
+      (*(ringing_info->ringing_nb))--;
 
-  g_idle_add (event_manager_ringing_idle_cb, user_data);
+      if (*(ringing_info->ringing_nb) > 0)
+        return;
+    }
+
+  empathy_sound_stop (ringing_info->ringing_sound);
 }
 
-static void
-event_manager_start_ringing (EmpathyEventManager *manager)
+void
+empathy_event_manager_start_outgoing_ring (EmpathyEventManager *manager)
 {
   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
+  RingingInfoPriv *ringing_info = g_slice_new (RingingInfoPriv);
 
-  priv->ringing++;
+  ringing_info->ringing_sound = EMPATHY_SOUND_PHONE_OUTGOING;
+  ringing_info->ringing_nb = &priv->outgoing_ringing_nb;
 
-  if (priv->ringing == 1)
-    {
-      empathy_sound_play_full (empathy_main_window_get (),
-        EMPATHY_SOUND_PHONE_INCOMING, event_manager_ringing_finished_cb,
-        manager);
-    }
+  event_manager_start_ringing (ringing_info);
 }
 
-static void
-event_manager_stop_ringing (EmpathyEventManager *manager)
+void
+empathy_event_manager_stop_outgoing_ring (EmpathyEventManager *manager)
 {
   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
+  RingingInfoPriv *ringing_info = g_slice_new (RingingInfoPriv);
 
-  priv->ringing--;
-
-  if (priv->ringing > 0)
-    return;
-
-  empathy_sound_stop (EMPATHY_SOUND_PHONE_INCOMING);
+  ringing_info->ringing_sound = EMPATHY_SOUND_PHONE_OUTGOING;
+  ringing_info->ringing_nb = &priv->outgoing_ringing_nb;
 
-  if (priv->voip_timeout != 0)
-    {
-      g_source_remove (priv->voip_timeout);
-      priv->voip_timeout = 0;
-    }
+  event_manager_stop_ringing (ringing_info);
 }
 
 static void
@@ -455,7 +443,11 @@ event_manager_approval_done (EventManagerApproval *approval)
           approval->operation);
       if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA)
         {
-          event_manager_stop_ringing (approval->manager);
+          RingingInfoPriv *ringing_info = g_slice_new (RingingInfoPriv);
+          ringing_info->ringing_sound = EMPATHY_SOUND_PHONE_INCOMING;
+          ringing_info->ringing_nb = &priv->incoming_ringing_nb;
+
+          event_manager_stop_ringing (ringing_info);
         }
     }
 
@@ -501,6 +493,8 @@ static void
 event_manager_media_channel_got_contact (EventManagerApproval *approval)
 {
   gchar *header;
+  EmpathyEventManagerPriv *priv = GET_PRIV (approval->manager);
+  RingingInfoPriv *ringing_info = g_slice_new (RingingInfoPriv);
 
   header = g_strdup_printf (_("Incoming call from %s"),
     empathy_contact_get_name (approval->contact));
@@ -510,7 +504,11 @@ event_manager_media_channel_got_contact (EventManagerApproval *approval)
     approval, event_channel_process_voip_func, NULL);
 
   g_free (header);
-  event_manager_start_ringing (approval->manager);
+
+  ringing_info->ringing_sound = EMPATHY_SOUND_PHONE_INCOMING;
+  ringing_info->ringing_nb = &priv->incoming_ringing_nb;
+
+  event_manager_start_ringing (ringing_info);
 }
 
 static void
@@ -981,6 +979,12 @@ event_manager_finalize (GObject *object)
 {
   EmpathyEventManagerPriv *priv = GET_PRIV (object);
 
+  if (priv->outgoing_ringing_nb > 0)
+    empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
+
+  if (priv->incoming_ringing_nb > 0)
+    empathy_sound_stop (EMPATHY_SOUND_PHONE_INCOMING);
+
   g_slist_foreach (priv->events, (GFunc) event_free, NULL);
   g_slist_free (priv->events);
   g_slist_foreach (priv->approvals, (GFunc) event_manager_approval_free, NULL);
@@ -1093,4 +1097,3 @@ empathy_event_inhibit_updates (EmpathyEvent *event_public)
 
   event->inhibit = TRUE;
 }
-
diff --git a/src/empathy-event-manager.h b/src/empathy-event-manager.h
index 42fd0c0..d862a02 100644
--- a/src/empathy-event-manager.h
+++ b/src/empathy-event-manager.h
@@ -29,7 +29,8 @@
 
 G_BEGIN_DECLS
 
-#define EMPATHY_TYPE_EVENT_MANAGER         (empathy_event_manager_get_type ())
+#define EMPATHY_TYPE_EVENT_MANAGER \
+    (empathy_event_manager_get_type ())
 #define EMPATHY_EVENT_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_EVENT_MANAGER, EmpathyEventManager))
 #define EMPATHY_EVENT_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_EVENT_MANAGER, EmpathyEventManagerClass))
 #define EMPATHY_IS_EVENT_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_EVENT_MANAGER))
@@ -61,6 +62,8 @@ EmpathyEvent *       empathy_event_manager_get_top_event (EmpathyEventManager *m
 GSList *             empathy_event_manager_get_events    (EmpathyEventManager *manager);
 void                 empathy_event_activate              (EmpathyEvent        *event);
 void                 empathy_event_inhibit_updates       (EmpathyEvent        *event);
+void		     empathy_event_manager_start_outgoing_ring (EmpathyEventManager *manager);
+void		     empathy_event_manager_stop_outgoing_ring  (EmpathyEventManager *manager);
 
 G_END_DECLS
 
diff --git a/src/empathy-main-window.c b/src/empathy-main-window.c
index 5830d2b..4a47087 100644
--- a/src/empathy-main-window.c
+++ b/src/empathy-main-window.c
@@ -37,16 +37,17 @@
 #include <libempathy/empathy-contact-manager.h>
 #include <libempathy/empathy-status-presets.h>
 
+#include <libempathy-gtk/empathy-conf.h>
 #include <libempathy-gtk/empathy-contact-dialogs.h>
 #include <libempathy-gtk/empathy-contact-list-store.h>
 #include <libempathy-gtk/empathy-contact-list-view.h>
-#include <libempathy-gtk/empathy-presence-chooser.h>
-#include <libempathy-gtk/empathy-ui-utils.h>
 #include <libempathy-gtk/empathy-geometry.h>
-#include <libempathy-gtk/empathy-conf.h>
-#include <libempathy-gtk/empathy-log-window.h>
-#include <libempathy-gtk/empathy-new-message-dialog.h>
 #include <libempathy-gtk/empathy-gtk-enum-types.h>
+#include <libempathy-gtk/empathy-new-message-dialog.h>
+#include <libempathy-gtk/empathy-log-window.h>
+#include <libempathy-gtk/empathy-presence-chooser.h>
+#include <libempathy-gtk/empathy-sound.h>
+#include <libempathy-gtk/empathy-ui-utils.h>
 
 #include <libmissioncontrol/mission-control.h>
 
diff --git a/src/empathy.c b/src/empathy.c
index f23ce91..00ab4c4 100644
--- a/src/empathy.c
+++ b/src/empathy.c
@@ -446,7 +446,7 @@ new_call_handler_cb (EmpathyCallFactory *factory, EmpathyCallHandler *handler,
 {
 	EmpathyCallWindow *window;
 
-	window = empathy_call_window_new (handler);
+	window = empathy_call_window_new (handler, outgoing);
 	gtk_widget_show (GTK_WIDGET (window));
 }
 
