telepathygabble/src/gabble-media-stream.c
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 02 Feb 2010 01:10:06 +0200
changeset 0 d0f3a028347a
permissions -rw-r--r--
Revision: 201003 Kit: 201005

/*
 * gabble-media-stream.c - Source for GabbleMediaStream
 * Copyright (C) 2006 Collabora Ltd.
 * 
 *   @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@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 <dbus/dbus-glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#include "ansi.h"
#include "debug.h"
#include "handles.h"
#include "namespaces.h"

#include "gabble-connection.h"
#include "gabble-media-channel.h"
#include "gabble-media-session.h"
#include "gabble-media-session-enumtypes.h"

#include "telepathy-helpers.h"
#include "telepathy-constants.h"

#include "gabble-media-stream.h"
#include "gabble-media-stream-signals-marshal.h"
#include "gabble-media-stream-glue.h"

#include "gabble_enums.h"

#ifndef EMULATOR
G_DEFINE_TYPE(GabbleMediaStream, gabble_media_stream, G_TYPE_OBJECT)
#endif

#define DEBUG_FLAG GABBLE_DEBUG_MEDIA

#ifdef DEBUG_FLAG
//#define DEBUG(format, ...)
#define DEBUGGING 0
//#define NODE_DEBUG(n, s) ;
#endif /* DEBUG_FLAG */

/* signal enum */
enum
{
    DESTROY,

    ADD_REMOTE_CANDIDATE,
    CLOSE,
    REMOVE_REMOTE_CANDIDATE,
    SET_ACTIVE_CANDIDATE_PAIR,
    SET_REMOTE_CANDIDATE_LIST,
    SET_REMOTE_CODECS,
    SET_STREAM_PLAYING,
    SET_STREAM_SENDING,

    NEW_ACTIVE_CANDIDATE_PAIR,
    NEW_NATIVE_CANDIDATE,
    SUPPORTED_CODECS,
    ERROR,

    LAST_SIGNAL 
#ifdef EMULATOR    
    = LAST_SIGNAL_MED_STREAM
#endif
    
};

#ifdef EMULATOR
#include "libgabble_wsd_solution.h"

	GET_STATIC_ARRAY_FROM_TLS(signals,gabble_med_stream,guint)
	#define signals (GET_WSD_VAR_NAME(signals,gabble_med_stream, s)())	
	
	GET_STATIC_VAR_FROM_TLS(gabble_media_stream_parent_class,gabble_med_stream,gpointer)
	#define gabble_media_stream_parent_class (*GET_WSD_VAR_NAME(gabble_media_stream_parent_class,gabble_med_stream,s)())
	
	GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_med_stream,GType)
	#define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_med_stream,s)())

	/*gchar** _s_gabble_med_stream_video_codec_params() { return (gchar**)((libgabble_ImpurePtr()->_s_gabble_med_stream_video_codec_params)); }

	#define video_codec_params (GET_WSD_VAR_NAME(video_codec_params,gabble_med_stream, s)())*/	

	
static void gabble_media_stream_init (GabbleMediaStream *self); 
static void gabble_media_stream_class_init (GabbleMediaStreamClass *klass); 
static void gabble_media_stream_class_intern_init (gpointer klass) 
{ 
gabble_media_stream_parent_class = g_type_class_peek_parent (klass);
 gabble_media_stream_class_init ((GabbleMediaStreamClass*) klass);
}
 EXPORT_C GType gabble_media_stream_get_type (void) 
 {
  if ((g_define_type_id == 0)) 
  {
   static const GTypeInfo g_define_type_info = { sizeof (GabbleMediaStreamClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_media_stream_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabbleMediaStream), 0, (GInstanceInitFunc) gabble_media_stream_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabbleMediaStream"), &g_define_type_info, (GTypeFlags) 0); { {} ; } } return g_define_type_id; 
  };

		
#else

	static guint signals[LAST_SIGNAL] = {0};

#endif


/* properties */
enum
{
  PROP_CONNECTION = 1,
  PROP_MEDIA_SESSION,
  PROP_OBJECT_PATH,
  PROP_MODE,
  PROP_NAME,
  PROP_ID,
  PROP_INITIATOR,
  PROP_MEDIA_TYPE,
  PROP_CONNECTION_STATE,
  PROP_READY,
  PROP_GOT_LOCAL_CODECS,
  PROP_SIGNALLING_STATE,
  PROP_PLAYING,
  PROP_COMBINED_DIRECTION,
  LAST_PROPERTY
};

/* private structure */
typedef struct _GabbleMediaStreamPrivate GabbleMediaStreamPrivate;

struct _GabbleMediaStreamPrivate
{
  GabbleConnection *conn;
  GabbleMediaSession *session;
  GabbleMediaSessionMode mode;
  gchar *object_path;
  guint id;
  guint media_type;

  gboolean ready;
  gboolean sending;

  GValue native_codecs;     /* intersected codec list */
  GValue native_candidates;

  GValue remote_codecs;
  GValue remote_candidates;

  guint remote_candidate_count;

  gboolean closed;
  gboolean dispose_has_run;
};

#define GABBLE_MEDIA_STREAM_GET_PRIVATE(obj) \
    ((GabbleMediaStreamPrivate *)obj->priv)
//Vinod: add below definition
#define ENABLE_DEBUG

#ifdef ENABLE_DEBUG
#if _GMS_DEBUG_LEVEL > 1
static const char *tp_protocols[] = {
  "TP_MEDIA_STREAM_PROTO_UDP (0)",
  "TP_MEDIA_STREAM_PROTO_TCP (1)"
};

static const char *tp_transports[] = {
  "TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL (0)",
  "TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED (1)",
  "TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY (2)"
};
#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
gabble_media_stream_init (GabbleMediaStream *self)
{
  GabbleMediaStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
      GABBLE_TYPE_MEDIA_STREAM, GabbleMediaStreamPrivate);

  self->priv = priv;

  g_value_init (&priv->native_codecs, TP_TYPE_CODEC_LIST);
  g_value_take_boxed (&priv->native_codecs,
      dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST));

  g_value_init (&priv->native_candidates, TP_TYPE_CANDIDATE_LIST);
  g_value_take_boxed (&priv->native_candidates,
      dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST));

  g_value_init (&priv->remote_codecs, TP_TYPE_CODEC_LIST);
  g_value_take_boxed (&priv->remote_codecs,
      dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST));

  g_value_init (&priv->remote_candidates, TP_TYPE_CANDIDATE_LIST);
  g_value_take_boxed (&priv->remote_candidates,
      dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST));
}

