view libpurple/media/backend-fs2.c @ 30018:7ed0ddbdd2a8

Use an inline RC style to get rid of some other padding to make the small buttons even smaller, as suggested by Paradox on trac, and nicked from nautilus. Refs #8727.
author Elliott Sales de Andrade <qulogic@pidgin.im>
date Sat, 20 Mar 2010 06:22:53 +0000
parents e75d6a51a5c4
children 8cb39e235f39 3d08b59ebf59
line wrap: on
line source

/**
 * @file backend-fs2.c Farsight 2 backend for media API
 * @ingroup core
 */

/* purple
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */

#include "internal.h"

#include "backend-fs2.h"

#ifdef USE_VV
#include "backend-iface.h"
#include "debug.h"
#include "network.h"
#include "media-gst.h"

#include <gst/farsight/fs-conference-iface.h>
#include <gst/farsight/fs-element-added-notifier.h>

/** @copydoc _PurpleMediaBackendFs2Class */
typedef struct _PurpleMediaBackendFs2Class PurpleMediaBackendFs2Class;
/** @copydoc _PurpleMediaBackendFs2Private */
typedef struct _PurpleMediaBackendFs2Private PurpleMediaBackendFs2Private;
/** @copydoc _PurpleMediaBackendFs2Session */
typedef struct _PurpleMediaBackendFs2Session PurpleMediaBackendFs2Session;
/** @copydoc _PurpleMediaBackendFs2Stream */
typedef struct _PurpleMediaBackendFs2Stream PurpleMediaBackendFs2Stream;

#define PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(obj) \
		(G_TYPE_INSTANCE_GET_PRIVATE((obj), \
		PURPLE_TYPE_MEDIA_BACKEND_FS2, PurpleMediaBackendFs2Private))

static void purple_media_backend_iface_init(PurpleMediaBackendIface *iface);

static gboolean
gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self);
static void
state_changed_cb(PurpleMedia *media, PurpleMediaState state,
		gchar *sid, gchar *name, PurpleMediaBackendFs2 *self);
static void
stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
		gchar *sid, gchar *name, gboolean local,
		PurpleMediaBackendFs2 *self);

static gboolean purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
		const gchar *sess_id, const gchar *who,
		PurpleMediaSessionType type, gboolean initiator,
		const gchar *transmitter,
		guint num_params, GParameter *params);
static void purple_media_backend_fs2_add_remote_candidates(
		PurpleMediaBackend *self,
		const gchar *sess_id, const gchar *participant,
		GList *remote_candidates);
static gboolean purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
		const gchar *sess_id);
static GList *purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
		const gchar *sess_id);
static GList *purple_media_backend_fs2_get_local_candidates(
		PurpleMediaBackend *self,
		const gchar *sess_id, const gchar *participant);
static gboolean purple_media_backend_fs2_set_remote_codecs(
		PurpleMediaBackend *self,
		const gchar *sess_id, const gchar *participant,
		GList *codecs);
static gboolean purple_media_backend_fs2_set_send_codec(
		PurpleMediaBackend *self, const gchar *sess_id,
		PurpleMediaCodec *codec);

struct _PurpleMediaBackendFs2Class
{
	GObjectClass parent_class;
};

struct _PurpleMediaBackendFs2
{
	GObject parent;
};

G_DEFINE_TYPE_WITH_CODE(PurpleMediaBackendFs2, purple_media_backend_fs2,
		G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(
		PURPLE_TYPE_MEDIA_BACKEND, purple_media_backend_iface_init));

struct _PurpleMediaBackendFs2Stream
{
	PurpleMediaBackendFs2Session *session;
	gchar *participant;
	FsStream *stream;

	GstElement *src;
	GstElement *tee;
	GstElement *volume;
	GstElement *level;

	GList *local_candidates;
	GList *remote_candidates;

	guint connected_cb_id;
};

struct _PurpleMediaBackendFs2Session
{
	PurpleMediaBackendFs2 *backend;
	gchar *id;
	FsSession *session;

	GstElement *src;
	GstElement *tee;

	PurpleMediaSessionType type;
};

struct _PurpleMediaBackendFs2Private
{
	PurpleMedia *media;
	GstElement *confbin;
	FsConference *conference;
	gchar *conference_type;

	GHashTable *sessions;
	GHashTable *participants;

	GList *streams;
};

enum {
	PROP_0,
	PROP_CONFERENCE_TYPE,
	PROP_MEDIA,
};

static void
purple_media_backend_fs2_init(PurpleMediaBackendFs2 *self)
{
}

static void
purple_media_backend_fs2_dispose(GObject *obj)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(obj);
	GList *iter = NULL;

	purple_debug_info("backend-fs2", "purple_media_backend_fs2_dispose\n");

	if (priv->confbin) {
		GstElement *pipeline;

		pipeline = purple_media_manager_get_pipeline(
				purple_media_get_manager(priv->media));

		gst_element_set_locked_state(priv->confbin, TRUE);
		gst_element_set_state(GST_ELEMENT(priv->confbin),
				GST_STATE_NULL);

		if (pipeline) {
			GstBus *bus;
			gst_bin_remove(GST_BIN(pipeline), priv->confbin);
			bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
			g_signal_handlers_disconnect_matched(G_OBJECT(bus),
					G_SIGNAL_MATCH_FUNC |
					G_SIGNAL_MATCH_DATA,
					0, 0, 0, gst_bus_cb, obj);
			gst_object_unref(bus);
		} else {
			purple_debug_warning("backend-fs2", "Unable to "
					"properly dispose the conference. "
					"Couldn't get the pipeline.\n");
		}

		priv->confbin = NULL;
		priv->conference = NULL;

	}

	if (priv->sessions) {
		GList *sessions = g_hash_table_get_values(priv->sessions);

		for (; sessions; sessions =
				g_list_delete_link(sessions, sessions)) {
			PurpleMediaBackendFs2Session *session =
					sessions->data;

			if (session->session) {
				g_object_unref(session->session);
				session->session = NULL;
			}
		}
	}

	if (priv->participants) {
		GList *participants =
				g_hash_table_get_values(priv->participants);
		for (; participants; participants = g_list_delete_link(
				participants, participants))
			g_object_unref(participants->data);
		priv->participants = NULL;
	}

	for (iter = priv->streams; iter; iter = g_list_next(iter)) {
		PurpleMediaBackendFs2Stream *stream = iter->data;
		if (stream->stream) {
			g_object_unref(stream->stream);
			stream->stream = NULL;
		}
	}

	if (priv->media) {
		g_object_remove_weak_pointer(G_OBJECT(priv->media),
				(gpointer*)&priv->media);
		priv->media = NULL;
	}

	G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->dispose(obj);
}

static void
purple_media_backend_fs2_finalize(GObject *obj)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(obj);

	purple_debug_info("backend-fs2", "purple_media_backend_fs2_finalize\n");

	g_free(priv->conference_type);

	for (; priv->streams; priv->streams =
			g_list_delete_link(priv->streams, priv->streams)) {
		PurpleMediaBackendFs2Stream *stream = priv->streams->data;

		/* Remove the connected_cb timeout */
		if (stream->connected_cb_id != 0)
			purple_timeout_remove(stream->connected_cb_id);

		g_free(stream->participant);

		if (stream->local_candidates)
			fs_candidate_list_destroy(stream->local_candidates);

		if (stream->remote_candidates)
			fs_candidate_list_destroy(stream->remote_candidates);

		g_free(stream);
	}

	if (priv->sessions) {
		GList *sessions = g_hash_table_get_values(priv->sessions);

		for (; sessions; sessions =
				g_list_delete_link(sessions, sessions)) {
			PurpleMediaBackendFs2Session *session =
					sessions->data;
			g_free(session->id);
			g_free(session);
		}

		g_hash_table_destroy(priv->sessions);
	}

	G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->finalize(obj);
}

