[Telepathy-commits] [telepathy-gabble/master] bolted the new jingle engine onto gabble

Senko Rasic senko at phyrexia.lan
Tue Dec 2 04:33:55 PST 2008


---
 src/Makefile.am                            |    6 +-
 src/jingle-content.c                       |  216 ++-
 src/jingle-content.h                       |   16 +-
 src/jingle-factory.c                       |  222 ++-
 src/jingle-factory.h                       |    7 +-
 src/jingle-media-rtp.c                     |   57 +-
 src/jingle-media-rtp.h                     |    3 +
 src/jingle-session.c                       |  476 ++++-
 src/jingle-session.h                       |   22 +-
 src/jingle-transport-google.c              |  109 +-
 src/jingle-transport-iface.c               |   10 +
 src/jingle-transport-iface.h               |    2 +
 src/media-channel.c                        |  942 ++++++----
 src/media-channel.h                        |    1 -
 src/media-factory.c                        |   24 +-
 src/media-session.c                        | 2933 ----------------------------
 src/media-session.h                        |  169 --
 src/media-stream.c                         |  257 +++-
 src/media-stream.h                         |   24 +-
 src/namespaces.h                           |    3 +
 src/presence-cache.c                       |    8 +
 src/types.h                                |    6 +-
 tests/twisted/jingle/jingletest.py         |    3 +-
 tests/twisted/jingle/test-incoming-call.py |   12 +-
 24 files changed, 1809 insertions(+), 3719 deletions(-)
 delete mode 100644 src/media-session.c
 delete mode 100644 src/media-session.h

diff --git a/src/Makefile.am b/src/Makefile.am
index eca6b00..a1ec7fc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,9 +1,7 @@
 BUILT_SOURCES = \
     gabble-signals-marshal.h \
     gabble-signals-marshal.c \
-    gabble-signals-marshal.list \
-    media-session-enumtypes.h \
-    media-session-enumtypes.c
+    gabble-signals-marshal.list
 
 CLEANFILES = $(BUILT_SOURCES)
 
@@ -65,8 +63,6 @@ libgabble_convenience_la_our_sources = \
     im-factory.c \
     media-channel.h \
     media-channel.c \
-    media-session.h \
-    media-session.c \
     media-stream.h \
     media-stream.c \
     media-factory.h \
diff --git a/src/jingle-content.c b/src/jingle-content.c
index 8d23d70..0129e4b 100644
--- a/src/jingle-content.c
+++ b/src/jingle-content.c
@@ -40,6 +40,7 @@
 enum
 {
   READY,
+  NEW_CANDIDATES,
   LAST_SIGNAL
 };
 
@@ -55,6 +56,7 @@ enum
   PROP_NAME,
   PROP_SENDERS,
   PROP_STATE,
+  PROP_READY,
   LAST_PROPERTY
 };
 
@@ -66,12 +68,12 @@ struct _GabbleJingleContentPrivate
   gboolean created_by_initiator;
   JingleContentState state;
   JingleContentSenders senders;
+  gboolean ready;
 
   gchar *content_ns;
   gchar *transport_ns;
 
   GabbleJingleTransportIface *transport;
-  gboolean has_local_codecs;
 
   gboolean dispose_has_run;
 };