static GObject *
gabble_media_stream_constructor (GType type, guint n_props,
                                 GObjectConstructParam *props)
{
  GObject *obj;
  GabbleMediaStreamPrivate *priv;
  DBusGConnection *bus;

  /* call base class constructor */
  obj = G_OBJECT_CLASS (gabble_media_stream_parent_class)->
           constructor (type, n_props, props);
  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (GABBLE_MEDIA_STREAM (obj));

  /* go for the bus */
  bus = tp_get_bus ();
  dbus_g_connection_register_g_object (bus, priv->object_path, obj);

  return obj;
}

static void
gabble_media_stream_get_property (GObject    *object,
                                  guint       property_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  switch (property_id) {
    case PROP_CONNECTION:
      g_value_set_object (value, priv->conn);
      break;
    case PROP_MEDIA_SESSION:
      g_value_set_object (value, priv->session);
      break;
    case PROP_OBJECT_PATH:
      g_value_set_string (value, priv->object_path);
      break;
    case PROP_MODE:
      g_value_set_enum (value, priv->mode);
      break;
    case PROP_NAME:
      g_value_set_string (value, stream->name);
      break;
    case PROP_ID:
      g_value_set_uint (value, priv->id);
      break;
    case PROP_INITIATOR:
      g_value_set_uint (value, stream->initiator);
      break;
    case PROP_MEDIA_TYPE:
      g_value_set_uint (value, priv->media_type);
      break;
    case PROP_CONNECTION_STATE:
      g_value_set_uint (value, stream->connection_state);
      break;
    case PROP_READY:
      g_value_set_boolean (value, priv->ready);
      break;
    case PROP_GOT_LOCAL_CODECS:
      g_value_set_boolean (value, stream->got_local_codecs);
      break;
    case PROP_SIGNALLING_STATE:
      g_value_set_uint (value, stream->signalling_state);
      break;
    case PROP_PLAYING:
      g_value_set_boolean (value, stream->playing);
      break;
    case PROP_COMBINED_DIRECTION:
      g_value_set_uint (value, stream->combined_direction);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
gabble_media_stream_set_property (GObject      *object,
                                  guint         property_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  switch (property_id) {
    case PROP_CONNECTION:
      priv->conn = g_value_get_object (value);
      break;
    case PROP_MEDIA_SESSION:
      priv->session = 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_MODE:
      priv->mode = g_value_get_enum (value);
      break;
    case PROP_NAME:
      g_free (stream->name);
      stream->name = g_value_dup_string (value);
      break;
    case PROP_ID:
      priv->id = g_value_get_uint (value);
      break;
    case PROP_INITIATOR:
      stream->initiator = g_value_get_uint (value);
      break;
    case PROP_MEDIA_TYPE:
      priv->media_type = g_value_get_uint (value);
      break;
    case PROP_CONNECTION_STATE:
      _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "stream %s connection state %d",
          stream->name, stream->connection_state);
      stream->connection_state = g_value_get_uint (value);
      break;
    case PROP_READY:
      priv->ready = g_value_get_boolean (value);
      break;
    case PROP_GOT_LOCAL_CODECS:
      stream->got_local_codecs = g_value_get_boolean (value);
      break;
    case PROP_SIGNALLING_STATE:
        {
          StreamSignallingState old = stream->signalling_state;
          stream->signalling_state = g_value_get_uint (value);
          _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "stream %s sig_state %d->%d",
              stream->name, old, stream->signalling_state);
          if (stream->signalling_state != old)
            push_native_candidates (stream);
        }
      break;
    case PROP_PLAYING:
        {
          gboolean old = stream->playing;
          stream->playing = g_value_get_boolean (value);
          if (stream->playing != old)
            push_playing (stream);
        }
      break;
    case PROP_COMBINED_DIRECTION:
      stream->combined_direction = g_value_get_uint (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void gabble_media_stream_dispose (GObject *object);
static void gabble_media_stream_finalize (GObject *object);

static void
gabble_media_stream_class_init (GabbleMediaStreamClass *gabble_media_stream_class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (gabble_media_stream_class);
  GParamSpec *param_spec;

  g_type_class_add_private (gabble_media_stream_class, sizeof (GabbleMediaStreamPrivate));

  object_class->constructor = gabble_media_stream_constructor;

  object_class->get_property = gabble_media_stream_get_property;
  object_class->set_property = gabble_media_stream_set_property;

  object_class->dispose = gabble_media_stream_dispose;
  object_class->finalize = gabble_media_stream_finalize;

  param_spec = g_param_spec_object ("connection", "GabbleConnection object",
                                    "Gabble connection object that owns this "
                                    "media stream'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-session", "GabbleMediaSession object",
                                    "Gabble media session object that owns this "
                                    "media stream object.",
                                    GABBLE_TYPE_MEDIA_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, 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_enum ("mode", "Signalling mode",
                                  "Which signalling mode used to control the "
                                  "stream.",
                                  gabble_media_session_mode_get_type(),
                                  MODE_JINGLE,
                                  G_PARAM_CONSTRUCT_ONLY |
                                  G_PARAM_READWRITE |
                                  G_PARAM_STATIC_NAME |
                                  G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_MODE, param_spec);

  param_spec = g_param_spec_string ("name", "Stream name",
                                    "An opaque name for the stream used in the "
                                    "signalling.",
                                    NULL,
                                    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);

  param_spec = g_param_spec_uint ("id", "Stream ID",
                                  "A stream number for the stream used in the "
                                  "D-Bus API.",
                                  0, G_MAXUINT, 0,
                                  G_PARAM_CONSTRUCT_ONLY |
                                  G_PARAM_READWRITE |
                                  G_PARAM_STATIC_NAME |
                                  G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_ID, param_spec);

  param_spec = g_param_spec_uint ("initiator", "Stream initiator",
                                  "An enum signifying which end initiated "
                                  "the stream.",
                                  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 ("media-type", "Stream media type",
                                  "A constant indicating which media type the "
                                  "stream carries.",
                                  TP_MEDIA_STREAM_TYPE_AUDIO,
                                  TP_MEDIA_STREAM_TYPE_VIDEO,
                                  TP_MEDIA_STREAM_TYPE_AUDIO,
                                  G_PARAM_CONSTRUCT_ONLY |
                                  G_PARAM_READWRITE |
                                  G_PARAM_STATIC_NAME |
                                  G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_MEDIA_TYPE, param_spec);

  param_spec = g_param_spec_uint ("connection-state", "Stream connection state",
                                  "An integer indicating the state of the"
                                  "stream's connection.",
                                  TP_MEDIA_STREAM_STATE_DISCONNECTED,
                                  TP_MEDIA_STREAM_STATE_CONNECTED,
                                  TP_MEDIA_STREAM_STATE_DISCONNECTED,
                                  G_PARAM_CONSTRUCT |
                                  G_PARAM_READWRITE |
                                  G_PARAM_STATIC_NAME |
                                  G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_CONNECTION_STATE, param_spec);

  param_spec = g_param_spec_boolean ("ready", "Ready?",
                                     "A boolean signifying whether the user "
                                     "is ready to handle signals from this "
                                     "object.",
                                     FALSE,
                                     G_PARAM_CONSTRUCT |
                                     G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NAME |
                                     G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_READY, param_spec);

  param_spec = g_param_spec_boolean ("got-local-codecs", "Got local codecs?",
                                     "A boolean signifying whether we've got "
                                     "the locally supported codecs from the user.",
                                     FALSE,
                                     G_PARAM_CONSTRUCT |
                                     G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NAME |
                                     G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_GOT_LOCAL_CODECS, param_spec);

  param_spec = g_param_spec_uint ("signalling-state", "Signalling state",
                                  "Whether the stream is newly created, "
                                  "sent to the peer, or acknowledged.",
                                  STREAM_SIG_STATE_NEW,
                                  STREAM_SIG_STATE_REMOVING,
                                  STREAM_SIG_STATE_NEW,
                                  G_PARAM_CONSTRUCT |
                                  G_PARAM_READWRITE |
                                  G_PARAM_STATIC_NAME |
                                  G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_SIGNALLING_STATE, param_spec);

  param_spec = g_param_spec_boolean ("playing", "Set playing",
                                     "A boolean signifying whether the stream "
                                     "has been set playing yet.",
                                     FALSE,
                                     G_PARAM_CONSTRUCT |
                                     G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NAME |
                                     G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_PLAYING, param_spec);

  param_spec = g_param_spec_uint ("combined-direction",
      "Combined direction",
      "An integer indicating the directions the stream currently sends in, "
      "and the peers who have been asked to send.",
      TP_MEDIA_STREAM_DIRECTION_NONE,
      MAKE_COMBINED_DIRECTION (TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
        TP_MEDIA_STREAM_PENDING_LOCAL_SEND |
        TP_MEDIA_STREAM_PENDING_REMOTE_SEND),
      TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
      G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_NAME |
      G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_COMBINED_DIRECTION,
      param_spec);

  /* signals exported by D-Bus interface */
  signals[DESTROY] =
    g_signal_new ("destroy",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  signals[ADD_REMOTE_CANDIDATE] =
    g_signal_new ("add-remote-candidate",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  gabble_media_stream_marshal_VOID__STRING_BOXED,
                  G_TYPE_NONE, 2, G_TYPE_STRING, TP_TYPE_TRANSPORT_LIST);

  signals[CLOSE] =
    g_signal_new ("close",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  signals[REMOVE_REMOTE_CANDIDATE] =
    g_signal_new ("remove-remote-candidate",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__STRING,
                  G_TYPE_NONE, 1, G_TYPE_STRING);

  signals[SET_ACTIVE_CANDIDATE_PAIR] =
    g_signal_new ("set-active-candidate-pair",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  gabble_media_stream_marshal_VOID__STRING_STRING,
                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);

  signals[SET_REMOTE_CANDIDATE_LIST] =
    g_signal_new ("set-remote-candidate-list",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__BOXED,
                  G_TYPE_NONE, 1, TP_TYPE_CANDIDATE_LIST);

  signals[SET_REMOTE_CODECS] =
    g_signal_new ("set-remote-codecs",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__BOXED,
                  G_TYPE_NONE, 1, TP_TYPE_CODEC_LIST);

  /* signals not exported by D-Bus interface */
  signals[NEW_ACTIVE_CANDIDATE_PAIR] =
    g_signal_new ("new-active-candidate-pair",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  gabble_media_stream_marshal_VOID__STRING_STRING,
                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);

  signals[NEW_NATIVE_CANDIDATE] =
    g_signal_new ("new-native-candidate",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  gabble_media_stream_marshal_VOID__STRING_BOXED,
                  G_TYPE_NONE, 2, G_TYPE_STRING, TP_TYPE_TRANSPORT_LIST);

  signals[SUPPORTED_CODECS] =
    g_signal_new ("supported-codecs",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__BOXED,
                  G_TYPE_NONE, 1, TP_TYPE_CODEC_LIST);

  signals[SET_STREAM_PLAYING] =
    g_signal_new ("set-stream-playing",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__BOOLEAN,
                  G_TYPE_NONE, 1, G_TYPE_BOOLEAN);

  signals[SET_STREAM_SENDING] =
    g_signal_new ("set-stream-sending",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__BOOLEAN,
                  G_TYPE_NONE, 1, G_TYPE_BOOLEAN);

  signals[ERROR] =
    g_signal_new ("error",
                  G_OBJECT_CLASS_TYPE (gabble_media_stream_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  gabble_media_stream_marshal_VOID__UINT_STRING,
                  G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);

  dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (gabble_media_stream_class), &dbus_glib_gabble_media_stream_object_info);
}

void
gabble_media_stream_dispose (GObject *object)
{
  GabbleMediaStream *self = GABBLE_MEDIA_STREAM (object);
  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  if (priv->dispose_has_run)
    return;

  _gabble_media_stream_close (self);

  g_signal_emit (self, signals[DESTROY], 0);

  priv->dispose_has_run = TRUE;

  if (G_OBJECT_CLASS (gabble_media_stream_parent_class)->dispose)
    G_OBJECT_CLASS (gabble_media_stream_parent_class)->dispose (object);
}

void
gabble_media_stream_finalize (GObject *object)
{
  GabbleMediaStream *self = GABBLE_MEDIA_STREAM (object);
  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  g_free (priv->object_path);

  g_value_unset (&priv->native_codecs);
  g_value_unset (&priv->native_candidates);

  g_value_unset (&priv->remote_codecs);
  g_value_unset (&priv->remote_candidates);

  G_OBJECT_CLASS (gabble_media_stream_parent_class)->finalize (object);
}

/**
 * gabble_media_stream_codec_choice
 *
 * Implements D-Bus method CodecChoice
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_codec_choice (GabbleMediaStream *self,
                                  guint codec_id,
                                  GError **error)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  return TRUE;
}


/**
 * gabble_media_stream_error
 *
 * Implements D-Bus method Error
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_error (GabbleMediaStream *self,
                           guint errno,
                           const gchar *message,
                           GError **error)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

 _gabble_media_session_debug (priv->session, DEBUG_MSG_WARNING, "Media.StreamHandler::Error called, error %u (%s) -- emitting signal", errno, message);

  g_signal_emit (self, signals[ERROR], 0, errno, message);

  return TRUE;
}


/**
 * gabble_media_stream_native_candidates_prepared
 *
 * Implements D-Bus method NativeCandidatesPrepared
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_native_candidates_prepared (GabbleMediaStream *self,
                                                GError **error)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  return TRUE;
}


/**
 * gabble_media_stream_new_active_candidate_pair
 *
 * Implements D-Bus method NewActiveCandidatePair
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_new_active_candidate_pair (GabbleMediaStream *self,
                                               const gchar *native_candidate_id,
                                               const gchar *remote_candidate_id,
                                               GError **error)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  g_signal_emit (self, signals[NEW_ACTIVE_CANDIDATE_PAIR], 0,
                 native_candidate_id, remote_candidate_id);

  return TRUE;
}


/**
 * gabble_media_stream_new_native_candidate
 *
 * Implements D-Bus method NewNativeCandidate
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_new_native_candidate (GabbleMediaStream *self,
                                          const gchar *candidate_id,
                                          const GPtrArray *transports,
                                          GError **error)
{
  GabbleMediaStreamPrivate *priv;
  JingleSessionState state;
  GPtrArray *candidates;
  GValue candidate = { 0, };
  GValueArray *transport;
  const gchar *addr;

  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  g_object_get (priv->session, "state", &state, NULL);

  /* FIXME: maybe this should be an assertion in case the channel
   * isn't closed early enough right now? */
  if (state > JS_STATE_ACTIVE)
    {
      gabble_debug (DEBUG_FLAG, "state > JS_STATE_ACTIVE, doing nothing");
      return TRUE;
    }

  candidates = g_value_get_boxed (&priv->native_candidates);

  g_value_init (&candidate, TP_TYPE_CANDIDATE_STRUCT);
  g_value_take_boxed (&candidate,
      dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_STRUCT));

  dbus_g_type_struct_set (&candidate,
      0, candidate_id,
      1, transports,
      G_MAXUINT);

  transport = g_ptr_array_index (transports, 0);
  addr = g_value_get_string (g_value_array_get_nth (transport, 1));
  if (!strcmp (addr, "127.0.0.1"))
    {
     _gabble_media_session_debug (priv->session, DEBUG_MSG_WARNING, "%s: ignoring native localhost candidate",
                         G_STRFUNC);
      return TRUE;
    }

  g_ptr_array_add (candidates, g_value_get_boxed (&candidate));

  _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "put 1 native candidate from stream-engine into cache");

  push_native_candidates (self);

  g_signal_emit (self, signals[NEW_NATIVE_CANDIDATE], 0,
                 candidate_id, transports);

  return TRUE;
}


/**
 * gabble_media_stream_ready
 *
 * Implements D-Bus method Ready
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_ready (GabbleMediaStream *self,
                           const GPtrArray *codecs,
                           GError **error)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "ready called");

  g_object_set (self, "ready", TRUE, NULL);

  push_remote_codecs (self);
  push_remote_candidates (self);
  push_playing (self);
  push_sending (self);

  return gabble_media_stream_set_local_codecs (self, codecs, error);
}


/**
 * gabble_media_stream_set_local_codecs
 *
 * Implements D-Bus method SetLocalCodecs
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_set_local_codecs (GabbleMediaStream *self,
                                      const GPtrArray *codecs,
                                      GError **error)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "putting list of all %d locally supported "
                  "codecs from stream-engine into cache", codecs->len);

  g_value_set_boxed (&priv->native_codecs, codecs);

  g_object_set (self, "got-local-codecs", TRUE, NULL);

  return TRUE;
}


/**
 * gabble_media_stream_stream_state
 *
 * Implements D-Bus method StreamState
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_stream_state (GabbleMediaStream *self,
                                  guint connection_state,
                                  GError **error)
{
  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  g_object_set (self, "connection-state", connection_state, NULL);

  return TRUE;
}


/**
 * gabble_media_stream_supported_codecs
 *
 * Implements D-Bus method SupportedCodecs
 * on interface org.freedesktop.Telepathy.Media.StreamHandler
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
gboolean
gabble_media_stream_supported_codecs (GabbleMediaStream *self,
                                      const GPtrArray *codecs,
                                      GError **error)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (self));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);

  _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "got codec intersection containing %d "
                  "codecs from stream-engine", codecs->len);

  /* store the intersection for later on */
  g_value_set_boxed (&priv->native_codecs, codecs);

  g_signal_emit (self, signals[SUPPORTED_CODECS], 0, codecs);

  return TRUE;
}

static LmHandlerResult
candidates_msg_reply_cb (GabbleConnection *conn,
                         LmMessage *sent_msg,
                         LmMessage *reply_msg,
                         GObject *object,
                         gpointer user_data)
{
  GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (priv->session, "candidates failed");

  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

static void
_add_rtp_candidate_node (GabbleMediaSession *session, LmMessageNode *parent,
                         GValueArray *candidate)
{
  gchar *addr;
  gchar *user;
  gchar *pass;
  gchar *port_str;
  gchar *pref_str;
  gchar *xml;
  const gchar *type_str;
  const gchar *candidate_id;
  guint port;
  gdouble pref;
  TpMediaStreamProto proto;
  TpMediaStreamTransportType type;
  const GPtrArray *transports;
  GValue transport = { 0, };
  LmMessageNode *cand_node;

  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));

  /* jingle audio only supports the concept of one transport per candidate */
  g_assert (transports->len == 1);

  g_value_init (&transport, TP_TYPE_TRANSPORT_STRUCT);
  g_value_set_static_boxed (&transport, g_ptr_array_index (transports, 0));

  dbus_g_type_struct_get (&transport,
      1, &addr,
      2, &port,
      3, &proto,
      6, &pref,
      7, &type,
      8, &user,
      9, &pass,
      G_MAXUINT);

  port_str = g_strdup_printf ("%d", port);
  pref_str = g_strdup_printf ("%f", pref);

  switch (type) {
    case TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL:
      type_str = "local";
      break;
    case TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED:
      type_str = "stun";
      break;
    case TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY:
      type_str = "relay";
      break;
    default:
      g_error ("%s: TpMediaStreamTransportType has an invalid value",
        G_STRFUNC);
      return;
  }

  cand_node = lm_message_node_add_child (parent, "candidate", NULL);
  lm_message_node_set_attributes (cand_node,
      "name", "rtp",
      "address", addr,
      "port", port_str,
      "username", user,
      "password", pass,
      "preference", pref_str,
      "protocol", (proto == TP_MEDIA_STREAM_PROTO_UDP) ? "udp" : "tcp",
      "type", type_str,
      "network", "0",
      "generation", "0",
      NULL);

  xml = lm_message_node_to_string (cand_node);
  _gabble_media_session_debug (session, DEBUG_MSG_DUMP,
    "  from Telepathy D-Bus struct: [%s\"%s\", %s[%s1, \"%s\", %d, %s, "
    "\"%s\", \"%s\", %f, %s, \"%s\", \"%s\"%s]]",
    ANSI_BOLD_OFF, candidate_id, ANSI_BOLD_ON, ANSI_BOLD_OFF, addr, port,
    tp_protocols[proto], "RTP", "AVP", pref, tp_transports[type], user, pass,
    ANSI_BOLD_ON);
  _gabble_media_session_debug (session, DEBUG_MSG_DUMP,
    "  to Jingle XML: [%s%s%s]", ANSI_BOLD_OFF, xml, ANSI_BOLD_ON);
  g_free (xml);

  g_free (addr);
  g_free (user);
  g_free (pass);
  g_free (port_str);
  g_free (pref_str);
}

static LmMessage *
_gabble_media_stream_message_new (GabbleMediaStream *stream,
                                  const gchar *action,
                                  LmMessageNode **content_node)
{
  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
  LmMessage *msg;
  LmMessageNode *session_node = NULL;

  /* construct a session message */
  msg = _gabble_media_session_message_new (priv->session, action,
      &session_node);

  /* add our content node to it if necessary */
  *content_node = _gabble_media_stream_add_content_node (stream, session_node);

  return msg;
}


static void
push_candidate (GabbleMediaStream *stream, GValueArray *candidate)
{
  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
  LmMessage *msg;
  LmMessageNode *content_node, *transport_node;
  const gchar *action;

  if (priv->mode == MODE_GOOGLE)
    action = "candidates";
  else
    action = "transport-info";

  /* construct a base message */
  msg = _gabble_media_stream_message_new (stream, action, &content_node);

  /* for jingle, add a transport */
  transport_node = _gabble_media_stream_content_node_add_transport (stream,
      content_node);

  /* add transport info to it */
  _add_rtp_candidate_node (priv->session, transport_node, candidate);

  _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "sending jingle session action \"%s\" to "
      "peer", action);

  /* send it */
  _gabble_connection_send_with_reply (priv->conn, msg, candidates_msg_reply_cb,
      G_OBJECT (stream), NULL, NULL);

  /* clean up */
  lm_message_unref (msg);
}

static void
push_native_candidates (GabbleMediaStream *stream)
{
  GabbleMediaStreamPrivate *priv;
  GPtrArray *candidates;
  guint i;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  if (stream->signalling_state == STREAM_SIG_STATE_NEW ||
      stream->signalling_state == STREAM_SIG_STATE_REMOVING)
    return;

  candidates = g_value_get_boxed (&priv->native_candidates);

  for (i = 0; i < candidates->len; i++)
    push_candidate (stream, g_ptr_array_index (candidates, i));

  g_value_take_boxed (&priv->native_candidates,
    dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST));
}