static void
purple_media_backend_fs2_set_property(GObject *object, guint prop_id,
		const GValue *value, GParamSpec *pspec)
{
	PurpleMediaBackendFs2Private *priv;
	g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object));

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(object);

	switch (prop_id) {
		case PROP_CONFERENCE_TYPE:
			priv->conference_type = g_value_dup_string(value);
			break;
		case PROP_MEDIA:
			priv->media = g_value_get_object(value);

			if (priv->media == NULL)
				break;

			g_object_add_weak_pointer(G_OBJECT(priv->media),
					(gpointer*)&priv->media);

			g_signal_connect(G_OBJECT(priv->media),
					"state-changed",
					G_CALLBACK(state_changed_cb),
					PURPLE_MEDIA_BACKEND_FS2(object));
			g_signal_connect(G_OBJECT(priv->media), "stream-info",
					G_CALLBACK(stream_info_cb),
					PURPLE_MEDIA_BACKEND_FS2(object));
			break;
		default:	
			G_OBJECT_WARN_INVALID_PROPERTY_ID(
					object, prop_id, pspec);
			break;
	}
}

static void
purple_media_backend_fs2_get_property(GObject *object, guint prop_id,
		GValue *value, GParamSpec *pspec)
{
	PurpleMediaBackendFs2Private *priv;
	g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object));
	
	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(object);

	switch (prop_id) {
		case PROP_CONFERENCE_TYPE:
			g_value_set_string(value, priv->conference_type);
			break;
		case PROP_MEDIA:
			g_value_set_object(value, priv->media);
			break;
		default:	
			G_OBJECT_WARN_INVALID_PROPERTY_ID(
					object, prop_id, pspec);
			break;
	}
}

static void
purple_media_backend_fs2_class_init(PurpleMediaBackendFs2Class *klass)
{
	GObjectClass *gobject_class = (GObjectClass*)klass;

	gobject_class->dispose = purple_media_backend_fs2_dispose;
	gobject_class->finalize = purple_media_backend_fs2_finalize;
	gobject_class->set_property = purple_media_backend_fs2_set_property;
	gobject_class->get_property = purple_media_backend_fs2_get_property;

	g_object_class_override_property(gobject_class, PROP_CONFERENCE_TYPE,
			"conference-type");
	g_object_class_override_property(gobject_class, PROP_MEDIA, "media");

	g_type_class_add_private(klass, sizeof(PurpleMediaBackendFs2Private));
}

static void
purple_media_backend_iface_init(PurpleMediaBackendIface *iface)
{
	iface->add_stream = purple_media_backend_fs2_add_stream;
	iface->add_remote_candidates =
			purple_media_backend_fs2_add_remote_candidates;
	iface->codecs_ready = purple_media_backend_fs2_codecs_ready;
	iface->get_codecs = purple_media_backend_fs2_get_codecs;
	iface->get_local_candidates =
			purple_media_backend_fs2_get_local_candidates;
	iface->set_remote_codecs = purple_media_backend_fs2_set_remote_codecs;
	iface->set_send_codec = purple_media_backend_fs2_set_send_codec;
}

static FsMediaType
session_type_to_fs_media_type(PurpleMediaSessionType type)
{
	if (type & PURPLE_MEDIA_AUDIO)
		return FS_MEDIA_TYPE_AUDIO;
	else if (type & PURPLE_MEDIA_VIDEO)
		return FS_MEDIA_TYPE_VIDEO;
	else
		return 0;
}

static FsStreamDirection
session_type_to_fs_stream_direction(PurpleMediaSessionType type)
{
	if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO ||
			(type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
		return FS_DIRECTION_BOTH;
	else if ((type & PURPLE_MEDIA_SEND_AUDIO) ||
			(type & PURPLE_MEDIA_SEND_VIDEO))
		return FS_DIRECTION_SEND;
	else if ((type & PURPLE_MEDIA_RECV_AUDIO) ||
			(type & PURPLE_MEDIA_RECV_VIDEO))
		return FS_DIRECTION_RECV;
	else
		return FS_DIRECTION_NONE;
}

static PurpleMediaSessionType
session_type_from_fs(FsMediaType type, FsStreamDirection direction)
{
	PurpleMediaSessionType result = PURPLE_MEDIA_NONE;
	if (type == FS_MEDIA_TYPE_AUDIO) {
		if (direction & FS_DIRECTION_SEND)
			result |= PURPLE_MEDIA_SEND_AUDIO;
		if (direction & FS_DIRECTION_RECV)
			result |= PURPLE_MEDIA_RECV_AUDIO;
	} else if (type == FS_MEDIA_TYPE_VIDEO) {
		if (direction & FS_DIRECTION_SEND)
			result |= PURPLE_MEDIA_SEND_VIDEO;
		if (direction & FS_DIRECTION_RECV)
			result |= PURPLE_MEDIA_RECV_VIDEO;
	}
	return result;
}

static FsCandidate *
candidate_to_fs(PurpleMediaCandidate *candidate)
{
	FsCandidate *fscandidate;
	gchar *foundation;
	guint component_id;
	gchar *ip;
	guint port;
	gchar *base_ip;
	guint base_port;
	PurpleMediaNetworkProtocol proto;
	guint32 priority;
	PurpleMediaCandidateType type;
	gchar *username;
	gchar *password;
	guint ttl;

	if (candidate == NULL)
		return NULL;

	g_object_get(G_OBJECT(candidate),
			"foundation", &foundation,
			"component-id", &component_id,
			"ip", &ip,
			"port", &port,
			"base-ip", &base_ip,
			"base-port", &base_port,
			"protocol", &proto,
			"priority", &priority,
			"type", &type,
			"username", &username,
			"password", &password,
			"ttl", &ttl,
			NULL);

	fscandidate = fs_candidate_new(foundation,
			component_id, type,
			proto, ip, port);

	fscandidate->base_ip = base_ip;
	fscandidate->base_port = base_port;
	fscandidate->priority = priority;
	fscandidate->username = username;
	fscandidate->password = password;
	fscandidate->ttl = ttl;

	g_free(foundation);
	g_free(ip);
	return fscandidate;
}

static GList *
candidate_list_to_fs(GList *candidates)
{
	GList *new_list = NULL;

	for (; candidates; candidates = g_list_next(candidates)) {
		new_list = g_list_prepend(new_list,
				candidate_to_fs(candidates->data));
	}

	new_list = g_list_reverse(new_list);
	return new_list;
}

static PurpleMediaCandidate *
candidate_from_fs(FsCandidate *fscandidate)
{
	PurpleMediaCandidate *candidate;

	if (fscandidate == NULL)
		return NULL;

	candidate = purple_media_candidate_new(fscandidate->foundation,
		fscandidate->component_id, fscandidate->type,
		fscandidate->proto, fscandidate->ip, fscandidate->port);
	g_object_set(candidate,
			"base-ip", fscandidate->base_ip,
			"base-port", fscandidate->base_port,
			"priority", fscandidate->priority,
			"username", fscandidate->username,
			"password", fscandidate->password,
			"ttl", fscandidate->ttl, NULL);
	return candidate;
}

static GList *
candidate_list_from_fs(GList *candidates)
{
	GList *new_list = NULL;

	for (; candidates; candidates = g_list_next(candidates)) {
		new_list = g_list_prepend(new_list,
			candidate_from_fs(candidates->data));
	}

	new_list = g_list_reverse(new_list);
	return new_list;
}

static FsCodec *
codec_to_fs(const PurpleMediaCodec *codec)
{
	FsCodec *new_codec;
	gint id;
	char *encoding_name;
	PurpleMediaSessionType media_type;
	guint clock_rate;
	guint channels;
	GList *iter;

	if (codec == NULL)
		return NULL;

	g_object_get(G_OBJECT(codec),
			"id", &id,
			"encoding-name", &encoding_name,
			"media-type", &media_type,
			"clock-rate", &clock_rate,
			"channels", &channels,
			"optional-params", &iter,
			NULL);

	new_codec = fs_codec_new(id, encoding_name,
			session_type_to_fs_media_type(media_type),
			clock_rate);
	new_codec->channels = channels;

	for (; iter; iter = g_list_next(iter)) {
		PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data;
		fs_codec_add_optional_parameter(new_codec,
				param->key, param->value);
	}

	g_free(encoding_name);
	return new_codec;
}

static PurpleMediaCodec *
codec_from_fs(const FsCodec *codec)
{
	PurpleMediaCodec *new_codec;
	GList *iter;

	if (codec == NULL)
		return NULL;

	new_codec = purple_media_codec_new(codec->id, codec->encoding_name,
			session_type_from_fs(codec->media_type,
			FS_DIRECTION_BOTH), codec->clock_rate);
	g_object_set(new_codec, "channels", codec->channels, NULL);

	for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
		FsCodecParameter *param = (FsCodecParameter*)iter->data;
		purple_media_codec_add_optional_parameter(new_codec,
				param->name, param->value);
	}

	return new_codec;
}