@@ -90,6 +92,9 @@ static const gchar *content_senders_table[] = {
 
 G_DEFINE_TYPE(GabbleJingleContent, gabble_jingle_content, G_TYPE_OBJECT);
 
+static void new_transport_candidates_cb (GabbleJingleTransportIface *trans,
+    GList *candidates, GabbleJingleContent *content);
+
 static void
 gabble_jingle_content_init (GabbleJingleContent *obj)
 {
@@ -159,6 +164,12 @@ gabble_jingle_content_get_property (GObject *object,
     case PROP_STATE:
       g_value_set_uint (value, priv->state);
       break;
+    case PROP_READY:
+      g_value_set_boolean (value, priv->ready);
+      break;
+    case PROP_CONTENT_NS:
+      g_value_set_string (value, priv->content_ns);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -189,6 +200,30 @@ gabble_jingle_content_set_property (GObject *object,
     case PROP_TRANSPORT_NS:
       g_free (priv->transport_ns);
       priv->transport_ns = g_value_dup_string (value);
+
+      /* We can't switch transports. */
+      g_assert (priv->transport == NULL);
+
+      if (priv->transport_ns != NULL) {
+          GType transport_type = GPOINTER_TO_INT (
+              g_hash_table_lookup (self->conn->jingle_factory->transports,
+                  priv->transport_ns));
+
+          g_assert (transport_type != 0);
+
+          priv->transport = g_object_new (transport_type,
+              "content", self, "transport-ns", priv->transport_ns, NULL);
+
+          g_signal_connect (priv->transport, "new-candidates",
+              (GCallback) new_transport_candidates_cb, self);
+
+      }
+      break;
+    case PROP_NAME:
+      /* can't rename */
+      g_assert (priv->name == NULL);
+
+      priv->name = g_value_dup_string (value);
       break;
     case PROP_SENDERS:
       priv->senders = g_value_get_uint (value);
@@ -196,6 +231,22 @@ gabble_jingle_content_set_property (GObject *object,
     case PROP_STATE:
       priv->state = g_value_get_uint (value);
       break;
+    case PROP_READY:
+      DEBUG ("setting content ready from %u to %u",
+          priv->ready, g_value_get_boolean (value));
+
+      if (priv->ready == g_value_get_boolean (value))
+        {
+          DEBUG ("we're already ready, doing nothing");
+          return;
+        }
+
+      priv->ready = g_value_get_boolean (value);
+
+      /* if we have pending local candidates, now's the time
+       * to transmit them */
+      gabble_jingle_transport_iface_retransmit_candidates (priv->transport);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -237,7 +288,8 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
   param_spec = g_param_spec_string ("name", "Content name",
                                     "A unique content name in the session.",
                                     NULL,
-                                    G_PARAM_READABLE |
+                                    G_PARAM_CONSTRUCT_ONLY |
+                                    G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NAME |
                                     G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_NAME, param_spec);
@@ -245,7 +297,6 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
   param_spec = g_param_spec_string ("content-ns", "Content namespace",
                                     "Namespace identifying the content type.",
                                     NULL,
-                                    G_PARAM_CONSTRUCT_ONLY |
                                     G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NAME |
                                     G_PARAM_STATIC_BLURB);
@@ -278,6 +329,15 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
                                   G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_STATE, param_spec);
 
+  param_spec = g_param_spec_boolean ("ready", "Ready?",
+                                     "A boolean signifying whether media for "
+                                     "this content is ready to be signalled.",
+                                     FALSE,
+                                     G_PARAM_READWRITE |
+                                     G_PARAM_STATIC_NAME |
+                                     G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_READY, param_spec);
+
   /* signal definitions */
 
   signals[READY] =
@@ -288,6 +348,15 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
                   NULL, NULL,
                   g_cclosure_marshal_VOID__VOID,
                   G_TYPE_NONE, 0);
+
+  signals[NEW_CANDIDATES] = g_signal_new (
+    "new-candidates",
+    G_TYPE_FROM_CLASS (cls),
+    G_SIGNAL_RUN_LAST,
+    0,
+    NULL, NULL,
+    g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
+
 }
 
 #define SET_BAD_REQ(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, txt)
@@ -295,6 +364,16 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
 #define SET_CONFLICT(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT, txt)
 
 static void
+new_transport_candidates_cb (GabbleJingleTransportIface *trans,
+    GList *candidates, GabbleJingleContent *content)
+{
+  DEBUG ("JingleContent %p: passing the signal on", content);
+
+  /* just pass the signal on */
+  g_signal_emit (content, signals[NEW_CANDIDATES], 0, candidates);
+}
+
+static void
 parse_description (GabbleJingleContent *c, LmMessageNode *desc_node,
     GError **error)
 {
@@ -314,6 +393,9 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
   LmMessageNode *trans_node, *desc_node;
   GType transport_type = 0;
   GabbleJingleTransportIface *trans = NULL;
+  JingleDialect dialect;
+
+  g_object_get (c->session, "dialect", &dialect, NULL);
 
   desc_node = lm_message_node_get_child (content_node, "description");
   trans_node = lm_message_node_get_child (content_node, "transport");
@@ -338,17 +420,28 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
       if (trans_node == NULL)
         {
           /* gtalk lj0.3 assumes google-p2p transport */
+          DEBUG ("detecting GTalk3 dialect");
+
+          dialect = JINGLE_DIALECT_GTALK3;
           g_object_set (c->session, "dialect", JINGLE_DIALECT_GTALK3, NULL);
           transport_type = GPOINTER_TO_INT (
               g_hash_table_lookup (c->conn->jingle_factory->transports, ""));
+          priv->transport_ns = g_strdup ("");
         }
     }
-
-
-  if ((trans_node == NULL) || (creator == NULL) || (name == NULL))
+  else
     {
-      SET_BAD_REQ ("missing required content attributes or elements");
-      return;
+      /* senders weren't mandatory back then */
+      if (dialect == JINGLE_DIALECT_V015) {
+        DEBUG ("old gabble detected, settings senders = both");
+        senders = "both";
+      }
+
+      if ((trans_node == NULL) || (creator == NULL) || (name == NULL) || (senders == NULL))
+        {
+          SET_BAD_REQ ("missing required content attributes or elements");
+          return;
+        }
     }
 
   /* if we didn't set it to google-p2p implicitly already, detect it */
@@ -370,7 +463,6 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
     }
 
   priv->created_by_initiator = (!tp_strdiff (creator, "initiator"));
-  DEBUG ("senders == %s", senders);
   priv->senders = _string_to_enum (content_senders_table, senders);
   if (priv->senders == JINGLE_CONTENT_SENDERS_NONE)
     {
@@ -389,6 +481,9 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
                        "transport-ns", priv->transport_ns,
                        NULL);
 
+  g_signal_connect (trans, "new-candidates",
+      (GCallback) new_transport_candidates_cb, c);
+
   /* FIXME: I think candidates can't be specified in content addition/session
    * init, so disabling this:
   gabble_jingle_transport_iface_parse_candidates (trans, trans_node, error);
@@ -413,6 +508,53 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
 }
 
 void
+gabble_jingle_content_parse_accept (GabbleJingleContent *c,
+    LmMessageNode *content_node, gboolean google_mode, GError **error)
+{
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (c);
+  const gchar *senders;
+  LmMessageNode *trans_node, *desc_node;
+
+  desc_node = lm_message_node_get_child (content_node, "description");
+  trans_node = lm_message_node_get_child (content_node, "transport");
+  senders = lm_message_node_get_attribute (content_node, "senders");
+
+  if (google_mode)
+    {
+      DEBUG ("parsing content-accept in google mode");
+
+      if (senders == NULL)
+          senders = "both";
+
+      if (trans_node == NULL)
+        {
+          DEBUG ("no transport node, assuming GTalk3 dialect");
+          /* gtalk lj0.3 assumes google-p2p transport */
+          g_object_set (c->session, "dialect", JINGLE_DIALECT_GTALK3, NULL);
+        }
+    }
+
+  DEBUG ("changing senders from %s to %s", _enum_to_string(content_senders_table, priv->senders), senders);
+  priv->senders = _string_to_enum (content_senders_table, senders);
+  if (priv->senders == JINGLE_CONTENT_SENDERS_NONE)
+    {
+      SET_BAD_REQ ("invalid content senders");
+      return;
+    }
+
+  parse_description (c, desc_node, error);
+  if (*error)
+      return;
+
+  // FIXME: this overlaps with _update_senders, maybe merge?
+  g_object_notify ((GObject *) c, "senders");
+
+  // If all went well, it means the content is finally ackd
+  priv->state = JINGLE_CONTENT_STATE_ACKNOWLEDGED;
+  g_object_notify ((GObject *) c, "state");
+}
+
+void
 gabble_jingle_content_produce_node (GabbleJingleContent *c,
   LmMessageNode *parent, gboolean full)
 {
@@ -443,7 +585,7 @@ gabble_jingle_content_produce_node (GabbleJingleContent *c,
 
       content_node = lm_message_node_add_child (parent, "content", NULL);
       lm_message_node_set_attributes (content_node,
-          "creator", priv->creator,
+          "creator", priv->created_by_initiator ? "initiator" : "responder",
           "name", priv->name,
           "senders", _enum_to_string (content_senders_table, priv->senders),
           NULL);
@@ -481,6 +623,15 @@ gabble_jingle_content_update_senders (GabbleJingleContent *c,
 }
 
 void
+gabble_jingle_content_parse_transport_info (GabbleJingleContent *self,
+  LmMessageNode *trans_node, GError **error)
+{
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
+
+  gabble_jingle_transport_iface_parse_candidates (priv->transport, trans_node, error);
+}
+
+void
 gabble_jingle_content_add_candidates (GabbleJingleContent *self, GList *li)
 {
   GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
@@ -489,45 +640,29 @@ gabble_jingle_content_add_candidates (GabbleJingleContent *self, GList *li)
 }
 
 gboolean
-gabble_jingle_content_is_ready (GabbleJingleContent *self)
+gabble_jingle_content_is_ready (GabbleJingleContent *self, gboolean for_acceptance)
 {
   GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
   JingleTransportState state;
 
-  if (!priv->has_local_codecs)
+  /* Content is ready for initiatiation the moment the media using it is ready
+   * (ie. has local codecs). But in order to accept it, it must also be
+   * connected. */
+
+  /* if local codecs are not set, we're definitely not ready */
+  if (!priv->ready)
       return FALSE;
 
+  /* we are okay for signalling content-add/session-initiate */
+  if (!for_acceptance)
+      return TRUE;
+
   g_object_get (priv->transport, "state", &state, NULL);
 
+  /* we are even okay for accepting content/session, hooray */
   return (state == JINGLE_TRANSPORT_STATE_CONNECTED);
 }
 
-/* FIXME: if local codecs are set multiple times, this will be emitted
- * multiple times - is that scenario possible at all? */
-static void
-maybe_emit_ready (GabbleJingleContent *self)
-{
-    if (!gabble_jingle_content_is_ready (self))
-        return;
-
-    g_signal_emit (self, signals[READY], 0);
-}
-
-void
-gabble_jingle_content_set_local_codecs (GabbleJingleContent *self)
-{
-  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
-  priv->has_local_codecs = TRUE;
-
-  /* FIXME: actually this function should call appropriate subclass interface
-   * method to set/push local codecs */
-
-  maybe_emit_ready (self);
-}
-
-/* FIXME: ok, situation sucks because Content sets the transport state,
- * but also catches the change notification. we ideally want user to
- * be able to access transprot directly, not through content? */
 void
 gabble_jingle_content_set_transport_state (GabbleJingleContent *self,
     JingleTransportState state)
@@ -535,6 +670,9 @@ gabble_jingle_content_set_transport_state (GabbleJingleContent *self,
   GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
 
   g_object_set (priv->transport, "state", state, NULL);
-  maybe_emit_ready (self);
+
+  /* FIXME: refactor this to use _is_ready, if neccessary 
+  if ((state == JINGLE_TRANSPORT_STATE_CONNECTED) && (priv->ready))
+      g_object_notify (G_OBJECT (self), "ready"); */
 }
 
diff --git a/src/jingle-content.h b/src/jingle-content.h
index 8ec0d37..d65bade 100644
--- a/src/jingle-content.h
+++ b/src/jingle-content.h
@@ -28,6 +28,12 @@
 G_BEGIN_DECLS
 
 typedef enum {
+  JINGLE_MEDIA_TYPE_NONE = 0,
+  JINGLE_MEDIA_TYPE_AUDIO,
+  JINGLE_MEDIA_TYPE_VIDEO
+} JingleMediaType;
+
+typedef enum {
   JINGLE_CONTENT_STATE_EMPTY = 0,
   JINGLE_CONTENT_STATE_NEW,
   JINGLE_CONTENT_STATE_SENT,
@@ -41,7 +47,7 @@ struct _JingleCandidate {
   int generation;
 
   JingleTransportProtocol protocol;
-  int preference;
+  gdouble preference;
   JingleCandidateType type;
   gchar *username;
   gchar *password;
@@ -92,10 +98,14 @@ void gabble_jingle_content_update_senders (GabbleJingleContent *c,
     LmMessageNode *content_node, GError **error);
 void gabble_jingle_content_produce_node (GabbleJingleContent *c,
   LmMessageNode *parent, gboolean full);
+void gabble_jingle_content_parse_accept (GabbleJingleContent *c,
+  LmMessageNode *content_node, gboolean google_mode, GError **error);
 
+void gabble_jingle_content_parse_transport_info (GabbleJingleContent *self,
+  LmMessageNode *trans_node, GError **error);
 void gabble_jingle_content_add_candidates (GabbleJingleContent *self, GList *li);
-gboolean gabble_jingle_content_is_ready (GabbleJingleContent *self);
-void gabble_jingle_content_set_local_codecs (GabbleJingleContent *content);
+gboolean gabble_jingle_content_is_ready (GabbleJingleContent *self, gboolean for_acceptance);
+void gabble_jingle_content_set_local_codecs (GabbleJingleContent *content, GList *li);
 void gabble_jingle_content_set_transport_state (GabbleJingleContent *content,
     JingleTransportState state);
 
diff --git a/src/jingle-factory.c b/src/jingle-factory.c
index 68c19f8..7bd8fc9 100644
--- a/src/jingle-factory.c
+++ b/src/jingle-factory.c
@@ -63,6 +63,7 @@ struct _GabbleJingleFactoryPrivate
 {
   GabbleConnection *conn;
   LmMessageHandler *jingle_cb;
+  LmMessageHandler *jingle_info_cb;
   GHashTable *sessions;
 
   gboolean dispose_has_run;
@@ -80,6 +81,9 @@ static GabbleJingleSession *create_session (GabbleJingleFactory *fac,
 static void session_terminated_cb (GabbleJingleSession *sess,
     gboolean local_terminator, GabbleJingleFactory *fac);
 
+static void connection_status_changed_cb (GabbleConnection *conn,
+    guint status, guint reason, GabbleJingleFactory *self);
+
 static void
 gabble_jingle_factory_init (GabbleJingleFactory *obj)
 {
@@ -103,6 +107,144 @@ gabble_jingle_factory_init (GabbleJingleFactory *obj)
   priv->dispose_has_run = FALSE;
 }
 
+/*
+ * jingle_info_cb
+ *
+ * Called by loudmouth when we get an incoming <iq>. This handler
+ * is concerned only with Jingle info queries.
+ */
+static LmHandlerResult
+jingle_info_cb (LmMessageHandler *handler,
+                LmConnection *lmconn,
+                LmMessage *message,
+                gpointer user_data)
+{
+  GabbleJingleFactory *fac = GABBLE_JINGLE_FACTORY (user_data);
+  GabbleJingleFactoryPrivate *priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (fac);
+  LmMessageSubType sub_type;
+  LmMessageNode *query_node, *node;
+
+  query_node = lm_message_node_get_child_with_namespace (message->node,
+      "query", NS_GOOGLE_JINGLE_INFO);
+
+  if (query_node == NULL)
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+  sub_type = lm_message_get_sub_type (message);
+
+  if (sub_type == LM_MESSAGE_SUB_TYPE_ERROR)
+    {
+      GabbleXmppError xmpp_error = XMPP_ERROR_UNDEFINED_CONDITION;
+
+      node = lm_message_node_get_child (message->node, "error");
+      if (node != NULL)
+        {
+          xmpp_error = gabble_xmpp_error_from_node (node);
+        }
+
+      DEBUG ("jingle info error: %s", gabble_xmpp_error_string (xmpp_error));
+
+      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+    }
+
+  if (sub_type != LM_MESSAGE_SUB_TYPE_RESULT &&
+      sub_type != LM_MESSAGE_SUB_TYPE_SET)
+    {
+      DEBUG ("jingle info: unexpected IQ type, ignoring");
+
+      return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+    }
+
+  if (fac->get_stun_from_jingle)
+    node = lm_message_node_get_child (query_node, "stun");
+  else
+    node = NULL;
+
+  if (node != NULL)
+    {
+      node = lm_message_node_get_child (node, "server");
+
+      if (node != NULL)
+        {
+          const gchar *server;
+          const gchar *port;
+
+          server = lm_message_node_get_attribute (node, "host");
+          port = lm_message_node_get_attribute (node, "udp");
+
+          if (server != NULL)
+            {
+              DEBUG ("jingle info: got stun server %s", server);
+              g_free (fac->stun_server);
+              fac->stun_server = g_strdup (server);
+            }
+
+          if (port != NULL)
+            {
+              DEBUG ("jingle info: got stun port %s", port);
+              fac->stun_port = atoi (port);
+            }
+        }
+    }
+
+  node = lm_message_node_get_child (query_node, "relay");
+
+  if (node != NULL)
+    {
+      node = lm_message_node_get_child (node, "token");
+
+      if (node != NULL)
+        {
+          const gchar *token;
+
+          token = lm_message_node_get_value (node);
+
+          if (token != NULL)
+            {
+              DEBUG ("jingle info: got relay token %s", token);
+              g_free (fac->relay_token);
+              fac->relay_token = g_strdup (token);
+            }
+        }
+    }
+
+  if (sub_type == LM_MESSAGE_SUB_TYPE_SET)
+    {
+      _gabble_connection_acknowledge_set_iq (priv->conn, message);
+    }
+
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static void
+jingle_info_send_request (GabbleJingleFactory *fac)
+{
+  GabbleJingleFactoryPrivate *priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (fac);
+  TpBaseConnection *base = (TpBaseConnection *) priv->conn;
+  LmMessage *msg;
+  LmMessageNode *node;
+  const gchar *jid;
+  GError *error = NULL;
+  TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+
+  jid = tp_handle_inspect (contact_handles, base->self_handle);
+  msg = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ,
+      LM_MESSAGE_SUB_TYPE_GET);
+
+  node = lm_message_node_add_child (msg->node, "query", NULL);
+  lm_message_node_set_attribute (node, "xmlns", NS_GOOGLE_JINGLE_INFO);
+
+  if (!_gabble_connection_send (priv->conn, msg, &error))
+    {
+      DEBUG ("jingle info send failed: %s\n", error->message);
+      g_error_free (error);
+    }
+
+  lm_message_unref (msg);
+}
+
+
 static void
 gabble_jingle_factory_dispose (GObject *object)
 {
@@ -126,6 +268,11 @@ gabble_jingle_factory_dispose (GObject *object)
 
   lm_connection_unregister_message_handler (priv->conn->lmconn,
       priv->jingle_cb, LM_MESSAGE_TYPE_IQ);
+  lm_connection_unregister_message_handler (priv->conn->lmconn,
+      priv->jingle_info_cb, LM_MESSAGE_TYPE_IQ);
+
+  g_free (fac->stun_server);
+  g_free (fac->relay_token);
 
   if (G_OBJECT_CLASS (gabble_jingle_factory_parent_class)->dispose)
     G_OBJECT_CLASS (gabble_jingle_factory_parent_class)->dispose (object);
@@ -184,11 +331,9 @@ gabble_jingle_factory_constructor (GType type,
   self = GABBLE_JINGLE_FACTORY (obj);
   priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (self);
 
-  priv->jingle_cb = lm_message_handler_new (jingle_cb, self, NULL);
-  lm_connection_register_message_handler (priv->conn->lmconn,
-      priv->jingle_cb, LM_MESSAGE_TYPE_IQ, LM_HANDLER_PRIORITY_FIRST);
-
-  // FIXME: better way to do it?
+  // FIXME: why was this in _constructed in media factory?
+  g_signal_connect (priv->conn,
+      "status-changed", (GCallback) connection_status_changed_cb, self);
 
   jingle_media_rtp_register (self);
   jingle_transport_google_register (self);
@@ -224,6 +369,70 @@ gabble_jingle_factory_class_init (GabbleJingleFactoryClass *cls)
         G_TYPE_NONE, 1, G_TYPE_POINTER);
 }
 
+static void
+connection_status_changed_cb (GabbleConnection *conn,
+                              guint status,
+                              guint reason,
+                              GabbleJingleFactory *self)
+{
+  GabbleJingleFactoryPrivate *priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (self);
+
+  switch (status)
+    {
+    case TP_CONNECTION_STATUS_CONNECTING:
+      g_assert (priv->conn != NULL);
+      g_assert (priv->conn->lmconn != NULL);
+
+      DEBUG ("adding callbacks");
+      g_assert (priv->jingle_cb == NULL);
+      g_assert (priv->jingle_info_cb == NULL);
+
+      priv->jingle_cb = lm_message_handler_new (jingle_cb,
+          self, NULL);
+      lm_connection_register_message_handler (priv->conn->lmconn,
+          priv->jingle_cb, LM_MESSAGE_TYPE_IQ,
+          LM_HANDLER_PRIORITY_NORMAL);
+
+      priv->jingle_info_cb = lm_message_handler_new (
+          jingle_info_cb, self, NULL);
+      lm_connection_register_message_handler (priv->conn->lmconn,
+          priv->jingle_info_cb, LM_MESSAGE_TYPE_IQ,
+          LM_HANDLER_PRIORITY_NORMAL);
+
+      break;
+
+    case TP_CONNECTION_STATUS_CONNECTED:
+        {
+          gchar *stun_server = NULL;
+          guint stun_port = 0;
+
+          g_object_get (priv->conn,
+              "stun-server", &stun_server,
+              "stun-port", &stun_port,
+              NULL);
+
+          if (stun_server == NULL)
+            {
+              self->get_stun_from_jingle = TRUE;
+            }
+          else
+            {
+              g_free (self->stun_server);
+              self->stun_server = stun_server;
+              self->stun_port = stun_port;
+            }
+
+          if (priv->conn->features &
+              GABBLE_CONNECTION_FEATURES_GOOGLE_JINGLE_INFO)
+            {
+              jingle_info_send_request (self);
+            }
+        }
+      break;
+    }
+}
+
+
 static gboolean
 sid_in_use (GabbleJingleFactory *factory, const gchar *sid)
 {
@@ -316,6 +525,9 @@ jingle_cb (LmMessageHandler *handler,
           g_signal_emit (self, signals[NEW_SESSION], 0, sess);
         }
 
+      /* all went well, we can acknowledge the IQ */
+      _gabble_connection_acknowledge_set_iq (priv->conn, msg);
+
       return LM_HANDLER_RESULT_REMOVE_MESSAGE;
     }
 
diff --git a/src/jingle-factory.h b/src/jingle-factory.h
index 352bb08..f948016 100644
--- a/src/jingle-factory.h
+++ b/src/jingle-factory.h
@@ -62,6 +62,7 @@ typedef enum {
   JINGLE_ACTION_SESSION_INITIATE,
   JINGLE_ACTION_SESSION_TERMINATE,
   JINGLE_ACTION_TRANSPORT_INFO,
+  JINGLE_ACTION_TRANSPORT_ACCEPT,
 } JingleAction;
 
 typedef enum {
@@ -85,7 +86,6 @@ typedef enum {
 
 typedef enum {
   JINGLE_CANDIDATE_TYPE_LOCAL,
-  JINGLE_CANDIDATE_TYPE_DERIVED,
   JINGLE_CANDIDATE_TYPE_STUN,
   JINGLE_CANDIDATE_TYPE_RELAY
 } JingleCandidateType;
@@ -124,6 +124,11 @@ struct _GabbleJingleFactory {
     GHashTable *content_types;
     GHashTable *transports;
 
+    gboolean get_stun_from_jingle;
+    gchar *stun_server;
+    guint16 stun_port;
+    gchar *relay_token;
+
     gpointer priv;
 };
 
diff --git a/src/jingle-media-rtp.c b/src/jingle-media-rtp.c
index 24c2e79..9ba8311 100644
--- a/src/jingle-media-rtp.c
+++ b/src/jingle-media-rtp.c
@@ -56,17 +56,11 @@ static guint signals[LAST_SIGNAL] = {0};
 /* properties */
 enum
 {
-  PROP_MEDIA_TYPE,
+  PROP_MEDIA_TYPE = 1,
   LAST_PROPERTY
 };
 
 typedef enum {
-  JINGLE_MEDIA_TYPE_NONE = -1,
-  JINGLE_MEDIA_TYPE_AUDIO = 0,
-  JINGLE_MEDIA_TYPE_VIDEO
-} JingleMediaType;
-
-typedef enum {
   JINGLE_MEDIA_PROFILE_RTP_AVP,
 } JingleMediaProfile;
 
@@ -81,7 +75,7 @@ typedef struct _GabbleJingleMediaRtpPrivate GabbleJingleMediaRtpPrivate;
 struct _GabbleJingleMediaRtpPrivate
 {
   GList *local_codecs;
-  // GList *remote_codecs;
+  GList *remote_codecs;
   JingleMediaType media_type;
   gboolean dispose_has_run;
 };
@@ -125,8 +119,8 @@ gabble_jingle_media_rtp_dispose (GObject *object)
   DEBUG ("dispose called");
   priv->dispose_has_run = TRUE;
 
-  // _free_codecs (priv->remote_codecs);
-  // priv->remote_codecs = NULL;
+  _free_codecs (priv->remote_codecs);
+  priv->remote_codecs = NULL;
 
   _free_codecs (priv->local_codecs);
   priv->local_codecs = NULL;
@@ -185,7 +179,7 @@ gabble_jingle_media_rtp_class_init (GabbleJingleMediaRtpClass *cls)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (cls);
   GabbleJingleContentClass *content_class = GABBLE_JINGLE_CONTENT_CLASS (cls);
-  // GParamSpec *param_spec;
+  GParamSpec *param_spec;
 
   g_type_class_add_private (cls, sizeof (GabbleJingleMediaRtpPrivate));
 
@@ -196,6 +190,13 @@ gabble_jingle_media_rtp_class_init (GabbleJingleMediaRtpClass *cls)
   content_class->parse_description = parse_description;
   content_class->produce_description = produce_description;
 
+  param_spec = g_param_spec_uint ("media-type", "RTP media type",
+      "Media type.",
+      JINGLE_MEDIA_TYPE_NONE, G_MAXUINT32, JINGLE_MEDIA_TYPE_NONE,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_MEDIA_TYPE, param_spec);
+
   /* signal definitions */
 
   signals[REMOTE_CODECS] = g_signal_new ("remote-codecs",
@@ -307,7 +308,7 @@ parse_description (GabbleJingleContent *content,
 
       p = g_new0 (JingleCodec, 1);
       p->id = id;
-      p->name = (gchar *) name;
+      p->name = g_strdup (name);
       p->clockrate = clockrate;
       p->channels = channels;
 
@@ -334,10 +335,11 @@ parse_description (GabbleJingleContent *content,
 
   priv->media_type = mtype;
 
+  DEBUG ("emitting remote-codecs signal");
   g_signal_emit (self, signals[REMOTE_CODECS], 0, codecs);
 
   /* append them to the known remote codecs */
-  // priv->remote_codecs = g_list_concat (priv->remote_codecs, codecs);
+  priv->remote_codecs = g_list_concat (priv->remote_codecs, codecs);
 }
 
 static void
@@ -427,16 +429,18 @@ produce_description (GabbleJingleContent *obj, LmMessageNode *content_node)
     }
 }
 
-/*
-static void
-content_iface_init (gpointer g_iface, gpointer iface_data)
+void
+jingle_media_rtp_set_local_codecs (GabbleJingleMediaRtp *self, GList *codecs)
 {
-  GabbleJingleContentClass *klass = (GabbleJingleContentClass *) g_iface;
+  GabbleJingleMediaRtpPrivate *priv =
+    GABBLE_JINGLE_MEDIA_RTP_GET_PRIVATE (self);
 
-  klass->parse_description = parse_description;
-  klass->produce_description = produce_node;
+  DEBUG ("adding new local codecs, yippie");
+
+  priv->local_codecs = g_list_concat (priv->local_codecs, codecs);
+
+  g_object_set (self, "ready", TRUE, NULL);
 }
-*/
 
 void
 jingle_media_rtp_register (GabbleJingleFactory *factory)
@@ -460,3 +464,16 @@ jingle_media_rtp_register (GabbleJingleFactory *factory)
       GABBLE_TYPE_JINGLE_MEDIA_RTP);
 }
 
+/* We can't get remote codecs when they're signalled, because
+ * the signal is emitted immediately upon JingleContent creation,
+ * and parsing, which is before a corresponding MediaStream is
+ * created. */
+GList *
+gabble_jingle_media_rtp_get_remote_codecs (GabbleJingleMediaRtp *self)
+{
+  GabbleJingleMediaRtpPrivate *priv =
+    GABBLE_JINGLE_MEDIA_RTP_GET_PRIVATE (self);
+
+  return priv->remote_codecs;
+}
+
diff --git a/src/jingle-media-rtp.h b/src/jingle-media-rtp.h
index ec8d107..1b7b2d5 100644
--- a/src/jingle-media-rtp.h
+++ b/src/jingle-media-rtp.h
@@ -61,6 +61,9 @@ struct _GabbleJingleMediaRtp {
 const gchar *gabble_jingle_media_rtp_parse (GabbleJingleMediaRtp *sess,
     LmMessage *message, GError **error);
 void jingle_media_rtp_register (GabbleJingleFactory *factory);
+void jingle_media_rtp_set_local_codecs (GabbleJingleMediaRtp *self,
+    GList *codecs);
+GList *gabble_jingle_media_rtp_get_remote_codecs (GabbleJingleMediaRtp *self);
 
 #endif /* __JINGLE_MEDIA_RTP_H__ */
 
diff --git a/src/jingle-session.c b/src/jingle-session.c
index 92d8abe..606f376 100644
--- a/src/jingle-session.c
+++ b/src/jingle-session.c
@@ -1,21 +1,21 @@
-  /*
-   * gabble-jingle-session.c - Source for GabbleJingleSession
-   * Copyright (C) 2008 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
-   */
+/*
+ * gabble-jingle-session.c - Source for GabbleJingleSession
+ * Copyright (C) 2008 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 "jingle-session.h"
 
@@ -35,8 +35,6 @@
 #include "jingle-factory.h"
 #include "jingle-content.h"
 
-#include "media-session.h"
-
 G_DEFINE_TYPE(GabbleJingleSession, gabble_jingle_session, G_TYPE_OBJECT);
 
 /* signal enum */
@@ -72,6 +70,7 @@ struct _GabbleJingleSessionPrivate
   gchar *initiator;
   gboolean local_initiator;
   GHashTable *contents;
+  GHashTable *initial_contents;
   JingleDialect dialect;
   JingleState state;
   gchar *sid;
@@ -97,6 +96,7 @@ static const gchar *action_table[] = {
   "session-initiate",
   "session-terminate",
   "transport-info",
+  "transport-accept",
   NULL
 };
 
@@ -109,7 +109,8 @@ static JingleAction allowed_actions[6][8] = {
   /* JS_STATE_PENDING_CREATED */
   { JINGLE_ACTION_SESSION_INITIATE, JINGLE_ACTION_UNKNOWN },
   /* JS_STATE_PENDING_INITIATE_SENT */
-  { JINGLE_ACTION_SESSION_TERMINATE, JINGLE_ACTION_UNKNOWN },
+  { JINGLE_ACTION_SESSION_TERMINATE, JINGLE_ACTION_SESSION_ACCEPT,
+    JINGLE_ACTION_TRANSPORT_INFO, JINGLE_ACTION_UNKNOWN },
   /* JS_STATE_PENDING_INITIATED */
   { JINGLE_ACTION_SESSION_ACCEPT, JINGLE_ACTION_SESSION_TERMINATE,
     JINGLE_ACTION_TRANSPORT_INFO,
@@ -136,7 +137,10 @@ gabble_jingle_session_init (GabbleJingleSession *obj)
 
   priv->contents = g_hash_table_new_full (g_str_hash, g_str_equal,
       g_free, g_object_unref);
+  priv->initial_contents = g_hash_table_new_full (g_str_hash,
+      g_str_equal, g_free, NULL);
 
+  priv->state = JS_STATE_PENDING_CREATED;
   priv->locally_accepted = FALSE;
   priv->dispose_has_run = FALSE;
 }
@@ -155,13 +159,12 @@ gabble_jingle_session_dispose (GObject *object)
   DEBUG ("dispose called");
   priv->dispose_has_run = TRUE;
 
-  // Note: dispose is called FROM unregister_session (because refcount
-  // decreases when hash table releses the session object)
-  // _jingle_factory_unregister_session (priv->conn->jingle_factory, priv->sid);
-
   g_hash_table_destroy (priv->contents);
   priv->contents = NULL;
 
+  g_hash_table_destroy (priv->initial_contents);
+  priv->initial_contents = NULL;
+
   if (sess->peer)
     {
       tp_handle_unref (contact_repo, sess->peer);
@@ -234,7 +237,6 @@ gabble_jingle_session_set_property (GObject *object,
   switch (property_id) {
     case PROP_CONNECTION:
       priv->conn = g_value_get_object (value);
-      g_debug ("i am %p, priv->conn == %p", sess, priv->conn);
       g_assert (priv->conn != NULL);
       break;
     case PROP_SESSION_ID:
@@ -253,6 +255,7 @@ gabble_jingle_session_set_property (GObject *object,
     case PROP_PEER_RESOURCE:
       g_free (priv->peer_resource);
       priv->peer_resource = g_value_dup_string (value);
+      DEBUG ("setting peer resource to %s", priv->peer_resource);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -319,8 +322,7 @@ gabble_jingle_session_class_init (GabbleJingleSessionClass *cls)
                                     "with whom this session communicates, "
                                     "if applicable",
                                     NULL,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_WRITABLE |
+                                    G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NAME |
                                     G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_PEER_RESOURCE,
@@ -345,6 +347,11 @@ gabble_jingle_session_class_init (GabbleJingleSessionClass *cls)
 
   /* signal definitions */
 
+  signals[NEW_CONTENT] = g_signal_new ("new-content",
+        G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+        0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
+        G_TYPE_NONE, 1, G_TYPE_POINTER);
+
   signals[TERMINATED] = g_signal_new ("terminated",
         G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
         0, NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN,
@@ -412,7 +419,10 @@ produce_action (JingleAction act, JingleDialect dialect)
         case JINGLE_ACTION_SESSION_ACCEPT:
           return "accept";
         case JINGLE_ACTION_TRANSPORT_INFO:
-          return "candidates";
+          if (dialect == JINGLE_DIALECT_GTALK3)
+              return "candidates";
+          else
+              break;
         default:
           return _enum_to_string (action_table, act);
       }
@@ -436,6 +446,7 @@ action_is_allowed (JingleAction action, JingleState state)
 }
 
 static void set_state (GabbleJingleSession *sess, JingleState state);
+static GabbleJingleContent *_get_any_content (GabbleJingleSession *session);
 
 #define SET_BAD_REQ(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, txt)
 #define SET_OUT_ORDER(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_JINGLE_OUT_OF_ORDER, txt)
@@ -466,7 +477,8 @@ _foreach_content (GabbleJingleSession *sess, LmMessageNode *node,
     }
 }
 
-static void content_ready_cb (GabbleJingleContent *c, GabbleJingleSession *sess);
+static void content_ready_cb (GabbleJingleContent *c, 
+    GParamSpec *arg, GabbleJingleSession *sess);
 
 static void
 _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c,
@@ -526,18 +538,29 @@ _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c,
                     "content-ns", content_ns,
                     NULL);
 
-  g_signal_connect (c, "ready",
+  g_signal_connect (c, "notify::ready",
       (GCallback) content_ready_cb, sess);
 
-  gabble_jingle_content_parse_add (c, content_node, FALSE, error);
+  gabble_jingle_content_parse_add (c, content_node, 
+    ((priv->dialect == JINGLE_DIALECT_GTALK3) ||
+    (priv->dialect == JINGLE_DIALECT_GTALK4)), error);
+
   if (*error)
     {
       g_object_unref (c);
       return;
     }
 
+  /* gtalk streams don't have name, so use whatever Content came up with */
+  if (name == NULL)
+    {
+      g_object_get (c, "name", &name, NULL);
+    }
+
   /* This will override existing content if it exists. */
   g_hash_table_replace (priv->contents, g_strdup (name), c);
+
+  g_signal_emit (sess, signals[NEW_CONTENT], 0, c);
 }
 
 static void
@@ -568,10 +591,9 @@ static void
 _each_content_modify (GabbleJingleSession *sess, GabbleJingleContent *c,
     LmMessageNode *content_node, GError **error)
 {
-  const gchar *name = lm_message_node_get_attribute (content_node, "name");
-
   if (c == NULL)
     {
+      const gchar *name = lm_message_node_get_attribute (content_node, "name");
       SET_BAD_REQ ("content called \"%s\" doesn't exist", name);
       return;
     }
@@ -595,6 +617,32 @@ _each_content_replace (GabbleJingleSession *sess, GabbleJingleContent *c,
 }
 
 static void
+_each_content_accept (GabbleJingleSession *sess, GabbleJingleContent *c,
+    LmMessageNode *content_node ,GError **error)
+{
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+
+  if (c == NULL)
+    {
+      const gchar *name = lm_message_node_get_attribute (content_node, "name");
+      SET_BAD_REQ ("content called \"%s\" doesn't exist", name);
+      return;
+    }
+
+  gabble_jingle_content_parse_accept (c, content_node,
+      (priv->dialect <= JINGLE_DIALECT_GTALK4), error);
+}
+
+static void
+_mark_each_initial_content (gpointer key, gpointer data, gpointer user_data)
+{
+  GabbleJingleSession *sess = GABBLE_JINGLE_SESSION (user_data);
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+
+  g_hash_table_insert (priv->initial_contents, key, GUINT_TO_POINTER (TRUE));
+}
+
+static void
 on_session_initiate (GabbleJingleSession *sess, LmMessageNode *node,
   GError **error)
 {
@@ -619,7 +667,14 @@ on_session_initiate (GabbleJingleSession *sess, LmMessageNode *node,
       _foreach_content (sess, node, _each_content_add, error);
     }
 
-  set_state (sess, JS_STATE_PENDING_INITIATED);
+  if (*error == NULL)
+    {
+      /* mark all newly defined contents as initial contents */
+      g_hash_table_foreach (priv->initial_contents,
+          _mark_each_initial_content, sess);
+
+      set_state (sess, JS_STATE_PENDING_INITIATED);
+    }
 }
 
 static void
@@ -659,6 +714,32 @@ on_content_accept (GabbleJingleSession *sess, LmMessageNode *node,
 }
 
 static void
+on_session_accept (GabbleJingleSession *sess, LmMessageNode *node,
+    GError **error)
+{
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+
+  DEBUG ("called");
+
+  /* handle single-content special case */
+  if ((priv->dialect == JINGLE_DIALECT_GTALK3) ||
+      (priv->dialect == JINGLE_DIALECT_GTALK4))
+    {
+      GabbleJingleContent *c = _get_any_content (sess);
+      _each_content_accept (sess, c, node, error);
+    }
+  else
+    {
+      _foreach_content (sess, node, _each_content_accept, error);
+    }
+
+  if (*error)
+      return;
+
+  set_state (sess, JS_STATE_ACTIVE);
+}
+
+static void
 on_session_terminate (GabbleJingleSession *sess, LmMessageNode *node,
     GError **error)
 {
@@ -667,34 +748,85 @@ on_session_terminate (GabbleJingleSession *sess, LmMessageNode *node,
   g_signal_emit (sess, signals[TERMINATED], 0, FALSE);
 }
 
+static void
+on_transport_info (GabbleJingleSession *sess, LmMessageNode *node,
+    GError **error)
+{
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+  GabbleJingleContent *c = NULL;
+
+  /* FIXME: we need to do dialect detection here!!! */
+
+  if (priv->dialect <= JINGLE_DIALECT_GTALK4)
+    {
+      /* GTalk has only one content anyways */
+      GList *cs = g_hash_table_get_values (priv->contents);
+
+      /* Check that's the case */
+      /* FIXME: report error to peer instead of assert */
+      g_assert (g_list_length (cs) == 1);
+
+      c = cs->data;
+
+      g_list_free (cs);
+
+      /* FIXME: here we need to take care if we receive <candidates>
+       * and we're in gtalk4 mode, to switch to gtalk3? */
+      /* GTalk4 includes "transport" subnode */
+      if (priv->dialect == JINGLE_DIALECT_GTALK4)
+        node = lm_message_node_get_child (node, "transport");
+    }
+  else
+    {
+      const gchar *name;
+
+      node = lm_message_node_get_child (node, "content");
+
+      name = lm_message_node_get_attribute (node, "name");
+
+      c = g_hash_table_lookup (priv->contents, name);
+
+      /* FIXME: report error to peer instead of assert */
+      g_assert (c != NULL);
+
+      /* we need transport child of content node */
+      node = lm_message_node_get_child (node, "transport");
+    }
+
+  gabble_jingle_content_parse_transport_info (c, node, error);
+}
+
+static void
+on_transport_accept (GabbleJingleSession *sess, LmMessageNode *node,
+    GError **error)
+{
+  DEBUG ("Ignoring 'transport-accept' action from peer");
+}
+
+
 static HandlerFunc handlers[] = {
-    on_content_accept,
-    on_content_add,
-    on_content_modify,
-    on_content_remove,
-    on_content_replace,
-    NULL, /* jingle_on_session_accept */
+  on_content_accept,
+  on_content_add,
+  on_content_modify,
+  on_content_remove,
+  on_content_replace,
+  on_session_accept, /* jingle_on_session_accept */
   NULL, /* jingle_on_session_info */
   on_session_initiate,
   on_session_terminate, /* jingle_on_session_terminate */
-  NULL /* jingle_on_transport_info */
+  on_transport_info, /* jingle_on_transport_info */
+  on_transport_accept
 };
 
 static void
 jingle_state_machine_dance (GabbleJingleSession *sess, JingleAction action,
     LmMessageNode *node, GError **error)
-{
-  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
-
-  if (!action_is_allowed (action, priv->state))
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_JINGLE_OUT_OF_ORDER,
-        "action \"%s\" not allowed in current state",
-        _enum_to_string (action_table, action));
-      return;
-    }
+  {
+    GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
 
-  g_assert (handlers[action] != NULL);
+    /* parser should've checked this already */
+    g_assert (action_is_allowed (action, priv->state));
+    g_assert (handlers[action] != NULL);
 
   handlers[action] (sess, node, error);
 }
@@ -816,9 +948,11 @@ gabble_jingle_session_parse (GabbleJingleSession *sess, LmMessage *message, GErr
   if (sess == NULL)
     return sid;
 
-  DEBUG("jingle action '%s' from '%s' in session '%s'", actxt, from, sid);
-
   priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+
+  DEBUG("jingle action '%s' from '%s' in session '%s' dialect %u", actxt, from,
+      sid, dialect);
+
   contact_repo = tp_base_connection_get_handles (
       (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
 
@@ -853,6 +987,43 @@ gabble_jingle_session_parse (GabbleJingleSession *sess, LmMessage *message, GErr
   return sid;
 }
 
+static gchar *
+get_jid_for_contact (GabbleJingleSession *session,
+                     TpHandle handle)
+{
+  GabbleJingleSessionPrivate *priv;
+  TpBaseConnection *conn;
+  const gchar *base_jid;
+  TpHandle self;
+  TpHandleRepoIface *contact_handles;
+
+  g_assert (GABBLE_IS_JINGLE_SESSION (session));
+
+  priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (session);
+  conn = (TpBaseConnection *)priv->conn;
+  contact_handles = tp_base_connection_get_handles (conn,
+      TP_HANDLE_TYPE_CONTACT);
+  self = conn->self_handle;
+
+  base_jid = tp_handle_inspect (contact_handles, handle);
+  g_assert (base_jid != NULL);
+
+  if (handle == self)
+    {
+      gchar *resource, *ret;
+      g_object_get (priv->conn, "resource", &resource, NULL);
+      g_assert (resource != NULL);
+      ret = g_strdup_printf ("%s/%s", base_jid, resource);
+      g_free (resource);
+      return ret;
+    }
+  else
+    {
+      g_assert (priv->peer_resource != NULL);
+      return g_strdup_printf ("%s/%s", base_jid, priv->peer_resource);
+    }
+}
+
 LmMessage *
 gabble_jingle_session_new_message (GabbleJingleSession *sess,
     JingleAction action, LmMessageNode **sess_node)
@@ -863,6 +1034,19 @@ gabble_jingle_session_new_message (GabbleJingleSession *sess,
   gchar *el = NULL, *ns = NULL;
   gboolean gtalk_mode = FALSE;
 
+  DEBUG ("creating new message to peer: %s", priv->peer_jid);
+
+  /* possibly this is the first message in an outgoing session,
+   * meaning that we have to set up initiator */
+  if (priv->initiator == NULL) {
+      TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
+      priv->initiator = get_jid_for_contact (sess, conn->self_handle);
+  }
+  /* likewise ^^ */
+  if (priv->peer_jid == NULL) {
+      priv->peer_jid = get_jid_for_contact (sess, sess->peer);
+  }
+
   msg = lm_message_new_with_sub_type (
       priv->peer_jid,
       LM_MESSAGE_TYPE_IQ,
@@ -905,12 +1089,21 @@ gabble_jingle_session_new_message (GabbleJingleSession *sess,
 }
 
 static void
-_try_session_accept_check (gpointer key, gpointer data, gpointer user_data)
+_check_content_for_acceptance (gpointer key, gpointer data, gpointer user_data)
 {
   GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (data);
   gboolean *is_ready = (gboolean *) user_data;
 
-  *is_ready = gabble_jingle_content_is_ready (c);
+  *is_ready = gabble_jingle_content_is_ready (c, TRUE);
+}
+
+static void
+_check_content_for_initiation (gpointer key, gpointer data, gpointer user_data)
+{
+  GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (data);
+  gboolean *is_ready = (gboolean *) user_data;
+
+  *is_ready = gabble_jingle_content_is_ready (c, FALSE);
 }
 
 static void
@@ -918,8 +1111,13 @@ _try_session_accept_fill (gpointer key, gpointer data, gpointer user_data)
 {
   GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (data);
   LmMessageNode *sess_node = user_data;
+  JingleContentState state;
+
+  g_object_get (c, "state", &state, NULL);
 
-  gabble_jingle_content_produce_node (c, sess_node, TRUE);
+  /* we only want to acknowledge newly added contents */
+  if (state == JINGLE_CONTENT_STATE_NEW)
+    gabble_jingle_content_produce_node (c, sess_node, TRUE);
 }
 
 static void
@@ -928,17 +1126,22 @@ try_session_accept (GabbleJingleSession *sess)
   GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
   LmMessage *msg;
   LmMessageNode *sess_node;
+  gboolean content_ready = TRUE;
 
-  /* FIXME: should check if we're in the state where accepting is possible */
+  g_assert (g_hash_table_size (priv->contents) > 0);
 
-  gboolean content_ready = TRUE;
+  if (priv->state != JS_STATE_PENDING_INITIATED)
+    {
+      DEBUG ("session is in state %u, won't try to accept", priv->state);
+      return;
+    }
 
   if (!priv->locally_accepted)
       return;
 
-  g_hash_table_foreach (priv->contents, _try_session_accept_check, &content_ready);
+  g_hash_table_foreach (priv->contents, _check_content_for_acceptance, &content_ready);
 
-  if ((g_hash_table_size (priv->contents) == 0) || (content_ready == FALSE))
+  if (!content_ready)
       return;
 
   msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_ACCEPT,
@@ -946,14 +1149,44 @@ try_session_accept (GabbleJingleSession *sess)
 
   g_hash_table_foreach (priv->contents, _try_session_accept_fill, sess_node);
 
-  DEBUG ("i'm really trying to accept this session!");
-
   _gabble_connection_send (priv->conn, msg, NULL);
 
   lm_message_unref (msg);
 
   set_state (sess, JS_STATE_ACTIVE);
-  /* FIXME: i'm certain i forgot something */
+}
+
+static void
+try_session_initiate (GabbleJingleSession *sess)
+{
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+  LmMessage *msg;
+  LmMessageNode *sess_node;
+  gboolean content_ready = TRUE;
+
+  g_assert (g_hash_table_size (priv->contents) > 0);
+
+  if (priv->state != JS_STATE_PENDING_CREATED)
+    {
+      DEBUG ("session is in state %u, won't try to initiate", priv->state);
+      return;
+    }
+
+  g_hash_table_foreach (priv->contents, _check_content_for_initiation, &content_ready);
+
+  if (!content_ready)
+      return;
+
+  msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_INITIATE,
+      &sess_node);
+
+  g_hash_table_foreach (priv->contents, _try_session_accept_fill, sess_node);
+
+  _gabble_connection_send (priv->conn, msg, NULL);
+
+  lm_message_unref (msg);
+
+  set_state (sess, JS_STATE_PENDING_INITIATE_SENT);
 }
 
 static void
@@ -962,6 +1195,7 @@ set_state (GabbleJingleSession *sess, JingleState state)
   GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
 
   priv->state = state;
+  DEBUG ("Setting state of JingleSession: %p (priv = %p), state = %u", sess, priv, priv->state);
   g_object_notify (G_OBJECT (sess), "state");
 }
 
@@ -978,14 +1212,24 @@ gabble_jingle_session_accept (GabbleJingleSession *sess)
 void
 gabble_jingle_session_terminate (GabbleJingleSession *sess)
 {
-  // GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
   LmMessage *msg;
   LmMessageNode *sess_node;
 
   msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_TERMINATE,
       &sess_node);
 
-  /* FIXME - unref and cleanup everything, change session state, etc */
+  _gabble_connection_send (priv->conn, msg, NULL);
+
+  lm_message_unref (msg);
+
+  /* NOTE: on "terminated", jingle factory and media channel will unref
+   * it, bringing refcount to 0, so dispose will be called, and it
+   * takes care of cleanup */
+
+  DEBUG ("we are terminating this session");
+  set_state (sess, JS_STATE_ENDED);
+  g_signal_emit (sess, signals[TERMINATED], 0, FALSE);
 }
 
 void
@@ -1012,18 +1256,22 @@ gabble_jingle_session_remove_content (GabbleJingleSession *sess,
   g_hash_table_remove (priv->contents, content_name);
 }
 
-gboolean
-gabble_jingle_session_add_content (GabbleJingleSession *sess, const gchar *name,
+GabbleJingleContent *
+gabble_jingle_session_add_content (GabbleJingleSession *sess, JingleMediaType mtype,
     const gchar *content_ns, const gchar *transport_ns)
 {
   GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
   GabbleJingleContent *c;
   GType content_type;
+  gchar *name = NULL;
+  gint id = g_hash_table_size (priv->contents) + 1;
 
-  if (g_hash_table_lookup (priv->contents, name) != NULL)
+  do
     {
-      return FALSE;
+      g_free (name);
+      name = g_strdup_printf ("stream%d", id++);
     }
+  while (g_hash_table_lookup (priv->contents, name) != NULL);
 
   content_type =
       GPOINTER_TO_INT (g_hash_table_lookup (priv->conn->jingle_factory->content_types,
@@ -1034,21 +1282,40 @@ gabble_jingle_session_add_content (GabbleJingleSession *sess, const gchar *name,
   c = g_object_new (content_type,
                     "connection", priv->conn,
                     "session", sess,
+                    "media-type", mtype,
                     "content-ns", content_ns,
                     "transport-ns", transport_ns,
+                    "name", name,
+                    "senders", JINGLE_CONTENT_SENDERS_BOTH,
                     NULL);
 
+  g_signal_connect (c, "notify::ready",
+      (GCallback) content_ready_cb, sess);
+
   g_hash_table_insert (priv->contents, g_strdup (name), c);
 
   g_signal_emit (sess, signals[NEW_CONTENT], 0, c);
 
-  if (!priv->local_initiator || (priv->state > JS_STATE_PENDING_CREATED))
+  /* FIXME BUG: for session acceptance, we should only consider
+   * contents with disposition != "session". we'll burn that bridge
+   * when we come to it. */
+
+  if (priv->dialect >= JINGLE_DIALECT_V026)
     {
-      /* FIXME: we should transmit "content-add" action to the peer. or
-       * when it's ready? */
+      /* In 0.30 onwards, content-add is ok in PENDING state, so */
+      LmMessage *msg;
+      LmMessageNode *sess_node;
+
+      msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_ACCEPT,
+          &sess_node);
+      gabble_jingle_content_produce_node (c, sess_node, TRUE);
+      _gabble_connection_send (priv->conn, msg, NULL);
+      lm_message_unref (msg);
+
+      g_object_set (c, "state", JINGLE_CONTENT_STATE_SENT, NULL);
     }
 
-  return TRUE;
+  return c;
 }
 
 void
@@ -1074,6 +1341,25 @@ gabble_jingle_session_change_direction (GabbleJingleSession *sess,
   /* FIXME: send the message, mark the nodes as pending change, etc */
 }
 
+/* Get any content. Either we're in google mode (so we only have one content
+ * anyways), or we just need any content type to figure out what use case
+ * we're in (media, ft, etc). */
+static GabbleJingleContent *
+_get_any_content (GabbleJingleSession *session)
+{
+  GabbleJingleContent *c;
+
+  GList *li = gabble_jingle_session_get_contents (session);
+
+  if (li == NULL)
+      return NULL;
+
+  c = li->data;
+  g_list_free (li);
+
+  return c;
+}
+
 /* Note: if there are multiple content types, not guaranteed which one will
  * be returned. Typically, the same GType will know how to handle related
  * contents found in a session (e.g. media-rtp for audio/video), so that
@@ -1081,17 +1367,11 @@ gabble_jingle_session_change_direction (GabbleJingleSession *sess,
 GType
 gabble_jingle_session_get_content_type (GabbleJingleSession *sess)
 {
-  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
-  GabbleJingleContent *c;
-
-  GList *li = g_hash_table_get_values (priv->contents);
+  GabbleJingleContent *c = _get_any_content (sess);
 
-  if (li == NULL)
+  if (c == NULL)
       return 0;
 
-  c = li->data;
-  g_list_free (li);
-
   return G_OBJECT_TYPE (c);
 }
 
@@ -1104,9 +1384,35 @@ gabble_jingle_session_get_contents (GabbleJingleSession *sess)
 }
 
 static void
-content_ready_cb (GabbleJingleContent *c, GabbleJingleSession *sess)
+content_ready_cb (GabbleJingleContent *c, 
+    GParamSpec *arg, GabbleJingleSession *sess)
 {
-  try_session_accept (sess);
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+  const gchar *name;
+
+  DEBUG ("called");
+
+  g_object_get (c, "name", &name, NULL);
+
+  if (g_hash_table_lookup (priv->initial_contents, name))
+    {
+      /* it's one of the contents from session-initiate */
+      if (priv->local_initiator)
+        {
+          try_session_initiate (sess);
+        }
+      else
+        {
+          try_session_accept (sess);
+        }
+    }
+  else
+    {
+      /* FIXME: should do analogous thing; if we are the content
+       * initiator (hm, maybe we should really remember that),
+       * and content is ready for acceptance, then accept it
+       * ... */
+    }
 }
 
 
diff --git a/src/jingle-session.h b/src/jingle-session.h
index 6fcad38..a54cd1e 100644
--- a/src/jingle-session.h
+++ b/src/jingle-session.h
@@ -25,8 +25,26 @@
 #include "types.h"
 #include "jingle-factory.h"
 
+#include "jingle-content.h"
+
 G_BEGIN_DECLS
 
+typedef enum
+{
+  MODE_GOOGLE,
+  MODE_JINGLE
+} GabbleMediaSessionMode;
+
+typedef enum {
+    JS_STATE_INVALID = -1,
+    JS_STATE_PENDING_CREATED = 0,
+    JS_STATE_PENDING_INITIATE_SENT,
+    JS_STATE_PENDING_INITIATED,
+    JS_STATE_PENDING_ACCEPT_SENT,
+    JS_STATE_ACTIVE,
+    JS_STATE_ENDED
+} JingleSessionState;
+
 typedef struct _GabbleJingleSessionClass GabbleJingleSessionClass;
 
 GType gabble_jingle_session_get_type (void);
@@ -74,8 +92,8 @@ void gabble_jingle_session_remove_content (GabbleJingleSession *sess,
 void gabble_jingle_session_change_direction (GabbleJingleSession *sess,
     const gchar *content_name, JingleContentSenders senders);
 
-gboolean
-gabble_jingle_session_add_content (GabbleJingleSession *sess, const gchar *name,
+GabbleJingleContent *
+gabble_jingle_session_add_content (GabbleJingleSession *sess, JingleMediaType mtype,
     const gchar *content_ns, const gchar *transport_ns);
 
 GType gabble_jingle_session_get_content_type (GabbleJingleSession *);
diff --git a/src/jingle-transport-google.c b/src/jingle-transport-google.c
index 202fb57..5d21a45 100644
--- a/src/jingle-transport-google.c
+++ b/src/jingle-transport-google.c
@@ -71,6 +71,12 @@ struct _GabbleJingleTransportGooglePrivate
   gchar *transport_ns;
 
   GList *local_candidates;
+
+  /* A pointer into "local_candidates" list to mark the
+   * candidates that are still not transmitted, or NULL
+   * if all of them are transmitted. */
+
+  GList *pending_candidates;
   // GList *remote_candidates;
   gboolean dispose_has_run;
 };
@@ -236,7 +242,7 @@ gabble_jingle_transport_google_class_init (GabbleJingleTransportGoogleClass *cls
     G_SIGNAL_RUN_LAST,
     0,
     NULL, NULL,
-    g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_POINTER);
+    g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
 
 }
 
@@ -248,22 +254,19 @@ static void
 parse_candidates (GabbleJingleTransportIface *obj,
     LmMessageNode *transport_node, GError **error)
 {
-  GabbleJingleTransportGoogle *t = GABBLE_JINGLE_TRANSPORT_GOOGLE (obj);
-  GabbleJingleTransportGooglePrivate *priv =
-    GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (t);
+//  GabbleJingleTransportGoogle *t = GABBLE_JINGLE_TRANSPORT_GOOGLE (obj);
+//  GabbleJingleTransportGooglePrivate *priv =
+//    GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (t);
   GList *candidates = NULL;
+  LmMessageNode *node;
 
-  LmMessageNode *cnode, *node;
-
-  /* special-case for GTalk libjingle0.3 */
-  cnode = lm_message_node_get_child (transport_node, "candidate");
+  DEBUG ("called");
 
-  if (cnode == NULL)
+#if 0
+  if (!tp_strdiff (transport_node->name, "candidate"))
     {
       JingleDialect dialect;
 
-      cnode = transport_node;
-
       g_object_get (priv->content->session, "dialect", &dialect, NULL);
 
       if (dialect == JINGLE_DIALECT_GTALK4)
@@ -276,8 +279,9 @@ parse_candidates (GabbleJingleTransportIface *obj,
           transmit_candidates (t, priv->local_candidates);
         }
     }
+#endif
 
-  for (node = cnode->children; node; node = node->next)
+  for (node = transport_node->children; node; node = node->next)
     {
       const gchar *name, *address, *user, *pass, *str;
       guint port, net, gen;
@@ -286,6 +290,8 @@ parse_candidates (GabbleJingleTransportIface *obj,
       JingleCandidateType ctype;
       JingleCandidate *c;
 
+      DEBUG ("Parsing node %s", node->name);
+
       if (tp_strdiff (node->name, "candidate"))
           continue;
 
@@ -296,6 +302,7 @@ parse_candidates (GabbleJingleTransportIface *obj,
       address = lm_message_node_get_attribute (node, "address");
       if (address == NULL)
           break;
+      DEBUG ("AAA");
 
       str = lm_message_node_get_attribute (node, "port");
       if (str == NULL)
@@ -330,6 +337,7 @@ parse_candidates (GabbleJingleTransportIface *obj,
       else
         {
           /* unknown protocol */
+          DEBUG ("unknown protocol: %s", str);
           break;
         }
 
@@ -349,7 +357,7 @@ parse_candidates (GabbleJingleTransportIface *obj,
         }
       else if (!tp_strdiff (str, "stun"))
         {
-          ctype = JINGLE_CANDIDATE_TYPE_DERIVED;
+          ctype = JINGLE_CANDIDATE_TYPE_STUN;
         }
       else if (!tp_strdiff (str, "relay"))
         {
@@ -358,9 +366,11 @@ parse_candidates (GabbleJingleTransportIface *obj,
       else
         {
           /* unknown candidate type */
+          DEBUG ("unknown candidate type: %s", str);
           break;
         }
 
+      DEBUG ("XXX");
       user = lm_message_node_get_attribute (node, "username");
       if (user == NULL)
           break;
@@ -373,6 +383,7 @@ parse_candidates (GabbleJingleTransportIface *obj,
       if (str == NULL)
           break;
       net = atoi (str);
+      DEBUG ("YYY");
 
       str = lm_message_node_get_attribute (node, "generation");
       if (str == NULL)
@@ -391,11 +402,13 @@ parse_candidates (GabbleJingleTransportIface *obj,
       c->network = net;
       c->generation = gen;
 
+      DEBUG ("all well, adding candidate %s:%d!", c->address, c->port);
       candidates = g_list_append (candidates, c);
     }
 
   if (node != NULL)
     {
+      DEBUG ("not all nodes were processed, reporting error");
       /* rollback these */
       while (candidates != NULL)
         {
@@ -409,7 +422,9 @@ parse_candidates (GabbleJingleTransportIface *obj,
       return;
     }
 
-  g_signal_emit_by_name (priv->content, "remote-candidates", candidates);
+  DEBUG ("emitting %d new remote candidates", g_list_length (candidates));
+
+  g_signal_emit (obj, signals[NEW_CANDIDATES], 0, candidates);
 
   /* append them to the known remote candidates */
 
@@ -437,11 +452,27 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
     {
       trans_node = sess_node;
     }
-  else
+  else if (dialect == JINGLE_DIALECT_GTALK4)
     {
       trans_node = lm_message_node_add_child (sess_node, "transport", NULL);
       lm_message_node_set_attribute (trans_node, "xmlns", NS_GOOGLE_TRANSPORT_P2P);
     }
+  else
+    {
+      const gchar *cname, *cns;
+
+      g_object_get (GABBLE_JINGLE_CONTENT (priv->content),
+          "name", &cname, "content-ns", &cns, NULL);
+
+      /* we need the <content> ... */
+      trans_node = lm_message_node_add_child (sess_node, "content", NULL);
+      lm_message_node_set_attribute (trans_node, "xmlns", cns);
+      lm_message_node_set_attribute (trans_node, "name", cname);
+
+      /* .. and the <transport> node */
+      trans_node = lm_message_node_add_child (trans_node, "transport", NULL);
+      lm_message_node_set_attribute (trans_node, "xmlns", priv->transport_ns);
+    }
 
   for (li = candidates; li; li = li->next)
     {
@@ -450,14 +481,14 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
       LmMessageNode *cnode;
 
       sprintf (port_str, "%d", c->port);
-      sprintf (pref_str, "%d", c->preference);
+      sprintf (pref_str, "%lf", c->preference);
 
       switch (c->type) {
         case JINGLE_CANDIDATE_TYPE_LOCAL:
           type_str = "local";
           break;
-        case JINGLE_CANDIDATE_TYPE_DERIVED:
-          type_str = "derived";
+        case JINGLE_CANDIDATE_TYPE_STUN:
+          type_str = "stun";
           break;
         case JINGLE_CANDIDATE_TYPE_RELAY:
           type_str = "relay";
@@ -488,6 +519,7 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
           "password", c->password,
           "preference", pref_str,
           "protocol", proto_str,
+          "type", type_str,
 
           "name", "rtp",
           "network", "0",
@@ -505,14 +537,50 @@ add_candidates (GabbleJingleTransportIface *obj, GList *new_candidates)
     GABBLE_JINGLE_TRANSPORT_GOOGLE (obj);
   GabbleJingleTransportGooglePrivate *priv =
     GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (transport);
-
-  transmit_candidates (transport, new_candidates);
+  gboolean ready;
+
+  g_object_get (priv->content, "ready", &ready, NULL);
+
+  if (ready) {
+      DEBUG ("content ready, transmitting new candidates");
+      transmit_candidates (transport, new_candidates);
+      priv->pending_candidates = NULL;
+  } else {
+      DEBUG ("content not ready, not transmitting candidates");
+
+      /* if we already have pending candidates, the new ones will
+       * be in the local_candidates list after them. but these
+       * are the first pending ones, we must mark them. */
+      if (priv->pending_candidates == NULL)
+        priv->pending_candidates = new_candidates;
+  }
 
   priv->local_candidates = g_list_concat (priv->local_candidates,
       new_candidates);
 }
 
 static void
+retransmit_candidates (GabbleJingleTransportIface *obj)
+{
+  GabbleJingleTransportGoogle *transport =
+    GABBLE_JINGLE_TRANSPORT_GOOGLE (obj);
+  GabbleJingleTransportGooglePrivate *priv =
+    GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (transport);
+  gboolean ready;
+
+  g_object_get (priv->content, "ready", &ready, NULL);
+
+  g_assert (ready);
+
+  /* now transmit all pending candidates */
+  if (priv->pending_candidates != NULL) {
+      transmit_candidates (transport, priv->pending_candidates);
+      priv->pending_candidates = NULL;
+  }
+}
+
+
+static void
 transport_iface_init (gpointer g_iface, gpointer iface_data)
 {
   GabbleJingleTransportIfaceClass *klass = (GabbleJingleTransportIfaceClass *) g_iface;
@@ -520,6 +588,7 @@ transport_iface_init (gpointer g_iface, gpointer iface_data)
   klass->parse_candidates = parse_candidates;
   // FIXME: klass->produce = produce_candidates;
   klass->add_candidates = add_candidates;
+  klass->retransmit_candidates = retransmit_candidates;
 }
 
 void
diff --git a/src/jingle-transport-iface.c b/src/jingle-transport-iface.c
index 1388f6f..852c530 100644
--- a/src/jingle-transport-iface.c
+++ b/src/jingle-transport-iface.c
@@ -60,6 +60,16 @@ gabble_jingle_transport_iface_add_candidates (GabbleJingleTransportIface *self,
   virtual_method (self, candidates);
 }
 
+void
+gabble_jingle_transport_iface_retransmit_candidates (GabbleJingleTransportIface *self)
+{
+  void (*virtual_method)(GabbleJingleTransportIface *) =
+    GABBLE_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->retransmit_candidates;
+
+  g_assert (virtual_method != NULL);
+  virtual_method (self);
+}
+
 
 static void
 gabble_jingle_transport_iface_base_init (gpointer klass)
diff --git a/src/jingle-transport-iface.h b/src/jingle-transport-iface.h
index 440a54f..e2675d0 100644
--- a/src/jingle-transport-iface.h
+++ b/src/jingle-transport-iface.h
@@ -47,6 +47,7 @@ struct _GabbleJingleTransportIfaceClass {
 
   void (*add_candidates) (GabbleJingleTransportIface *,
     GList *);
+  void (*retransmit_candidates) (GabbleJingleTransportIface *);
 };
 
 GType gabble_jingle_transport_iface_get_type (void);
@@ -66,6 +67,7 @@ void gabble_jingle_transport_iface_parse_candidates (GabbleJingleTransportIface
     LmMessageNode *, GError **);
 void gabble_jingle_transport_iface_produce (GabbleJingleTransportIface *, LmMessageNode *);
 void gabble_jingle_transport_iface_add_candidates (GabbleJingleTransportIface *, GList *);
+void gabble_jingle_transport_iface_retransmit_candidates (GabbleJingleTransportIface *);
 
 G_END_DECLS
 
diff --git a/src/media-channel.c b/src/media-channel.c
index c9d96e3..432252c 100644
--- a/src/media-channel.c
+++ b/src/media-channel.c
@@ -40,8 +40,6 @@
 #include "debug.h"
 #include "exportable-channel.h"
 #include "media-factory.h"
-#include "media-session.h"
-#include "media-session.h"
 #include "media-stream.h"
 #include "presence-cache.h"
 #include "presence.h"
@@ -50,6 +48,9 @@
 #include "jingle-session.h"
 #include "jingle-content.h"
 #include "jingle-media-rtp.h"
+#include "namespaces.h"
+
+#define MAX_STREAMS 99
 
 static void call_state_iface_init (gpointer, gpointer);
 static void channel_iface_init (gpointer, gpointer);
@@ -177,144 +178,60 @@ gabble_media_channel_init (GabbleMediaChannel *self)
 
 static void session_state_changed_cb (GabbleJingleSession *session,
     GParamSpec *arg1, GabbleMediaChannel *channel);
-static void session_stream_added_cb (GabbleJingleSession *session,
-    GabbleMediaStream  *stream, GabbleMediaChannel *chan);
 static void session_terminated_cb (GabbleJingleSession *session,
     gboolean local_terminator, gpointer user_data);
+static void session_new_content_cb (GabbleJingleSession *session,
+    GabbleJingleContent *c, gpointer user_data);
+static GabbleMediaStream *
+create_stream_from_content (GabbleMediaChannel *chan, GabbleJingleContent *c);
+static gboolean contact_is_media_capable (GabbleMediaChannel *chan, TpHandle peer);
 
-
-static void
-_ensure_session (GabbleMediaChannel *chan,
-    TpHandle peer, const gchar *peer_resource, GError **error)
+static gboolean
+_create_streams (GabbleMediaChannel *chan)
 {
   GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+  GList *contents, *li;
 
-  if (priv->session != NULL)
+  contents = gabble_jingle_session_get_contents (priv->session);
+  for (li = contents; li; li = li->next)
     {
-      TpIntSet *set;
-      GList *contents, *li;
-
-      DEBUG ("%p: Latching onto incoming session %p", chan, priv->session);
-
-      /* make us local pending */
-      set = tp_intset_new ();
-      tp_intset_add (set, ((TpBaseConnection *) priv->conn)->self_handle);
-
-      tp_group_mixin_change_members (G_OBJECT (chan),
-          "", NULL, NULL, set, NULL, priv->session->peer, 0);
-
-      tp_intset_destroy (set);
+      create_stream_from_content (chan, GABBLE_JINGLE_CONTENT (li->data));
+    }
 
-      /* and update flags accordingly */
-      tp_group_mixin_change_flags (G_OBJECT (chan),
-          TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
-          0);
+  g_list_free (contents);
 
-      priv->streams = g_ptr_array_sized_new (1);
+  return FALSE;
+}
 
-      /* FIXME: put these in separate function and execute afterwards? */
-      /* For each supported content in the session, create accompanying MediaStream. */
-      contents = gabble_jingle_session_get_contents (priv->session);
-      for (li = contents; li; li = li->next)
-        {
-          GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (li->data);
-          if (G_OBJECT_TYPE (c) == GABBLE_TYPE_JINGLE_MEDIA_RTP)
-            {
-              GabbleMediaStream *stream;
-              gchar *name;
-              guint id = _gabble_media_channel_get_stream_id (chan);
-              gchar *object_path = g_strdup_printf ("%s/MediaStream%u",
-                  priv->object_path, id);
-
-              g_object_get (c, "name", &name, NULL);
-
-              stream = g_object_new (GABBLE_TYPE_MEDIA_STREAM,
-                  "object-path", object_path,
-                  "content", c,
-                  "name", name,
-                  NULL);
-
-              /* FIXME port functions to media chan from media session
-              g_signal_connect (stream, "notify::connection-state",
-                                (GCallback) stream_connection_state_changed_cb,
-                                session);
-              g_signal_connect (stream, "notify::got-local-codecs",
-                                (GCallback) stream_got_local_codecs_changed_cb,
-                                session);
-              */
-              g_ptr_array_add (priv->streams, stream);
-
-              /* fugly hack, clean it up! */
-              session_stream_added_cb (priv->session, stream, chan);
-
-              /* all of the streams are bidirectional from farsight's point of view, it's
-               * just in the signalling they change */
-              tp_svc_media_session_handler_emit_new_stream_handler (chan,
-                  object_path, id, TP_MEDIA_STREAM_TYPE_AUDIO, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
-
-              g_free (object_path);
-
-              /*  FIXME copyypasted from media session
-              if (priv->ready) {
-                // _emit_new_stream (session, stream);
-              }A
-              */
-            }
-        }
+static void
+create_session (GabbleMediaChannel *chan, TpHandle peer)
+{
+  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
 
-      g_list_free (contents);
-    }
-  else
-    {
-      GabblePresence *presence;
-#ifdef ENABLE_DEBUG
-      TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
-      TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
-          conn, TP_HANDLE_TYPE_CONTACT);
-#endif
+  g_assert (priv->session == NULL);
 
-      DEBUG ("%p: Creating new outgoing session", chan);
+  DEBUG ("%p: Creating new outgoing session", chan);
 
-      presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+  priv->session = gabble_jingle_factory_create_session (
+      priv->conn->jingle_factory, peer, NULL);
 
-      if (presence == NULL)
-        {
-          DEBUG ("failed to add contact %d (%s) to media channel: "
-              "no presence available", peer,
-              tp_handle_inspect (contact_handles, peer));
-          goto NO_CAPS;
-        }
+  g_signal_connect (priv->session, "notify::state",
+                    (GCallback) session_state_changed_cb, chan);
 
-      if ((_gabble_media_channel_caps_to_typeflags (presence->caps) &
-            (TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
-             TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)) == 0)
-        {
-          DEBUG ("failed to add contact %d (%s) to media channel: "
-              "caps %x aren't sufficient", peer,
-              tp_handle_inspect (contact_handles, peer),
-              presence->caps);
-          goto NO_CAPS;
-        }
+  g_signal_connect (priv->session, "new-content",
+                    (GCallback) session_new_content_cb, chan);
 
-      priv->session = gabble_jingle_factory_create_session (
-          priv->conn->jingle_factory, peer, peer_resource);
+  g_signal_connect (priv->session, "terminated",
+                    (GCallback) session_terminated_cb, chan);
 
-      priv->streams = g_ptr_array_sized_new (1);
-    }
+  g_assert (priv->streams == NULL);
 
-  g_signal_connect (priv->session, "terminated",
-    (GCallback) session_terminated_cb, chan);
+  priv->streams = g_ptr_array_sized_new (1);
 
-  /* FIXME is it complete? */
   tp_svc_channel_interface_media_signalling_emit_new_session_handler (
       G_OBJECT (chan), priv->object_path, "rtp");
 
   return;
-
-NO_CAPS:
-  g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-      "handle %u has no media capabilities", peer);
-  return;
 }
 
 static GObject *
@@ -327,6 +244,7 @@ gabble_media_channel_constructor (GType type, guint n_props,
   DBusGConnection *bus;
   TpIntSet *set;
   TpHandleRepoIface *contact_handles;
+  GabbleJingleFactory *jf;
 
   obj = G_OBJECT_CLASS (gabble_media_channel_parent_class)->
            constructor (type, n_props, props);
@@ -364,185 +282,43 @@ gabble_media_channel_constructor (GType type, guint n_props,
   tp_group_mixin_change_flags (obj,
       TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
 
+  /* Set up Google relay related properties */
+  jf = priv->conn->jingle_factory;
+  if (jf->stun_server != NULL)
+      g_object_set (obj, "stun-server", jf->stun_server, NULL);
+  if (jf->stun_port != 0)
+      g_object_set (obj, "stun-port", jf->stun_port, NULL);
+  if (jf->relay_token != NULL)
+      g_object_set (obj, "gtalk-p2p-relay-token", jf->relay_token, NULL);
+
   /* act on incoming session */
   if (priv->session != NULL)
     {
-      _ensure_session (GABBLE_MEDIA_CHANNEL (obj), 0, NULL, NULL);
-    }
-
-  return obj;
-}
-
-
-/**
- * create_session
- *
- * Creates a GabbleJingleSession object for given peer.
- *
- * If sid is set to NULL a unique sid is generated and
- * the "initiator" property of the newly created
- * GabbleJingleSession is set to our own handle.
- */
-static GabbleJingleSession *
-create_session (GabbleMediaChannel *channel,
-                TpHandle peer,
-                const gchar *peer_resource,
-                const gchar *sid,
-                GError **error)
-{
-  GabbleMediaChannelPrivate *priv;
-  GabbleJingleSession *session;
-  gchar *object_path;
-  JingleInitiator initiator;
-
-  g_assert (GABBLE_IS_MEDIA_CHANNEL (channel));
-
-  priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (channel);
-
-  g_assert (priv->session == NULL);
-
-  object_path = g_strdup_printf ("%s/MediaSession%u", priv->object_path, peer);
-
-  if (sid == NULL)
-    {
-      /* We are the initiator */
-      GabblePresence *presence;
-#ifdef ENABLE_DEBUG
-      TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
-      TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
-          conn, TP_HANDLE_TYPE_CONTACT);
-#endif
-
-      initiator = INITIATOR_LOCAL;
-
-      presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
-
-      if (presence == NULL)
-        {
-          DEBUG ("failed to add contact %d (%s) to media channel: "
-              "no presence available", peer,
-              tp_handle_inspect (contact_handles, peer));
-          goto NO_CAPS;
-        }
-
-      if ((_gabble_media_channel_caps_to_typeflags (presence->caps) &
-            (TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
-             TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)) == 0)
-        {
-          DEBUG ("failed to add contact %d (%s) to media channel: "
-              "caps %x aren't sufficient", peer,
-              tp_handle_inspect (contact_handles, peer),
-              presence->caps);
-          goto NO_CAPS;
-        }
-
-      // FIXME sid = _gabble_media_factory_allocate_sid (priv->factory, channel);
-      sid = NULL;
-      g_assert_not_reached ();
-    }
-  else
-    {
-      initiator = INITIATOR_REMOTE;
-      g_assert_not_reached ();
-      // FIXME _gabble_media_factory_register_sid (priv->factory, sid, channel);
-    }
-
-  session = g_object_new (GABBLE_TYPE_JINGLE_SESSION,
-                          "connection", priv->conn,
-                          "media-channel", channel,
-                          "object-path", object_path,
-                          "session-id", sid,
-                          "initiator", initiator,
-                          "peer", peer,
-                          "peer-resource", peer_resource,
-                          NULL);
-
-  g_signal_connect (session, "notify::state",
-                    (GCallback) session_state_changed_cb, channel);
-  g_signal_connect (session, "stream-added",
-                    (GCallback) session_stream_added_cb, channel);
-  g_signal_connect (session, "terminated",
-                    (GCallback) session_terminated_cb, channel);
-
-  priv->session = session;
-
-  priv->streams = g_ptr_array_sized_new (1);
-
-  tp_svc_channel_interface_media_signalling_emit_new_session_handler (
-      channel, object_path, "rtp");
-
-  g_free (object_path);
-
-  return session;
-
-NO_CAPS:
-  g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-      "handle %u has no media capabilities", peer);
-  return NULL;
-}
-
-gboolean
-_gabble_media_channel_dispatch_session_action (GabbleMediaChannel *chan,
-                                               TpHandle peer,
-                                               const gchar *peer_resource,
-                                               const gchar *sid,
-                                               LmMessage *message,
-                                               LmMessageNode *session_node,
-                                               const gchar *action,
-                                               GError **error)
-{
-  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
-  GabbleJingleSession *session = priv->session;
-  gboolean session_is_new = FALSE;
-
-  /* If this assertion fails, create_session() would think we're the
-   * initiator. However, GabbleMediaFactory checks this, so it can't fail */
-  g_return_val_if_fail (sid != NULL, FALSE);
-
-  if (session == NULL)
-    {
-      TpGroupMixin *mixin = TP_GROUP_MIXIN (chan);
-      TpIntSet *set;
-
-      session = create_session (chan, peer, peer_resource, sid, NULL);
-      g_assert (session != NULL);
-      session_is_new = TRUE;
+      DEBUG ("%p: Latching onto incoming session %p", obj, priv->session);
 
       /* make us local pending */
       set = tp_intset_new ();
-      tp_intset_add (set, mixin->self_handle);
+      tp_intset_add (set, ((TpBaseConnection *) priv->conn)->self_handle);
 
-      tp_group_mixin_change_members ((GObject *) chan,
-          "", NULL, NULL, set, NULL, peer, 0);
+      tp_group_mixin_change_members (obj, "", NULL, NULL, set, NULL,
+          priv->session->peer, 0);
 
       tp_intset_destroy (set);
 
       /* and update flags accordingly */
-      tp_group_mixin_change_flags ((GObject *) chan,
-          TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
-          0);
-    }
+      tp_group_mixin_change_flags (obj,
+          TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0);
 
-  g_object_ref (session);
+      priv->streams = g_ptr_array_sized_new (1);
 
-  /* TODO
-  if (_gabble_media_session_handle_action (session, message, session_node,
-        action, error))
-    {
-      g_object_unref (session);
-      return TRUE;
-    }
-  else
-    {
-      if (session_is_new)
-        _gabble_media_session_terminate (session, INITIATOR_LOCAL,
-            TP_CHANNEL_GROUP_CHANGE_REASON_ERROR);
+      /* We want streams to appear on DBus after the channel is signalled */
+      g_idle_add ((GSourceFunc) _create_streams, GABBLE_MEDIA_CHANNEL (obj));
 
-      g_object_unref (session);
-      return FALSE;
-    } */
+      tp_svc_channel_interface_media_signalling_emit_new_session_handler (
+          obj, priv->object_path, "rtp");
+    }
 
-  return FALSE;
+  return obj;
 }
 
 static void
@@ -667,7 +443,21 @@ gabble_media_channel_set_property (GObject     *object,
       priv->factory = g_value_get_object (value);
       break;
     case PROP_SESSION:
+      g_assert (priv->session == NULL);
+
       priv->session = g_value_dup_object (value);
+
+      if (priv->session)
+        {
+          g_signal_connect (priv->session, "notify::state",
+                            (GCallback) session_state_changed_cb, chan);
+
+          g_signal_connect (priv->session, "new-content",
+                            (GCallback) session_new_content_cb, chan);
+
+          g_signal_connect (priv->session, "terminated",
+                            (GCallback) session_terminated_cb, chan);
+        }
       break;
     default:
       param_name = g_param_spec_get_name (pspec);
@@ -936,9 +726,7 @@ gabble_media_channel_close (GabbleMediaChannel *self)
 
   if (priv->session)
     {
-      /* TODO
-      _gabble_media_session_terminate (priv->session, INITIATOR_LOCAL,
-          TP_CHANNEL_GROUP_CHANGE_REASON_NONE); */
+      gabble_jingle_session_terminate (priv->session);
     }
 
   tp_svc_channel_emit_closed (self);
@@ -1012,7 +800,6 @@ gabble_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling
     {
       GValue handler = { 0, };
       TpHandle member;
-      gchar *path;
 
       g_value_init (&handler, info_type);
       g_value_take_boxed (&handler,
@@ -1020,16 +807,13 @@ gabble_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling
 
       g_object_get (priv->session,
                     "peer", &member,
-                    "object-path", &path,
                     NULL);
 
       dbus_g_type_struct_set (&handler,
-          0, path,
+          0, priv->object_path,
           1, "rtp",
           G_MAXUINT);
 
-      g_free (path);
-
       ret = g_ptr_array_sized_new (1);
       g_ptr_array_add (ret, g_value_get_boxed (&handler));
     }
@@ -1210,7 +994,14 @@ gabble_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *iface,
   /* TODO
   if (stream_objs->len > 0)
     _gabble_media_session_remove_streams (priv->session, (GabbleMediaStream **)
-        stream_objs->pdata, stream_objs->len); */
+        stream_objs->pdata, stream_objs->len);
+  
+  For each stream, call gabble_media_session_remove_content (stream->priv->content),
+  unref the stream, and remove it from the list. When removing the last content,
+  this should force jinglesession to terminate
+        
+        
+        */
 
 OUT:
   g_ptr_array_free (stream_objs, TRUE);
@@ -1284,6 +1075,312 @@ gabble_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *if
     } */
 }
 
+static const gchar *
+_pick_best_content_type (GabbleMediaChannel *chan, TpHandle peer,
+  const gchar *resource, JingleMediaType type)
+{
+  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+  GabblePresence *presence;
+
+  presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+
+  if (gabble_presence_resource_has_caps (presence, resource,
+          PRESENCE_CAP_JINGLE_RTP_TMP))
+    {
+      return NS_JINGLE_RTP_TMP;
+    }
+
+  if ((type == JINGLE_MEDIA_TYPE_VIDEO) &&
+      gabble_presence_resource_has_caps (presence, resource,
+          PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO))
+    {
+      return NS_JINGLE_DESCRIPTION_VIDEO;
+    }
+
+  if ((type == JINGLE_MEDIA_TYPE_AUDIO) &&
+      gabble_presence_resource_has_caps (presence, resource,
+          PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO))
+    {
+      return NS_JINGLE_DESCRIPTION_VIDEO;
+    }
+  if ((type == JINGLE_MEDIA_TYPE_AUDIO) &&
+      gabble_presence_resource_has_caps (presence, resource,
+          PRESENCE_CAP_GOOGLE_VOICE))
+    {
+      return NS_GOOGLE_SESSION_PHONE;
+    }
+
+  return NULL;
+}
+
+
+static const gchar *
+_pick_best_resource (GabbleMediaChannel *chan,
+  TpHandle peer, gboolean want_audio, gboolean want_video,
+  const char **transport_ns, JingleDialect *dialect)
+{
+  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+  GabblePresence *presence;
+  GabblePresenceCapabilities caps;
+  const gchar *resource = NULL;
+
+  presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+
+#if 0
+  /* FORCE GTALK!!3 FIXME */
+  caps = PRESENCE_CAP_GOOGLE_VOICE;
+  resource = gabble_presence_pick_resource_by_caps (presence, caps);
+  g_assert (resource != NULL);
+  *content_ns = NS_GOOGLE_SESSION_PHONE;
+  *transport_ns = "";
+  *dialect = JINGLE_DIALECT_GTALK3;
+  return resource;
+#endif
+
+  *dialect = JINGLE_DIALECT_ERROR;
+  *transport_ns = NULL;
+
+  g_return_val_if_fail (want_audio || want_video, NULL);
+
+  /* Try newest Jingle standard */
+  caps = PRESENCE_CAP_JINGLE_RTP_TMP;
+  resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+  if (resource != NULL)
+    {
+      *dialect = JINGLE_DIALECT_V026;
+      goto CHOOSE_TRANSPORT;
+    }
+
+  /* Else try older Jingle draft, audio + video */
+  caps = PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO |
+      PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO;
+  resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+  if (resource != NULL)
+    {
+      *dialect = JINGLE_DIALECT_V015;
+      goto CHOOSE_TRANSPORT;
+    }
+
+  /* In this unlikely case, we can get by with just video */
+  if (!want_audio)
+    {
+      caps = PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO;
+      resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+      if (resource != NULL)
+        {
+          *dialect = JINGLE_DIALECT_V015;
+          goto CHOOSE_TRANSPORT;
+        }
+    }
+
+  /* Uh, huh, we can't provide what's requested. */
+  if (want_video)
+      return NULL;
+
+  /* Ok, try just older Jingle draft, audio */
+  caps = PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO;
+  resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+  if (resource != NULL)
+    {
+      *dialect = JINGLE_DIALECT_V015;
+      goto CHOOSE_TRANSPORT;
+    }
+
+  /* There is still hope, try GTalk */
+  caps = PRESENCE_CAP_GOOGLE_VOICE;
+  resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+  if (resource != NULL)
+    {
+      *dialect = JINGLE_DIALECT_GTALK4;
+      goto CHOOSE_TRANSPORT;
+    }
+
+  /* Nope, nothing we can do. */
+  return NULL;
+
+CHOOSE_TRANSPORT:
+  /* We prefer ICE, Google-P2P, then raw UDP */
+
+  if (gabble_presence_resource_has_caps (presence, resource,
+        PRESENCE_CAP_JINGLE_TRANSPORT_ICE))
+    {
+      *transport_ns = NS_JINGLE_TRANSPORT_ICE;
+    }
+  else if (gabble_presence_resource_has_caps (presence, resource,
+        PRESENCE_CAP_GOOGLE_TRANSPORT_P2P))
+    {
+      *transport_ns = NS_GOOGLE_TRANSPORT_P2P;
+    }
+  else if (gabble_presence_resource_has_caps (presence, resource,
+        PRESENCE_CAP_JINGLE_TRANSPORT_RAWUDP))
+    {
+      *transport_ns = NS_JINGLE_TRANSPORT_RAWUDP;
+    }
+  else if (*dialect == JINGLE_DIALECT_GTALK4)
+    {
+      /* (Some) GTalk clients don't advertise gtalk-p2p, though
+       * they support it. If we know it's GTalk and there's no
+       * transport, we can assume it also. */
+      *transport_ns = NS_GOOGLE_TRANSPORT_P2P;
+    }
+
+  if (*transport_ns == NULL)
+      return NULL;
+
+  return resource;
+}
+
+static gboolean
+_gabble_media_channel_request_streams (GabbleMediaChannel *chan,
+                                       const GArray *media_types,
+                                       GPtrArray **ret,
+                                       GError **error)
+{
+  GabbleMediaChannelPrivate *priv;
+  gboolean want_audio, want_video;
+  JingleDialect dialect;
+  guint idx;
+  TpHandle peer;
+  const gchar *peer_resource;
+  const gchar *transport_ns = NULL;
+
+  priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+
+  DEBUG ("called");
+
+  g_object_get (priv->session, "peer", &peer,
+      "peer-resource", &peer_resource, NULL);
+
+  if (!contact_is_media_capable (chan, peer))
+    {
+      DEBUG ("peer has no a/v capabilities");
+      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+          "member has no audio/video capabilities");
+
+      return FALSE;
+    }
+
+  want_audio = want_video = FALSE;
+
+  for (idx = 0; idx < media_types->len; idx++)
+    {
+      guint media_type = g_array_index (media_types, guint, idx);
+
+      if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
+        {
+          want_audio = TRUE;
+        }
+      else if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
+        {
+          want_video = TRUE;
+        }
+      else
+        {
+          g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+              "given media type %u is invalid", media_type);
+          return FALSE;
+        }
+    }
+
+  g_object_get(priv->session, "dialect", &dialect, NULL);
+
+  /* existing call; the recipient and the mode has already been decided */
+  if (dialect != JINGLE_DIALECT_ERROR) 
+    {
+      g_assert_not_reached ();
+
+      /* is a google call... we have no other option */
+      if (dialect <= JINGLE_DIALECT_GTALK4)
+        {
+          DEBUG ("already in Google mode; can't add new stream");
+
+          g_assert (priv->streams->len == 1);
+
+          g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+              "Google Talk calls may only contain one stream");
+
+          return FALSE;
+        }
+
+      DEBUG ("in Jingle mode, and have necessary caps");
+    }
+  /* no existing call; we should choose a recipient and a mode */
+  else
+    {
+      DEBUG ("picking the best resource (want audio: %u, want video: %u",
+            want_audio, want_video);
+
+      g_assert (priv->streams->len == 0);
+
+      peer_resource = _pick_best_resource (chan, peer, want_audio, want_video,
+          &transport_ns, &dialect);
+
+      if (peer_resource == NULL)
+        {
+          DEBUG ("contact doesn't have a resource with suitable capabilities");
+
+          g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+              "member does not have the desired audio/video capabilities");
+
+          return FALSE;
+        }
+
+      DEBUG ("Picking resource '%s' (transport: %s, dialect: %u)",
+          peer_resource, transport_ns, dialect);
+
+      g_object_set (priv->session, "dialect", dialect,
+          "peer-resource", peer_resource, NULL);
+    }
+
+  /* check it's not a ridiculous number of streams */
+  if ((priv->streams->len + media_types->len) > MAX_STREAMS)
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+          "I think that's quite enough streams already");
+      return FALSE;
+    }
+
+  /* if we've got here, we're good to make the streams */
+
+  *ret = g_ptr_array_sized_new (media_types->len);
+
+  for (idx = 0; idx < media_types->len; idx++)
+    {
+      guint media_type = g_array_index (media_types, guint, idx);
+      GabbleJingleContent *c;
+      GabbleMediaStream *stream;
+      const gchar *content_ns;
+
+      content_ns = _pick_best_content_type (chan, peer, peer_resource,
+          media_type == TP_MEDIA_STREAM_TYPE_AUDIO ?
+            JINGLE_MEDIA_TYPE_AUDIO : JINGLE_MEDIA_TYPE_VIDEO);
+
+      /* if we got this far, resource should be capable enough, so we
+       * should not fail in choosing ns */
+      g_assert (content_ns != NULL);
+
+      DEBUG ("Creating new jingle content with namespace %s", content_ns);
+
+      c = gabble_jingle_session_add_content (priv->session,
+          media_type == TP_MEDIA_STREAM_TYPE_AUDIO ?
+            JINGLE_MEDIA_TYPE_AUDIO : JINGLE_MEDIA_TYPE_VIDEO,
+            content_ns, transport_ns);
+
+      g_assert (c != NULL);
+
+      /* stream is created in "new-content" callback, and appended to streams */
+      stream = g_ptr_array_index (priv->streams, priv->streams->len - 1);
+      g_ptr_array_add (*ret, stream);
+    }
+
+  return TRUE;
+}
+
 
 /**
  * gabble_media_channel_request_streams
@@ -1319,13 +1416,7 @@ gabble_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
 
   if (priv->session == NULL)
     {
-      _ensure_session (self, contact_handle, NULL, &error);
-      if (error != NULL)
-        {
-          dbus_g_method_return_error (context, error);
-          g_error_free (error);
-          return;
-        }
+      create_session (self, contact_handle);
     }
   else
     {
@@ -1346,11 +1437,9 @@ gabble_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
 
   g_assert (priv->session != NULL);
 
-  /* TODO
-  if (!_gabble_media_session_request_streams (priv->session, types, &streams,
+  if (!_gabble_media_channel_request_streams (self, types, &streams,
         &error))
-    goto error; */
-  streams = NULL;
+    goto error;
 
   ret = make_stream_list (self, streams);
 
@@ -1366,6 +1455,38 @@ error:
 }
 
 
+static gboolean
+contact_is_media_capable (GabbleMediaChannel *chan, TpHandle peer)
+{
+  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+  GabblePresence *presence;
+  TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
+  TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
+      conn, TP_HANDLE_TYPE_CONTACT);
+  GabblePresenceCapabilities caps;
+
+  caps = PRESENCE_CAP_GOOGLE_VOICE | PRESENCE_CAP_JINGLE_RTP_TMP |
+    PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO | PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO;
+
+  presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+
+  if (presence == NULL)
+    {
+      DEBUG ("contact %d (%s) has no presence available", peer,
+          tp_handle_inspect (contact_handles, peer));
+      return FALSE;
+    }
+
+  if ((presence->caps & caps) == 0)
+    {
+      DEBUG ("contact %d (%s) doesn't have sufficient media caps", peer,
+          tp_handle_inspect (contact_handles, peer));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
 gboolean
 _gabble_media_channel_add_member (GObject *obj,
                                   TpHandle handle,
@@ -1381,21 +1502,13 @@ _gabble_media_channel_add_member (GObject *obj,
     {
       TpIntSet *set;
 
-      /* yes: invite the peer */
+      /* yes: check we don't have a peer already, invite this onis one */
 
-      if (priv->session == NULL)
-        {
-          /* create a new session */
-          if (create_session (chan, handle, NULL, NULL, error) == NULL)
-            return FALSE;
-        }
-      else
+      if (priv->session != NULL)
         {
           TpHandle peer;
 
-          g_object_get (priv->session,
-              "peer", &peer,
-              NULL);
+          g_object_get (priv->session, "peer", &peer, NULL);
 
           if (peer != handle)
             {
@@ -1406,6 +1519,14 @@ _gabble_media_channel_add_member (GObject *obj,
             }
         }
 
+      if (!contact_is_media_capable (chan, handle))
+        {
+          g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+              "handle %u cannot be added: has no media capabilities",
+              handle);
+          return FALSE;
+        }
+
       /* make the peer remote pending */
       set = tp_intset_new ();
       tp_intset_add (set, handle);
@@ -1424,7 +1545,7 @@ _gabble_media_channel_add_member (GObject *obj,
   else
     {
       /* no: has a session been created, is the handle being added ours,
-       *     and are we in local pending? */
+       *     and are we in local pending? (call answer) */
 
       if (priv->session &&
           handle == mixin->self_handle &&
@@ -1488,9 +1609,8 @@ gabble_media_channel_remove_member (GObject *obj,
       return FALSE;
     }
 
-  /* TODO
-  _gabble_media_session_terminate (priv->session, INITIATOR_LOCAL,
-      TP_CHANNEL_GROUP_CHANGE_REASON_NONE); */
+  // FIXME: initiator = LOCAL, change reason = NONE 
+  gabble_jingle_session_terminate (priv->session);
 
   /* remove the member */
   set = tp_intset_new ();
@@ -1517,7 +1637,6 @@ session_terminated_cb (GabbleJingleSession *session,
   TpGroupMixin *mixin = TP_GROUP_MIXIN (channel);
   guint reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE;
   guint terminator;
-  gchar *sid;
   JingleSessionState state;
   TpHandle peer;
   TpIntSet *set;
@@ -1546,11 +1665,6 @@ session_terminated_cb (GabbleJingleSession *session,
       TP_CHANNEL_GROUP_FLAG_CAN_ADD,
       TP_CHANNEL_GROUP_FLAG_CAN_REMOVE);
 
-  /* free the session ID */
-  g_object_get (priv->session, "session-id", &sid, NULL);
-  // FIXME _gabble_media_factory_free_sid (priv->factory, sid);
-  g_free (sid);
-
   /* unref streams */
   if (priv->streams != NULL)
     {
@@ -1583,6 +1697,8 @@ session_state_changed_cb (GabbleJingleSession *session,
   TpHandle peer;
   TpIntSet *set;
 
+  DEBUG ("called");
+
   g_object_get (session,
                 "state", &state,
                 "peer", &peer,
@@ -1610,6 +1726,9 @@ session_state_changed_cb (GabbleJingleSession *session,
   if (state == JS_STATE_ACTIVE &&
       priv->creator == mixin->self_handle)
     {
+
+      DEBUG ("adding peer to the member list and updating flags");
+
       /* add the peer to the member list */
       tp_group_mixin_change_members ((GObject *) channel,
           "", set, NULL, NULL, NULL, 0, 0);
@@ -1619,6 +1738,23 @@ session_state_changed_cb (GabbleJingleSession *session,
       tp_group_mixin_change_flags ((GObject *) channel,
           TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
           TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_RESCIND);
+
+    }
+
+  if (state == JS_STATE_ACTIVE)
+    {
+      guint i;
+
+      /* set all streams as playing */
+      /* FIXME: all? */
+      for (i = 0; i < priv->streams->len; i++)
+        {
+          GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
+
+          g_object_set (stream, "playing", TRUE, NULL);
+          _gabble_media_stream_update_sending (stream, TRUE);
+
+        }
     }
 
   tp_intset_destroy (set);
@@ -1887,14 +2023,80 @@ stream_direction_changed_cb (GabbleMediaStream *stream,
       chan, id, direction, pending_send);
 }
 
-static void
-session_stream_added_cb (GabbleJingleSession *session,
-                         GabbleMediaStream  *stream,
-                         GabbleMediaChannel *chan)
+#define GTALK_CAPS \
+  ( PRESENCE_CAP_GOOGLE_VOICE )
+
+#define JINGLE_CAPS \
+  ( PRESENCE_CAP_JINGLE \
+  | PRESENCE_CAP_GOOGLE_TRANSPORT_P2P )
+
+#define JINGLE_AUDIO_CAPS \
+  ( PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO )
+
+#define JINGLE_VIDEO_CAPS \
+  ( PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO )
+
+GabblePresenceCapabilities
+_gabble_media_channel_typeflags_to_caps (TpChannelMediaCapabilities flags)
+{
+  GabblePresenceCapabilities caps = 0;
+
+  /* currently we can only signal any (GTalk or Jingle calls) using
+   * the GTalk-P2P transport */
+  if (flags & TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P)
+    {
+      caps |= JINGLE_CAPS;
+
+      if (flags & TP_CHANNEL_MEDIA_CAPABILITY_AUDIO)
+        caps |= GTALK_CAPS | JINGLE_AUDIO_CAPS;
+
+      if (flags & TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)
+        caps |= JINGLE_VIDEO_CAPS;
+    }
+
+  return caps;
+}
+
+static GabbleMediaStream *
+create_stream_from_content (GabbleMediaChannel *chan, GabbleJingleContent *c)
 {
   GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+  GabbleMediaStream *stream;
+  JingleMediaType type;
+  gchar *name;
+  guint id;
+  gchar *object_path;
+
+  g_object_get (c, "name", &name, NULL);
+
+  if (G_OBJECT_TYPE (c) != GABBLE_TYPE_JINGLE_MEDIA_RTP)
+    {
+      DEBUG ("ignoring non MediaRtp content '%s'", name);
+      return NULL;
+    }
+
+  /* This onelier replaces "get_channel_stream_id()" function */
+  id = priv->next_stream_id++;
+
+  object_path = g_strdup_printf ("%s/MediaStream%u",
+      priv->object_path, id);
+
+  stream = g_object_new (GABBLE_TYPE_MEDIA_STREAM,
+      "object-path", object_path,
+      "content", c,
+      "name", name,
+      NULL);
+
+  DEBUG ("created new MediaStream %p for content '%s'", stream, name);
 
-  guint id, handle, type;
+  /* FIXME port functions to media chan from media session
+  g_signal_connect (stream, "notify::connection-state",
+                    (GCallback) stream_connection_state_changed_cb,
+                    session);
+  g_signal_connect (stream, "notify::got-local-codecs",
+                    (GCallback) stream_got_local_codecs_changed_cb,
+                    session);
+  */
 
   /* keep track of the stream */
   g_object_ref (stream);
@@ -1914,57 +2116,38 @@ session_stream_added_cb (GabbleJingleSession *session,
                     (GCallback) stream_hold_state_changed, chan);
 
   /* emit StreamAdded */
-  g_object_get (session, "peer", &handle, NULL);
-  g_object_get (stream, "id", &id, "media-type", &type, NULL);
+  g_object_get (GABBLE_JINGLE_MEDIA_RTP (c), "media-type", &type, NULL);
 
-  DEBUG ("emitting stream added");
   tp_svc_channel_type_streamed_media_emit_stream_added (
-      chan, id, handle, type);
+      chan, id, priv->session->peer,
+      type == JINGLE_MEDIA_TYPE_AUDIO ?
+        TP_MEDIA_STREAM_TYPE_AUDIO : TP_MEDIA_STREAM_TYPE_VIDEO);
 
   /* A stream being added might cause the "total" hold state to change */
   stream_hold_state_changed (stream, NULL, chan);
-}
-
-guint
-_gabble_media_channel_get_stream_id (GabbleMediaChannel *chan)
-{
-  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
-
-  return priv->next_stream_id++;
-}
 
-#define GTALK_CAPS \
-  ( PRESENCE_CAP_GOOGLE_VOICE )
-
-#define JINGLE_CAPS \
-  ( PRESENCE_CAP_JINGLE \
-  | PRESENCE_CAP_GOOGLE_TRANSPORT_P2P )
+  if (priv->ready)
+    {
+      /* all of the streams are bidirectional from farsight's point of view, it's
+       * just in the signalling they change */
+      tp_svc_media_session_handler_emit_new_stream_handler (chan,
+        object_path, id, TP_MEDIA_STREAM_TYPE_AUDIO, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+    }
 
-#define JINGLE_AUDIO_CAPS \
-  ( PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO )
+  g_free (object_path);
 
-#define JINGLE_VIDEO_CAPS \
-  ( PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO )
+  return stream;
+}
 
-GabblePresenceCapabilities
-_gabble_media_channel_typeflags_to_caps (TpChannelMediaCapabilities flags)
+static void
+session_new_content_cb (GabbleJingleSession *session,
+    GabbleJingleContent *c, gpointer user_data)
 {
-  GabblePresenceCapabilities caps = 0;
+  GabbleMediaChannel *chan = GABBLE_MEDIA_CHANNEL (user_data);
 
-  /* currently we can only signal any (GTalk or Jingle calls) using
-   * the GTalk-P2P transport */
-  if (flags & TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P)
-    {
-      caps |= JINGLE_CAPS;
-
-      if (flags & TP_CHANNEL_MEDIA_CAPABILITY_AUDIO)
-        caps |= GTALK_CAPS | JINGLE_AUDIO_CAPS;
+  DEBUG ("called");
 
-      if (flags & TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)
-        caps |= JINGLE_VIDEO_CAPS;
-    }
-
-  return caps;
+  create_stream_from_content (chan, c);
 }
 
 TpChannelMediaCapabilities
@@ -2072,6 +2255,29 @@ gabble_media_channel_request_hold (TpSvcChannelInterfaceHold *iface,
 }
 
 static void
+_emit_new_stream (GabbleMediaChannel *chan,
+                  GabbleMediaStream *stream)
+{
+  gchar *object_path;
+  guint id, media_type;
+
+  g_object_get (stream,
+                "object-path", &object_path,
+                "id", &id,
+                "media-type", &media_type,
+                NULL);
+
+  /* all of the streams are bidirectional from farsight's point of view, it's
+   * just in the signalling they change */
+  DEBUG ("emitting MediaSessionHandler:NewStreamHandler signal");
+  tp_svc_media_session_handler_emit_new_stream_handler (chan,
+      object_path, id, media_type, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+
+  g_free (object_path);
+}
+
+
+static void
 gabble_media_channel_ready (TpSvcMediaSessionHandler *iface,
                             DBusGMethodInvocation *context)
 {
@@ -2081,14 +2287,12 @@ gabble_media_channel_ready (TpSvcMediaSessionHandler *iface,
 
   if (!priv->ready)
     {
-      // guint i;
+      guint i;
 
       priv->ready = TRUE;
 
-      /* FIXME 
       for (i = 0; i < priv->streams->len; i++)
         _emit_new_stream (self, g_ptr_array_index (priv->streams, i));
-        */
     }
 
   tp_svc_media_session_handler_return_from_ready (context);
diff --git a/src/media-channel.h b/src/media-channel.h
index a0dc459..ec7bedc 100644
--- a/src/media-channel.h
+++ b/src/media-channel.h
@@ -27,7 +27,6 @@
 #include <telepathy-glib/group-mixin.h>
 #include <telepathy-glib/properties-mixin.h>
 
-#include "media-session.h"
 #include "presence.h"
 
 G_BEGIN_DECLS
diff --git a/src/media-factory.c b/src/media-factory.c
index 9e355f0..56602c6 100644
--- a/src/media-factory.c
+++ b/src/media-factory.c
@@ -70,11 +70,6 @@ struct _GabbleMediaFactoryPrivate
 
   GHashTable *session_chans;
 
-  gboolean get_stun_from_jingle;
-  gchar *stun_server;
-  guint16 stun_port;
-  gchar *relay_token;
-
   gboolean dispose_has_run;
 };
 
@@ -123,9 +118,6 @@ gabble_media_factory_dispose (GObject *object)
       priv->session_chans = NULL;
     }
 
-  g_free (priv->stun_server);
-  g_free (priv->relay_token);
-
   if (G_OBJECT_CLASS (gabble_media_factory_parent_class)->dispose)
     G_OBJECT_CLASS (gabble_media_factory_parent_class)->dispose (object);
 }
@@ -276,20 +268,6 @@ new_media_channel (GabbleMediaFactory *fac,
                        "session", sess,
                        NULL);
 
-  if (priv->stun_server != NULL)
-    {
-      g_object_set ((GObject *) chan, "stun-server", priv->stun_server, NULL);
-
-      if (priv->stun_port != 0)
-        g_object_set ((GObject *) chan, "stun-port", priv->stun_port, NULL);
-    }
-
-  if (priv->relay_token != NULL)
-    {
-      g_object_set ((GObject *) chan, "gtalk-p2p-relay-token",
-          priv->relay_token, NULL);
-    }
-
   DEBUG ("object path %s", object_path);
 
   g_signal_connect (chan, "closed", (GCallback) media_channel_closed_cb, fac);
@@ -371,6 +349,7 @@ connection_status_changed_cb (GabbleConnection *conn,
           G_CALLBACK (new_jingle_session_cb), self);
       break;
 
+#if 0
     case TP_CONNECTION_STATUS_CONNECTED:
         {
           gchar *stun_server = NULL;
@@ -399,6 +378,7 @@ connection_status_changed_cb (GabbleConnection *conn,
             }
         }
       break;
+#endif
 
     case TP_CONNECTION_STATUS_DISCONNECTED:
       gabble_media_factory_close_all (self);
diff --git a/src/media-session.c b/src/media-session.c
deleted file mode 100644
index 7ec010b..0000000
--- a/src/media-session.c
+++ /dev/null
@@ -1,2933 +0,0 @@
-/*
- * gabble-media-session.c - Source for GabbleMediaSession
- * Copyright (C) 2006 Collabora Ltd.
- * Copyright (C) 2006 Nokia Corporation
- *   @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas at collabora.co.uk>
- *
- * 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 "media-session.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#include <dbus/dbus-glib.h>
-#include <telepathy-glib/debug-ansi.h>
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/errors.h>
-#include <telepathy-glib/svc-media-interfaces.h>
-
-#define DEBUG_FLAG GABBLE_DEBUG_MEDIA
-
-#include "connection.h"
-#include "debug.h"
-#include "gabble-signals-marshal.h"
-#include "media-channel.h"
-#include "media-stream.h"
-#include "namespaces.h"
-#include "presence-cache.h"
-#include "presence.h"
-#include "util.h"
-
-static void session_handler_iface_init (gpointer, gpointer);
-
-G_DEFINE_TYPE_WITH_CODE(GabbleMediaSession,
-    gabble_media_session,
-    G_TYPE_OBJECT,
-    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_SESSION_HANDLER,
-      session_handler_iface_init)
-    )
-
-#define DEFAULT_SESSION_TIMEOUT 60000
-
-#define GTALK_STREAM_NAME "gtalk"
-
-/* 99 streams gives us a max name length of 8 (videoXX\0 or audioXX\0) */
-#define MAX_STREAMS 99
-#define MAX_STREAM_NAME_LEN 8
-
-/* signal enum */
-enum
-{
-    STREAM_ADDED,
-    TERMINATED,
-    LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = {0};
-
-/* properties */
-enum
-{
-  PROP_CONNECTION = 1,
-  PROP_MEDIA_CHANNEL,
-  PROP_OBJECT_PATH,
-  PROP_SESSION_ID,
-  PROP_INITIATOR,
-  PROP_PEER,
-  PROP_PEER_RESOURCE,
-  PROP_STATE,
-  LAST_PROPERTY
-};
-
-/* private structure */
-
-struct _GabbleMediaSessionPrivate
-{
-  GabbleConnection *conn;
-  GabbleMediaChannel *channel;
-  GabbleMediaSessionMode mode;
-  gchar *object_path;
-
-  GPtrArray *streams;
-  GPtrArray *remove_requests;
-
-  gchar *id;
-  TpHandle peer;
-  gchar *peer_resource;
-
-  JingleSessionState state;
-  gboolean ready;
-  gboolean locally_accepted;
-  gboolean terminated;
-
-  guint timer_id;
-
-  gboolean dispose_has_run;
-};
-
-#define GABBLE_MEDIA_SESSION_GET_PRIVATE(obj) ((obj)->priv)
-
-typedef struct {
-    gchar *name;
-    gchar *attributes;
-} SessionStateDescription;
-
-static const SessionStateDescription session_states[] =
-{
-    { "JS_STATE_PENDING_CREATED",
-      TP_ANSI_BOLD_ON TP_ANSI_FG_BLACK TP_ANSI_BG_WHITE   },
-    { "JS_STATE_PENDING_INITIATE_SENT",
-      TP_ANSI_BOLD_ON                  TP_ANSI_BG_CYAN    },
-    { "JS_STATE_PENDING_INITIATED",
-      TP_ANSI_BOLD_ON                  TP_ANSI_BG_MAGENTA },
-    { "JS_STATE_PENDING_ACCEPT_SENT",
-      TP_ANSI_BOLD_ON                  TP_ANSI_BG_CYAN    },
-    { "JS_STATE_ACTIVE",
-      TP_ANSI_BOLD_ON                  TP_ANSI_BG_BLUE    },
-    { "JS_STATE_ENDED",
-                                       TP_ANSI_BG_RED     }
-};
-
-static void
-gabble_media_session_init (GabbleMediaSession *self)
-{
-  GabbleMediaSessionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
-      GABBLE_TYPE_MEDIA_SESSION, GabbleMediaSessionPrivate);
-
-  self->priv = priv;
-
-  priv->mode = MODE_JINGLE;
-  priv->state = JS_STATE_PENDING_CREATED;
-  priv->streams = g_ptr_array_new ();
-  priv->remove_requests = g_ptr_array_new ();
-}
-
-static void stream_connection_state_changed_cb (GabbleMediaStream *stream,
-                                                GParamSpec *param,
-                                                GabbleMediaSession *session);
-static void stream_got_local_codecs_changed_cb (GabbleMediaStream *stream,
-                                                GParamSpec *param,
-                                                GabbleMediaSession *session);
-
-static void
-_emit_new_stream (GabbleMediaSession *session,
-                  GabbleMediaStream *stream)
-{
-  gchar *object_path;
-  guint id, media_type;
-
-  g_object_get (stream,
-                "object-path", &object_path,
-                "id", &id,
-                "media-type", &media_type,
-                NULL);
-
-  /* all of the streams are bidirectional from farsight's point of view, it's
-   * just in the signalling they change */
-  tp_svc_media_session_handler_emit_new_stream_handler (session,
-      object_path, id, media_type, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
-
-  g_free (object_path);
-}
-
-
-static GabbleMediaStream *
-_lookup_stream_by_name_and_initiator (GabbleMediaSession *session,
-                                      const gchar *stream_name,
-                                      JingleInitiator stream_initiator)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i;
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (tp_strdiff (stream->name, stream_name))
-        continue;
-
-      if (stream_initiator != INITIATOR_INVALID &&
-          stream_initiator != stream->initiator)
-        continue;
-
-      return stream;
-    }
-
-  return NULL;
-}
-
-
-static GabbleMediaStream *
-create_media_stream (GabbleMediaSession *session,
-                     const gchar *name,
-                     JingleInitiator initiator,
-                     guint media_type)
-{
-  GabbleMediaSessionPrivate *priv;
-  gchar *object_path;
-  GabbleMediaStream *stream;
-  guint id;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (session));
-  g_assert (name != NULL);
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  /* assert that if we're in google mode:
-   *  - we only try to make one stream
-   *  - it's an audio stream
-   *  - it's called GTALK_STREAM_NAME */
-  if (priv->mode == MODE_GOOGLE)
-    {
-      g_assert (priv->streams->len == 0);
-      g_assert (media_type == TP_MEDIA_STREAM_TYPE_AUDIO);
-      g_assert (!tp_strdiff (name, GTALK_STREAM_NAME));
-    }
-
-  g_assert (priv->streams->len < MAX_STREAMS);
-  g_assert (_lookup_stream_by_name_and_initiator (session, name, initiator) ==
-      NULL);
-
-  id = _gabble_media_channel_get_stream_id (priv->channel);
-
-  GMS_DEBUG_INFO (session,
-      "creating new %s %s stream called \"%s\" with id %u",
-      priv->mode == MODE_GOOGLE ? "google" : "jingle",
-      media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video",
-      name, id);
-
-  object_path = g_strdup_printf ("%s/MediaStream%u", priv->object_path, id);
-
-  stream = g_object_new (GABBLE_TYPE_MEDIA_STREAM,
-                         "connection", priv->conn,
-                         "media-session", session,
-                         "object-path", object_path,
-                         "mode", priv->mode,
-                         "name", name,
-                         "id", id,
-                         "initiator", initiator,
-                         "media-type", media_type,
-                         NULL);
-
-  g_signal_connect (stream, "notify::connection-state",
-                    (GCallback) stream_connection_state_changed_cb,
-                    session);
-  g_signal_connect (stream, "notify::got-local-codecs",
-                    (GCallback) stream_got_local_codecs_changed_cb,
-                    session);
-
-  g_ptr_array_add (priv->streams, stream);
-
-  g_free (object_path);
-
-  if (priv->ready)
-    _emit_new_stream (session, stream);
-
-  g_signal_emit (session, signals[STREAM_ADDED], 0, stream);
-
-  return stream;
-}
-
-static void
-destroy_media_stream (GabbleMediaSession *session,
-                      GabbleMediaStream *stream)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  _gabble_media_stream_close (stream);
-  g_ptr_array_remove_fast (priv->streams, stream);
-  g_object_unref (stream);
-}
-
-static GObject *
-gabble_media_session_constructor (GType type, guint n_props,
-                                  GObjectConstructParam *props)
-{
-  GObject *obj;
-  GabbleMediaSessionPrivate *priv;
-  DBusGConnection *bus;
-  TpHandleRepoIface *contact_handles;
-
-  obj = G_OBJECT_CLASS (gabble_media_session_parent_class)->
-           constructor (type, n_props, props);
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (GABBLE_MEDIA_SESSION (obj));
-
-  bus = tp_get_bus ();
-  dbus_g_connection_register_g_object (bus, priv->object_path, obj);
-
-  contact_handles = tp_base_connection_get_handles (
-      (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
-
-  tp_handle_ref (contact_handles, priv->peer);
-
-  return obj;
-}
-
-static void
-gabble_media_session_get_property (GObject    *object,
-                                   guint       property_id,
-                                   GValue     *value,
-                                   GParamSpec *pspec)
-{
-  GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  switch (property_id) {
-    case PROP_CONNECTION:
-      g_value_set_object (value, priv->conn);
-      break;
-    case PROP_MEDIA_CHANNEL:
-      g_value_set_object (value, priv->channel);
-      break;
-    case PROP_OBJECT_PATH:
-      g_value_set_string (value, priv->object_path);
-      break;
-    case PROP_SESSION_ID:
-      g_value_set_string (value, priv->id);
-      break;
-    case PROP_INITIATOR:
-      g_value_set_uint (value, session->initiator);
-      break;
-    case PROP_PEER:
-      g_value_set_uint (value, priv->peer);
-      break;
-    case PROP_PEER_RESOURCE:
-      g_value_set_string (value, priv->peer_resource);
-      break;
-    case PROP_STATE:
-      g_value_set_uint (value, priv->state);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-      break;
-  }
-}
-
-static void session_state_changed (GabbleMediaSession *session,
-                                   JingleSessionState prev_state,
-                                   JingleSessionState new_state);
-
-static void
-gabble_media_session_set_property (GObject      *object,
-                                   guint         property_id,
-                                   const GValue *value,
-                                   GParamSpec   *pspec)
-{
-  GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  JingleSessionState prev_state;
-
-  switch (property_id) {
-    case PROP_CONNECTION:
-      priv->conn = g_value_get_object (value);
-      break;
-    case PROP_MEDIA_CHANNEL:
-      priv->channel = g_value_get_object (value);
-      break;
-    case PROP_OBJECT_PATH:
-      g_free (priv->object_path);
-      priv->object_path = g_value_dup_string (value);
-      break;
-    case PROP_SESSION_ID:
-      g_free (priv->id);
-      priv->id = g_value_dup_string (value);
-      break;
-    case PROP_INITIATOR:
-      session->initiator = g_value_get_uint (value);
-      break;
-    case PROP_PEER:
-      priv->peer = g_value_get_uint (value);
-      break;
-    case PROP_PEER_RESOURCE:
-      g_free (priv->peer_resource);
-      priv->peer_resource = g_value_dup_string (value);
-      break;
-    case PROP_STATE:
-      prev_state = priv->state;
-      priv->state = g_value_get_uint (value);
-
-      if (priv->state == JS_STATE_ENDED)
-        g_assert (priv->terminated);
-
-      if (priv->state != prev_state)
-        session_state_changed (session, prev_state, priv->state);
-
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-      break;
-  }
-}
-
-static void gabble_media_session_dispose (GObject *object);
-static void gabble_media_session_finalize (GObject *object);
-
-static void
-gabble_media_session_class_init (GabbleMediaSessionClass *gabble_media_session_class)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (gabble_media_session_class);
-  GParamSpec *param_spec;
-
-  g_type_class_add_private (gabble_media_session_class,
-      sizeof (GabbleMediaSessionPrivate));
-
-  object_class->constructor = gabble_media_session_constructor;
-
-  object_class->get_property = gabble_media_session_get_property;
-  object_class->set_property = gabble_media_session_set_property;
-
-  object_class->dispose = gabble_media_session_dispose;
-  object_class->finalize = gabble_media_session_finalize;
-
-  param_spec = g_param_spec_object ("connection", "GabbleConnection object",
-                                    "Gabble connection object that owns this "
-                                    "media session's channel.",
-                                    GABBLE_TYPE_CONNECTION,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
-  param_spec = g_param_spec_object ("media-channel",
-      "GabbleMediaChannel object",
-      "Gabble media channel object that owns this media session object.",
-      GABBLE_TYPE_MEDIA_CHANNEL,
-      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NICK |
-      G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_MEDIA_CHANNEL,
-      param_spec);
-
-  param_spec = g_param_spec_string ("object-path", "D-Bus object path",
-                                    "The D-Bus object path used for this "
-                                    "object on the bus.",
-                                    NULL,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NAME |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
-
-  param_spec = g_param_spec_string ("session-id", "Session ID",
-                                    "A unique session identifier used "
-                                    "throughout all communication.",
-                                    NULL,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NAME |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_SESSION_ID, param_spec);
-
-  param_spec = g_param_spec_uint ("initiator", "Session initiator",
-                                  "An enum signifying which end initiated "
-                                  "the session.",
-                                  INITIATOR_LOCAL,
-                                  INITIATOR_REMOTE,
-                                  INITIATOR_LOCAL,
-                                  G_PARAM_CONSTRUCT_ONLY |
-                                  G_PARAM_READWRITE |
-                                  G_PARAM_STATIC_NAME |
-                                  G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_INITIATOR, param_spec);
-
-  param_spec = g_param_spec_uint ("peer", "Session peer",
-                                  "The TpHandle representing the contact "
-                                  "with whom this session communicates.",
-                                  0, G_MAXUINT32, 0,
-                                  G_PARAM_CONSTRUCT_ONLY |
-                                  G_PARAM_READWRITE |
-                                  G_PARAM_STATIC_NAME |
-                                  G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_PEER, param_spec);
-
-  param_spec = g_param_spec_string ("peer-resource",
-                                    "Session peer's resource",
-                                    "The resource of the contact "
-                                    "with whom this session communicates, "
-                                    "if applicable",
-                                    NULL,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_WRITABLE |
-                                    G_PARAM_STATIC_NAME |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_PEER_RESOURCE,
-                                   param_spec);
-
-  param_spec = g_param_spec_uint ("state", "Session state",
-                                  "The current state that the session is in.",
-                                  0, G_MAXUINT32, 0,
-                                  G_PARAM_READWRITE |
-                                  G_PARAM_STATIC_NAME |
-                                  G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_STATE, param_spec);
-
-  signals[STREAM_ADDED] =
-    g_signal_new ("stream-added",
-                  G_OBJECT_CLASS_TYPE (gabble_media_session_class),
-                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
-                  0,
-                  NULL, NULL,
-                  g_cclosure_marshal_VOID__OBJECT,
-                  G_TYPE_NONE, 1, G_TYPE_OBJECT);
-
-  signals[TERMINATED] =
-    g_signal_new ("terminated",
-                  G_OBJECT_CLASS_TYPE (gabble_media_session_class),
-                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
-                  0,
-                  NULL, NULL,
-                  gabble_marshal_VOID__UINT_UINT,
-                  G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
-}
-
-static void
-gabble_media_session_dispose (GObject *object)
-{
-  GabbleMediaSession *self = GABBLE_MEDIA_SESSION (object);
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
-  guint i;
-  TpHandleRepoIface *contact_handles;
-
-  DEBUG ("called");
-
-  if (priv->dispose_has_run)
-    return;
-
-  priv->dispose_has_run = TRUE;
-
-  _gabble_media_session_terminate (self, INITIATOR_LOCAL,
-      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
-  if (priv->timer_id != 0)
-    g_source_remove (priv->timer_id);
-
-  if (priv->streams != NULL)
-    {
-      for (i = 0; i < priv->streams->len; i++)
-        g_object_unref (g_ptr_array_index (priv->streams, i));
-      g_ptr_array_free (priv->streams, TRUE);
-      priv->streams = NULL;
-    }
-
-  for (i = 0; i < priv->remove_requests->len; i++)
-    g_ptr_array_free (g_ptr_array_index (priv->remove_requests, i), TRUE);
-  g_ptr_array_free (priv->remove_requests, TRUE);
-  priv->remove_requests = NULL;
-
-  contact_handles = tp_base_connection_get_handles (
-      (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
-
-  tp_handle_unref (contact_handles, priv->peer);
-
-  if (G_OBJECT_CLASS (gabble_media_session_parent_class)->dispose)
-    G_OBJECT_CLASS (gabble_media_session_parent_class)->dispose (object);
-}
-
-static void
-gabble_media_session_finalize (GObject *object)
-{
-  GabbleMediaSession *self = GABBLE_MEDIA_SESSION (object);
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
-
-  g_free (priv->id);
-  g_free (priv->object_path);
-  g_free (priv->peer_resource);
-  G_OBJECT_CLASS (gabble_media_session_parent_class)->finalize (object);
-}
-
-
-/**
- * gabble_media_session_error
- *
- * Implements D-Bus method Error
- * on interface org.freedesktop.Telepathy.Media.SessionHandler
- */
-static void
-gabble_media_session_error (TpSvcMediaSessionHandler *iface,
-                            guint errno,
-                            const gchar *message,
-                            DBusGMethodInvocation *context)
-{
-  GabbleMediaSession *self = GABBLE_MEDIA_SESSION (iface);
-  GabbleMediaSessionPrivate *priv;
-  GPtrArray *tmp;
-  guint i;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (self));
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
-
-  GMS_DEBUG_INFO (self, "Media.SessionHandler::Error called, error %u (%s) -- "
-      "emitting error on each stream", errno, message);
-
-  if (priv->state == JS_STATE_ENDED)
-    {
-      tp_svc_media_session_handler_return_from_error (context);
-      return;
-    }
-  else if (priv->state == JS_STATE_PENDING_CREATED)
-    {
-      /* shortcut to prevent sending remove actions if we haven't sent an
-       * initiate yet */
-      g_object_set (self, "state", JS_STATE_ENDED, NULL);
-      tp_svc_media_session_handler_return_from_error (context);
-      return;
-    }
-
-  g_assert (priv->streams != NULL);
-
-  tmp = priv->streams;
-  priv->streams = NULL;
-
-  for (i = 0; i < tmp->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (tmp, i);
-
-      gabble_media_stream_error (stream, errno, message, NULL);
-    }
-
-  g_ptr_array_free (tmp, TRUE);
-
-  tp_svc_media_session_handler_return_from_error (context);
-}
-
-
-/**
- * gabble_media_session_ready
- *
- * Implements D-Bus method Ready
- * on interface org.freedesktop.Telepathy.Media.SessionHandler
- */
-static void
-gabble_media_session_ready (TpSvcMediaSessionHandler *iface,
-                            DBusGMethodInvocation *context)
-{
-  GabbleMediaSession *self = GABBLE_MEDIA_SESSION (iface);
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
-
-  if (!priv->ready)
-    {
-      guint i;
-
-      priv->ready = TRUE;
-
-      for (i = 0; i < priv->streams->len; i++)
-        _emit_new_stream (self, g_ptr_array_index (priv->streams, i));
-    }
-
-  tp_svc_media_session_handler_return_from_ready (context);
-}
-
-
-static gboolean
-_handle_create (GabbleMediaSession *session,
-                LmMessage *message,
-                LmMessageNode *content_node,
-                const gchar *stream_name,
-                GabbleMediaStream *stream,
-                LmMessageNode *desc_node,
-                LmMessageNode *trans_node,
-                GError **error)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  GabbleMediaSessionMode session_mode;
-  TpMediaStreamType stream_type;
-  gboolean override_existing = FALSE;
-
-  if ((priv->state == JS_STATE_PENDING_CREATED) &&
-      (session->initiator == INITIATOR_LOCAL))
-    {
-      DEBUG ("we're trying to call ourselves, rejecting with busy");
-      _gabble_media_session_terminate (session, INITIATOR_REMOTE,
-          TP_CHANNEL_GROUP_CHANGE_REASON_BUSY);
-          return FALSE;
-    }
-
-
-  if (stream != NULL)
-    {
-      /* streams added by the session initiator may replace similarly-named
-       * streams which we are trying to add (but havn't had acknowledged) */
-      if (stream->signalling_state < STREAM_SIG_STATE_ACKNOWLEDGED)
-        {
-          if (session->initiator == INITIATOR_REMOTE)
-            {
-              override_existing = TRUE;
-            }
-          else
-            {
-              g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT,
-                  "session initiator is creating a stream named \"%s\" "
-                  "already", stream_name);
-              return FALSE;
-            }
-        }
-      else
-        {
-          g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT,
-              "can't create new stream called \"%s\", it already exists, "
-              "rejecting", stream_name);
-          return FALSE;
-        }
-    }
-
-  if (desc_node == NULL)
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
-          "unable to create stream without a content description");
-      return FALSE;
-    }
-
-  if (lm_message_node_has_namespace (desc_node,
-        NS_GOOGLE_SESSION_PHONE, NULL))
-    {
-      session_mode = MODE_GOOGLE;
-      stream_type = TP_MEDIA_STREAM_TYPE_AUDIO;
-    }
-  else if (lm_message_node_has_namespace (desc_node,
-        NS_JINGLE_DESCRIPTION_AUDIO, NULL))
-    {
-      session_mode = MODE_JINGLE;
-      stream_type = TP_MEDIA_STREAM_TYPE_AUDIO;
-    }
-  else if (lm_message_node_has_namespace (desc_node,
-        NS_JINGLE_DESCRIPTION_VIDEO, NULL))
-    {
-      session_mode = MODE_JINGLE;
-      stream_type = TP_MEDIA_STREAM_TYPE_VIDEO;
-    }
-  else
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR,
-          XMPP_ERROR_JINGLE_UNSUPPORTED_CONTENT,
-          "refusing to create stream for unsupported content description");
-      return FALSE;
-    }
-
-  /* MODE_GOOGLE is allowed to have a null transport node */
-  if (session_mode == MODE_JINGLE && trans_node == NULL)
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR,
-          XMPP_ERROR_JINGLE_UNSUPPORTED_TRANSPORT,
-          "refusing to create stream for unsupported transport");
-      return FALSE;
-    }
-
-  if (session_mode != priv->mode)
-    {
-      if (priv->streams->len > 0)
-        {
-          g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_UNEXPECTED_REQUEST,
-              "refusing to change mode because streams already exist");
-          return FALSE;
-        }
-      else
-        {
-          GMS_DEBUG_INFO (session, "setting session mode to %s",
-              session_mode == MODE_GOOGLE ? "google" : "jingle");
-          priv->mode = session_mode;
-        }
-    }
-
-  if (override_existing)
-    {
-      GMS_DEBUG_INFO (session, "removing our unacknowledged stream \"%s\" "
-          "in favour of the session initiator's", stream_name);
-
-      /* disappear this stream */
-      destroy_media_stream (session, stream);
-
-      stream = NULL;
-    }
-
-  if (priv->streams->len == MAX_STREAMS)
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_RESOURCE_CONSTRAINT,
-          "refusing to create more than " G_STRINGIFY (MAX_STREAMS)
-          " streams");
-      return FALSE;
-    }
-
-  stream = create_media_stream (session, stream_name, INITIATOR_REMOTE,
-      stream_type);
-
-  /* set the signalling state to ACKNOWLEDGED */
-  g_object_set (stream,
-      "signalling-state", STREAM_SIG_STATE_ACKNOWLEDGED,
-      NULL);
-
-  /* for jingle streams, set the direction to none, so that the
-   * direction handler adds the right flags */
-  if (priv->mode == MODE_JINGLE)
-    g_object_set (stream,
-        "combined-direction", TP_MEDIA_STREAM_DIRECTION_NONE,
-        NULL);
-
-  return TRUE;
-}
-
-
-static TpMediaStreamDirection
-_senders_to_direction (GabbleMediaSession *session,
-                       const gchar *senders)
-{
-  TpMediaStreamDirection ret = TP_MEDIA_STREAM_DIRECTION_NONE;
-
-  if (!tp_strdiff (senders, "initiator"))
-    {
-      if (session->initiator == INITIATOR_LOCAL)
-        ret = TP_MEDIA_STREAM_DIRECTION_SEND;
-      else
-        ret = TP_MEDIA_STREAM_DIRECTION_RECEIVE;
-    }
-  else if (!tp_strdiff (senders, "responder"))
-    {
-      if (session->initiator == INITIATOR_REMOTE)
-        ret = TP_MEDIA_STREAM_DIRECTION_SEND;
-      else
-        ret = TP_MEDIA_STREAM_DIRECTION_RECEIVE;
-    }
-  else if (!tp_strdiff (senders, "both"))
-    {
-      ret = TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
-    }
-
-  return ret;
-}
-
-static gboolean
-_handle_direction (GabbleMediaSession *session,
-                   LmMessage *message,
-                   LmMessageNode *content_node,
-                   const gchar *stream_name,
-                   GabbleMediaStream *stream,
-                   LmMessageNode *desc_node,
-                   LmMessageNode *trans_node,
-                   GError **error)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  const gchar *senders;
-  CombinedStreamDirection new_combined_dir;
-  TpMediaStreamDirection requested_dir, current_dir;
-  TpMediaStreamPendingSend pending_send;
-
-  if (priv->mode == MODE_GOOGLE)
-    return TRUE;
-
-  requested_dir = TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
-
-  senders = lm_message_node_get_attribute (content_node, "senders");
-  if (senders != NULL)
-    requested_dir = _senders_to_direction (session, senders);
-
-  if (requested_dir == TP_MEDIA_STREAM_DIRECTION_NONE)
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
-          "received invalid content senders value \"%s\" on stream \"%s\"; "
-          "rejecting", senders, stream_name);
-      return FALSE;
-    }
-
-  current_dir = COMBINED_DIRECTION_GET_DIRECTION (stream->combined_direction);
-  pending_send = COMBINED_DIRECTION_GET_PENDING_SEND
-    (stream->combined_direction);
-
-  GMS_DEBUG_INFO (session, "received request for senders \"%s\" on stream "
-      "\"%s\"", senders, stream_name);
-
-  /* if local sending has been added, remove it,
-   * and set the pending local send flag */
-  if (((current_dir & TP_MEDIA_STREAM_DIRECTION_SEND) == 0) &&
-    ((requested_dir & TP_MEDIA_STREAM_DIRECTION_SEND) != 0))
-    {
-      GMS_DEBUG_INFO (session, "setting pending local send flag");
-      requested_dir &= ~TP_MEDIA_STREAM_DIRECTION_SEND;
-      pending_send |= TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
-    }
-
-#if 0
-  /* clear any pending remote send */
-  if ((pending_send & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0)
-    {
-      GMS_DEBUG_INFO (session, "setting pending local send flag");
-      pending_send &= ~TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
-    }
-#endif
-
-  /* make any necessary changes */
-  new_combined_dir = MAKE_COMBINED_DIRECTION (requested_dir, pending_send);
-  if (new_combined_dir != stream->combined_direction)
-    {
-      g_object_set (stream, "combined-direction", new_combined_dir, NULL);
-      _gabble_media_stream_update_sending (stream, FALSE);
-    }
-
-  return TRUE;
-}
-
-
-static gboolean
-_handle_accept (GabbleMediaSession *session,
-                LmMessage *message,
-                LmMessageNode *content_node,
-                const gchar *stream_name,
-                GabbleMediaStream *stream,
-                LmMessageNode *desc_node,
-                LmMessageNode *trans_node,
-                GError **error)
-{
-  g_object_set (stream, "playing", TRUE, NULL);
-
-  _gabble_media_stream_update_sending (stream, TRUE);
-
-  return TRUE;
-}
-
-
-static gboolean
-_handle_codecs (GabbleMediaSession *session,
-                LmMessage *message,
-                LmMessageNode *content_node,
-                const gchar *stream_name,
-                GabbleMediaStream *stream,
-                LmMessageNode *desc_node,
-                LmMessageNode *trans_node,
-                GError **error)
-{
-  if (desc_node == NULL)
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
-          "unable to handle codecs without a content description node");
-      return FALSE;
-    }
-
-  if (!_gabble_media_stream_post_remote_codecs (stream, message, desc_node,
-        error))
-    return FALSE;
-
-  return TRUE;
-}
-
-
-static gboolean
-_handle_candidates (GabbleMediaSession *session,
-                    LmMessage *message,
-                    LmMessageNode *content_node,
-                    const gchar *stream_name,
-                    GabbleMediaStream *stream,
-                    LmMessageNode *desc_node,
-                    LmMessageNode *trans_node,
-                    GError **error)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  if (trans_node == NULL)
-    {
-      if (priv->mode == MODE_GOOGLE)
-        {
-          trans_node = content_node;
-        }
-      else
-        {
-          g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
-              "unable to handle candidates without a transport node");
-          return FALSE;
-        }
-    }
-
-  if (!_gabble_media_stream_post_remote_candidates (stream, message,
-        trans_node, error))
-    return FALSE;
-
-  return TRUE;
-}
-
-static guint
-_count_non_removing_streams (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i, ret = 0;
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (stream->signalling_state < STREAM_SIG_STATE_REMOVING)
-        ret++;
-    }
-
-  return ret;
-}
-
-static gboolean
-_handle_remove (GabbleMediaSession *session,
-                LmMessage *message,
-                LmMessageNode *content_node,
-                const gchar *stream_name,
-                GabbleMediaStream *stream,
-                LmMessageNode *desc_node,
-                LmMessageNode *trans_node,
-                GError **error)
-{
-  /* reducing a session to contain 0 streams is invalid; instead the peer
-   * should terminate the session. I guess we'll do it for them... */
-  if (_count_non_removing_streams (session) == 1)
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
-          "unable to remove the last stream in a Jingle call");
-      return FALSE;
-    }
-
-  /* close the stream */
-  destroy_media_stream (session, stream);
-
-  return TRUE;
-}
-
-
-static gboolean
-_handle_terminate (GabbleMediaSession *session,
-                   LmMessage *message,
-                   LmMessageNode *content_node,
-                   const gchar *stream_name,
-                   GabbleMediaStream *stream,
-                   LmMessageNode *desc_node,
-                   LmMessageNode *trans_node,
-                   GError **error)
-{
-  DEBUG ("called for %s", stream_name);
-
-  _gabble_media_session_terminate (session, INITIATOR_REMOTE,
-      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
-  return TRUE;
-}
-
-
-typedef gboolean (*StreamHandlerFunc)(GabbleMediaSession *session,
-                                      LmMessage *message,
-                                      LmMessageNode *content_node,
-                                      const gchar *stream_name,
-                                      GabbleMediaStream *stream,
-                                      LmMessageNode *desc_node,
-                                      LmMessageNode *trans_node,
-                                      GError **error);
-
-typedef struct _Handler Handler;
-
-struct _Handler {
-  const gchar *actions[3];
-  JingleSessionState min_allowed_state;
-  JingleSessionState max_allowed_state;
-  StreamHandlerFunc stream_handlers[4];
-  JingleSessionState new_state;
-};
-
-static Handler handlers[] = {
-  {
-    { "initiate", "session-initiate", NULL },
-    JS_STATE_PENDING_CREATED,
-    JS_STATE_PENDING_CREATED,
-    { _handle_create, _handle_direction, _handle_codecs, NULL },
-    JS_STATE_PENDING_INITIATED
-  },
-  {
-    { "accept", "session-accept", NULL },
-    JS_STATE_PENDING_INITIATED,
-    JS_STATE_PENDING_INITIATED,
-    { _handle_direction, _handle_codecs, _handle_accept, NULL },
-    JS_STATE_ACTIVE
-  },
-  {
-    { "reject", NULL },
-    JS_STATE_PENDING_INITIATE_SENT,
-    JS_STATE_PENDING_INITIATED,
-    { _handle_terminate, NULL },
-    JS_STATE_INVALID
-  },
-  {
-    { "terminate", "session-terminate", NULL },
-    JS_STATE_PENDING_INITIATED,
-    JS_STATE_ENDED,
-    { _handle_terminate, NULL },
-    JS_STATE_INVALID
-  },
-  {
-    { "candidates", "transport-info", NULL },
-    JS_STATE_PENDING_INITIATED,
-    JS_STATE_ACTIVE,
-    { _handle_candidates, NULL },
-    JS_STATE_INVALID
-  },
-  {
-    { "content-add", NULL },
-    JS_STATE_ACTIVE,
-    JS_STATE_ACTIVE,
-    { _handle_create, _handle_direction, _handle_codecs, NULL },
-    JS_STATE_INVALID,
-  },
-  {
-    { "content-modify", NULL },
-    JS_STATE_PENDING_INITIATED,
-    JS_STATE_ACTIVE,
-    { _handle_direction, NULL },
-    JS_STATE_INVALID
-  },
-  {
-    { "content-accept", NULL },
-    JS_STATE_PENDING_INITIATED,
-    JS_STATE_ACTIVE,
-    { _handle_direction, _handle_codecs, _handle_accept, NULL },
-    JS_STATE_INVALID
-  },
-  {
-    { "content-remove", "content-decline", NULL },
-    JS_STATE_PENDING_INITIATED,
-    JS_STATE_ACTIVE,
-    { _handle_remove, NULL },
-    JS_STATE_INVALID
-  },
-  {
-    { NULL },
-    JS_STATE_INVALID,
-    JS_STATE_INVALID,
-    { NULL },
-    JS_STATE_INVALID
-  }
-};
-
-
-static gboolean
-_call_handlers_on_stream (GabbleMediaSession *session,
-                          LmMessage *message,
-                          LmMessageNode *content_node,
-                          const gchar *stream_name,
-                          JingleInitiator stream_creator,
-                          StreamHandlerFunc *func,
-                          GError **error)
-{
-  GabbleMediaStream *stream = NULL;
-  LmMessageNode *desc_node = NULL, *trans_node = NULL;
-  StreamHandlerFunc *tmp;
-  gboolean stream_created = FALSE;
-
-  if (content_node != NULL)
-    {
-      desc_node = lm_message_node_get_child (content_node, "description");
-
-      trans_node = lm_message_node_get_child_with_namespace (content_node,
-          "transport", NS_GOOGLE_TRANSPORT_P2P);
-    }
-
-  for (tmp = func; *tmp != NULL; tmp++)
-    {
-      /* handlers may create the stream */
-      if (stream == NULL && stream_name != NULL)
-        stream = _lookup_stream_by_name_and_initiator (session, stream_name,
-            stream_creator);
-
-      /* the create handler is able to check whether or not the stream
-       * exists, and act accordingly (sometimes it will replace an existing
-       * stream, sometimes it will reject). the termination handler
-       * also requires no stream to do it's job. */
-      if (*tmp != _handle_create && *tmp != _handle_terminate)
-        {
-          /* all other handlers require the stream to exist */
-          if (stream == NULL)
-            {
-              const gchar *created = "";
-
-              if (stream_creator == INITIATOR_LOCAL)
-                created = "locally-created ";
-              else if (stream_creator == INITIATOR_REMOTE)
-                created = "remotely-created ";
-
-              g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_ITEM_NOT_FOUND,
-                  "unable to handle action for unknown %sstream \"%s\" ",
-                  created, stream_name);
-
-              return FALSE;
-            }
-          else
-            {
-              /* don't do anything with actions on streams which have not been
-               * acknowledged, or that we're trying to remove, to deal with
-               * adding/removing race conditions (actions sent by the other end
-               * before they're aware that we've added or removed a stream) */
-              if (stream->signalling_state != STREAM_SIG_STATE_ACKNOWLEDGED)
-                {
-                  GMS_DEBUG_WARNING (session, "ignoring action because stream "
-                      "%s is in state %d, not ACKNOWLEDGED", stream->name,
-                      stream->signalling_state);
-                  return TRUE;
-                }
-            }
-        }
-
-      if (!(*tmp) (session, message, content_node, stream_name, stream,
-            desc_node, trans_node, error))
-        {
-          /* if we successfully created the stream but failed to do something
-           * with it later, remove it */
-          if (stream_created)
-            destroy_media_stream (session, stream);
-
-          return FALSE;
-        }
-
-      if (*tmp == _handle_create)
-        {
-          stream_created = TRUE;
-          /* force a stream lookup after the create handler, even if we
-           * already had one (it has replacement semantics in certain
-           * situations) */
-          stream = NULL;
-        }
-    }
-
-  return TRUE;
-}
-
-
-static JingleInitiator
-_creator_to_initiator (GabbleMediaSession *session, const gchar *creator)
-{
-  if (!tp_strdiff (creator, "initiator"))
-    {
-      if (session->initiator == INITIATOR_LOCAL)
-        return INITIATOR_LOCAL;
-      else
-        return INITIATOR_REMOTE;
-    }
-  else if (!tp_strdiff (creator, "responder"))
-    {
-      if (session->initiator == INITIATOR_LOCAL)
-        return INITIATOR_REMOTE;
-      else
-        return INITIATOR_LOCAL;
-    }
-  else
-    return INITIATOR_INVALID;
-}
-
-
-static gboolean
-_call_handlers_on_streams (GabbleMediaSession *session,
-                           LmMessage *message,
-                           LmMessageNode *session_node,
-                           StreamHandlerFunc *func,
-                           GError **error)
-{
-  LmMessageNode *content_node;
-
-  if (lm_message_node_has_namespace (session_node, NS_GOOGLE_SESSION, NULL))
-    return _call_handlers_on_stream (session, message, session_node,
-        GTALK_STREAM_NAME, INITIATOR_INVALID, func, error);
-
-  if (session_node->children == NULL)
-    return _call_handlers_on_stream (session, message, NULL, NULL,
-        INITIATOR_INVALID, func, error);
-
-  for (content_node = session_node->children;
-       NULL != content_node;
-       content_node = content_node->next)
-    {
-      const gchar *stream_name, *stream_creator;
-      JingleInitiator stream_initiator;
-
-      if (tp_strdiff (content_node->name, "content"))
-        continue;
-
-      stream_name = lm_message_node_get_attribute (content_node, "name");
-
-      if (stream_name == NULL)
-        {
-          g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
-              "rejecting content node with no name");
-          return FALSE;
-        }
-
-      stream_creator = lm_message_node_get_attribute (content_node, "creator");
-      stream_initiator = _creator_to_initiator (session, stream_creator);
-
-      /* we allow NULL creator to mean INITIATOR_INVALID for backwards
-       * compatibility with clients that don't put a creator attribute in */
-      if (stream_creator != NULL && stream_initiator == INITIATOR_INVALID)
-        {
-          g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
-              "rejecting content node with invalid creators value");
-          return FALSE;
-        }
-
-      if (!_call_handlers_on_stream (session, message, content_node,
-            stream_name, stream_initiator, func, error))
-        return FALSE;
-    }
-
-  return TRUE;
-}
-
-
-gboolean
-_gabble_media_session_handle_action (GabbleMediaSession *session,
-                                     LmMessage *message,
-                                     LmMessageNode *session_node,
-                                     const gchar *action,
-                                     GError **error)
-{
-  GabbleMediaSessionPrivate *priv;
-  StreamHandlerFunc *funcs = NULL;
-  JingleSessionState new_state = JS_STATE_INVALID;
-  Handler *i;
-  const gchar **tmp;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  GMS_DEBUG_INFO (session, "got jingle session action \"%s\" from peer",
-      action);
-
-  /* do the state machine dance */
-
-  /* search the table of handlers for the action */
-  for (i = handlers; NULL != i->actions[0]; i++)
-    {
-      for (tmp = i->actions; NULL != *tmp; tmp++)
-        if (0 == strcmp (*tmp, action))
-          break;
-
-      if (NULL == *tmp)
-        continue;
-
-      /* if we're outside the allowable states for this action, return an error
-       * immediately */
-      if (priv->state < i->min_allowed_state ||
-          priv->state > i->max_allowed_state)
-        {
-          g_set_error (error, GABBLE_XMPP_ERROR,
-              XMPP_ERROR_JINGLE_OUT_OF_ORDER,
-              "action \"%s\" not allowed in current state", action);
-          goto ERROR;
-        }
-
-      funcs = i->stream_handlers;
-      new_state = i->new_state;
-
-      break;
-    }
-
-  /* pointer is not NULL if we found a matching action */
-  if (NULL == funcs)
-    {
-      g_set_error (error, GABBLE_XMPP_ERROR,
-          XMPP_ERROR_FEATURE_NOT_IMPLEMENTED, "action \"%s\" not implemented",
-          action);
-      goto ERROR;
-    }
-
-  /* call handlers if there are any (NULL-terminated array) */
-  if (NULL != *funcs)
-    {
-      if (!_call_handlers_on_streams (session, message, session_node, funcs,
-            error))
-        {
-          if (*error == NULL)
-            g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
-                "unknown error encountered with action \"%s\"",
-                action);
-
-          goto ERROR;
-        }
-    }
-
-  /* acknowledge the IQ before changing the state because the new state
-   * could perform some actions which the other end will only accept
-   * if this action has been acknowledged */
-  _gabble_connection_acknowledge_set_iq (priv->conn, message);
-
-  /* if the action specified a new state to go to, set it */
-  if (JS_STATE_INVALID != new_state)
-    g_object_set (session, "state", new_state, NULL);
-
-  return TRUE;
-
-ERROR:
-  g_assert (error != NULL);
-  GMS_DEBUG_ERROR (session, (*error)->message);
-  return FALSE;
-}
-
-static gboolean
-timeout_session (gpointer data)
-{
-  GabbleMediaSession *session = data;
-
-  DEBUG ("session timed out");
-
-  if (session->initiator == INITIATOR_LOCAL)
-      _gabble_media_session_terminate (session, INITIATOR_LOCAL,
-          TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER);
-  else
-      _gabble_media_session_terminate (session, INITIATOR_LOCAL,
-          TP_CHANNEL_GROUP_CHANGE_REASON_ERROR);
-
-  return FALSE;
-}
-
-static void do_content_add (GabbleMediaSession *, GabbleMediaStream *);
-
-static void
-_add_ready_new_streams (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i;
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      GMS_DEBUG_DUMP (session, "pondering accept-time add for stream: %s, got "
-          "local codecs: %s, initiator: %s, signalling state: %d",
-          stream->name, stream->got_local_codecs ? "true" : "false",
-          stream->initiator == INITIATOR_LOCAL ? "local" : "remote",
-          stream->signalling_state);
-
-      if (stream->got_local_codecs == FALSE)
-        continue;
-
-      if (stream->initiator == INITIATOR_REMOTE)
-        continue;
-
-      if (stream->signalling_state > STREAM_SIG_STATE_NEW)
-        continue;
-
-      do_content_add (session, stream);
-    }
-}
-
-static void
-session_state_changed (GabbleMediaSession *session,
-                       JingleSessionState prev_state,
-                       JingleSessionState new_state)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  GMS_DEBUG_EVENT (session, "state changed from %s to %s",
-                   session_states[prev_state].name,
-                   session_states[new_state].name);
-
-  /*
-   * If the state goes from CREATED to INITIATED (which means the remote
-   * end initiated), set the timer. If, OTOH, we're the end which just sent an
-   * initiate, set the timer.
-   */
-  if ((prev_state == JS_STATE_PENDING_CREATED &&
-       new_state == JS_STATE_PENDING_INITIATED) ||
-      (new_state == JS_STATE_PENDING_INITIATE_SENT))
-    {
-      priv->timer_id =
-        g_timeout_add (DEFAULT_SESSION_TIMEOUT, timeout_session, session);
-    }
-  else if (new_state == JS_STATE_ACTIVE)
-    {
-      g_source_remove (priv->timer_id);
-      priv->timer_id = 0;
-
-      /* signal any streams to the remote end which were added locally &
-       * became ready before the session was accepted, so haven't been
-       * mentioned yet */
-      _add_ready_new_streams (session);
-    }
-}
-
-static void
-_mark_local_streams_sent (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i;
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (stream->initiator == INITIATOR_REMOTE)
-        continue;
-
-      GMS_DEBUG_INFO (session, "marking local stream %s as signalled",
-          stream->name);
-
-      g_object_set (stream, "signalling-state", STREAM_SIG_STATE_SENT, NULL);
-    }
-}
-
-static void
-_mark_local_streams_acked (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i;
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (stream->initiator == INITIATOR_REMOTE)
-        continue;
-
-      if (stream->signalling_state != STREAM_SIG_STATE_SENT)
-        continue;
-
-      GMS_DEBUG_INFO (session, "marking local stream %s as acknowledged",
-          stream->name);
-
-      g_object_set (stream,
-          "signalling-state", STREAM_SIG_STATE_ACKNOWLEDGED,
-          NULL);
-    }
-}
-
-static void
-_set_remote_streams_playing (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i;
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (stream->initiator == INITIATOR_LOCAL)
-        continue;
-
-      GMS_DEBUG_INFO (session, "setting remote stream %s as playing",
-          stream->name);
-
-      g_object_set (stream, "playing", TRUE, NULL);
-    }
-}
-
-static const gchar *_direction_to_senders (GabbleMediaSession *,
-    TpMediaStreamDirection);
-
-static void
-_add_content_descriptions_one (GabbleMediaSession *session,
-                               GabbleMediaStream *stream,
-                               LmMessageNode *session_node)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  LmMessageNode *content_node;
-
-  if (priv->mode == MODE_GOOGLE)
-    {
-      content_node = session_node;
-    }
-  else
-    {
-      TpMediaStreamDirection direction;
-      TpMediaStreamPendingSend pending_send;
-
-      content_node = _gabble_media_stream_add_content_node (stream,
-          session_node);
-
-      direction = COMBINED_DIRECTION_GET_DIRECTION (stream->combined_direction);
-      pending_send = COMBINED_DIRECTION_GET_PENDING_SEND
-        (stream->combined_direction);
-
-      /* if we have a pending local send flag set, the signalled (ie understood
-       * by both ends) direction of the stream is assuming that we are actually
-       * sending, so we should OR that into the direction before deciding what
-       * to signal the stream with. we don't need to consider pending remote
-       * send because it doesn't happen in Jingle */
-
-      if ((pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
-        direction |= TP_MEDIA_STREAM_DIRECTION_SEND;
-
-      if (direction != TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
-        {
-          const gchar *senders;
-          senders = _direction_to_senders (session, direction);
-          lm_message_node_set_attribute (content_node, "senders", senders);
-        }
-    }
-
-  _gabble_media_stream_content_node_add_description (stream, content_node);
-
-  _gabble_media_stream_content_node_add_transport (stream, content_node);
-}
-
-static void
-_add_content_descriptions (GabbleMediaSession *session,
-                           LmMessageNode *session_node,
-                           JingleInitiator stream_initiator)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i;
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (stream->initiator != stream_initiator)
-        {
-          GMS_DEBUG_INFO (session,
-              "not adding content description for %s stream %s",
-              stream->initiator == INITIATOR_LOCAL ? "local" : "remote",
-              stream->name);
-          continue;
-        }
-
-      _add_content_descriptions_one (session, stream, session_node);
-    }
-}
-
-static LmHandlerResult
-accept_msg_reply_cb (GabbleConnection *conn,
-                     LmMessage *sent_msg,
-                     LmMessage *reply_msg,
-                     GObject *object,
-                     gpointer user_data)
-{
-  GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i;
-
-  MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (session, "accept failed");
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (stream->initiator == INITIATOR_LOCAL)
-        continue;
-
-      _gabble_media_stream_update_sending (stream, TRUE);
-    }
-
-  g_object_set (session, "state", JS_STATE_ACTIVE, NULL);
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static gboolean
-_stream_not_ready_for_accept (GabbleMediaSession *session,
-                              GabbleMediaStream *stream)
-{
-  /* locally initiated streams shouldn't delay acceptance */
-  if (stream->initiator == INITIATOR_LOCAL)
-    return FALSE;
-
-  if (!stream->got_local_codecs)
-    {
-      GMS_DEBUG_INFO (session, "stream %s does not yet have local codecs",
-          stream->name);
-
-      return TRUE;
-    }
-
-  if (stream->connection_state != TP_MEDIA_STREAM_STATE_CONNECTED)
-    {
-      GMS_DEBUG_INFO (session, "stream %s is not yet connected", stream->name);
-
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static void
-try_session_accept (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  LmMessage *msg;
-  LmMessageNode *session_node;
-  const gchar *action;
-  guint i;
-
-  if (priv->state < JS_STATE_ACTIVE && !priv->locally_accepted)
-    {
-      GMS_DEBUG_INFO (session, "not sending accept yet, waiting for local "
-          "user to accept call");
-      return;
-    }
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (_stream_not_ready_for_accept (session, stream))
-        {
-          GMS_DEBUG_INFO (session, "not sending accept yet, found a stream "
-              "which was not yet connected or was missing local codecs");
-          return;
-        }
-    }
-
-  if (priv->mode == MODE_GOOGLE)
-    action = "accept";
-  else
-    action = "session-accept";
-
-  /* construct a session acceptance message */
-  msg = _gabble_media_session_message_new (session, action, &session_node);
-
-  /* only accept REMOTE streams; any LOCAL streams were added by the local
-   * user before accepting and should be signalled after the accept */
-  _add_content_descriptions (session, session_node, INITIATOR_REMOTE);
-
-  GMS_DEBUG_INFO (session, "sending jingle session action \"%s\" to peer",
-      action);
-
-  /* send the final acceptance message */
-  _gabble_connection_send_with_reply (priv->conn, msg, accept_msg_reply_cb,
-                                      G_OBJECT (session), NULL, NULL);
-
-  lm_message_unref (msg);
-
-  /* set remote streams playing */
-  _set_remote_streams_playing (session);
-
-  g_object_set (session, "state", JS_STATE_PENDING_ACCEPT_SENT, NULL);
-}
-
-static LmHandlerResult
-content_accept_msg_reply_cb (GabbleConnection *conn,
-                             LmMessage *sent_msg,
-                             LmMessage *reply_msg,
-                             GObject *object,
-                             gpointer user_data)
-{
-  GabbleMediaSession *session = GABBLE_MEDIA_SESSION (user_data);
-  GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
-
-  if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT)
-    {
-      GMS_DEBUG_ERROR (session, "content-accept failed; removing stream");
-      NODE_DEBUG (sent_msg->node, "message sent");
-      NODE_DEBUG (reply_msg->node, "message reply");
-
-      _gabble_media_session_remove_streams (session, &stream, 1);
-
-      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-    }
-
-  _gabble_media_stream_update_sending (stream, TRUE);
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static void
-try_content_accept (GabbleMediaSession *session,
-                    GabbleMediaStream *stream)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  LmMessage *msg;
-  LmMessageNode *session_node;
-
-  g_assert (priv->state == JS_STATE_ACTIVE);
-  g_assert (priv->mode == MODE_JINGLE);
-
-  if (_stream_not_ready_for_accept (session, stream))
-    {
-      GMS_DEBUG_INFO (session, "not sending content-accept yet, stream %s "
-          "is disconnected or missing local codecs", stream->name);
-      return;
-    }
-
-  /* send a content acceptance message */
-  msg = _gabble_media_session_message_new (session, "content-accept",
-      &session_node);
-
-  _add_content_descriptions_one (session, stream, session_node);
-
-  GMS_DEBUG_INFO (session, "sending jingle session action \"content-accept\" "
-      "to peer for stream %s", stream->name);
-
-  _gabble_connection_send_with_reply (priv->conn, msg,
-      content_accept_msg_reply_cb, G_OBJECT (stream), session, NULL);
-
-  lm_message_unref (msg);
-
-  /* set stream playing */
-  g_object_set (stream, "playing", TRUE, NULL);
-}
-
-static LmHandlerResult
-initiate_msg_reply_cb (GabbleConnection *conn,
-                       LmMessage *sent_msg,
-                       LmMessage *reply_msg,
-                       GObject *object,
-                       gpointer user_data)
-{
-  GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
-
-  MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (session, "initiate failed");
-
-  g_object_set (session, "state", JS_STATE_PENDING_INITIATED, NULL);
-
-  /* mark all of the streams that we sent in the initiate as acknowledged */
-  _mark_local_streams_acked (session);
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static gboolean
-_stream_not_ready_for_initiate (GabbleMediaSession *session,
-                                GabbleMediaStream *stream)
-{
-  if (!stream->got_local_codecs)
-    {
-      GMS_DEBUG_INFO (session, "stream %s does not yet have local codecs",
-          stream->name);
-
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static void
-try_session_initiate (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  LmMessage *msg;
-  LmMessageNode *session_node;
-  const gchar *action;
-  guint i;
-
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
-      if (_stream_not_ready_for_initiate (session, stream))
-        {
-          GMS_DEBUG_INFO (session, "not sending initiate yet, found a stream "
-            "which was missing local codecs");
-          return;
-        }
-    }
-
-  if (priv->mode == MODE_GOOGLE)
-      action = "initiate";
-  else
-      action = "session-initiate";
-
-  msg = _gabble_media_session_message_new (session, action, &session_node);
-
-  _add_content_descriptions (session, session_node, INITIATOR_LOCAL);
-
-  GMS_DEBUG_INFO (session, "sending jingle action \"%s\" to peer", action);
-
-  _gabble_connection_send_with_reply (priv->conn, msg, initiate_msg_reply_cb,
-                                      G_OBJECT (session), NULL, NULL);
-
-  lm_message_unref (msg);
-
-  /* mark local streams as sent (so that eg candidates will be sent) */
-  _mark_local_streams_sent (session);
-
-  g_object_set (session, "state", JS_STATE_PENDING_INITIATE_SENT, NULL);
-}
-
-static LmHandlerResult
-content_add_msg_reply_cb (GabbleConnection *conn,
-                          LmMessage *sent_msg,
-                          LmMessage *reply_msg,
-                          GObject *object,
-                          gpointer user_data)
-{
-  GabbleMediaSession *session = GABBLE_MEDIA_SESSION (user_data);
-  GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
-
-  if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT)
-    {
-      if (session->initiator == INITIATOR_REMOTE &&
-          stream->signalling_state == STREAM_SIG_STATE_ACKNOWLEDGED)
-        {
-          GMS_DEBUG_INFO (session, "ignoring content-add failure, stream has "
-              "been successfully created by the session initiator");
-        }
-      else
-        {
-          GMS_DEBUG_ERROR (session, "content-add failed; removing stream");
-          NODE_DEBUG (sent_msg->node, "message sent");
-          NODE_DEBUG (reply_msg->node, "message reply");
-
-          _gabble_media_stream_close (stream);
-        }
-    }
-  else
-    {
-      if (stream->signalling_state == STREAM_SIG_STATE_SENT)
-        {
-          GMS_DEBUG_INFO (session, "content-add succeeded, marking stream as "
-              "ACKNOWLEDGED");
-
-          g_object_set (stream,
-              "signalling-state", STREAM_SIG_STATE_ACKNOWLEDGED,
-              NULL);
-        }
-      else
-        {
-          GMS_DEBUG_INFO (session, "content-add succeeded, but not marking"
-              "stream as ACKNOWLEDGED, it's in state %d",
-              stream->signalling_state);
-        }
-    }
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static void
-do_content_add (GabbleMediaSession *session,
-                GabbleMediaStream *stream)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  LmMessage *msg;
-  LmMessageNode *session_node;
-
-  g_assert (priv->state == JS_STATE_ACTIVE);
-  g_assert (priv->mode == MODE_JINGLE);
-
-  if (_stream_not_ready_for_initiate (session, stream))
-    {
-      GMS_DEBUG_ERROR (session, "trying to send content-add for stream %s "
-          "but we have no local codecs. what?!", stream->name);
-      g_assert_not_reached ();
-      return;
-    }
-
-  msg = _gabble_media_session_message_new (session, "content-add",
-      &session_node);
-
-  _add_content_descriptions_one (session, stream, session_node);
-
-  GMS_DEBUG_INFO (session, "sending jingle action \"content-add\" to peer for "
-      "stream %s", stream->name);
-
-  _gabble_connection_send_with_reply (priv->conn, msg,
-      content_add_msg_reply_cb, G_OBJECT (stream), session, NULL);
-
-  lm_message_unref (msg);
-
-  /* mark stream as sent */
-  g_object_set (stream, "signalling-state", STREAM_SIG_STATE_SENT, NULL);
-}
-
-static void
-stream_connection_state_changed_cb (GabbleMediaStream *stream,
-                                    GParamSpec *param,
-                                    GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  if (stream->connection_state != TP_MEDIA_STREAM_STATE_CONNECTED)
-    return;
-
-  GMS_DEBUG_INFO (session, "stream %s has gone connected", stream->name);
-
-  if (stream->playing)
-    {
-      GMS_DEBUG_INFO (session, "doing nothing, stream is already playing");
-      return;
-    }
-
-  /* after session is active, we do things per-stream with content-* actions */
-  if (priv->state < JS_STATE_ACTIVE)
-    {
-      /* send a session accept if the session was initiated by the peer */
-      if (session->initiator == INITIATOR_REMOTE)
-        {
-          try_session_accept (session);
-        }
-      else
-        {
-          GMS_DEBUG_INFO (session, "session initiated by us, so we're not "
-              "going to consider sending an accept");
-        }
-    }
-  else
-    {
-      /* send a content accept if the stream was added by the peer */
-      if (stream->initiator == INITIATOR_REMOTE)
-        {
-          try_content_accept (session, stream);
-        }
-      else
-        {
-          GMS_DEBUG_INFO (session, "stream added by us, so we're not going "
-              "to send an accept");
-        }
-    }
-}
-
-static void
-stream_got_local_codecs_changed_cb (GabbleMediaStream *stream,
-                                    GParamSpec *param,
-                                    GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  if (!stream->got_local_codecs)
-    return;
-
-  GMS_DEBUG_INFO (session, "stream %s has got local codecs", stream->name);
-
-  if (stream->playing)
-    {
-      GMS_DEBUG_ERROR (session, "stream was already playing and we got local "
-          "codecs. what?!");
-      g_assert_not_reached ();
-      return;
-    }
-
-  /* after session is active, we do things per-stream with content-* actions */
-  if (priv->state < JS_STATE_ACTIVE)
-    {
-      if (session->initiator == INITIATOR_REMOTE)
-        {
-          if (priv->state < JS_STATE_PENDING_ACCEPT_SENT)
-            {
-              try_session_accept (session);
-            }
-          else
-            {
-              GMS_DEBUG_INFO (session, "stream added after sending accept; "
-                  "not doing content-add until remote end acknowledges");
-            }
-        }
-      else
-        {
-          if (priv->state < JS_STATE_PENDING_INITIATE_SENT)
-            {
-              try_session_initiate (session);
-            }
-          else
-            {
-              GMS_DEBUG_INFO (session, "stream added after sending initiate; "
-                  "not doing content-add until remote end accepts");
-            }
-        }
-    }
-  else
-    {
-      if (stream->initiator == INITIATOR_REMOTE)
-        {
-          try_content_accept (session, stream);
-        }
-      else
-        {
-          do_content_add (session, stream);
-        }
-    }
-}
-
-static gchar *
-get_jid_for_contact (GabbleMediaSession *session,
-                     TpHandle handle)
-{
-  GabbleMediaSessionPrivate *priv;
-  TpBaseConnection *conn;
-  const gchar *base_jid;
-  TpHandle self;
-  TpHandleRepoIface *contact_handles;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  conn = (TpBaseConnection *) priv->conn;
-  contact_handles = tp_base_connection_get_handles (conn,
-      TP_HANDLE_TYPE_CONTACT);
-  self = conn->self_handle;
-
-  base_jid = tp_handle_inspect (contact_handles, handle);
-  g_assert (base_jid != NULL);
-
-  if (handle == self)
-    {
-      gchar *resource, *ret;
-      g_object_get (priv->conn, "resource", &resource, NULL);
-      g_assert (resource != NULL);
-      ret = g_strdup_printf ("%s/%s", base_jid, resource);
-      g_free (resource);
-      return ret;
-    }
-  else
-    {
-      g_assert (priv->peer_resource != NULL);
-      return g_strdup_printf ("%s/%s", base_jid, priv->peer_resource);
-    }
-}
-
-LmMessage *
-_gabble_media_session_message_new (GabbleMediaSession *session,
-                                   const gchar *action,
-                                   LmMessageNode **session_node)
-{
-  GabbleMediaSessionPrivate *priv;
-  TpBaseConnection *conn;
-  LmMessage *msg;
-  LmMessageNode *iq_node, *node;
-  gchar *peer_jid, *initiator_jid;
-  TpHandle initiator_handle;
-  const gchar *element, *xmlns;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  conn = (TpBaseConnection *) priv->conn;
-
-  peer_jid = get_jid_for_contact (session, priv->peer);
-
-  msg = lm_message_new_with_sub_type (
-      peer_jid,
-      LM_MESSAGE_TYPE_IQ,
-      LM_MESSAGE_SUB_TYPE_SET);
-
-  g_free (peer_jid);
-
-  iq_node = lm_message_get_node (msg);
-
-  if (priv->mode == MODE_GOOGLE)
-    element = "session";
-  else
-    element = "jingle";
-
-  if (session->initiator == INITIATOR_LOCAL)
-    initiator_handle = conn->self_handle;
-  else
-    initiator_handle = priv->peer;
-
-  node = lm_message_node_add_child (iq_node, element, NULL);
-  initiator_jid = get_jid_for_contact (session, initiator_handle);
-
-  lm_message_node_set_attributes (node,
-      (priv->mode == MODE_GOOGLE) ? "id" : "sid", priv->id,
-      (priv->mode == MODE_GOOGLE) ? "type" : "action", action,
-      "initiator", initiator_jid,
-      NULL);
-
-  if (priv->mode == MODE_GOOGLE)
-    xmlns = NS_GOOGLE_SESSION;
-  else
-    xmlns = NS_JINGLE;
-
-  lm_message_node_set_attribute (node, "xmlns", xmlns);
-  g_free (initiator_jid);
-
-  if (session_node)
-    *session_node = node;
-
-  return msg;
-}
-
-void
-_gabble_media_session_accept (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  guint i;
-
-  priv->locally_accepted = TRUE;
-
-  /* accept any local pending sends */
-  for (i = 0; i < priv->streams->len; i++)
-    {
-      GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-      CombinedStreamDirection combined_dir = stream->combined_direction;
-      TpMediaStreamDirection current_dir;
-      TpMediaStreamPendingSend pending_send;
-
-      current_dir = COMBINED_DIRECTION_GET_DIRECTION (combined_dir);
-      pending_send = COMBINED_DIRECTION_GET_PENDING_SEND (combined_dir);
-
-      if ((pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
-        {
-          GMS_DEBUG_INFO (session, "accepting pending local send on stream %s",
-              stream->name);
-
-          current_dir |= TP_MEDIA_STREAM_DIRECTION_SEND;
-          pending_send &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
-          combined_dir = MAKE_COMBINED_DIRECTION (current_dir, pending_send);
-          g_object_set (stream, "combined-direction", combined_dir, NULL);
-          _gabble_media_stream_update_sending (stream, FALSE);
-        }
-    }
-
-  try_session_accept (session);
-}
-
-static LmHandlerResult
-content_remove_msg_reply_cb (GabbleConnection *conn,
-                             LmMessage *sent_msg,
-                             LmMessage *reply_msg,
-                             GObject *object,
-                             gpointer user_data)
-{
-  GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  GPtrArray *removing = (GPtrArray *) user_data;
-  guint i;
-
-  MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (session,
-      "stream removal failed");
-
-  for (i = 0; i < removing->len; i++)
-    destroy_media_stream (session,
-        GABBLE_MEDIA_STREAM (g_ptr_array_index (removing, i)));
-
-  g_ptr_array_remove_fast (priv->remove_requests, removing);
-  g_ptr_array_free (removing, TRUE);
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-void
-_gabble_media_session_remove_streams (GabbleMediaSession *session,
-                                      GabbleMediaStream **streams,
-                                      guint len)
-{
-  GabbleMediaSessionPrivate *priv;
-  LmMessage *msg = NULL;
-  LmMessageNode *session_node;
-  GPtrArray *removing = NULL;
-  guint i;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  /* end the session if there'd be no streams left after reducing it */
-  if (_count_non_removing_streams (session) == len)
-    {
-      _gabble_media_session_terminate (session, INITIATOR_LOCAL,
-          TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-      return;
-    }
-
-  /* construct a remove message if we're in a state greater than CREATED (ie
-   * something has been sent/received about this session) */
-  if (priv->state > JS_STATE_PENDING_CREATED)
-    {
-      msg = _gabble_media_session_message_new (session, "content-remove",
-          &session_node);
-      removing = g_ptr_array_sized_new (len);
-    }
-
-  /* right, remove them */
-  for (i = 0; i < len; i++)
-    {
-      GabbleMediaStream *stream = streams[i];
-
-      switch (stream->signalling_state)
-        {
-        case STREAM_SIG_STATE_NEW:
-          destroy_media_stream (session, stream);
-          break;
-        case STREAM_SIG_STATE_SENT:
-        case STREAM_SIG_STATE_ACKNOWLEDGED:
-          {
-            g_assert (priv->state > JS_STATE_PENDING_CREATED);
-            g_assert (msg != NULL);
-            g_assert (removing != NULL);
-            g_assert (session_node != NULL);
-
-            _gabble_media_stream_add_content_node (stream, session_node);
-
-            g_object_set (stream,
-                "playing", FALSE,
-                "signalling-state", STREAM_SIG_STATE_REMOVING,
-                NULL);
-
-            /* close the stream now, but don't forget about it until the
-             * removal message is acknowledged, since we need to be able to
-             * detect content-remove cross-talk */
-            _gabble_media_stream_close (stream);
-            g_ptr_array_add (removing, stream);
-          }
-          break;
-        case STREAM_SIG_STATE_REMOVING:
-          break;
-        }
-    }
-
-  /* send the remove message if necessary */
-  if (msg != NULL)
-    {
-      if (removing->len > 0)
-        {
-          GMS_DEBUG_INFO (session, "sending jingle session action "
-              "\"content-remove\" to peer");
-
-          _gabble_connection_send_with_reply (priv->conn, msg,
-              content_remove_msg_reply_cb, G_OBJECT (session), removing, NULL);
-
-          g_ptr_array_add (priv->remove_requests, removing);
-        }
-      else
-        {
-          g_ptr_array_free (removing, TRUE);
-        }
-
-      lm_message_unref (msg);
-    }
-  else
-    {
-      GMS_DEBUG_INFO (session, "not sending jingle session action "
-          "\"content-remove\" to peer, no initiates or adds sent for "
-          "these streams");
-      if (priv->state < JS_STATE_PENDING_INITIATE_SENT)
-        {
-           try_session_initiate (session);
-        }
-    }
-}
-
-static void
-send_reject_message (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  LmMessage *msg;
-  LmMessageNode *session_node;
-
-  /* this should only happen in google mode, and we should only arrive in that
-   * mode when we've ended up talking to a resource that doesn't support
-   * jingle */
-  g_assert (priv->mode == MODE_GOOGLE);
-  g_assert (priv->peer_resource != NULL);
-
-  /* construct a session terminate message */
-  msg = _gabble_media_session_message_new (session, "reject", &session_node);
-
-  GMS_DEBUG_INFO (session, "sending jingle session action \"reject\" to peer");
-
-  /* send it */
-  _gabble_connection_send_with_reply (priv->conn, msg, NULL,
-                                      G_OBJECT (session), NULL, NULL);
-
-  lm_message_unref (msg);
-}
-
-static void
-send_terminate_message (GabbleMediaSession *session)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  const gchar *action;
-  LmMessage *msg;
-  LmMessageNode *session_node;
-
-  /* construct a session terminate message */
-  if (priv->mode == MODE_GOOGLE)
-    action = "terminate";
-  else
-    action = "session-terminate";
-
-  msg = _gabble_media_session_message_new (session, action, &session_node);
-
-  GMS_DEBUG_INFO (session, "sending jingle session action \"%s\" to peer",
-      action);
-
-  /* send it */
-  _gabble_connection_send_with_reply (priv->conn, msg, NULL,
-                                      G_OBJECT (session), NULL, NULL);
-
-  lm_message_unref (msg);
-}
-
-void
-_gabble_media_session_terminate (GabbleMediaSession *session,
-                                 JingleInitiator who,
-                                 TpChannelGroupChangeReason why)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
-  TpHandle actor;
-
-  if (priv->state == JS_STATE_ENDED)
-    return;
-
-  if (who == INITIATOR_REMOTE)
-    {
-      actor = priv->peer;
-    }
-  else
-    {
-      actor = conn->self_handle;
-
-      /* Need to tell them that it's all over. */
-
-      /* Jingle doesn't have a "reject" action; a termination before an
-       * acceptance indicates that the call has been declined */
-
-      if (session->initiator == INITIATOR_REMOTE &&
-          priv->state == JS_STATE_PENDING_INITIATED &&
-          priv->mode == MODE_GOOGLE)
-        {
-          send_reject_message (session);
-        }
-
-      /* if we're still in CREATED, then we've not sent or received any
-       * messages about this session yet, so no terminate is necessary */
-      else if (priv->state > JS_STATE_PENDING_CREATED)
-        {
-          send_terminate_message (session);
-        }
-
-      while (priv->streams->len > 0)
-        destroy_media_stream (session, g_ptr_array_index (priv->streams, 0));
-    }
-
-  priv->terminated = TRUE;
-  g_object_set (session, "state", JS_STATE_ENDED, NULL);
-  g_signal_emit (session, signals[TERMINATED], 0, actor, why);
-}
-
-#if defined (ENABLE_DEBUG) && _GMS_DEBUG_LEVEL
-void
-_gabble_media_session_debug (GabbleMediaSession *session,
-                             DebugMessageType type,
-                             const gchar *format, ...)
-{
-  if (DEBUGGING)
-    {
-      va_list list;
-      gchar buf[512];
-      GabbleMediaSessionPrivate *priv;
-      time_t curtime;
-      struct tm *loctime;
-      gchar stamp[10];
-      const gchar *type_str;
-
-      g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
-      priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-      curtime = time (NULL);
-      loctime = localtime (&curtime);
-
-      strftime (stamp, sizeof (stamp), "%T", loctime);
-
-      va_start (list, format);
-
-      vsnprintf (buf, sizeof (buf), format, list);
-
-      va_end (list);
-
-      switch (type) {
-        case DEBUG_MSG_INFO:
-          type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_WHITE;
-          break;
-        case DEBUG_MSG_DUMP:
-          type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_GREEN;
-          break;
-        case DEBUG_MSG_WARNING:
-          type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_YELLOW;
-          break;
-        case DEBUG_MSG_ERROR:
-          type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_WHITE TP_ANSI_BG_RED;
-          break;
-        case DEBUG_MSG_EVENT:
-          type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_CYAN;
-          break;
-        default:
-          g_assert_not_reached ();
-          return;
-      }
-
-      printf ("[%s%s%s] %s%-26s%s %s%s%s\n",
-          TP_ANSI_BOLD_ON TP_ANSI_FG_WHITE,
-          stamp,
-          TP_ANSI_RESET,
-          session_states[priv->state].attributes,
-          session_states[priv->state].name,
-          TP_ANSI_RESET,
-          type_str,
-          buf,
-          TP_ANSI_RESET);
-
-      fflush (stdout);
-    }
-}
-
-#endif /* _GMS_DEBUG_LEVEL */
-
-static const gchar *
-_name_stream (GabbleMediaSession *session,
-              TpMediaStreamType media_type)
-{
-  GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  static gchar ret[MAX_STREAM_NAME_LEN] = GTALK_STREAM_NAME;
-
-  if (priv->mode != MODE_GOOGLE)
-    {
-      guint i = 1;
-
-      do {
-          g_snprintf (ret, MAX_STREAM_NAME_LEN, "%s%u",
-              media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video",
-              i++);
-
-          /* even though we now have seperate namespaces for local and remote,
-           * actually check in both so that we can still support clients which
-           * have 1 namespace (such as our older selves :D) */
-          if (_lookup_stream_by_name_and_initiator (session, ret,
-                INITIATOR_INVALID) != NULL)
-            {
-              ret[0] = '\0';
-            }
-      } while (ret[0] == '\0');
-    }
-
-  return ret;
-}
-
-
-gboolean
-_gabble_media_session_request_streams (GabbleMediaSession *session,
-                                       const GArray *media_types,
-                                       GPtrArray **ret,
-                                       GError **error)
-{
-  /* if you change the caps in this function, you almost certainly also need
-   * to change _gabble_media_channel_typeflags_to_caps and
-   * _gabble_media_channel_caps_to_typeflags in media-channel.c */
-  static GabblePresenceCapabilities google_audio_caps =
-    PRESENCE_CAP_GOOGLE_VOICE;
-  static GabblePresenceCapabilities jingle_audio_caps =
-    PRESENCE_CAP_JINGLE | PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO |
-    PRESENCE_CAP_GOOGLE_TRANSPORT_P2P;
-  static GabblePresenceCapabilities jingle_video_caps =
-    PRESENCE_CAP_JINGLE | PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO |
-    PRESENCE_CAP_GOOGLE_TRANSPORT_P2P;
-
-  GabbleMediaSessionPrivate *priv;
-  GabblePresence *presence;
-  gboolean want_audio, want_video;
-  GabblePresenceCapabilities jingle_desired_caps;
-  guint idx;
-  gchar *dump;
-
-  g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  presence = gabble_presence_cache_get (priv->conn->presence_cache,
-      priv->peer);
-
-  if (presence == NULL)
-    {
-      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-          "member has no audio/video capabilities");
-
-      return FALSE;
-    }
-
-  dump = gabble_presence_dump (presence);
-  GMS_DEBUG_DUMP (session, "presence for peer %d:\n%s", priv->peer, dump);
-  g_free (dump);
-
-  want_audio = want_video = FALSE;
-
-  for (idx = 0; idx < media_types->len; idx++)
-    {
-      guint media_type = g_array_index (media_types, guint, idx);
-
-      if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
-        {
-          want_audio = TRUE;
-        }
-      else if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
-        {
-          want_video = TRUE;
-        }
-      else
-        {
-          g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
-              "given media type %u is invalid", media_type);
-          return FALSE;
-        }
-    }
-
-  /* work out what we'd need to do these streams with jingle */
-  jingle_desired_caps = 0;
-
-  if (want_audio)
-    jingle_desired_caps |= jingle_audio_caps;
-
-  if (want_video)
-    jingle_desired_caps |= jingle_video_caps;
-
-  GMS_DEBUG_INFO (session, "want audio: %s; want video: %s",
-    want_audio ? "yes" : "no", want_video ? "yes" : "no");
-
-  /* existing call; the recipient and the mode has already been decided */
-  if (priv->peer_resource)
-    {
-      /* is a google call... we have no other option */
-      if (priv->mode == MODE_GOOGLE)
-        {
-          GMS_DEBUG_INFO (session, "already in Google mode; can't add new "
-              "stream");
-
-          g_assert (priv->streams->len == 1);
-
-          g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-              "Google Talk calls may only contain one stream");
-
-          return FALSE;
-        }
-
-      if (!gabble_presence_resource_has_caps (presence, priv->peer_resource,
-            jingle_desired_caps))
-        {
-          GMS_DEBUG_INFO (session,
-            "in Jingle mode but have insufficient caps for requested streams");
-
-          g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-              "existing call member doesn't support all requested media"
-              " types");
-
-          return FALSE;
-        }
-
-      GMS_DEBUG_INFO (session,
-        "in Jingle mode, and have necessary caps");
-    }
-
-  /* no existing call; we should choose a recipient and a mode */
-  else
-    {
-      const gchar *resource;
-
-      g_assert (priv->streams->len == 0);
-
-      /* see if we have a fully-capable jingle resource; regardless of the
-       * desired media type it's best if we can add/remove the others later */
-      resource = gabble_presence_pick_resource_by_caps (presence,
-          jingle_audio_caps | jingle_video_caps);
-
-      if (resource == NULL)
-        {
-          GMS_DEBUG_INFO (session, "contact is not fully jingle-capable");
-
-          /* ok, no problem. see if we can do just what's wanted with jingle */
-          resource = gabble_presence_pick_resource_by_caps (presence,
-              jingle_desired_caps);
-
-          if (resource == NULL && want_audio && !want_video)
-            {
-              GMS_DEBUG_INFO (session,
-                "contact doesn't have desired Jingle capabilities");
-
-              /* last ditch... if we want only audio and not video, we can make
-               * do with google talk */
-              resource = gabble_presence_pick_resource_by_caps (presence,
-                  google_audio_caps);
-
-              if (resource != NULL)
-                {
-                  /* only one stream possible with google */
-                  if (media_types->len == 1)
-                    {
-                      GMS_DEBUG_INFO (session,
-                        "contact has no Jingle capabilities; "
-                        "falling back to Google audio call");
-                      priv->mode = MODE_GOOGLE;
-                    }
-                  else
-                    {
-                      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-                          "Google Talk calls may only contain one stream");
-
-                      return FALSE;
-                    }
-                }
-              else
-                {
-                  GMS_DEBUG_INFO (session,
-                    "contact doesn't have desired Google capabilities");
-                }
-            }
-        }
-
-      if (resource == NULL)
-        {
-          GMS_DEBUG_INFO (session,
-            "contact doesn't have a resource with suitable capabilities");
-
-          g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-              "member does not have the desired audio/video capabilities");
-
-          return FALSE;
-        }
-
-      priv->peer_resource = g_strdup (resource);
-    }
-
-  /* check it's not a ridiculous number of streams */
-  if ((priv->streams->len + media_types->len) > MAX_STREAMS)
-    {
-      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-          "I think that's quite enough streams already");
-      return FALSE;
-    }
-
-  /* if we've got here, we're good to make the streams */
-
-  *ret = g_ptr_array_sized_new (media_types->len);
-
-  for (idx = 0; idx < media_types->len; idx++)
-    {
-      guint media_type = g_array_index (media_types, guint, idx);
-      GabbleMediaStream *stream;
-      const gchar *stream_name;
-
-      if (priv->mode == MODE_GOOGLE)
-        stream_name = GTALK_STREAM_NAME;
-      else
-        stream_name = _name_stream (session, media_type);
-
-      stream = create_media_stream (session, stream_name, INITIATOR_LOCAL,
-                                    media_type);
-
-      g_ptr_array_add (*ret, stream);
-    }
-
-  return TRUE;
-}
-
-static const gchar *
-_direction_to_senders (GabbleMediaSession *session,
-                       TpMediaStreamDirection dir)
-{
-  const gchar *ret = NULL;
-
-  switch (dir)
-    {
-      case TP_MEDIA_STREAM_DIRECTION_NONE:
-        g_assert_not_reached ();
-        break;
-      case TP_MEDIA_STREAM_DIRECTION_SEND:
-        if (session->initiator == INITIATOR_LOCAL)
-          ret = "initiator";
-        else
-          ret = "responder";
-        break;
-      case TP_MEDIA_STREAM_DIRECTION_RECEIVE:
-        if (session->initiator == INITIATOR_REMOTE)
-          ret = "initiator";
-        else
-          ret = "responder";
-        break;
-      case TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL:
-        ret = "both";
-        break;
-    }
-
-  g_assert (ret != NULL);
-
-  return ret;
-}
-
-static LmHandlerResult
-direction_msg_reply_cb (GabbleConnection *conn,
-                        LmMessage *sent_msg,
-                        LmMessage *reply_msg,
-                        GObject *object,
-                        gpointer user_data)
-{
-  GabbleMediaSession *session = GABBLE_MEDIA_SESSION (user_data);
-  GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
-
-  MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (session,
-      "direction change failed");
-
-  if (stream->playing)
-    {
-      _gabble_media_stream_update_sending (stream, TRUE);
-    }
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static gboolean
-send_direction_change (GabbleMediaSession *session,
-                       GabbleMediaStream *stream,
-                       TpMediaStreamDirection dir,
-                       GError **error)
-{
-  GabbleMediaSessionPrivate *priv;
-  const gchar *senders;
-  LmMessage *msg;
-  LmMessageNode *session_node, *content_node;
-  gboolean ret;
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-  senders = _direction_to_senders (session, dir);
-
-  if (stream->signalling_state == STREAM_SIG_STATE_NEW ||
-      stream->signalling_state == STREAM_SIG_STATE_REMOVING)
-    {
-      GMS_DEBUG_INFO (session, "not sending content-modify for %s stream %s",
-          stream->signalling_state == STREAM_SIG_STATE_NEW ? "new" : "removing",
-          stream->name);
-      return TRUE;
-    }
-
-  GMS_DEBUG_INFO (session, "sending jingle session action \"content-modify\" "
-      "to peer for stream %s (senders=%s)", stream->name, senders);
-
-  msg = _gabble_media_session_message_new (session, "content-modify",
-      &session_node);
-  content_node = _gabble_media_stream_add_content_node (stream, session_node);
-
-  lm_message_node_set_attribute (content_node, "senders", senders);
-
-  ret = _gabble_connection_send_with_reply (priv->conn, msg,
-      direction_msg_reply_cb, G_OBJECT (stream), session, error);
-
-  lm_message_unref (msg);
-
-  return ret;
-}
-
-gboolean
-_gabble_media_session_request_stream_direction (GabbleMediaSession *session,
-                                                GabbleMediaStream *stream,
-                                                TpMediaStreamDirection requested_dir,
-                                                GError **error)
-{
-  GabbleMediaSessionPrivate *priv;
-  CombinedStreamDirection new_combined_dir;
-  TpMediaStreamDirection current_dir; //, new_dir;
-  TpMediaStreamPendingSend pending_send;
-
-  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
-  current_dir = COMBINED_DIRECTION_GET_DIRECTION (stream->combined_direction);
-  pending_send = COMBINED_DIRECTION_GET_PENDING_SEND
-    (stream->combined_direction);
-
-  if (priv->mode == MODE_GOOGLE)
-    {
-      g_assert (current_dir == TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
-
-      if (requested_dir == TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
-        return TRUE;
-
-      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-          "Google Talk calls can only be bi-directional");
-      return FALSE;
-    }
-
-  if (requested_dir == TP_MEDIA_STREAM_DIRECTION_NONE)
-    {
-      GMS_DEBUG_INFO (session, "request for NONE direction; removing stream");
-
-      _gabble_media_session_remove_streams (session, &stream, 1);
-
-      return TRUE;
-    }
-
-  /* if we're awaiting a local decision on sending... */
-  if ((pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
-    {
-      /* clear the flag */
-      pending_send &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
-
-      /* make our current_dir match what other end thinks (he thinks we're
-       * bidirectional) so that we send the correct transitions */
-      current_dir ^= TP_MEDIA_STREAM_DIRECTION_SEND;
-    }
-
-#if 0
-  /* if we're asking the remote end to start sending, set the pending flag and
-   * don't change our directionality just yet */
-  new_dir = requested_dir;
-  if (((current_dir & TP_MEDIA_STREAM_DIRECTION_RECEIVE) == 0) &&
-      ((new_dir & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0))
-    {
-      pending_send ^= TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
-      new_dir &= ~TP_MEDIA_STREAM_DIRECTION_RECEIVE;
-    }
-#endif
-
-  /* make any necessary changes */
-  new_combined_dir = MAKE_COMBINED_DIRECTION (requested_dir, pending_send);
-  if (new_combined_dir != stream->combined_direction)
-    {
-      g_object_set (stream, "combined-direction", new_combined_dir, NULL);
-      _gabble_media_stream_update_sending (stream, FALSE);
-    }
-
-  /* short-circuit sending a request if we're not asking for anything new */
-  if (current_dir == requested_dir)
-    return TRUE;
-
-  /* send request */
-  return send_direction_change (session, stream, requested_dir, error);
-}
-
-static void
-session_handler_iface_init (gpointer g_iface, gpointer iface_data)
-{
-  TpSvcMediaSessionHandlerClass *klass =
-    (TpSvcMediaSessionHandlerClass *) g_iface;
-
-#define IMPLEMENT(x) tp_svc_media_session_handler_implement_##x (\
-    klass, gabble_media_session_##x)
-  IMPLEMENT(error);
-  IMPLEMENT(ready);
-#undef IMPLEMENT
-}
diff --git a/src/media-session.h b/src/media-session.h
deleted file mode 100644
index a7765f0..0000000
--- a/src/media-session.h
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * gabble-media-session.h - Header for GabbleMediaSession
- * Copyright (C) 2006 Collabora Ltd.
- * Copyright (C) 2006 Nokia Corporation
- *
- * 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 __GABBLE_MEDIA_SESSION_H__
-#define __GABBLE_MEDIA_SESSION_H__
-
-#include <glib-object.h>
-#include <loudmouth/loudmouth.h>
-
-#include "types.h"
-#include "media-stream.h"
-#include <telepathy-glib/enums.h>
-
-G_BEGIN_DECLS
-
-typedef enum
-{
-  MODE_GOOGLE,
-  MODE_JINGLE
-} GabbleMediaSessionMode;
-
-typedef enum {
-    JS_STATE_INVALID = -1,
-    JS_STATE_PENDING_CREATED = 0,
-    JS_STATE_PENDING_INITIATE_SENT,
-    JS_STATE_PENDING_INITIATED,
-    JS_STATE_PENDING_ACCEPT_SENT,
-    JS_STATE_ACTIVE,
-    JS_STATE_ENDED
-} JingleSessionState;
-
-typedef enum {
-    DEBUG_MSG_INFO = 0,
-    DEBUG_MSG_DUMP,
-    DEBUG_MSG_WARNING,
-    DEBUG_MSG_ERROR,
-    DEBUG_MSG_EVENT
-} DebugMessageType;
-
-typedef struct _GabbleMediaSession GabbleMediaSession;
-typedef struct _GabbleMediaSessionClass GabbleMediaSessionClass;
-typedef struct _GabbleMediaSessionPrivate GabbleMediaSessionPrivate;
-
-struct _GabbleMediaSessionClass {
-    GObjectClass parent_class;
-};
-
-struct _GabbleMediaSession {
-    GObject parent;
-
-    JingleInitiator initiator;
-
-    GabbleMediaSessionPrivate *priv;
-};
-
-GType gabble_media_session_get_type (void);
-
-/* TYPE MACROS */
-#define GABBLE_TYPE_MEDIA_SESSION \
-  (gabble_media_session_get_type ())
-#define GABBLE_MEDIA_SESSION(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST((obj), GABBLE_TYPE_MEDIA_SESSION, \
-                              GabbleMediaSession))
-#define GABBLE_MEDIA_SESSION_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST((klass), GABBLE_TYPE_MEDIA_SESSION, \
-                           GabbleMediaSessionClass))
-#define GABBLE_IS_MEDIA_SESSION(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE((obj), GABBLE_TYPE_MEDIA_SESSION))
-#define GABBLE_IS_MEDIA_SESSION_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE((klass), GABBLE_TYPE_MEDIA_SESSION))
-#define GABBLE_MEDIA_SESSION_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GABBLE_TYPE_MEDIA_SESSION, \
-                              GabbleMediaSessionClass))
-
-/* CONVENIENCE MACROS */
-#define MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL(s, m) \
-  G_STMT_START { \
-  if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT) \
-    { \
-      GMS_DEBUG_ERROR (s, m); \
-      NODE_DEBUG (sent_msg->node, "message sent"); \
-      NODE_DEBUG (reply_msg->node, "message reply"); \
-      _gabble_media_session_terminate (s, INITIATOR_LOCAL, \
-          TP_CHANNEL_GROUP_CHANGE_REASON_ERROR); \
-      return LM_HANDLER_RESULT_REMOVE_MESSAGE; \
-    } \
-  } G_STMT_END
-
-gboolean
-_gabble_media_session_handle_action (GabbleMediaSession *session,
-                                     LmMessage *message,
-                                     LmMessageNode *session_node,
-                                     const gchar *action,
-                                     GError **error);
-
-LmMessage *_gabble_media_session_message_new (GabbleMediaSession *session,
-                                              const gchar *action,
-                                              LmMessageNode **session_node);
-
-void _gabble_media_session_accept (GabbleMediaSession *session);
-void _gabble_media_session_remove_streams (GabbleMediaSession *session,
-    GabbleMediaStream **streams, guint len);
-void _gabble_media_session_terminate (GabbleMediaSession *session,
-    JingleInitiator who, TpChannelGroupChangeReason why);
-
-gboolean _gabble_media_session_request_streams (GabbleMediaSession *session,
-                                                const GArray *types,
-                                                GPtrArray **ret,
-                                                GError **error);
-
-gboolean _gabble_media_session_request_stream_direction (GabbleMediaSession *,
-    GabbleMediaStream *, TpMediaStreamDirection, GError **);
-
-#ifndef _GMS_DEBUG_LEVEL
-#define _GMS_DEBUG_LEVEL 2
-#endif
-
-#if defined (ENABLE_DEBUG) && _GMS_DEBUG_LEVEL
-
-#define GMS_DEBUG_INFO(s, ...) \
-  _gabble_media_session_debug (s, DEBUG_MSG_INFO, __VA_ARGS__)
-#if _GMS_DEBUG_LEVEL > 1
-#define GMS_DEBUG_DUMP(s, ...) \
-  _gabble_media_session_debug (s, DEBUG_MSG_DUMP, __VA_ARGS__)
-#else
-#define GMS_DEBUG_DUMP(s, ...)
-#endif
-#define GMS_DEBUG_WARNING(s, ...) \
-  _gabble_media_session_debug (s, DEBUG_MSG_WARNING, __VA_ARGS__)
-#define GMS_DEBUG_ERROR(s, ...) \
-  _gabble_media_session_debug (s, DEBUG_MSG_ERROR, __VA_ARGS__)
-#define GMS_DEBUG_EVENT(s, ...) \
-  _gabble_media_session_debug (s, DEBUG_MSG_EVENT, __VA_ARGS__)
-
-void _gabble_media_session_debug (GabbleMediaSession *session,
-                                  DebugMessageType type,
-                                  const gchar *format, ...)
-    G_GNUC_PRINTF (3, 4);
-
-#else
-
-#define GMS_DEBUG_INFO(s, ...)
-#define GMS_DEBUG_DUMP(s, ...)
-#define GMS_DEBUG_WARNING(s, ...)
-#define GMS_DEBUG_ERROR(s, ...)
-#define GMS_DEBUG_EVENT(s, ...)
-
-#endif /* defined (ENABLE_DEBUG) && _GMS_DEBUG_LEVEL */
-
-G_END_DECLS
-
-#endif /* #ifndef __GABBLE_MEDIA_SESSION_H__*/
diff --git a/src/media-stream.c b/src/media-stream.c
index 2a2690b..b506602 100644
--- a/src/media-stream.c
+++ b/src/media-stream.c
@@ -42,10 +42,11 @@
 #include "gabble-signals-marshal.h"
 #include "media-channel.h"
 #include "media-session-enumtypes.h"