void
_gabble_media_stream_close (GabbleMediaStream *stream)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  if (!priv->closed)
    {
      priv->closed = TRUE;
      g_signal_emit (stream, signals[CLOSE], 0);
    }
}

gboolean
_gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
                                         LmMessage *message,
                                         LmMessageNode *desc_node,
                                         GError **error)
{
  GabbleMediaStreamPrivate *priv;
  LmMessageNode *node;
  GPtrArray *codecs;

  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 (node = desc_node->children; node; node = node->next)
    {
      guchar id;
      const gchar *name, *str;
      guint clockrate, channels;
      GHashTable *params;
      GValue codec = { 0, };

      if (g_strdiff (node->name, "payload-type"))
        continue;

      /* id of codec */
      str = lm_message_node_get_attribute (node, "id");
      if (str == NULL)
        {
          g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
              "description has no ID");
          return FALSE;
        }

      id = atoi(str);

      /* codec name */
      name = lm_message_node_get_attribute (node, "name");
      if (name == NULL)
        {
          name = "";
        }

      /* clock rate: jingle and newer GTalk */
      str = lm_message_node_get_attribute (node, "clockrate"); /* google */
      if (str == NULL)
        str = lm_message_node_get_attribute (node, "rate"); /* jingle */

      if (str != NULL)
        {
          clockrate = atoi (str);
        }
      else
        {
          clockrate = 0;
        }

      /* number of channels: jingle only */
      str = lm_message_node_get_attribute (node, "channels");
      if (str != NULL)
        {
          channels = atoi (str);
        }
      else
        {
          channels = 1;
        }

      params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);

      /* bitrate: newer GTalk only */
      str = lm_message_node_get_attribute (node, "bitrate");
      if (str != NULL)
        {
          g_hash_table_insert (params, "bitrate", g_strdup (str));
        }

      g_value_init (&codec, TP_TYPE_CODEC_STRUCT);
      g_value_take_boxed (&codec,
          dbus_g_type_specialized_construct (TP_TYPE_CODEC_STRUCT));

      dbus_g_type_struct_set (&codec,
          0, id,
          1, name,
          2, TP_CODEC_MEDIA_TYPE_AUDIO,
          3, clockrate,
          4, channels,
          5, params,
          G_MAXUINT);

      g_ptr_array_add (codecs, g_value_get_boxed (&codec));
    }

  _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "put %d remote codecs from peer into cache",
                  codecs->len);

  push_remote_codecs (stream);

  return TRUE;
}