static GList *
codec_list_from_fs(GList *codecs)
{
	GList *new_list = NULL;

	for (; codecs; codecs = g_list_next(codecs)) {
		new_list = g_list_prepend(new_list,
				codec_from_fs(codecs->data));
	}

	new_list = g_list_reverse(new_list);
	return new_list;
}

static GList *
codec_list_to_fs(GList *codecs)
{
	GList *new_list = NULL;

	for (; codecs; codecs = g_list_next(codecs)) {
		new_list = g_list_prepend(new_list,
				codec_to_fs(codecs->data));
	}

	new_list = g_list_reverse(new_list);
	return new_list;
}

static PurpleMediaBackendFs2Session *
get_session(PurpleMediaBackendFs2 *self, const gchar *sess_id)
{
	PurpleMediaBackendFs2Private *priv;
	PurpleMediaBackendFs2Session *session = NULL;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);

	if (priv->sessions != NULL)
		session = g_hash_table_lookup(priv->sessions, sess_id);

	return session;
}

static FsParticipant *
get_participant(PurpleMediaBackendFs2 *self, const gchar *name)
{
	PurpleMediaBackendFs2Private *priv;
	FsParticipant *participant = NULL;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);

	if (priv->participants != NULL)
		participant = g_hash_table_lookup(priv->participants, name);

	return participant;
}

static PurpleMediaBackendFs2Stream *
get_stream(PurpleMediaBackendFs2 *self,
		const gchar *sess_id, const gchar *name)
{
	PurpleMediaBackendFs2Private *priv;
	GList *streams;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	streams = priv->streams;

	for (; streams; streams = g_list_next(streams)) {
		PurpleMediaBackendFs2Stream *stream = streams->data;
		if (!strcmp(stream->session->id, sess_id) &&
				!strcmp(stream->participant, name))
			return stream;
	}

	return NULL;
}

static GList *
get_streams(PurpleMediaBackendFs2 *self,
		const gchar *sess_id, const gchar *name)
{
	PurpleMediaBackendFs2Private *priv;
	GList *streams, *ret = NULL;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	streams = priv->streams;

	for (; streams; streams = g_list_next(streams)) {
		PurpleMediaBackendFs2Stream *stream = streams->data;

		if (sess_id != NULL && strcmp(stream->session->id, sess_id))
			continue;
		else if (name != NULL && strcmp(stream->participant, name))
			continue;
		else
			ret = g_list_prepend(ret, stream);
	}

	ret = g_list_reverse(ret);
	return ret;
}

static PurpleMediaBackendFs2Session *
get_session_from_fs_stream(PurpleMediaBackendFs2 *self, FsStream *stream)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	FsSession *fssession;
	GList *values;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
	g_return_val_if_fail(FS_IS_STREAM(stream), NULL);

	g_object_get(stream, "session", &fssession, NULL);

	values = g_hash_table_get_values(priv->sessions);

	for (; values; values = g_list_delete_link(values, values)) {
		PurpleMediaBackendFs2Session *session = values->data;

		if (session->session == fssession) {
			g_list_free(values);
			g_object_unref(fssession);
			return session;
		}
	}

	g_object_unref(fssession);
	return NULL;
}