-#include "media-session.h"
 #include "namespaces.h"
 
 #include "jingle-content.h"
+#include "jingle-session.h"
+#include "jingle-media-rtp.h"
 
 static void stream_handler_iface_init (gpointer, gpointer);
 
@@ -101,7 +102,7 @@ struct _GabbleMediaStreamPrivate
   GabbleJingleContent *content;
 
   GabbleConnection *conn;
-  GabbleMediaSession *session;
+  gpointer session;
   GabbleMediaSessionMode mode;
   gchar *object_path;
   guint id;
@@ -140,12 +141,16 @@ static const char *tp_transports[] = {
 #endif
 #endif
 
-static void push_native_candidates (GabbleMediaStream *stream);
 static void push_remote_codecs (GabbleMediaStream *stream);
 static void push_remote_candidates (GabbleMediaStream *stream);
 static void push_playing (GabbleMediaStream *stream);
 static void push_sending (GabbleMediaStream *stream);
 
+static void new_remote_candidates_cb (GabbleJingleContent *content,
+    GList *clist, GabbleMediaStream *stream);
+static void new_remote_codecs_cb (GabbleJingleContent *content,
+    GList *clist, GabbleMediaStream *stream);
+
 static void
 gabble_media_stream_init (GabbleMediaStream *self)
 {
@@ -215,7 +220,7 @@ gabble_media_stream_get_property (GObject    *object,
       g_value_set_string (value, priv->object_path);
       break;
     case PROP_MODE:
-      g_value_set_enum (value, priv->mode);
+      g_value_set_uint (value, priv->mode);
       break;
     case PROP_NAME:
       g_value_set_string (value, stream->name);
@@ -280,7 +285,7 @@ gabble_media_stream_set_property (GObject      *object,
       priv->object_path = g_value_dup_string (value);
       break;
     case PROP_MODE:
-      priv->mode = g_value_get_enum (value);
+      priv->mode = g_value_get_uint (value);
       break;
     case PROP_NAME:
       g_free (stream->name);
@@ -312,8 +317,10 @@ gabble_media_stream_set_property (GObject      *object,
           stream->signalling_state = g_value_get_uint (value);
           DEBUG ("stream %s sig_state %d->%d",
               stream->name, old, stream->signalling_state);
+          /* FIXME 
           if (stream->signalling_state != old)
             push_native_candidates (stream);
+            */
         }
       break;
     case PROP_PLAYING:
@@ -328,7 +335,23 @@ gabble_media_stream_set_property (GObject      *object,
       stream->combined_direction = g_value_get_uint (value);
       break;
     case PROP_CONTENT:
+      g_assert (priv->content == NULL);
+
       priv->content = g_value_get_object (value);
+
+      DEBUG ("connecting to content %p signals", priv->content);
+      g_signal_connect (priv->content, "new-candidates",
+          (GCallback) new_remote_candidates_cb, stream);
+
+      /* we need this also, if we're the initiator of the stream
+       * (so remote codecs arrive later) */
+      g_signal_connect (priv->content, "remote-codecs",
+          (GCallback) new_remote_codecs_cb, stream);
+
+      /* we can immediately get the codecs if we're responder */
+      new_remote_codecs_cb (priv->content,
+          gabble_jingle_media_rtp_get_remote_codecs (GABBLE_JINGLE_MEDIA_RTP (priv->content)),
+          stream);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -373,7 +396,7 @@ gabble_media_stream_class_init (GabbleMediaStreamClass *gabble_media_stream_clas
   param_spec = g_param_spec_object ("media-session",
       "GabbleMediaSession object",
       "Gabble media session object that owns this media stream object.",
-      GABBLE_TYPE_MEDIA_SESSION,
+      GABBLE_TYPE_JINGLE_SESSION,
       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NICK |
       G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_MEDIA_SESSION,
@@ -389,11 +412,10 @@ gabble_media_stream_class_init (GabbleMediaStreamClass *gabble_media_stream_clas
                                     G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
 
-  param_spec = g_param_spec_enum ("mode", "Signalling mode",
+  param_spec = g_param_spec_uint ("mode", "Signalling mode",
                                   "Which signalling mode used to control the "
                                   "stream.",
-                                  gabble_media_session_mode_get_type (),
-                                  MODE_JINGLE,
+                                  0, G_MAXUINT, MODE_JINGLE, // FIXME
                                   G_PARAM_CONSTRUCT_ONLY |
                                   G_PARAM_READWRITE |
                                   G_PARAM_STATIC_NAME |
@@ -612,6 +634,13 @@ gabble_media_stream_finalize (GObject *object)
   G_OBJECT_CLASS (gabble_media_stream_parent_class)->finalize (object);
 }
 
+typedef struct {
+  guchar id;
+  gchar *name;
+  guint clockrate;
+  guint channels;
+} JingleCodec;
+
 /**
  * gabble_media_stream_codec_choice
  *
@@ -646,8 +675,7 @@ gabble_media_stream_error (GabbleMediaStream *self,
 
   priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);
 
-  GMS_DEBUG_WARNING (priv->session,
-      "Media.StreamHandler::Error called, error %u (%s) -- emitting signal",
+  DEBUG ( "Media.StreamHandler::Error called, error %u (%s) -- emitting signal",
       errno, message);
 
   g_signal_emit (self, signals[ERROR], 0, errno, message);
@@ -849,7 +877,7 @@ gabble_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface,
       GError only_one = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "google p2p "
           "connections only support the concept of one transport per "
           "candidate" };
-      GMS_DEBUG_WARNING (priv->session, "%s: number of transports was not 1; "
+      DEBUG ("%s: number of transports was not 1; "
           "rejecting", G_STRFUNC);
       dbus_g_method_return_error (context, &only_one);
       return;
@@ -859,8 +887,11 @@ gabble_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface,
   addr = g_value_get_string (g_value_array_get_nth (transport, 1));
   if (!strcmp (addr, "127.0.0.1"))
     {
+      DEBUG ("ignoring native localhost candidate");
+      /*
       GMS_DEBUG_WARNING (priv->session,
-          "%s: ignoring native localhost candidate", G_STRFUNC);
+          "%s: ignoring native localhost candidate", G_STRFUNC); */
+
       tp_svc_media_stream_handler_return_from_new_native_candidate (context);
       return;
     }
@@ -920,18 +951,24 @@ gabble_media_stream_ready (TpSvcMediaStreamHandler *iface,
 
   DEBUG ("ready called");
 
-  g_object_set (self, "ready", TRUE, NULL);
+  if (priv->ready == FALSE)
+    {
+      g_object_set (self, "ready", TRUE, NULL);
 
-  push_remote_codecs (self);
-  push_remote_candidates (self);
-  push_playing (self);
-  push_sending (self);
+      push_remote_codecs (self);
+      push_remote_candidates (self);
+      push_playing (self);
+      push_sending (self);
+    }
+  else
+    {
+      DEBUG ("Ready called twice, running plain SetLocalCodecs instead");
+    }
 
   /* set_local_codecs and ready return the same thing, so we can do... */
   gabble_media_stream_set_local_codecs (iface, codecs, context);
 }
 
-
 /**
  * gabble_media_stream_set_local_codecs
  *
@@ -945,6 +982,9 @@ gabble_media_stream_set_local_codecs (TpSvcMediaStreamHandler *iface,
 {
   GabbleMediaStream *self = GABBLE_MEDIA_STREAM (iface);
   GabbleMediaStreamPrivate *priv;
+  GList *li = NULL;
+  JingleCodec *c;
+  guint i;
 
   g_assert (GABBLE_IS_MEDIA_STREAM (self));
 
@@ -957,8 +997,38 @@ gabble_media_stream_set_local_codecs (TpSvcMediaStreamHandler *iface,
 
   g_object_set (self, "got-local-codecs", TRUE, NULL);
 
-  /* FIXME: actually set them :-) */
-  gabble_jingle_content_set_local_codecs (priv->content);
+  for (i = 0; i < codecs->len; i++)
+    {
+      GType codec_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC;
+
+      GValue codec = { 0, };
+      guint id, clock_rate, channels;
+      gchar *name;
+      GHashTable *params;
+
+      g_value_init (&codec, codec_struct_type);
+      g_value_set_static_boxed (&codec, g_ptr_array_index (codecs, i));
+
+      dbus_g_type_struct_get (&codec,
+          0, &id,
+          1, &name,
+          3, &clock_rate,
+          4, &channels,
+          5, &params,
+          G_MAXUINT);
+
+      c = g_new0 (JingleCodec, 1);
+
+      c->id = id;
+      c->name = g_strdup (name);
+      c->clockrate = clock_rate;
+      c->channels = channels;
+
+      DEBUG ("adding codec %s (%u %u %u)", c->name, c->id, c->clockrate, c->channels);
+      li = g_list_append (li, c);
+    }
+
+  jingle_media_rtp_set_local_codecs (GABBLE_JINGLE_MEDIA_RTP (priv->content), li);
 
   tp_svc_media_stream_handler_return_from_set_local_codecs (context);
 }
@@ -1005,7 +1075,7 @@ gabble_media_stream_supported_codecs (TpSvcMediaStreamHandler *iface,
 
   priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);
 
-  GMS_DEBUG_INFO (priv->session, "got codec intersection containing %d "
+  DEBUG ("got codec intersection containing %d "
                   "codecs from stream-engine", codecs->len);
 
   /* store the intersection for later on */
@@ -1016,6 +1086,7 @@ gabble_media_stream_supported_codecs (TpSvcMediaStreamHandler *iface,
   tp_svc_media_stream_handler_return_from_supported_codecs (context);
 }
 
+#if 0
 static LmHandlerResult
 candidates_msg_reply_cb (GabbleConnection *conn,
                          LmMessage *sent_msg,
@@ -1030,12 +1101,15 @@ candidates_msg_reply_cb (GabbleConnection *conn,
 
   priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
 
+  /* FIXME 
   MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (priv->session,
-      "candidates failed");
+      "candidates failed"); */
 
   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
 }
+#endif
 
+#if 0
 static void
 _add_rtp_candidate_node (GabbleMediaSession *session, LmMessageNode *parent,
                          GValueArray *candidate)
@@ -1142,7 +1216,9 @@ out:
   g_free (port_str);
   g_free (pref_str);
 }
+#endif
 
+#if 0
 static LmMessage *
 _gabble_media_stream_message_new (GabbleMediaStream *stream,
                                   const gchar *action,
@@ -1161,8 +1237,9 @@ _gabble_media_stream_message_new (GabbleMediaStream *stream,
 
   return msg;
 }
+#endif
 
-
+#if 0
 static void
 push_candidate (GabbleMediaStream *stream, GValueArray *candidate)
 {
@@ -1196,7 +1273,9 @@ push_candidate (GabbleMediaStream *stream, GValueArray *candidate)
   /* clean up */
   lm_message_unref (msg);
 }
+#endif
 
+#if 0
 static void
 push_native_candidates (GabbleMediaStream *stream)
 {
@@ -1222,6 +1301,7 @@ push_native_candidates (GabbleMediaStream *stream)
   g_value_take_boxed (&priv->native_candidates,
     dbus_g_type_specialized_construct (candidate_list_type));
 }
+#endif
 
 void
 _gabble_media_stream_close (GabbleMediaStream *stream)
@@ -1239,6 +1319,7 @@ _gabble_media_stream_close (GabbleMediaStream *stream)
     }
 }
 
+#if 0
 gboolean
 _gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
                                          LmMessage *message,
@@ -1344,6 +1425,56 @@ _gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
 
   return TRUE;
 }
+#endif
+
+static void
+new_remote_codecs_cb (GabbleJingleContent *content,
+    GList *clist, GabbleMediaStream *stream)
+{
+  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
+  GList *li;
+  GPtrArray *codecs;
+  GType codec_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC;
+
+  DEBUG ("called");
+
+  g_assert (GABBLE_IS_MEDIA_STREAM (stream));
+
+  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
+
+  codecs = g_value_get_boxed (&priv->remote_codecs);
+
+  g_assert (codecs->len == 0);
+
+  for (li = clist; li; li = li->next)
+    {
+      GValue codec = { 0, };
+      JingleCodec *c = li->data;
+
+      g_value_init (&codec, codec_struct_type);
+      g_value_take_boxed (&codec,
+          dbus_g_type_specialized_construct (codec_struct_type));
+
+      DEBUG ("new remote codec: %u '%s' %u %u %u",
+          c->id, c->name, priv->media_type, c->clockrate, c->channels);
+
+      dbus_g_type_struct_set (&codec,
+          0, c->id,
+          1, c->name,
+          2, priv->media_type,
+          3, c->clockrate,
+          4, c->channels,
+          5, g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free),
+          G_MAXUINT);
+
+      g_ptr_array_add (codecs, g_value_get_boxed (&codec));
+    }
+
+  DEBUG ("pushing remote codecs");
+
+  push_remote_codecs (stream);
+}
+
 
 static void
 push_remote_codecs (GabbleMediaStream *stream)
@@ -1364,7 +1495,7 @@ push_remote_codecs (GabbleMediaStream *stream)
   if (codecs->len == 0)
     return;
 
-  GMS_DEBUG_EVENT (priv->session, "passing %d remote codecs to stream-engine",
+  DEBUG ("passing %d remote codecs to stream-engine",
                    codecs->len);
 
   tp_svc_media_stream_handler_emit_set_remote_codecs (stream, codecs);
@@ -1373,6 +1504,73 @@ push_remote_codecs (GabbleMediaStream *stream)
       dbus_g_type_specialized_construct (codec_list_type));
 }
 
+static void
+new_remote_candidates_cb (GabbleJingleContent *content,
+    GList *clist, GabbleMediaStream *stream)
+{
+  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
+  GPtrArray *candidates;
+  GList *li;
+
+  candidates = g_value_get_boxed (&priv->remote_candidates);
+
+  DEBUG ("got new remote candidates");
+
+  for (li = clist; li; li = li->next)
+    {
+      gchar *candidate_id;
+      GValue candidate = { 0, };
+      GPtrArray *transports;
+      GValue transport = { 0, };
+      JingleCandidate *c = li->data;
+      GType transport_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT;
+      GType candidate_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE;
+
+      g_value_init (&transport, transport_struct_type);
+      g_value_take_boxed (&transport,
+          dbus_g_type_specialized_construct (transport_struct_type));
+
+      dbus_g_type_struct_set (&transport,
+          0, 1,         /* component number */
+          1, c->address,
+          2, c->port,
+          3, c->protocol == JINGLE_TRANSPORT_PROTOCOL_UDP ? 0 : 1,
+          4, "RTP",
+          5, "AVP",
+          6, c->preference,
+          7, c->type, /* FIXME: we're relying on 1:1 tp/jingle candidate type enums */
+          8, c->username,
+          9, c->password,
+          G_MAXUINT);
+
+      transports = g_ptr_array_sized_new (1);
+      g_ptr_array_add (transports, g_value_get_boxed (&transport));
+
+      g_value_init (&candidate, candidate_struct_type);
+      g_value_take_boxed (&candidate,
+          dbus_g_type_specialized_construct (candidate_struct_type));
+
+      /* FIXME: is this naming scheme sensible? */
+      candidate_id = g_strdup_printf ("R%d", ++priv->remote_candidate_count);
+
+      dbus_g_type_struct_set (&candidate,
+          0, candidate_id,
+          1, transports,
+          G_MAXUINT);
+
+      g_ptr_array_add (candidates, g_value_get_boxed (&candidate));
+    }
+
+  push_remote_candidates (stream);
+
+  while (clist != NULL)
+    {
+      g_free (clist->data);
+      clist = clist->next;
+    }
+}
+
+#if 0
 gboolean
 _gabble_media_stream_post_remote_candidates (GabbleMediaStream *stream,
                                              LmMessage *message,
@@ -1583,6 +1781,7 @@ FAILURE:
 
   return FALSE;
 }
+#endif
 
 static void
 push_remote_candidates (GabbleMediaStream *stream)
@@ -1614,8 +1813,11 @@ push_remote_candidates (GabbleMediaStream *stream)
       candidate_id = g_value_get_string (g_value_array_get_nth (candidate, 0));
       transports = g_value_get_boxed (g_value_array_get_nth (candidate, 1));
 
+      DEBUG ("passing 1 remote candidate to stream engine: %s", candidate_id);
+      /*
       GMS_DEBUG_EVENT (priv->session, "passing 1 remote candidate "
                        "to stream-engine");
+                       */
 
       tp_svc_media_stream_handler_emit_add_remote_candidate (
           stream, candidate_id, transports);
@@ -1683,10 +1885,12 @@ typedef struct {
     LmMessageNode *pt_node;
 } CodecParamsFromTpContext;
 
+#if 0
 static const gchar *video_codec_params[] = {
   "x", "y", "width", "height", "layer", "transparent",
 };
 
+// FIXME - we need to have extended params for video stream
 static void
 codec_params_from_tp_foreach (gpointer key, gpointer value, gpointer user_data)
 {
@@ -1720,7 +1924,9 @@ codec_params_from_tp_foreach (gpointer key, gpointer value, gpointer user_data)
       (priv->mode == MODE_JINGLE) ? "jingle" : "google",
       (priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO) ? "audio" : "video");
 }
+#endif
 
+#if 0
 LmMessageNode *
 _gabble_media_stream_add_content_node (GabbleMediaStream *stream,
                                        LmMessageNode *session_node)
@@ -1851,6 +2057,7 @@ _gabble_media_stream_content_node_add_transport (GabbleMediaStream *stream,
 
   return node;
 }
+#endif
 
 void
 _gabble_media_stream_update_sending (GabbleMediaStream *stream,
diff --git a/src/media-stream.h b/src/media-stream.h
index 719a5b6..c378d90 100644
--- a/src/media-stream.h
+++ b/src/media-stream.h
@@ -22,7 +22,6 @@
 #define __GABBLE_MEDIA_STREAM_H__
 
 #include <glib-object.h>
-#include <loudmouth/loudmouth.h>
 
 #include "types.h"
 #include <telepathy-glib/enums.h>
@@ -93,17 +92,18 @@ gboolean gabble_media_stream_error (GabbleMediaStream *self, guint errno,
     const gchar *message, GError **error);
 
 void _gabble_media_stream_close (GabbleMediaStream *close);
-gboolean _gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
-    LmMessage *message, LmMessageNode *desc_node, GError **error);
-gboolean _gabble_media_stream_post_remote_candidates (
-    GabbleMediaStream *stream, LmMessage *message,
-    LmMessageNode *transport_node, GError **error);
-LmMessageNode *_gabble_media_stream_add_content_node (
-    GabbleMediaStream *stream, LmMessageNode *session_node);
-void _gabble_media_stream_content_node_add_description (
-    GabbleMediaStream *stream, LmMessageNode *content_node);
-LmMessageNode *_gabble_media_stream_content_node_add_transport (
-    GabbleMediaStream *stream, LmMessageNode *content_node);
+// gboolean _gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
+//     LmMessage *message, LmMessageNode *desc_node, GError **error);
+// gboolean _gabble_media_stream_post_remote_candidates (
+//    GabbleMediaStream *stream, LmMessage *message,
+//     LmMessageNode *transport_node, GError **error);
+//LmMessageNode *_gabble_media_stream_add_content_node (
+//    GabbleMediaStream *stream, LmMessageNode *session_node);
+//void _gabble_media_stream_content_node_add_description (
+//    GabbleMediaStream *stream, LmMessageNode *content_node);
+//LmMessageNode *_gabble_media_stream_content_node_add_transport (
+//    GabbleMediaStream *stream, LmMessageNode *content_node);
+
 void _gabble_media_stream_update_sending (GabbleMediaStream *stream,
     gboolean start_sending);
 void gabble_media_stream_hold (GabbleMediaStream *stream, gboolean hold);
diff --git a/src/namespaces.h b/src/namespaces.h
index d46db58..59140a0 100644
--- a/src/namespaces.h
+++ b/src/namespaces.h
@@ -77,6 +77,9 @@
 #define NS_GOOGLE_SESSION       "http://www.google.com/session"
 #define NS_GOOGLE_SESSION_PHONE "http://www.google.com/session/phone"
 #define NS_GOOGLE_TRANSPORT_P2P "http://www.google.com/transport/p2p"
+#define NS_JINGLE_TMP           "urn:xmpp:tmp:jingle"
 #define NS_JINGLE_RTP_TMP       "urn:xmpp:tmp:jingle:apps:rtp"
+#define NS_JINGLE_TRANSPORT_RAWUDP \
+  "http://jabber.org/protocol/jingle/transport/rawudp"
 
 #endif /* __GABBLE_NAMESPACES__H__ */
diff --git a/src/presence-cache.c b/src/presence-cache.c
index b21f94e..cc4c9f2 100644
--- a/src/presence-cache.c
+++ b/src/presence-cache.c
@@ -805,6 +805,14 @@ _caps_disco_cb (GabbleDisco *disco,
           !tp_strdiff (var, NS_OLPC_CURRENT_ACTIVITY "+notify") ||
           !tp_strdiff (var, NS_OLPC_ACTIVITY_PROPS "+notify"))
         caps |= PRESENCE_CAP_OLPC_1;
+      else if (!tp_strdiff (var, NS_JINGLE_RTP_TMP))
+        caps |= PRESENCE_CAP_JINGLE_RTP_TMP;
+      else if (!tp_strdiff (var, NS_JINGLE_TMP))
+        caps |= PRESENCE_CAP_JINGLE_TMP;
+      else if (!tp_strdiff (var, NS_JINGLE_TRANSPORT_ICE))
+        caps |= PRESENCE_CAP_JINGLE_TRANSPORT_ICE;
+      else if (!tp_strdiff (var, NS_JINGLE_TRANSPORT_RAWUDP))
+        caps |= PRESENCE_CAP_JINGLE_TRANSPORT_RAWUDP;
     }
 
   handle = tp_handle_ensure (contact_repo, jid, NULL, NULL);
diff --git a/src/types.h b/src/types.h
index 557b25b..539e560 100644
--- a/src/types.h
+++ b/src/types.h
@@ -67,7 +67,11 @@ typedef enum {
     PRESENCE_CAP_IBB = 1 << 8,
     PRESENCE_CAP_SI_TUBES = 1 << 9,
     PRESENCE_CAP_FILE_TRANSFER = 1 << 10,
-    PRESENCE_CAP_OLPC_1 = 1 << 11
+    PRESENCE_CAP_OLPC_1 = 1 << 11,
+    PRESENCE_CAP_JINGLE_RTP_TMP = 1 << 12,
+    PRESENCE_CAP_JINGLE_TMP = 1 << 13,
+    PRESENCE_CAP_JINGLE_TRANSPORT_ICE = 1 << 14,
+    PRESENCE_CAP_JINGLE_TRANSPORT_RAWUDP = 1 << 15,
 } GabblePresenceCapabilities;
 
 G_END_DECLS
diff --git a/tests/twisted/jingle/jingletest.py b/tests/twisted/jingle/jingletest.py
index 6704dfd..1102451 100644
--- a/tests/twisted/jingle/jingletest.py
+++ b/tests/twisted/jingle/jingletest.py
@@ -190,7 +190,8 @@ class JingleTest:
 
         content = domish.Element((None, 'content'))
         content['creator'] = 'initiator'
-        content['name'] = 'audio1'
+        content['name'] = 'stream1'
+        content['senders'] = 'both'
         jingle.addChild(content)
 
         desc = domish.Element(("http://jabber.org/protocol/jingle/description/audio", 'description'))
diff --git a/tests/twisted/jingle/test-incoming-call.py b/tests/twisted/jingle/test-incoming-call.py
index e40717e..393f58f 100644
--- a/tests/twisted/jingle/test-incoming-call.py
+++ b/tests/twisted/jingle/test-incoming-call.py
@@ -59,12 +59,6 @@ def test(q, bus, conn, stream):
 
     media_chan = make_channel_proxy(conn, tp_path_prefix + e.path, 'Channel.Interface.Group')
 
-    # S-E gets notified about a newly-created stream
-    e = q.expect('dbus-signal', signal='NewStreamHandler')
-
-    stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
-
-
     # S-E gets notified about new session handler, and calls Ready on it
     e = q.expect('dbus-signal', signal='NewSessionHandler')
     assert e.args[1] == 'rtp'
@@ -72,6 +66,12 @@ def test(q, bus, conn, stream):
     session_handler = make_channel_proxy(conn, e.args[0], 'Media.SessionHandler')
     session_handler.Ready()
 
+
+    # S-E gets notified about a newly-created stream
+    e = q.expect('dbus-signal', signal='NewStreamHandler')
+
+    stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
+
     """
     # Exercise channel properties
     future_props = media_chan.GetAll(
-- 
1.5.6.5




More information about the Telepathy-commits mailing list