static void
push_remote_codecs (GabbleMediaStream *stream)
{
  GabbleMediaStreamPrivate *priv;
  GPtrArray *codecs;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  if (!priv->ready)
    return;

  codecs = g_value_get_boxed (&priv->remote_codecs);
  if (codecs->len == 0)
    return;

  _gabble_media_session_debug (priv->session, DEBUG_MSG_EVENT, "passing %d remote codecs to stream-engine",
                   codecs->len);

  g_signal_emit (stream, signals[SET_REMOTE_CODECS], 0, codecs);

  g_value_take_boxed (&priv->remote_codecs,
      dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST));
}

gboolean
_gabble_media_stream_post_remote_candidates (GabbleMediaStream *stream,
                                             LmMessage *message,
                                             LmMessageNode *transport_node,
                                             GError **error)
{
  GabbleMediaStreamPrivate *priv;
  LmMessageNode *node;
  const gchar *str;
  GPtrArray *candidates;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  candidates = g_value_get_boxed (&priv->remote_candidates);

  for (node = transport_node->children; node; node = node->next)
    {
      gchar *candidate_id;
      const gchar *name, *addr;
      guint16 port;
      TpMediaStreamProto proto;
      gdouble pref;
      TpMediaStreamTransportType type;
      const gchar *user, *pass;
      guchar net, gen;
      GValue candidate = { 0, };
      GPtrArray *transports;
      GValue transport = { 0, };
      gchar *xml;

      if (g_strdiff (node->name, "candidate"))
        continue;

      /*
       * Candidate
       */

      /* stream name */
      name = lm_message_node_get_attribute (node, "name");
      if (name == NULL || strcmp (name, "rtp") != 0)
        goto FAILURE;


      /*
       * Transport
       */

      /* ip address */
      addr = lm_message_node_get_attribute (node, "address");
      if (addr == NULL)
        goto FAILURE;

      /* port */
      str = lm_message_node_get_attribute (node, "port");
      if (str == NULL)
        goto FAILURE;
      port = atoi (str);

      /* protocol */
      str = lm_message_node_get_attribute (node, "protocol");
      if (str == NULL)
        goto FAILURE;

      if (strcmp (str, "udp") == 0)
        {
          proto = TP_MEDIA_STREAM_PROTO_UDP;
        }
      else if (strcmp (str, "tcp") == 0)
        {
          proto = TP_MEDIA_STREAM_PROTO_TCP;
        }
      else if (strcmp (str, "ssltcp") == 0)
        {
         _gabble_media_session_debug (priv->session, DEBUG_MSG_WARNING, "%s: ssltcp candidates "
                             "not yet supported", G_STRFUNC);
          continue;
        }
      else
        goto FAILURE;

      /* protocol profile: hardcoded to "AVP" for now */

      /* preference */
      str = lm_message_node_get_attribute (node, "preference");
      if (str == NULL)
        goto FAILURE;
      pref = g_ascii_strtod (str, NULL);

      /* type */
      str = lm_message_node_get_attribute (node, "type");
      if (str == NULL)
        goto FAILURE;

      if (strcmp (str, "local") == 0)
        {
          type = TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL;
        }
      else if (strcmp (str, "stun") == 0)
        {
          type = TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED;
        }
      else if (strcmp (str, "relay") == 0)
        {
          type = TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY;
        }
      else
        goto FAILURE;

      /* username */
      user = lm_message_node_get_attribute (node, "username");
      if (user == NULL)
        goto FAILURE;

      /* password */
      pass = lm_message_node_get_attribute (node, "password");
      if (pass == NULL)
        goto FAILURE;

      /* unknown */
      str = lm_message_node_get_attribute (node, "network");
      if (str == NULL)
        goto FAILURE;
      net = atoi (str);

      /* unknown */
      str = lm_message_node_get_attribute (node, "generation");
      if (str == NULL)
        goto FAILURE;
      gen = atoi (str);


      g_value_init (&transport, TP_TYPE_TRANSPORT_STRUCT);
      g_value_take_boxed (&transport,
          dbus_g_type_specialized_construct (TP_TYPE_TRANSPORT_STRUCT));

      dbus_g_type_struct_set (&transport,
          0, 1,         /* component number */
          1, addr,
          2, port,
          3, proto,
          4, "RTP",
          5, "AVP",
          6, pref,
          7, type,
          8, user,
          9, pass,
          G_MAXUINT);

      transports = g_ptr_array_sized_new (1);
      g_ptr_array_add (transports, g_value_get_boxed (&transport));


      g_value_init (&candidate, TP_TYPE_CANDIDATE_STRUCT);
      g_value_take_boxed (&candidate,
          dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_STRUCT));

      /* 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));

      xml = lm_message_node_to_string (node);
      _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "put 1 remote candidate from peer into cache");
      _gabble_media_session_debug (priv->session, DEBUG_MSG_DUMP, "  from Jingle XML: [%s%s%s]",
                      ANSI_BOLD_OFF, xml, ANSI_BOLD_ON);
      _gabble_media_session_debug (priv->session, DEBUG_MSG_DUMP, "  to Telepathy D-Bus struct: [%s\"%s\", %s[%s1, \"%s\", %d, %s, \"%s\", \"%s\", %f, %s, \"%s\", \"%s\"%s]]",
                      ANSI_BOLD_OFF, candidate_id, ANSI_BOLD_ON,
                      ANSI_BOLD_OFF, addr, port, tp_protocols[proto], "RTP", "AVP", pref, tp_transports[type], user, pass, ANSI_BOLD_ON);
      g_free (xml);

      g_free (candidate_id);
    }

/*SUCCESS:*/
  push_remote_candidates (stream);

  return TRUE;

FAILURE:
  g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
      "unable to parse candidate");

  return FALSE;
}