static void
gst_handle_message_element(GstBus *bus, GstMessage *msg,
		PurpleMediaBackendFs2 *self)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
	static guint level_id = 0;

	if (level_id == 0)
		level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA);

	if (g_signal_has_handler_pending(priv->media, level_id, 0, FALSE)
			&& gst_structure_has_name(
			gst_message_get_structure(msg), "level")) {
		GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
		gchar *name;
		gchar *participant = NULL;
		PurpleMediaBackendFs2Session *session = NULL;
		gdouble rms_db;
		gdouble percent;
		const GValue *list;
		const GValue *value;

		if (!PURPLE_IS_MEDIA(priv->media) ||
				GST_ELEMENT_PARENT(src) != priv->confbin)
			return;

		name = gst_element_get_name(src);

		if (!strncmp(name, "sendlevel_", 10)) {
			session = get_session(self, name+10);
		} else {
			GList *iter = priv->streams;
			PurpleMediaBackendFs2Stream *stream;
			for (; iter; iter = g_list_next(iter)) {
				stream = iter->data;
				if (stream->level == src) {
					session = stream->session;
					participant = stream->participant;
					break;
				}
			}
		}

		g_free(name);

		if (!session)
			return;

		list = gst_structure_get_value(
				gst_message_get_structure(msg), "rms");
		value = gst_value_list_get_value(list, 0);
		rms_db = g_value_get_double(value);
		percent = pow(10, rms_db / 20) * 5;

		if(percent > 1.0)
			percent = 1.0;

		g_signal_emit(priv->media, level_id, 0,
				session->id, participant, percent);
		return;
	}

	if (!FS_IS_CONFERENCE(src) || !PURPLE_IS_MEDIA_BACKEND(self) ||
			priv->conference != FS_CONFERENCE(src))
		return;

	if (gst_structure_has_name(msg->structure, "farsight-error")) {
		FsError error_no;
		gst_structure_get_enum(msg->structure, "error-no",
				FS_TYPE_ERROR, (gint*)&error_no);
		switch (error_no) {
			case FS_ERROR_NO_CODECS:
				purple_media_error(priv->media, _("No codecs"
						" found. Install some"
						" GStreamer codecs found"
						" in GStreamer plugins"
						" packages."));
				purple_media_end(priv->media, NULL, NULL);
				break;
			case FS_ERROR_NO_CODECS_LEFT:
				purple_media_error(priv->media, _("No codecs"
						" left. Your codec"
						" preferences in"
						" fs-codecs.conf are too"
						" strict."));
				purple_media_end(priv->media, NULL, NULL);
				break;
			case FS_ERROR_UNKNOWN_CNAME:
			/*
			 * Unknown CName is only a problem for the
			 * multicast transmitter which isn't used.
			 * It is also deprecated.
			 */
				break;
			default:
				purple_debug_error("backend-fs2",
						"farsight-error: %i: %s\n",
						error_no,
					  	gst_structure_get_string(
						msg->structure, "error-msg"));
				break;
		}

		if (FS_ERROR_IS_FATAL(error_no)) {
			purple_media_error(priv->media, _("A non-recoverable "
					"Farsight2 error has occurred."));
			purple_media_end(priv->media, NULL, NULL);
		}
	} else if (gst_structure_has_name(msg->structure,
			"farsight-new-local-candidate")) {
		const GValue *value;
		FsStream *stream;
		FsCandidate *local_candidate;
		PurpleMediaCandidate *candidate;
		FsParticipant *participant;
		PurpleMediaBackendFs2Session *session;
		PurpleMediaBackendFs2Stream *media_stream;
		gchar *name;

		value = gst_structure_get_value(msg->structure, "stream");
		stream = g_value_get_object(value);
		value = gst_structure_get_value(msg->structure, "candidate");
		local_candidate = g_value_get_boxed(value);

		session = get_session_from_fs_stream(self, stream);

		purple_debug_info("backend-fs2",
				"got new local candidate: %s\n",
				local_candidate->foundation);

		g_object_get(stream, "participant", &participant, NULL);
		g_object_get(participant, "cname", &name, NULL);
		g_object_unref(participant);

		media_stream = get_stream(self, session->id, name);
		media_stream->local_candidates = g_list_append(
				media_stream->local_candidates,
				fs_candidate_copy(local_candidate));

		candidate = candidate_from_fs(local_candidate);
		g_signal_emit_by_name(self, "new-candidate",
				session->id, name, candidate);
		g_object_unref(candidate);
	} else if (gst_structure_has_name(msg->structure,
			"farsight-local-candidates-prepared")) {
		const GValue *value;
		FsStream *stream;
		FsParticipant *participant;
		PurpleMediaBackendFs2Session *session;
		gchar *name;

		value = gst_structure_get_value(msg->structure, "stream");
		stream = g_value_get_object(value);
		session = get_session_from_fs_stream(self, stream);

		g_object_get(stream, "participant", &participant, NULL);
		g_object_get(participant, "cname", &name, NULL);
		g_object_unref(participant);

		g_signal_emit_by_name(self, "candidates-prepared",
				session->id, name);
	} else if (gst_structure_has_name(msg->structure,
			"farsight-new-active-candidate-pair")) {
		const GValue *value;
		FsStream *stream;
		FsCandidate *local_candidate;
		FsCandidate *remote_candidate;
		FsParticipant *participant;
		PurpleMediaBackendFs2Session *session;
		PurpleMediaCandidate *lcandidate, *rcandidate;
		gchar *name;

		value = gst_structure_get_value(msg->structure, "stream");
		stream = g_value_get_object(value);
		value = gst_structure_get_value(msg->structure,
				"local-candidate");
		local_candidate = g_value_get_boxed(value);
		value = gst_structure_get_value(msg->structure,
				"remote-candidate");
		remote_candidate = g_value_get_boxed(value);

		g_object_get(stream, "participant", &participant, NULL);
		g_object_get(participant, "cname", &name, NULL);
		g_object_unref(participant);

		session = get_session_from_fs_stream(self, stream);

		lcandidate = candidate_from_fs(local_candidate);
		rcandidate = candidate_from_fs(remote_candidate);

		g_signal_emit_by_name(self, "active-candidate-pair",
				session->id, name, lcandidate, rcandidate);

		g_object_unref(lcandidate);
		g_object_unref(rcandidate);
	} else if (gst_structure_has_name(msg->structure,
			"farsight-recv-codecs-changed")) {
		const GValue *value;
		GList *codecs;
		FsCodec *codec;

		value = gst_structure_get_value(msg->structure, "codecs");
		codecs = g_value_get_boxed(value);
		codec = codecs->data;

		purple_debug_info("backend-fs2",
				"farsight-recv-codecs-changed: %s\n",
				codec->encoding_name);
	} else if (gst_structure_has_name(msg->structure,
			"farsight-component-state-changed")) {
		const GValue *value;
		FsStreamState fsstate;
		guint component;
		const gchar *state;

		value = gst_structure_get_value(msg->structure, "state");
		fsstate = g_value_get_enum(value);
		value = gst_structure_get_value(msg->structure, "component");
		component = g_value_get_uint(value);

		switch (fsstate) {
			case FS_STREAM_STATE_FAILED:
				state = "FAILED";
				break;
			case FS_STREAM_STATE_DISCONNECTED:
				state = "DISCONNECTED";
				break;
			case FS_STREAM_STATE_GATHERING:
				state = "GATHERING";
				break;
			case FS_STREAM_STATE_CONNECTING:
				state = "CONNECTING";
				break;
			case FS_STREAM_STATE_CONNECTED:
				state = "CONNECTED";
				break;
			case FS_STREAM_STATE_READY:
				state = "READY";
				break;
			default:
				state = "UNKNOWN";
				break;
		}

		purple_debug_info("backend-fs2",
				"farsight-component-state-changed: "
				"component: %u state: %s\n",
				component, state);
	} else if (gst_structure_has_name(msg->structure,
			"farsight-send-codec-changed")) {
		const GValue *value;
		FsCodec *codec;
		gchar *codec_str;

		value = gst_structure_get_value(msg->structure, "codec");
		codec = g_value_get_boxed(value);
		codec_str = fs_codec_to_string(codec);

		purple_debug_info("backend-fs2",
				"farsight-send-codec-changed: codec: %s\n",
				codec_str);

		g_free(codec_str);
	} else if (gst_structure_has_name(msg->structure,
			"farsight-codecs-changed")) {
		const GValue *value;
		FsSession *fssession;
		GList *sessions;

		value = gst_structure_get_value(msg->structure, "session");
		fssession = g_value_get_object(value);
		sessions = g_hash_table_get_values(priv->sessions);

		for (; sessions; sessions =
				g_list_delete_link(sessions, sessions)) {
			PurpleMediaBackendFs2Session *session = sessions->data;
			gchar *session_id;

			if (session->session != fssession)
				continue;

			session_id = g_strdup(session->id);
			g_signal_emit_by_name(self, "codecs-changed",
					session_id);
			g_free(session_id);
			g_list_free(sessions);
			break;
		}
	}
}

static void
gst_handle_message_error(GstBus *bus, GstMessage *msg,
		PurpleMediaBackendFs2 *self)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg));
	GstElement *lastElement = NULL;
	GList *sessions;

	while (!GST_IS_PIPELINE(element)) {
		if (element == priv->confbin)
			break;

		lastElement = element;
		element = GST_ELEMENT_PARENT(element);
	}

	if (!GST_IS_PIPELINE(element))
		return;

	sessions = purple_media_get_session_ids(priv->media);

	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
		if (purple_media_get_src(priv->media, sessions->data)
				!= lastElement)
			continue;

		if (purple_media_get_session_type(priv->media, sessions->data)
				& PURPLE_MEDIA_AUDIO)
			purple_media_error(priv->media,
					_("Error with your microphone"));
		else
			purple_media_error(priv->media,
					_("Error with your webcam"));

		break;
	}

	g_list_free(sessions);

	purple_media_error(priv->media, _("Conference error"));
	purple_media_end(priv->media, NULL, NULL);
}

static gboolean
gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self)
{
	switch(GST_MESSAGE_TYPE(msg)) {
		case GST_MESSAGE_ELEMENT:
			gst_handle_message_element(bus, msg, self);
			break;
		case GST_MESSAGE_ERROR:
			gst_handle_message_error(bus, msg, self);
			break;
		default:
			break;
	}

	return TRUE;
}

static void
state_changed_cb(PurpleMedia *media, PurpleMediaState state,
		gchar *sid, gchar *name, PurpleMediaBackendFs2 *self)
{
}

static void
stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
		gchar *sid, gchar *name, gboolean local,
		PurpleMediaBackendFs2 *self)
{
	if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) {
		PurpleMediaBackendFs2Stream *stream =
				get_stream(self, sid, name);
		GError *err = NULL;

		g_object_set(G_OBJECT(stream->stream), "direction",
				session_type_to_fs_stream_direction(
				stream->session->type), NULL);

		if (stream->remote_candidates == NULL ||
				purple_media_is_initiator(media, sid, name))
			return;

		fs_stream_set_remote_candidates(stream->stream,
				stream->remote_candidates, &err);

		if (err == NULL)
			return;

		purple_debug_error("backend-fs2", "Error adding "
				"remote candidates: %s\n",
				err->message);
		g_error_free(err);
	} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE ||
			type == PURPLE_MEDIA_INFO_UNMUTE)) {
		PurpleMediaBackendFs2Private *priv =
				PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
		gboolean active = (type == PURPLE_MEDIA_INFO_MUTE);
		GList *sessions;

		if (sid == NULL)
			sessions = g_hash_table_get_values(priv->sessions);
		else
			sessions = g_list_prepend(NULL,
					get_session(self, sid));

		purple_debug_info("media", "Turning mute %s\n",
				active ? "on" : "off");

		for (; sessions; sessions = g_list_delete_link(
				sessions, sessions)) {
			PurpleMediaBackendFs2Session *session =
					sessions->data;

			if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
				gchar *name = g_strdup_printf("volume_%s",
						session->id);
				GstElement *volume = gst_bin_get_by_name(
						GST_BIN(priv->confbin), name);
				g_free(name);
				g_object_set(volume, "mute", active, NULL);
			}
		}
	} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE ||
			type == PURPLE_MEDIA_INFO_UNPAUSE)) {
		gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE);
		GList *streams = get_streams(self, sid, name);
		for (; streams; streams =
				g_list_delete_link(streams, streams)) {
			PurpleMediaBackendFs2Stream *stream = streams->data;
			if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
				g_object_set(stream->stream, "direction",
						session_type_to_fs_stream_direction(
						stream->session->type & ((active) ?
						~PURPLE_MEDIA_SEND_VIDEO :
						PURPLE_MEDIA_VIDEO)), NULL);
			}
		}
	}
}

static gboolean
init_conference(PurpleMediaBackendFs2 *self)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	GstElement *pipeline;
	GstBus *bus;
	gchar *name;

	priv->conference = FS_CONFERENCE(
			gst_element_factory_make(priv->conference_type, NULL));

	if (priv->conference == NULL) {
		purple_debug_error("backend-fs2", "Conference == NULL\n");
		return FALSE;
	}

	pipeline = purple_media_manager_get_pipeline(
			purple_media_get_manager(priv->media));

	if (pipeline == NULL) {
		purple_debug_error("backend-fs2",
				"Couldn't retrieve pipeline.\n");
		return FALSE;
	}

	name = g_strdup_printf("conf_%p", priv->conference);
	priv->confbin = gst_bin_new(name);
	if (priv->confbin == NULL) {
		purple_debug_error("backend-fs2",
				"Couldn't create confbin.\n");
		return FALSE;
	}

	g_free(name);

	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
	if (bus == NULL) {
		purple_debug_error("backend-fs2",
				"Couldn't get the pipeline's bus.\n");
		return FALSE;
	}

	g_signal_connect(G_OBJECT(bus), "message",
			G_CALLBACK(gst_bus_cb), self);
	gst_object_unref(bus);

	if (!gst_bin_add(GST_BIN(pipeline),
			GST_ELEMENT(priv->confbin))) {
		purple_debug_error("backend-fs2", "Couldn't add confbin "
				"element to the pipeline\n");
		return FALSE;
	}

	if (!gst_bin_add(GST_BIN(priv->confbin),
			GST_ELEMENT(priv->conference))) {
		purple_debug_error("backend-fs2", "Couldn't add conference "
				"element to the confbin\n");
		return FALSE;
	}

	if (gst_element_set_state(GST_ELEMENT(priv->confbin),
			GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
		purple_debug_error("backend-fs2",
				"Failed to start conference.\n");
		return FALSE;
	}

	return TRUE;
}

static void
gst_element_added_cb(FsElementAddedNotifier *self,
		GstBin *bin, GstElement *element, gpointer user_data)
{
	/*
	 * Hack to make H264 work with Gmail video.
	 */
	if (!strncmp(GST_ELEMENT_NAME(element), "x264", 4)) {
		g_object_set(GST_OBJECT(element), "cabac", FALSE, NULL);
	}
}

static gboolean
create_src(PurpleMediaBackendFs2 *self, const gchar *sess_id,
		PurpleMediaSessionType type)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	PurpleMediaBackendFs2Session *session;
	PurpleMediaSessionType session_type;
	FsMediaType media_type = session_type_to_fs_media_type(type);
	FsStreamDirection type_direction =
			session_type_to_fs_stream_direction(type);
	GstElement *src;
	GstPad *sinkpad, *srcpad;

	if ((type_direction & FS_DIRECTION_SEND) == 0)
		return TRUE;
 
	session_type = session_type_from_fs(
			media_type, FS_DIRECTION_SEND);
	src = purple_media_manager_get_element(
			purple_media_get_manager(priv->media),
			session_type, priv->media, sess_id, NULL);

	if (!GST_IS_ELEMENT(src)) {
		purple_debug_error("backend-fs2",
				"Error creating src for session %s\n",
				sess_id);
		return FALSE;
	}

	session = get_session(self, sess_id);

	if (session == NULL) {
		purple_debug_warning("backend-fs2",
				"purple_media_set_src: trying to set"
				" src on non-existent session\n");
		return FALSE;
	}

	if (session->src)
		gst_object_unref(session->src);

	session->src = src;
	gst_element_set_locked_state(session->src, TRUE);

	session->tee = gst_element_factory_make("tee", NULL);
	gst_bin_add(GST_BIN(priv->confbin), session->tee);

	/* This supposedly isn't necessary, but it silences some warnings */
	if (GST_ELEMENT_PARENT(priv->confbin)
			== GST_ELEMENT_PARENT(session->src)) {
		GstPad *pad = gst_element_get_static_pad(session->tee, "sink");
		GstPad *ghost = gst_ghost_pad_new(NULL, pad);
		gst_object_unref(pad);
		gst_pad_set_active(ghost, TRUE);
		gst_element_add_pad(priv->confbin, ghost);
	}

	gst_element_set_state(session->tee, GST_STATE_PLAYING);
	gst_element_link(session->src, priv->confbin);

	g_object_get(session->session, "sink-pad", &sinkpad, NULL);
	if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
		gchar *name = g_strdup_printf("volume_%s", session->id);
		GstElement *level;
		GstElement *volume = gst_element_factory_make("volume", name);
		double input_volume = purple_prefs_get_int(
				"/purple/media/audio/volume/input")/10.0;
		g_free(name);
		name = g_strdup_printf("sendlevel_%s", session->id);
		level = gst_element_factory_make("level", name);
		g_free(name);
		gst_bin_add(GST_BIN(priv->confbin), volume);
		gst_bin_add(GST_BIN(priv->confbin), level);
		gst_element_set_state(level, GST_STATE_PLAYING);
		gst_element_set_state(volume, GST_STATE_PLAYING);
		gst_element_link(volume, level);
		gst_element_link(session->tee, volume);
		srcpad = gst_element_get_static_pad(level, "src");
		g_object_set(volume, "volume", input_volume, NULL);
	} else {
		srcpad = gst_element_get_request_pad(session->tee, "src%d");
	}

	purple_debug_info("backend-fs2", "connecting pad: %s\n", 
			  gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
			  ? "success" : "failure");
	gst_element_set_locked_state(session->src, FALSE);
	gst_object_unref(session->src);

	gst_element_set_state(session->src, GST_STATE_PLAYING);

	purple_media_manager_create_output_window(purple_media_get_manager(
			priv->media), priv->media, sess_id, NULL);

	return TRUE;
}