static void
push_remote_candidates (GabbleMediaStream *stream)
{
  GabbleMediaStreamPrivate *priv;
  GPtrArray *candidates;
  guint i;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  candidates = g_value_get_boxed (&priv->remote_candidates);

  if (candidates->len == 0)
    return;

  if (!priv->ready)
    return;

  for (i = 0; i < candidates->len; i++)
    {
      GValueArray *candidate = g_ptr_array_index (candidates, i);
      const gchar *candidate_id;
      const GPtrArray *transports;

      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));

      _gabble_media_session_debug (priv->session, DEBUG_MSG_EVENT, "passing 1 remote candidate "
                       "to stream-engine");

      g_signal_emit (stream, signals[ADD_REMOTE_CANDIDATE], 0,
                     candidate_id, transports);
    }

  g_value_take_boxed (&priv->remote_candidates,
      dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST));
}

static void
push_playing (GabbleMediaStream *stream)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  if (!priv->ready)
    return;

  _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "stream %s emitting SetStreamPlaying(%s)",
      stream->name, stream->playing ? "true" : "false");

  g_signal_emit (stream, signals[SET_STREAM_PLAYING], 0, stream->playing);
}

static void
push_sending (GabbleMediaStream *stream)
{
  GabbleMediaStreamPrivate *priv;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  if (!priv->ready)
    return;

  _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "stream %s emitting SetStreamSending(%s)",
      stream->name, priv->sending ? "true" : "false");

  g_signal_emit (stream, signals[SET_STREAM_SENDING], 0, priv->sending);
}