static gboolean
create_session(PurpleMediaBackendFs2 *self, const gchar *sess_id,
		PurpleMediaSessionType type, gboolean initiator,
		const gchar *transmitter)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	PurpleMediaBackendFs2Session *session;
	GError *err = NULL;
	GList *codec_conf = NULL, *iter = NULL;
	gchar *filename = NULL;
	gboolean is_nice = !strcmp(transmitter, "nice");

	session = g_new0(PurpleMediaBackendFs2Session, 1);

	session->session = fs_conference_new_session(priv->conference,
			session_type_to_fs_media_type(type), &err);

	if (err != NULL) {
		purple_media_error(priv->media,
				_("Error creating session: %s"),
				err->message);
		g_error_free(err);
		g_free(session);
		return FALSE;
	}

	filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
	codec_conf = fs_codec_list_from_keyfile(filename, &err);
	g_free(filename);

	if (err != NULL) {
		if (err->code == 4)
			purple_debug_info("backend-fs2", "Couldn't read "
					"fs-codec.conf: %s\n",
					err->message);
		else
			purple_debug_error("backend-fs2", "Error reading "
					"fs-codec.conf: %s\n",
					err->message);
		g_error_free(err);
	}

	/*
	 * Add SPEEX if the configuration file doesn't exist or
	 * there isn't a speex entry.
	 */
	for (iter = codec_conf; iter; iter = g_list_next(iter)) {
		FsCodec *codec = iter->data;
		if (!g_ascii_strcasecmp(codec->encoding_name, "speex"))
			break;
	}

	if (iter == NULL) {
		codec_conf = g_list_prepend(codec_conf,
				fs_codec_new(FS_CODEC_ID_ANY,
				"SPEEX", FS_MEDIA_TYPE_AUDIO, 8000));
		codec_conf = g_list_prepend(codec_conf,
				fs_codec_new(FS_CODEC_ID_ANY,
				"SPEEX", FS_MEDIA_TYPE_AUDIO, 16000));
	}

	fs_session_set_codec_preferences(session->session, codec_conf, NULL);
	fs_codec_list_destroy(codec_conf);

	/*
	 * Removes a 5-7 second delay before
	 * receiving the src-pad-added signal.
	 * Only works for non-multicast FsRtpSessions.
	 */
	if (is_nice || !strcmp(transmitter, "rawudp"))
		g_object_set(G_OBJECT(session->session),
				"no-rtcp-timeout", 0, NULL);

	/*
	 * Hack to make x264 work with Gmail video.
	 */
	if (is_nice && !strcmp(sess_id, "google-video")) {
		FsElementAddedNotifier *notifier =
				fs_element_added_notifier_new();
		g_signal_connect(G_OBJECT(notifier), "element-added",
				G_CALLBACK(gst_element_added_cb), NULL);
		fs_element_added_notifier_add(notifier,
				GST_BIN(priv->conference));
	}

	session->id = g_strdup(sess_id);
	session->backend = self;
	session->type = type;

	if (!priv->sessions) {
		purple_debug_info("backend-fs2",
				"Creating hash table for sessions\n");
		priv->sessions = g_hash_table_new(g_str_hash, g_str_equal);
	}

	g_hash_table_insert(priv->sessions, g_strdup(session->id), session);

	if (!create_src(self, sess_id, type)) {
		purple_debug_info("backend-fs2", "Error creating the src\n");
		return FALSE;
	}

	return TRUE;
}

static gboolean
create_participant(PurpleMediaBackendFs2 *self, const gchar *name)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	FsParticipant *participant;
	GError *err = NULL;

	participant = fs_conference_new_participant(
			priv->conference, name, &err);

	if (err) {
		purple_debug_error("backend-fs2",
				"Error creating participant: %s\n",
				err->message);
		g_error_free(err);
		return FALSE;
	}

	if (!priv->participants) {
		purple_debug_info("backend-fs2",
				"Creating hash table for participants\n");
		priv->participants = g_hash_table_new_full(g_str_hash,
				g_str_equal, g_free, NULL);
	}

	g_hash_table_insert(priv->participants, g_strdup(name), participant);

	return TRUE;
}

static gboolean
src_pad_added_cb_cb(PurpleMediaBackendFs2Stream *stream)
{
	PurpleMediaBackendFs2Private *priv;

	g_return_val_if_fail(stream != NULL, FALSE);

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(stream->session->backend);
	stream->connected_cb_id = 0;

	purple_media_manager_create_output_window(
			purple_media_get_manager(priv->media), priv->media,
			stream->session->id, stream->participant);

	g_signal_emit_by_name(priv->media, "state-changed",
			PURPLE_MEDIA_STATE_CONNECTED,
			stream->session->id, stream->participant);
	return FALSE;
}

static void
src_pad_added_cb(FsStream *fsstream, GstPad *srcpad,
		FsCodec *codec, PurpleMediaBackendFs2Stream *stream)
{
	PurpleMediaBackendFs2Private *priv;
	GstPad *sinkpad;

	g_return_if_fail(FS_IS_STREAM(fsstream));
	g_return_if_fail(stream != NULL);

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(stream->session->backend);

	if (stream->src == NULL) {
		GstElement *sink = NULL;

		if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
			GstElement *queue = NULL;
			double output_volume = purple_prefs_get_int(
					"/purple/media/audio/volume/output")/10.0;
			/*
			 * Should this instead be:
			 *  audioconvert ! audioresample ! liveadder !
			 *   audioresample ! audioconvert ! realsink
			 */
			queue = gst_element_factory_make("queue", NULL);
			stream->volume = gst_element_factory_make(
					"volume", NULL);
			g_object_set(stream->volume, "volume",
					output_volume, NULL);
			stream->level = gst_element_factory_make(
					"level", NULL);
			stream->src = gst_element_factory_make(
					"liveadder", NULL);
			sink = purple_media_manager_get_element(
					purple_media_get_manager(priv->media),
					PURPLE_MEDIA_RECV_AUDIO, priv->media,
					stream->session->id,
					stream->participant);
			gst_bin_add(GST_BIN(priv->confbin), queue);
			gst_bin_add(GST_BIN(priv->confbin), stream->volume);
			gst_bin_add(GST_BIN(priv->confbin), stream->level);
			gst_bin_add(GST_BIN(priv->confbin), sink);
			gst_element_set_state(sink, GST_STATE_PLAYING);
			gst_element_set_state(stream->level, GST_STATE_PLAYING);
			gst_element_set_state(stream->volume, GST_STATE_PLAYING);
			gst_element_set_state(queue, GST_STATE_PLAYING);
			gst_element_link(stream->level, sink);
			gst_element_link(stream->volume, stream->level);
			gst_element_link(queue, stream->volume);
			sink = queue;
		} else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) {
			stream->src = gst_element_factory_make(
					"fsfunnel", NULL);
			sink = gst_element_factory_make(
					"fakesink", NULL);
			g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
			gst_bin_add(GST_BIN(priv->confbin), sink);
			gst_element_set_state(sink, GST_STATE_PLAYING);
		}
		stream->tee = gst_element_factory_make("tee", NULL);
		gst_bin_add_many(GST_BIN(priv->confbin),
				stream->src, stream->tee, NULL);
		gst_element_set_state(stream->tee, GST_STATE_PLAYING);
		gst_element_set_state(stream->src, GST_STATE_PLAYING);
		gst_element_link_many(stream->src, stream->tee, sink, NULL);
	}

	sinkpad = gst_element_get_request_pad(stream->src, "sink%d");
	gst_pad_link(srcpad, sinkpad);
	gst_object_unref(sinkpad);

	stream->connected_cb_id = purple_timeout_add(0,
			(GSourceFunc)src_pad_added_cb_cb, stream);
}

static gboolean
create_stream(PurpleMediaBackendFs2 *self,
		const gchar *sess_id, const gchar *who,
		PurpleMediaSessionType type, gboolean initiator,
		const gchar *transmitter,
		guint num_params, GParameter *params)
{
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	GError *err = NULL;
	FsStream *fsstream = NULL;
	const gchar *stun_ip = purple_network_get_stun_ip();
	const gchar *turn_ip = purple_network_get_turn_ip();
	guint _num_params = num_params;
	GParameter *_params = g_new0(GParameter, num_params + 3);
	FsStreamDirection type_direction =
			session_type_to_fs_stream_direction(type);
	PurpleMediaBackendFs2Session *session;
	PurpleMediaBackendFs2Stream *stream;
	FsParticipant *participant;

	memcpy(_params, params, sizeof(GParameter) * num_params);

	/* set the controlling mode parameter */
	_params[_num_params].name = "controlling-mode";
	g_value_init(&_params[_num_params].value, G_TYPE_BOOLEAN);
	g_value_set_boolean(&_params[_num_params].value, initiator);
	++_num_params;

	if (stun_ip) {
		purple_debug_info("backend-fs2", 
			"Setting stun-ip on new stream: %s\n", stun_ip);

		_params[_num_params].name = "stun-ip";
		g_value_init(&_params[_num_params].value, G_TYPE_STRING);
		g_value_set_string(&_params[_num_params].value, stun_ip);
		++_num_params;
	}

	if (turn_ip && !strcmp("nice", transmitter)) {
		GValueArray *relay_info = g_value_array_new(0);
		GValue value;
		gint turn_port = purple_prefs_get_int(
				"/purple/network/turn_port");
		const gchar *username =	purple_prefs_get_string(
				"/purple/network/turn_username");
		const gchar *password = purple_prefs_get_string(
				"/purple/network/turn_password");
		GstStructure *turn_setup = gst_structure_new("relay-info",
				"ip", G_TYPE_STRING, turn_ip, 
				"port", G_TYPE_UINT, turn_port,
				"username", G_TYPE_STRING, username,
				"password", G_TYPE_STRING, password,
				NULL);

		if (!turn_setup) {
			purple_debug_error("backend-fs2",
					"Error creating relay info structure");
			return FALSE;
		}

		memset(&value, 0, sizeof(GValue));
		g_value_init(&value, GST_TYPE_STRUCTURE);
		gst_value_set_structure(&value, turn_setup);
		relay_info = g_value_array_append(relay_info, &value);
		gst_structure_free(turn_setup);

		purple_debug_info("backend-fs2",
			"Setting relay-info on new stream\n");
		_params[_num_params].name = "relay-info";
		g_value_init(&_params[_num_params].value, 
			G_TYPE_VALUE_ARRAY);
		g_value_set_boxed(&_params[_num_params].value,
			relay_info);
		g_value_array_free(relay_info);
	}

	session = get_session(self, sess_id);

	if (session == NULL) {
		purple_debug_error("backend-fs2",
				"Couldn't find session to create stream.\n");
		return FALSE;
	}

	participant = get_participant(self, who);

	if (participant == NULL) {
		purple_debug_error("backend-fs2", "Couldn't find "
				"participant to create stream.\n");
		return FALSE;
	}

	fsstream = fs_session_new_stream(session->session, participant,
			initiator == TRUE ? type_direction :
			(type_direction & FS_DIRECTION_RECV), transmitter,
			_num_params, _params, &err);
	g_free(_params);

	if (fsstream == NULL) {
		if (err) {
			purple_debug_error("backend-fs2",
					"Error creating stream: %s\n",
					err && err->message ?
					err->message : "NULL");
			g_error_free(err);
		} else
			purple_debug_error("backend-fs2",
					"Error creating stream\n");
		return FALSE;
	}

	stream = g_new0(PurpleMediaBackendFs2Stream, 1);
	stream->participant = g_strdup(who);
	stream->session = session;
	stream->stream = fsstream;

	priv->streams =	g_list_append(priv->streams, stream);

	g_signal_connect(G_OBJECT(fsstream), "src-pad-added",
			G_CALLBACK(src_pad_added_cb), stream);

	return TRUE;
}

static gboolean
purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
		const gchar *sess_id, const gchar *who,
		PurpleMediaSessionType type, gboolean initiator,
		const gchar *transmitter,
		guint num_params, GParameter *params)
{
	PurpleMediaBackendFs2 *backend = PURPLE_MEDIA_BACKEND_FS2(self);
	PurpleMediaBackendFs2Private *priv =
			PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(backend);
	PurpleMediaBackendFs2Stream *stream;

	if (priv->conference == NULL && !init_conference(backend)) {
		purple_debug_error("backend-fs2",
				"Error initializing the conference.\n");
		return FALSE;
	}

	if (get_session(backend, sess_id) == NULL &&
			!create_session(backend, sess_id, type,
			initiator, transmitter)) {
		purple_debug_error("backend-fs2",
				"Error creating the session.\n");
		return FALSE;
	}

	if (get_participant(backend, who) == NULL &&
			!create_participant(backend, who)) {
		purple_debug_error("backend-fs2",
				"Error creating the participant.\n");
		return FALSE;
	}

	stream = get_stream(backend, sess_id, who);

	if (stream != NULL) {
		FsStreamDirection type_direction =
				session_type_to_fs_stream_direction(type);

		if (session_type_to_fs_stream_direction(
				stream->session->type) != type_direction) {
			/* change direction */
			g_object_set(stream->stream, "direction",
					type_direction, NULL);
		}
	} else if (!create_stream(backend, sess_id, who, type,
			initiator, transmitter, num_params, params)) {
		purple_debug_error("backend-fs2",
				"Error creating the stream.\n");
		return FALSE;
	}

	return TRUE;
}