/*
 * oh sweet g_hash_table_foreach how beautiful thou be'st
 *
 *    _\ / ^/
 *  \/ \// 7_   __
 *  ( 7 ) (__) (__)
 *  ^\\ |/__/___/
 *   \\/_/     | <-- TP-cable kindly provided by Mika N.
 *    \ /      O
 *     ||     /|\
 *     ||     / \
 *     ||
 * ____||_____________
 */

typedef struct {
    GabbleMediaStreamPrivate *priv;
    LmMessageNode *pt_node;
} CodecParamsFromTpContext;

//#ifndef EMULATOR
static const gchar *video_codec_params[] = {
  "x", "y", "width", "height", "layer", "transparent",
};
//#endif

static void
codec_params_from_tp_foreach (gpointer key, gpointer value, gpointer user_data)
{
  CodecParamsFromTpContext *ctx = user_data;
  GabbleMediaStreamPrivate *priv = ctx->priv;
  const gchar *pname = key, *pvalue = value;

  if (priv->media_type == TP_CODEC_MEDIA_TYPE_AUDIO)
    {
      if (priv->mode == MODE_GOOGLE && strcmp (pname, "bitrate") == 0)
        {
          lm_message_node_set_attribute (ctx->pt_node, pname, pvalue);
          return;
        }
    }
  else if (priv->mode == MODE_JINGLE)
    {
      gint i;

      for (i = 0; video_codec_params[i] != NULL; i++)
        {
          if (strcmp (pname, video_codec_params[i]) == 0)
            {
              lm_message_node_set_attribute (ctx->pt_node, pname, pvalue);
              return;
            }
        }
    }

  gabble_debug (DEBUG_FLAG, "ignoring %s=%s for %s %s stream", pname, pvalue,
      (priv->mode == MODE_JINGLE) ? "jingle" : "google",
      (priv->media_type == TP_CODEC_MEDIA_TYPE_AUDIO) ? "audio" : "video");
}