static void
purple_media_backend_fs2_add_remote_candidates(PurpleMediaBackend *self,
		const gchar *sess_id, const gchar *participant,
		GList *remote_candidates)
{
	PurpleMediaBackendFs2Private *priv;
	PurpleMediaBackendFs2Stream *stream;
	GError *err = NULL;

	g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
	stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
			sess_id, participant);

	if (stream == NULL) {
		purple_debug_error("backend-fs2",
				"purple_media_add_remote_candidates: "
				"couldn't find stream %s %s.\n",
				sess_id ? sess_id : "(null)",
				participant ? participant : "(null)");
		return;
	}

	stream->remote_candidates = g_list_concat(stream->remote_candidates,
			candidate_list_to_fs(remote_candidates));

	if (purple_media_is_initiator(priv->media, sess_id, participant) ||
			purple_media_accepted(
			priv->media, sess_id, participant)) {
		fs_stream_set_remote_candidates(stream->stream,
				stream->remote_candidates, &err);

		if (err) {
			purple_debug_error("backend-fs2", "Error adding remote"
					" candidates: %s\n", err->message);
			g_error_free(err);
		}
	}
}

static gboolean
purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
		const gchar *sess_id)
{
	PurpleMediaBackendFs2Private *priv;
	gboolean ret;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);

	if (sess_id != NULL) {
		PurpleMediaBackendFs2Session *session = get_session(
				PURPLE_MEDIA_BACKEND_FS2(self), sess_id);

		if (session == NULL)
			return FALSE;

		if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
				PURPLE_MEDIA_SEND_VIDEO))
			g_object_get(session->session,
					"codecs-ready", &ret, NULL);
		else
			ret = TRUE;
	} else {
		GList *values = g_hash_table_get_values(priv->sessions);

		for (; values; values = g_list_delete_link(values, values)) {
			PurpleMediaBackendFs2Session *session = values->data;
			if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
					PURPLE_MEDIA_SEND_VIDEO))
				g_object_get(session->session,
						"codecs-ready", &ret, NULL);
			else
				ret = TRUE;

			if (ret == FALSE)
				break;
		}

		if (values != NULL)
			g_list_free(values);
	}

	return ret;
}

static GList *
purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
		const gchar *sess_id)
{
	PurpleMediaBackendFs2Private *priv;
	PurpleMediaBackendFs2Session *session;
	GList *fscodecs;
	GList *codecs;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);

	session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);

	if (session == NULL)
		return NULL;

	g_object_get(G_OBJECT(session->session),
		     "codecs", &fscodecs, NULL);
	codecs = codec_list_from_fs(fscodecs);
	fs_codec_list_destroy(fscodecs);

	return codecs;
}

static GList *
purple_media_backend_fs2_get_local_candidates(PurpleMediaBackend *self,
		const gchar *sess_id, const gchar *participant)
{
	PurpleMediaBackendFs2Stream *stream;
	GList *candidates = NULL;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);

	stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
			sess_id, participant);

	if (stream != NULL)
		candidates = candidate_list_from_fs(
				stream->local_candidates);
	return candidates;
}

static gboolean
purple_media_backend_fs2_set_remote_codecs(PurpleMediaBackend *self,
		const gchar *sess_id, const gchar *participant,
		GList *codecs)
{
	PurpleMediaBackendFs2Stream *stream;
	GList *fscodecs;
	GError *err = NULL;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
	stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
			sess_id, participant);

	if (stream == NULL)
		return FALSE;

	fscodecs = codec_list_to_fs(codecs);
	fs_stream_set_remote_codecs(stream->stream, fscodecs, &err);
	fs_codec_list_destroy(fscodecs);

	if (err) {
		purple_debug_error("backend-fs2",
				"Error setting remote codecs: %s\n",
				err->message);
		g_error_free(err);
		return FALSE;
	}

	return TRUE;
}

static gboolean
purple_media_backend_fs2_set_send_codec(PurpleMediaBackend *self,
		const gchar *sess_id, PurpleMediaCodec *codec)
{
	PurpleMediaBackendFs2Session *session;
	FsCodec *fscodec;
	GError *err = NULL;

	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);

	session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);

	if (session == NULL)
		return FALSE;

	fscodec = codec_to_fs(codec);
	fs_session_set_send_codec(session->session, fscodec, &err);
	fs_codec_destroy(fscodec);

	if (err) {
		purple_debug_error("media", "Error setting send codec\n");
		g_error_free(err);
		return FALSE;
	}

	return TRUE;
}
#else
GType
purple_media_backend_fs2_get_type(void)
{
	return G_TYPE_NONE;
}
#endif /* USE_VV */

#ifdef USE_GSTREAMER
GstElement *
purple_media_backend_fs2_get_src(PurpleMediaBackendFs2 *self,
		const gchar *sess_id)
{
#ifdef USE_VV
	PurpleMediaBackendFs2Session *session = get_session(self, sess_id);
	return session != NULL ? session->src : NULL;
#else
	return NULL;
#endif
}

GstElement *
purple_media_backend_fs2_get_tee(PurpleMediaBackendFs2 *self,
		const gchar *sess_id, const gchar *who)
{
#ifdef USE_VV
	if (sess_id != NULL && who == NULL) {
		PurpleMediaBackendFs2Session *session =
				get_session(self, sess_id);
		return (session != NULL) ? session->tee : NULL;
	} else if (sess_id != NULL && who != NULL) {
		PurpleMediaBackendFs2Stream *stream =
				get_stream(self, sess_id, who);
		return (stream != NULL) ? stream->tee : NULL;
	}

#endif /* USE_VV */
	g_return_val_if_reached(NULL);
}

void
purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self,
		const gchar *sess_id, double level)
{
#ifdef USE_VV
	PurpleMediaBackendFs2Private *priv;
	GList *sessions;

	g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);

	purple_prefs_set_int("/purple/media/audio/volume/input", level);

	if (sess_id == NULL)
		sessions = g_hash_table_get_values(priv->sessions);
	else
		sessions = g_list_append(NULL, get_session(self, sess_id));

	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
		PurpleMediaBackendFs2Session *session = sessions->data;

		if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
			gchar *name = g_strdup_printf("volume_%s",
					session->id);
			GstElement *volume = gst_bin_get_by_name(
					GST_BIN(priv->confbin), name);
			g_free(name);
			g_object_set(volume, "volume", level/10.0, NULL);
		}
	}
#endif /* USE_VV */
}

void
purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
		const gchar *sess_id, const gchar *who, double level)
{
#ifdef USE_VV
	PurpleMediaBackendFs2Private *priv;
	GList *streams;

	g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));

	priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);

	purple_prefs_set_int("/purple/media/audio/volume/output", level);

	streams = get_streams(self, sess_id, who);

	for (; streams; streams = g_list_delete_link(streams, streams)) {
		PurpleMediaBackendFs2Stream *stream = streams->data;

		if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO
				&& GST_IS_ELEMENT(stream->volume)) {
			g_object_set(stream->volume, "volume",
					level/10.0, NULL);
		}
	}
#endif /* USE_VV */
}
#endif /* USE_GSTREAMER */