LmMessageNode *
_gabble_media_stream_add_content_node (GabbleMediaStream *stream,
                                       LmMessageNode *session_node)
{
  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
  LmMessageNode *node = session_node;

  /* add our content node to it if in jingle mode */
  if (priv->mode == MODE_JINGLE)
    {
      node = lm_message_node_add_child (session_node, "content", NULL);
      lm_message_node_set_attribute (node, "name", stream->name);

      if (priv->session->initiator == stream->initiator)
        lm_message_node_set_attribute (node, "creator", "initiator");
      else
        lm_message_node_set_attribute (node, "creator", "responder");
    }

  return node;
}

void
_gabble_media_stream_content_node_add_description (GabbleMediaStream *stream,
                                                   LmMessageNode *content_node)
{
  GabbleMediaStreamPrivate *priv;
  const GPtrArray *codecs;
  LmMessageNode *desc_node;
  guint i;
  const gchar *xmlns;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  codecs = g_value_get_boxed (&priv->native_codecs);

  desc_node = lm_message_node_add_child (content_node, "description", NULL);

  if (priv->mode == MODE_GOOGLE)
    xmlns = NS_GOOGLE_SESSION_PHONE;
  else if (priv->media_type == TP_CODEC_MEDIA_TYPE_VIDEO)
    xmlns = NS_JINGLE_DESCRIPTION_VIDEO;
  else
    xmlns = NS_JINGLE_DESCRIPTION_AUDIO;

  lm_message_node_set_attribute (desc_node, "xmlns", xmlns);

  for (i = 0; i < codecs->len; i++)
    {
      GValue codec = { 0, };
      guint id, clock_rate, channels;
      gchar *name, buf[16];
      GHashTable *params;
      LmMessageNode *pt_node;
      CodecParamsFromTpContext ctx;

      g_value_init (&codec, TP_TYPE_CODEC_STRUCT);
      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);

      /* create a sub-node called "payload-type" and fill it */
      pt_node = lm_message_node_add_child (desc_node, "payload-type", NULL);

      /* id: required */
      sprintf (buf, "%u", id);
      lm_message_node_set_attribute (pt_node, "id", buf);

      /* name: optional */
      if (*name != '\0')
        {
          lm_message_node_set_attribute (pt_node, "name", name);
        }

      /* clock rate: optional */
      if (clock_rate != 0)
        {
          sprintf (buf, "%u", clock_rate);
          lm_message_node_set_attribute (pt_node,
              (priv->mode == MODE_GOOGLE) ? "clockrate" : "rate", buf);
        }

      /* number of channels: optional, jingle only */
      /* FIXME: is it? */
      if (channels != 0 && priv->mode == MODE_JINGLE)
        {
          sprintf (buf, "%u", channels);
          lm_message_node_set_attribute (pt_node, "channels", buf);
        }

      /* parse the optional params */
      ctx.priv = priv;
      ctx.pt_node = pt_node;
      g_hash_table_foreach (params, codec_params_from_tp_foreach, &ctx);

      /* clean up */
      g_free (name);
      g_hash_table_destroy (params);
    }
}

LmMessageNode *
_gabble_media_stream_content_node_add_transport (GabbleMediaStream *stream,
                                                 LmMessageNode *content_node)
{
  GabbleMediaStreamPrivate *priv;
  LmMessageNode *node;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  if (priv->mode != MODE_JINGLE)
    return content_node;

  node = lm_message_node_add_child (content_node, "transport", NULL);

  lm_message_node_set_attribute (node, "xmlns", NS_GOOGLE_TRANSPORT_P2P);

  return node;
}

void
_gabble_media_stream_update_sending (GabbleMediaStream *stream,
                                     gboolean start_sending)
{
  GabbleMediaStreamPrivate *priv;
  gboolean new_sending;

  g_assert (GABBLE_IS_MEDIA_STREAM (stream));

  priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);

  new_sending =
    ((stream->combined_direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 0);

  if (priv->sending == new_sending)
    return;

  if (new_sending && !start_sending)
    return;

  priv->sending = new_sending;
  push_sending (stream);
}