changeset 29632:e4884dbf0c02

propagate from branch 'im.pidgin.cpw.maiku.media_refactor' (head 69be24ee75b8f957cfee3cfb7fd71721078b7f81) to branch 'im.pidgin.pidgin.next.minor' (head 25b1417fdd880468e1d6f027f8f325b4c56e9f02)
author maiku@pidgin.im
date Wed, 11 Nov 2009 03:07:21 +0000
parents f93ac891ff01 (current diff) 1eb68d854dfc (diff)
children 9da83d5f48c3
files ChangeLog.API libpurple/Makefile.am libpurple/dnsquery.c libpurple/dnssrv.c libpurple/media.c libpurple/protocols/jabber/JEPS libpurple/protocols/jabber/jingle/rtp.c po/POTFILES.in
diffstat 22 files changed, 5461 insertions(+), 3693 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Wed Nov 11 02:47:50 2009 +0000
+++ b/ChangeLog.API	Wed Nov 11 03:07:21 2009 +0000
@@ -4,6 +4,10 @@
 	libpurple:
 		Added:
 		* purple_account_get_name_for_display
+		* purple_media_candidate_copy
+		* purple_media_codec_copy
+		* purple_media_manager_get_backend_type
+		* purple_media_manager_set_backend_type
 		* purple_network_get_all_local_system_ips
 		* purple_prpl_got_media_caps
 		* purple_uuid_random
--- a/libpurple/Makefile.am	Wed Nov 11 02:47:50 2009 +0000
+++ b/libpurple/Makefile.am	Wed Nov 11 03:07:21 2009 +0000
@@ -53,7 +53,12 @@
 	idle.c \
 	imgstore.c \
 	log.c \
-	media.c \
+	media/backend-fs2.c \
+	media/backend-iface.c \
+	media/candidate.c \
+	media/codec.c \
+	media/enum-types.c \
+	media/media.c \
 	mediamanager.c \
 	mime.c \
 	nat-pmp.c \
@@ -156,6 +161,12 @@
 	xmlnode.h \
 	whiteboard.h
 
+purple_mediaheaders = \
+	backend-iface.h \
+	candidate.h \
+	codec.h \
+	enum-types.h
+
 purple_builtheaders = purple.h version.h marshallers.h
 
 marshallers.h: marshallers.list
@@ -192,6 +203,7 @@
                 savedstatuses.h smiley.h status.h server.h util.h xmlnode.h prpl.h
 
 purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders)) \
+		$(addprefix $(srcdir)/media/, $(purple_mediaheaders)) \
 		$(purple_builtheaders)
 dbus_build_exported = $(addprefix $(srcdir)/, $(dbus_exported))
 # We should probably make this better
@@ -226,7 +238,7 @@
 purple-client-bindings.c: dbus-analyze-functions.py $(dbus_exported)
 	cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client > $@
 
-purple-client-bindings.h: dbus-analyze-types.py dbus-analyze-functions.py $(purple_coreheaders) $(purple_builtheaders) $(dbus_exported)
+purple-client-bindings.h: dbus-analyze-types.py dbus-analyze-functions.py $(purple_coreheaders) $(addprefix media/, $(purple_mediaheaders)) $(purple_builtheaders) $(dbus_exported)
 	cat $(purple_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --keyword=enum --verbatim > $@
 	cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client --headers >> $@
 
@@ -275,6 +287,7 @@
 
 noinst_HEADERS= \
 	internal.h \
+	media/backend-fs2.h \
 	valgrind.h
 
 libpurpleincludedir=$(includedir)/libpurple
@@ -283,6 +296,10 @@
 	$(purple_builtheaders) \
 	$(dbus_headers)
 
+mediaincludedir=$(includedir)/libpurple/media
+mediainclude_HEADERS = \
+	$(addprefix $(srcdir)/media/, $(purple_mediaheaders))
+
 libpurple_la_DEPENDENCIES = $(STATIC_LINK_LIBS)
 libpurple_la_LDFLAGS = -export-dynamic -version-info $(PURPLE_LT_VERSION_INFO) -no-undefined
 libpurple_la_LIBADD = \
--- a/libpurple/Makefile.mingw	Wed Nov 11 02:47:50 2009 +0000
+++ b/libpurple/Makefile.mingw	Wed Nov 11 03:07:21 2009 +0000
@@ -48,7 +48,12 @@
 			idle.c \
 			imgstore.c \
 			log.c \
-			media.c \
+			media/backend-fs2.c \
+			media/backend-iface.c \
+			media/candidate.c \
+			media/codec.c \
+			media/enum-types.c \
+			media/media.c \
 			mediamanager.c \
 			mime.c \
 			nat-pmp.c \
--- a/libpurple/marshallers.list	Wed Nov 11 02:47:50 2009 +0000
+++ b/libpurple/marshallers.list	Wed Nov 11 03:07:21 2009 +0000
@@ -5,3 +5,4 @@
 VOID:ENUM,STRING,STRING
 VOID:ENUM,STRING,STRING,BOOLEAN
 VOID:FLAGS,FLAGS
+VOID:STRING,STRING,OBJECT,OBJECT
--- a/libpurple/media.c	Wed Nov 11 02:47:50 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3358 +0,0 @@
-/**
- * @file media.c 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 <string.h>
-
-#include "internal.h"
-
-#include "account.h"
-#include "media.h"
-#include "mediamanager.h"
-#include "network.h"
-
-#include "debug.h"
-
-#ifdef USE_GSTREAMER
-#include "marshallers.h"
-#include "media-gst.h"
-#endif
-
-#ifdef USE_VV
-
-#include <gst/farsight/fs-conference-iface.h>
-#include <gst/farsight/fs-element-added-notifier.h>
-
-/** @copydoc _PurpleMediaSession */
-typedef struct _PurpleMediaSession PurpleMediaSession;
-/** @copydoc _PurpleMediaStream */
-typedef struct _PurpleMediaStream PurpleMediaStream;
-/** @copydoc _PurpleMediaClass */
-typedef struct _PurpleMediaClass PurpleMediaClass;
-/** @copydoc _PurpleMediaPrivate */
-typedef struct _PurpleMediaPrivate PurpleMediaPrivate;
-/** @copydoc _PurpleMediaCandidateClass */
-typedef struct _PurpleMediaCandidateClass PurpleMediaCandidateClass;
-/** @copydoc _PurpleMediaCandidatePrivate */
-typedef struct _PurpleMediaCandidatePrivate PurpleMediaCandidatePrivate;
-/** @copydoc _PurpleMediaCodecClass */
-typedef struct _PurpleMediaCodecClass PurpleMediaCodecClass;
-/** @copydoc _PurpleMediaCodecPrivate */
-typedef struct _PurpleMediaCodecPrivate PurpleMediaCodecPrivate;
-
-/** The media class */
-struct _PurpleMediaClass
-{
-	GObjectClass parent_class;     /**< The parent class. */
-};
-
-/** The media class's private data */
-struct _PurpleMedia
-{
-	GObject parent;                /**< The parent of this object. */
-	PurpleMediaPrivate *priv;      /**< The private data of this object. */
-};
-
-struct _PurpleMediaSession
-{
-	gchar *id;
-	PurpleMedia *media;
-	GstElement *src;
-	GstElement *tee;
-	FsSession *session;
-
-	PurpleMediaSessionType type;
-	gboolean initiator;
-};
-
-struct _PurpleMediaStream
-{
-	PurpleMediaSession *session;
-	gchar *participant;
-	FsStream *stream;
-	GstElement *src;
-	GstElement *tee;
-	GstElement *volume;
-	GstElement *level;
-
-	GList *local_candidates;
-	GList *remote_candidates;
-
-	gboolean initiator;
-	gboolean accepted;
-	gboolean candidates_prepared;
-	gboolean held;
-	gboolean paused;
-
-	GList *active_local_candidates;
-	GList *active_remote_candidates;
-
-	guint connected_cb_id;
-};
-#endif
-
-struct _PurpleMediaPrivate
-{
-#ifdef USE_VV
-	PurpleMediaManager *manager;
-	PurpleAccount *account;
-	FsConference *conference;
-	gboolean initiator;
-	gpointer prpl_data;
-
-	GHashTable *sessions;	/* PurpleMediaSession table */
-	GHashTable *participants; /* FsParticipant table */
-
-	GList *streams;		/* PurpleMediaStream table */
-
-	GstElement *confbin;
-#else
-	gpointer dummy;
-#endif
-};
-
-#ifdef USE_VV
-#define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate))
-#define PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidatePrivate))
-#define PURPLE_MEDIA_CODEC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodecPrivate))
-
-static void purple_media_class_init (PurpleMediaClass *klass);
-static void purple_media_init (PurpleMedia *media);
-static void purple_media_dispose (GObject *object);
-static void purple_media_finalize (GObject *object);
-static void purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
-static void purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
-
-static void purple_media_new_local_candidate_cb(FsStream *stream,
-		FsCandidate *local_candidate, PurpleMediaSession *session);
-static void purple_media_candidates_prepared_cb(FsStream *stream,
-		PurpleMediaSession *session);
-static void purple_media_candidate_pair_established_cb(FsStream *stream,
-		FsCandidate *native_candidate, FsCandidate *remote_candidate,
-		PurpleMediaSession *session);
-static gboolean media_bus_call(GstBus *bus,
-		GstMessage *msg, PurpleMedia *media);
-
-static GObjectClass *parent_class = NULL;
-
-
-
-enum {
-	S_ERROR,
-	CANDIDATES_PREPARED,
-	CODECS_CHANGED,
-	LEVEL,
-	NEW_CANDIDATE,
-	STATE_CHANGED,
-	STREAM_INFO,
-	LAST_SIGNAL
-};
-static guint purple_media_signals[LAST_SIGNAL] = {0};
-
-enum {
-	PROP_0,
-	PROP_MANAGER,
-	PROP_ACCOUNT,
-	PROP_CONFERENCE,
-	PROP_INITIATOR,
-	PROP_PRPL_DATA,
-};
-#endif
-
-
-/*
- * PurpleMediaElementType
- */
-
-GType
-purple_media_session_type_get_type()
-{
-	static GType type = 0;
-	if (type == 0) {
-		static const GFlagsValue values[] = {
-			{ PURPLE_MEDIA_NONE,
-				"PURPLE_MEDIA_NONE", "none" },
-			{ PURPLE_MEDIA_RECV_AUDIO,
-				"PURPLE_MEDIA_RECV_AUDIO", "recv-audio" },
-			{ PURPLE_MEDIA_SEND_AUDIO,
-				"PURPLE_MEDIA_SEND_AUDIO", "send-audio" },
-			{ PURPLE_MEDIA_RECV_VIDEO,
-				"PURPLE_MEDIA_RECV_VIDEO", "recv-video" },
-			{ PURPLE_MEDIA_SEND_VIDEO,
-				"PURPLE_MEDIA_SEND_VIDEO", "send-audio" },
-			{ PURPLE_MEDIA_AUDIO,
-				"PURPLE_MEDIA_AUDIO", "audio" },
-			{ PURPLE_MEDIA_VIDEO,
-				"PURPLE_MEDIA_VIDEO", "video" },
-			{ 0, NULL, NULL }
-		};
-		type = g_flags_register_static(
-				"PurpleMediaSessionType", values);
-	}
-	return type;
-}
-
-GType
-purple_media_get_type()
-{
-#ifdef USE_VV
-	static GType type = 0;
-
-	if (type == 0) {
-		static const GTypeInfo info = {
-			sizeof(PurpleMediaClass),
-			NULL,
-			NULL,
-			(GClassInitFunc) purple_media_class_init,
-			NULL,
-			NULL,
-			sizeof(PurpleMedia),
-			0,
-			(GInstanceInitFunc) purple_media_init,
-			NULL
-		};
-		type = g_type_register_static(G_TYPE_OBJECT, "PurpleMedia", &info, 0);
-	}
-	return type;
-#else
-	return G_TYPE_NONE;
-#endif
-}
-
-GType
-purple_media_state_changed_get_type()
-{
-	static GType type = 0;
-	if (type == 0) {
-		static const GEnumValue values[] = {
-			{ PURPLE_MEDIA_STATE_NEW,
-				"PURPLE_MEDIA_STATE_NEW", "new" },
-			{ PURPLE_MEDIA_STATE_CONNECTED,
-				"PURPLE_MEDIA_STATE_CONNECTED", "connected" },
-			{ PURPLE_MEDIA_STATE_END,
-				"PURPLE_MEDIA_STATE_END", "end" },
-			{ 0, NULL, NULL }
-		};
-		type = g_enum_register_static("PurpleMediaState", values);
-	}
-	return type;
-}
-
-GType
-purple_media_info_type_get_type()
-{
-	static GType type = 0;
-	if (type == 0) {
-		static const GEnumValue values[] = {
-			{ PURPLE_MEDIA_INFO_HANGUP,
-					"PURPLE_MEDIA_INFO_HANGUP", "hangup" },
-			{ PURPLE_MEDIA_INFO_ACCEPT,
-					"PURPLE_MEDIA_INFO_ACCEPT", "accept" },
-			{ PURPLE_MEDIA_INFO_REJECT,
-					"PURPLE_MEDIA_INFO_REJECT", "reject" },
-			{ PURPLE_MEDIA_INFO_MUTE,
-					"PURPLE_MEDIA_INFO_MUTE", "mute" },
-			{ PURPLE_MEDIA_INFO_UNMUTE,
-					"PURPLE_MEDIA_INFO_UNMUTE", "unmute" },
-			{ PURPLE_MEDIA_INFO_PAUSE,
-					"PURPLE_MEDIA_INFO_PAUSE", "pause" },
-			{ PURPLE_MEDIA_INFO_UNPAUSE,
-					"PURPLE_MEDIA_INFO_UNPAUSE", "unpause" },
-			{ PURPLE_MEDIA_INFO_HOLD,
-					"PURPLE_MEDIA_INFO_HOLD", "hold" },
-			{ PURPLE_MEDIA_INFO_UNHOLD,
-					"PURPLE_MEDIA_INFO_UNHOLD", "unhold" },
-			{ 0, NULL, NULL }
-		};
-		type = g_enum_register_static("PurpleMediaInfoType", values);
-	}
-	return type;
-}
-
-GType
-purple_media_caps_get_type()
-{
-	static GType type = 0;
-	if (type == 0) {
-		static const GEnumValue values[] = {
-			{ PURPLE_MEDIA_CAPS_NONE,
-					"PURPLE_MEDIA_CAPS_NONE", "none" },
-			{ PURPLE_MEDIA_CAPS_AUDIO,
-					"PURPLE_MEDIA_CAPS_AUDIO", "audio" },
-			{ PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION,
-					"PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION",
-					"audio-single-direction" },
-			{ PURPLE_MEDIA_CAPS_VIDEO,
-					"PURPLE_MEDIA_CAPS_VIDEO", "video" },
-			{ PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION,
-					"PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION",
-					"video-single-direction" },
-			{ PURPLE_MEDIA_CAPS_AUDIO_VIDEO,
-					"PURPLE_MEDIA_CAPS_AUDIO_VIDEO",
-					"audio-video" },
-			{ PURPLE_MEDIA_CAPS_MODIFY_SESSION,
-					"PURPLE_MEDIA_CAPS_MODIFY_SESSION",
-					"modify-session" },
-			{ PURPLE_MEDIA_CAPS_CHANGE_DIRECTION,
-					"PURPLE_MEDIA_CAPS_CHANGE_DIRECTION",
-					"change-direction" },
-			{ 0, NULL, NULL }
-		};
-		type = g_enum_register_static("PurpleMediaCaps", values);
-	}
-	return type;
-}
-
-#ifdef USE_VV
-static void
-purple_media_class_init (PurpleMediaClass *klass)
-{
-	GObjectClass *gobject_class = (GObjectClass*)klass;
-	parent_class = g_type_class_peek_parent(klass);
-	
-	gobject_class->dispose = purple_media_dispose;
-	gobject_class->finalize = purple_media_finalize;
-	gobject_class->set_property = purple_media_set_property;
-	gobject_class->get_property = purple_media_get_property;
-
-	g_object_class_install_property(gobject_class, PROP_MANAGER,
-			g_param_spec_object("manager",
-			"Purple Media Manager",
-			"The media manager that contains this media session.",
-			PURPLE_TYPE_MEDIA_MANAGER,
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_ACCOUNT,
-			g_param_spec_pointer("account",
-			"PurpleAccount",
-			"The account this media session is on.",
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_CONFERENCE,
-			g_param_spec_object("conference",
-			"Farsight conference",
-			"The FsConference associated with this media.",
-			FS_TYPE_CONFERENCE,
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
-
-	g_object_class_install_property(gobject_class, PROP_INITIATOR,
-			g_param_spec_boolean("initiator",
-			"initiator",
-			"If the local user initiated the conference.",
-			FALSE,
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_PRPL_DATA,
-			g_param_spec_pointer("prpl-data",
-			"gpointer",
-			"Data the prpl plugin set on the media session.",
-			G_PARAM_READWRITE));
-
-	purple_media_signals[S_ERROR] = g_signal_new("error", G_TYPE_FROM_CLASS(klass),
-					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 g_cclosure_marshal_VOID__STRING,
-					 G_TYPE_NONE, 1, G_TYPE_STRING);
-	purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass),
-					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 purple_smarshal_VOID__STRING_STRING,
-					 G_TYPE_NONE, 2, G_TYPE_STRING,
-					 G_TYPE_STRING);
-	purple_media_signals[CODECS_CHANGED] = g_signal_new("codecs-changed", G_TYPE_FROM_CLASS(klass),
-					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 g_cclosure_marshal_VOID__STRING,
-					 G_TYPE_NONE, 1, G_TYPE_STRING);
-	purple_media_signals[LEVEL] = g_signal_new("level", G_TYPE_FROM_CLASS(klass),
-					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 purple_smarshal_VOID__STRING_STRING_DOUBLE,
-					 G_TYPE_NONE, 3, G_TYPE_STRING,
-					 G_TYPE_STRING, G_TYPE_DOUBLE);
-	purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass),
-					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 purple_smarshal_VOID__POINTER_POINTER_OBJECT,
-					 G_TYPE_NONE, 3, G_TYPE_POINTER,
-					 G_TYPE_POINTER, PURPLE_TYPE_MEDIA_CANDIDATE);
-	purple_media_signals[STATE_CHANGED] = g_signal_new("state-changed", G_TYPE_FROM_CLASS(klass),
-					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 purple_smarshal_VOID__ENUM_STRING_STRING,
-					 G_TYPE_NONE, 3, PURPLE_MEDIA_TYPE_STATE,
-					 G_TYPE_STRING, G_TYPE_STRING);
-	purple_media_signals[STREAM_INFO] = g_signal_new("stream-info", G_TYPE_FROM_CLASS(klass),
-					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 purple_smarshal_VOID__ENUM_STRING_STRING_BOOLEAN,
-					 G_TYPE_NONE, 4, PURPLE_MEDIA_TYPE_INFO_TYPE,
-					 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
-	g_type_class_add_private(klass, sizeof(PurpleMediaPrivate));
-}
-
-
-static void
-purple_media_init (PurpleMedia *media)
-{
-	media->priv = PURPLE_MEDIA_GET_PRIVATE(media);
-	memset(media->priv, 0, sizeof(*media->priv));
-}
-
-static void
-purple_media_stream_free(PurpleMediaStream *stream)
-{
-	if (stream == NULL)
-		return;
-
-	/* 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);
-
-	if (stream->active_local_candidates)
-		fs_candidate_list_destroy(stream->active_local_candidates);
-	if (stream->active_remote_candidates)
-		fs_candidate_list_destroy(stream->active_remote_candidates);
-
-	g_free(stream);
-}
-
-static void
-purple_media_session_free(PurpleMediaSession *session)
-{
-	if (session == NULL)
-		return;
-
-	g_free(session->id);
-	g_free(session);
-}
-
-static void
-purple_media_dispose(GObject *media)
-{
-	PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media);
-	GList *iter = NULL;
-
-	purple_debug_info("media","purple_media_dispose\n");
-
-	purple_media_manager_remove_media(priv->manager, PURPLE_MEDIA(media));
-
-	if (priv->confbin) {
-		gst_element_set_locked_state(priv->confbin, TRUE);
-		gst_element_set_state(GST_ELEMENT(priv->confbin),
-				GST_STATE_NULL);
-		gst_bin_remove(GST_BIN(purple_media_manager_get_pipeline(
-				priv->manager)), priv->confbin);
-		priv->confbin = NULL;
-		priv->conference = NULL;
-	}
-
-	for (iter = priv->streams; iter; iter = g_list_next(iter)) {
-		PurpleMediaStream *stream = iter->data;
-		if (stream->stream) {
-			g_object_unref(stream->stream);
-			stream->stream = NULL;
-		}
-	}
-
-	if (priv->sessions) {
-		GList *sessions = g_hash_table_get_values(priv->sessions);
-		for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
-			PurpleMediaSession *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);
-	}
-
-	if (priv->manager) {
-		GstElement *pipeline = purple_media_manager_get_pipeline(
-				priv->manager);
-		GstBus *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, media_bus_call, media);
-		gst_object_unref(bus);
-
-		g_object_unref(priv->manager);
-		priv->manager = NULL;
-	}
-
-	G_OBJECT_CLASS(parent_class)->dispose(media);
-}
-
-static void
-purple_media_finalize(GObject *media)
-{
-	PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media);
-	purple_debug_info("media","purple_media_finalize\n");
-
-	for (; priv->streams; priv->streams = g_list_delete_link(priv->streams, priv->streams))
-		purple_media_stream_free(priv->streams->data);
-
-	if (priv->sessions) {
-		GList *sessions = g_hash_table_get_values(priv->sessions);
-		for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
-			purple_media_session_free(sessions->data);
-		}
-		g_hash_table_destroy(priv->sessions);
-	}
-
-	G_OBJECT_CLASS(parent_class)->finalize(media);
-}
-
-static void
-purple_media_setup_pipeline(PurpleMedia *media)
-{
-	GstBus *bus;
-	gchar *name;
-	GstElement *pipeline;
-
-	if (media->priv->conference == NULL || media->priv->manager == NULL)
-		return;
-
-	pipeline = purple_media_manager_get_pipeline(media->priv->manager);
-
-	name = g_strdup_printf("conf_%p",
-			media->priv->conference);
-	media->priv->confbin = gst_bin_new(name);
-	g_free(name);
-
-	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
-	g_signal_connect(G_OBJECT(bus), "message",
-			G_CALLBACK(media_bus_call), media);
-	gst_object_unref(bus);
-
-	gst_bin_add(GST_BIN(pipeline),
-			media->priv->confbin);
-	gst_bin_add(GST_BIN(media->priv->confbin),
-			GST_ELEMENT(media->priv->conference));
-	gst_element_set_state(GST_ELEMENT(media->priv->confbin),
-			GST_STATE_PLAYING);
-}
-
-static void
-purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
-	PurpleMedia *media;
-	g_return_if_fail(PURPLE_IS_MEDIA(object));
-
-	media = PURPLE_MEDIA(object);
-
-	switch (prop_id) {
-		case PROP_MANAGER:
-			media->priv->manager = g_value_get_object(value);
-			g_object_ref(media->priv->manager);
-
-			purple_media_setup_pipeline(media);
-			break;
-		case PROP_ACCOUNT:
-			media->priv->account = g_value_get_pointer(value);
-			break;
-		case PROP_CONFERENCE: {
-			if (media->priv->conference)
-				gst_object_unref(media->priv->conference);
-			media->priv->conference = g_value_get_object(value);
-			gst_object_ref(media->priv->conference);
-
-			purple_media_setup_pipeline(media);
-			break;
-		}
-		case PROP_INITIATOR:
-			media->priv->initiator = g_value_get_boolean(value);
-			break;
-		case PROP_PRPL_DATA:
-			media->priv->prpl_data = g_value_get_pointer(value);
-			break;
-		default:	
-			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-			break;
-	}
-}
-
-static void
-purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
-{
-	PurpleMedia *media;
-	g_return_if_fail(PURPLE_IS_MEDIA(object));
-	
-	media = PURPLE_MEDIA(object);
-
-	switch (prop_id) {
-		case PROP_MANAGER:
-			g_value_set_object(value, media->priv->manager);
-			break;
-		case PROP_ACCOUNT:
-			g_value_set_pointer(value, media->priv->account);
-			break;
-		case PROP_CONFERENCE:
-			g_value_set_object(value, media->priv->conference);
-			break;
-		case PROP_INITIATOR:
-			g_value_set_boolean(value, media->priv->initiator);
-			break;
-		case PROP_PRPL_DATA:
-			g_value_set_pointer(value, media->priv->prpl_data);
-			break;
-		default:	
-			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
-			break;
-	}
-
-}
-#endif
-
-/*
- * PurpleMediaCandidateType
- */
-
-GType
-purple_media_candidate_type_get_type()
-{
-	static GType type = 0;
-	if (type == 0) {
-		static const GEnumValue values[] = {
-			{ PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
-					"PURPLE_MEDIA_CANDIDATE_TYPE_HOST",
-					"host" },
-			{ PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX,
-					"PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX",
-					"srflx" },
-			{ PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX,
-					"PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX",
-					"prflx" },
-			{ PURPLE_MEDIA_CANDIDATE_TYPE_RELAY,
-					"PPURPLE_MEDIA_CANDIDATE_TYPE_RELAY",
-					"relay" },
-			{ PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST,
-					"PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST",
-					"multicast" },
-			{ 0, NULL, NULL }
-		};
-		type = g_enum_register_static("PurpleMediaCandidateType",
-				values);
-	}
-	return type;
-}
-
-/*
- * PurpleMediaNetworkProtocol
- */
-
-GType
-purple_media_network_protocol_get_type()
-{
-	static GType type = 0;
-	if (type == 0) {
-		static const GEnumValue values[] = {
-			{ PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
-					"PURPLE_MEDIA_NETWORK_PROTOCOL_UDP",
-					"udp" },
-			{ PURPLE_MEDIA_NETWORK_PROTOCOL_TCP,
-					"PURPLE_MEDIA_NETWORK_PROTOCOL_TCP",
-					"tcp" },
-			{ 0, NULL, NULL }
-		};
-		type = g_enum_register_static("PurpleMediaNetworkProtocol",
-				values);
-	}
-	return type;
-}
-
-/*
- * PurpleMediaCandidate
- */
-
-struct _PurpleMediaCandidateClass
-{
-	GObjectClass parent_class;
-};
-
-struct _PurpleMediaCandidate
-{
-	GObject parent;
-};
-
-#ifdef USE_VV
-struct _PurpleMediaCandidatePrivate
-{
-	gchar *foundation;
-	guint component_id;
-	gchar *ip;
-	guint16 port;
-	gchar *base_ip;
-	guint16 base_port;
-	PurpleMediaNetworkProtocol proto;
-	guint32 priority;
-	PurpleMediaCandidateType type;
-	gchar *username;
-	gchar *password;
-	guint ttl;
-};
-
-enum {
-	PROP_CANDIDATE_0,
-	PROP_FOUNDATION,
-	PROP_COMPONENT_ID,
-	PROP_IP,
-	PROP_PORT,
-	PROP_BASE_IP,
-	PROP_BASE_PORT,
-	PROP_PROTOCOL,
-	PROP_PRIORITY,
-	PROP_TYPE,
-	PROP_USERNAME,
-	PROP_PASSWORD,
-	PROP_TTL,
-};
-
-static void
-purple_media_candidate_init(PurpleMediaCandidate *info)
-{
-	PurpleMediaCandidatePrivate *priv =
-			PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(info);
-	priv->foundation = NULL;
-	priv->component_id = 0;
-	priv->ip = NULL;
-	priv->port = 0;
-	priv->base_ip = NULL;
-	priv->proto = PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
-	priv->priority = 0;
-	priv->type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
-	priv->username = NULL;
-	priv->password = NULL;
-	priv->ttl = 0;
-}
-
-static void
-purple_media_candidate_finalize(GObject *info)
-{
-	PurpleMediaCandidatePrivate *priv =
-			PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(info);
-
-	g_free(priv->foundation);
-	g_free(priv->ip);
-	g_free(priv->base_ip);
-	g_free(priv->username);
-	g_free(priv->password);
-}
-
-static void
-purple_media_candidate_set_property (GObject *object, guint prop_id,
-		const GValue *value, GParamSpec *pspec)
-{
-	PurpleMediaCandidatePrivate *priv;
-	g_return_if_fail(PURPLE_IS_MEDIA_CANDIDATE(object));
-
-	priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(object);
-
-	switch (prop_id) {
-		case PROP_FOUNDATION:
-			g_free(priv->foundation);
-			priv->foundation = g_value_dup_string(value);
-			break;
-		case PROP_COMPONENT_ID:
-			priv->component_id = g_value_get_uint(value);
-			break;
-		case PROP_IP:
-			g_free(priv->ip);
-			priv->ip = g_value_dup_string(value);
-			break;
-		case PROP_PORT:
-			priv->port = g_value_get_uint(value);
-			break;
-		case PROP_BASE_IP:
-			g_free(priv->base_ip);
-			priv->base_ip = g_value_dup_string(value);
-			break;
-		case PROP_BASE_PORT:
-			priv->base_port = g_value_get_uint(value);
-			break;
-		case PROP_PROTOCOL:
-			priv->proto = g_value_get_enum(value);
-			break;
-		case PROP_PRIORITY:
-			priv->priority = g_value_get_uint(value);
-			break;
-		case PROP_TYPE:
-			priv->type = g_value_get_enum(value);
-			break;
-		case PROP_USERNAME:
-			g_free(priv->username);
-			priv->username = g_value_dup_string(value);
-			break;
-		case PROP_PASSWORD:
-			g_free(priv->password);
-			priv->password = g_value_dup_string(value);
-			break;
-		case PROP_TTL:
-			priv->ttl = g_value_get_uint(value);
-			break;
-		default:	
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(
-					object, prop_id, pspec);
-			break;
-	}
-}
-
-static void
-purple_media_candidate_get_property (GObject *object, guint prop_id,
-		GValue *value, GParamSpec *pspec)
-{
-	PurpleMediaCandidatePrivate *priv;
-	g_return_if_fail(PURPLE_IS_MEDIA_CANDIDATE(object));
-	
-	priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(object);
-
-	switch (prop_id) {
-		case PROP_FOUNDATION:
-			g_value_set_string(value, priv->foundation);
-			break;
-		case PROP_COMPONENT_ID:
-			g_value_set_uint(value, priv->component_id);
-			break;
-		case PROP_IP:
-			g_value_set_string(value, priv->ip);
-			break;
-		case PROP_PORT:
-			g_value_set_uint(value, priv->port);
-			break;
-		case PROP_BASE_IP:
-			g_value_set_string(value, priv->base_ip);
-			break;
-		case PROP_BASE_PORT:
-			g_value_set_uint(value, priv->base_port);
-			break;
-		case PROP_PROTOCOL:
-			g_value_set_enum(value, priv->proto);
-			break;
-		case PROP_PRIORITY:
-			g_value_set_uint(value, priv->priority);
-			break;
-		case PROP_TYPE:
-			g_value_set_enum(value, priv->type);
-			break;
-		case PROP_USERNAME:
-			g_value_set_string(value, priv->username);
-			break;
-		case PROP_PASSWORD:
-			g_value_set_string(value, priv->password);
-			break;
-		case PROP_TTL:
-			g_value_set_uint(value, priv->ttl);
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(
-					object, prop_id, pspec);
-			break;
-	}
-}
-
-static void
-purple_media_candidate_class_init(PurpleMediaCandidateClass *klass)
-{
-	GObjectClass *gobject_class = (GObjectClass*)klass;
-	
-	gobject_class->finalize = purple_media_candidate_finalize;
-	gobject_class->set_property = purple_media_candidate_set_property;
-	gobject_class->get_property = purple_media_candidate_get_property;
-
-	g_object_class_install_property(gobject_class, PROP_FOUNDATION,
-			g_param_spec_string("foundation",
-			"Foundation",
-			"The foundation of the candidate.",
-			NULL,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_COMPONENT_ID,
-			g_param_spec_uint("component-id",
-			"Component ID",
-			"The component id of the candidate.",
-			0, G_MAXUINT, 0,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_IP,
-			g_param_spec_string("ip",
-			"IP Address",
-			"The IP address of the candidate.",
-			NULL,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_PORT,
-			g_param_spec_uint("port",
-			"Port",
-			"The port of the candidate.",
-			0, G_MAXUINT16, 0,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_BASE_IP,
-			g_param_spec_string("base-ip",
-			"Base IP",
-			"The internal IP address of the candidate.",
-			NULL,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_BASE_PORT,
-			g_param_spec_uint("base-port",
-			"Base Port",
-			"The internal port of the candidate.",
-			0, G_MAXUINT16, 0,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_PROTOCOL,
-			g_param_spec_enum("protocol",
-			"Protocol",
-			"The protocol of the candidate.",
-			PURPLE_TYPE_MEDIA_NETWORK_PROTOCOL,
-			PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_PRIORITY,
-			g_param_spec_uint("priority",
-			"Priority",
-			"The priority of the candidate.",
-			0, G_MAXUINT32, 0,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_TYPE,
-			g_param_spec_enum("type",
-			"Type",
-			"The type of the candidate.",
-			PURPLE_TYPE_MEDIA_CANDIDATE_TYPE,
-			PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_USERNAME,
-			g_param_spec_string("username",
-			"Username",
-			"The username used to connect to the candidate.",
-			NULL,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_PASSWORD,
-			g_param_spec_string("password",
-			"Password",
-			"The password use to connect to the candidate.",
-			NULL,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_TTL,
-			g_param_spec_uint("ttl",
-			"TTL",
-			"The TTL of the candidate.",
-			0, G_MAXUINT, 0,
-			G_PARAM_READWRITE));
-
-	g_type_class_add_private(klass, sizeof(PurpleMediaCandidatePrivate));
-}
-
-G_DEFINE_TYPE(PurpleMediaCandidate,
-		purple_media_candidate, G_TYPE_OBJECT);
-#else
-GType
-purple_media_candidate_get_type()
-{
-	return G_TYPE_NONE;
-}
-#endif
-
-PurpleMediaCandidate *
-purple_media_candidate_new(const gchar *foundation, guint component_id,
-		PurpleMediaCandidateType type,
-		PurpleMediaNetworkProtocol proto,
-		const gchar *ip, guint port)
-{
-	return g_object_new(PURPLE_TYPE_MEDIA_CANDIDATE,
-			"foundation", foundation,
-			"component-id", component_id,
-			"type", type,
-			"protocol", proto,
-			"ip", ip,
-			"port", port, NULL);
-}
-
-static PurpleMediaCandidate *
-purple_media_candidate_copy(PurpleMediaCandidate *candidate)
-{
-#ifdef USE_VV
-	PurpleMediaCandidatePrivate *priv;
-	PurpleMediaCandidate *new_candidate;
-
-	if (candidate == NULL)
-		return NULL;
-
-	priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(candidate);
-
-	new_candidate = purple_media_candidate_new(priv->foundation,
-			priv->component_id, priv->type, priv->proto,
-			priv->ip, priv->port);
-	g_object_set(new_candidate,
-			"base-ip", priv->base_ip,
-			"base-port", priv->base_port,
-			"priority", priv->priority,
-			"username", priv->username,
-			"password", priv->password,
-			"ttl", priv->ttl, NULL);
-	return new_candidate;
-#else
-	return NULL;
-#endif
-}
-
-#ifdef USE_VV
-static FsCandidate *
-purple_media_candidate_to_fs(PurpleMediaCandidate *candidate)
-{
-	PurpleMediaCandidatePrivate *priv;
-	FsCandidate *fscandidate;
-
-	if (candidate == NULL)
-		return NULL;
-
-	priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(candidate);
-
-	fscandidate = fs_candidate_new(priv->foundation,
-			priv->component_id, priv->type,
-			priv->proto, priv->ip, priv->port);
-
-	fscandidate->base_ip = g_strdup(priv->base_ip);
-	fscandidate->base_port = priv->base_port;
-	fscandidate->priority = priv->priority;
-	fscandidate->username = g_strdup(priv->username);
-	fscandidate->password = g_strdup(priv->password);
-	fscandidate->ttl = priv->ttl;
-	return fscandidate;
-}
-
-static PurpleMediaCandidate *
-purple_media_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 *
-purple_media_candidate_list_from_fs(GList *candidates)
-{
-	GList *new_list = NULL;
-
-	for (; candidates; candidates = g_list_next(candidates)) {
-		new_list = g_list_prepend(new_list,
-				purple_media_candidate_from_fs(
-				candidates->data));
-	}
-
-	new_list = g_list_reverse(new_list);
-	return new_list;
-}
-
-static GList *
-purple_media_candidate_list_to_fs(GList *candidates)
-{
-	GList *new_list = NULL;
-
-	for (; candidates; candidates = g_list_next(candidates)) {
-		new_list = g_list_prepend(new_list,
-				purple_media_candidate_to_fs(
-				candidates->data));
-	}
-
-	new_list = g_list_reverse(new_list);
-	return new_list;
-}
-#endif
-
-GList *
-purple_media_candidate_list_copy(GList *candidates)
-{
-	GList *new_list = NULL;
-
-	for (; candidates; candidates = g_list_next(candidates)) {
-		new_list = g_list_prepend(new_list,
-				purple_media_candidate_copy(candidates->data));
-	}
-
-	new_list = g_list_reverse(new_list);
-	return new_list;
-}
-
-void
-purple_media_candidate_list_free(GList *candidates)
-{
-	for (; candidates; candidates =
-			g_list_delete_link(candidates, candidates)) {
-		g_object_unref(candidates->data);
-	}
-}
-
-gchar *
-purple_media_candidate_get_foundation(PurpleMediaCandidate *candidate)
-{
-	gchar *foundation;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
-	g_object_get(candidate, "foundation", &foundation, NULL);
-	return foundation;
-}
-
-guint
-purple_media_candidate_get_component_id(PurpleMediaCandidate *candidate)
-{
-	guint component_id;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
-	g_object_get(candidate, "component-id", &component_id, NULL);
-	return component_id;
-}
-
-gchar *
-purple_media_candidate_get_ip(PurpleMediaCandidate *candidate)
-{
-	gchar *ip;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
-	g_object_get(candidate, "ip", &ip, NULL);
-	return ip;
-}
-
-guint16
-purple_media_candidate_get_port(PurpleMediaCandidate *candidate)
-{
-	guint port;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
-	g_object_get(candidate, "port", &port, NULL);
-	return port;
-}
-
-gchar *
-purple_media_candidate_get_base_ip(PurpleMediaCandidate *candidate)
-{
-	gchar *base_ip;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
-	g_object_get(candidate, "base-ip", &base_ip, NULL);
-	return base_ip;
-}
-
-guint16
-purple_media_candidate_get_base_port(PurpleMediaCandidate *candidate)
-{
-	guint base_port;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
-	g_object_get(candidate, "base_port", &base_port, NULL);
-	return base_port;
-}
-
-PurpleMediaNetworkProtocol
-purple_media_candidate_get_protocol(PurpleMediaCandidate *candidate)
-{
-	PurpleMediaNetworkProtocol protocol;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate),
-			PURPLE_MEDIA_NETWORK_PROTOCOL_UDP);
-	g_object_get(candidate, "protocol", &protocol, NULL);
-	return protocol;
-}
-
-guint32
-purple_media_candidate_get_priority(PurpleMediaCandidate *candidate)
-{
-	guint priority;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
-	g_object_get(candidate, "priority", &priority, NULL);
-	return priority;
-}
-
-PurpleMediaCandidateType
-purple_media_candidate_get_candidate_type(PurpleMediaCandidate *candidate)
-{
-	PurpleMediaCandidateType type;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate),
-			PURPLE_MEDIA_CANDIDATE_TYPE_HOST);
-	g_object_get(candidate, "type", &type, NULL);
-	return type;
-}
-
-gchar *
-purple_media_candidate_get_username(PurpleMediaCandidate *candidate)
-{
-	gchar *username;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
-	g_object_get(candidate, "username", &username, NULL);
-	return username;
-}
-
-gchar *
-purple_media_candidate_get_password(PurpleMediaCandidate *candidate)
-{
-	gchar *password;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
-	g_object_get(candidate, "password", &password, NULL);
-	return password;
-}
-
-guint
-purple_media_candidate_get_ttl(PurpleMediaCandidate *candidate)
-{
-	guint ttl;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
-	g_object_get(candidate, "ttl", &ttl, NULL);
-	return ttl;
-}
-
-#ifdef USE_VV
-static FsMediaType
-purple_media_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
-purple_media_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
-purple_media_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;
-}
-#endif
-
-/*
- * PurpleMediaCodec
- */
-
-struct _PurpleMediaCodecClass
-{
-	GObjectClass parent_class;
-};
-
-struct _PurpleMediaCodec
-{
-	GObject parent;
-};
-
-#ifdef USE_VV
-struct _PurpleMediaCodecPrivate
-{
-	gint id;
-	char *encoding_name;
-	PurpleMediaSessionType media_type;
-	guint clock_rate;
-	guint channels;
-	GList *optional_params;
-};
-
-enum {
-	PROP_CODEC_0,
-	PROP_ID,
-	PROP_ENCODING_NAME,
-	PROP_MEDIA_TYPE,
-	PROP_CLOCK_RATE,
-	PROP_CHANNELS,
-	PROP_OPTIONAL_PARAMS,
-};
-
-static void
-purple_media_codec_init(PurpleMediaCodec *info)
-{
-	PurpleMediaCodecPrivate *priv =
-			PURPLE_MEDIA_CODEC_GET_PRIVATE(info);
-	priv->encoding_name = NULL;
-	priv->optional_params = NULL;
-}
-
-static void
-purple_media_codec_finalize(GObject *info)
-{
-	PurpleMediaCodecPrivate *priv =
-			PURPLE_MEDIA_CODEC_GET_PRIVATE(info);
-	g_free(priv->encoding_name);
-	for (; priv->optional_params; priv->optional_params =
-			g_list_delete_link(priv->optional_params,
-			priv->optional_params)) {
-		g_free(priv->optional_params->data);
-	}
-}
-
-static void
-purple_media_codec_set_property (GObject *object, guint prop_id,
-		const GValue *value, GParamSpec *pspec)
-{
-	PurpleMediaCodecPrivate *priv;
-	g_return_if_fail(PURPLE_IS_MEDIA_CODEC(object));
-
-	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(object);
-
-	switch (prop_id) {
-		case PROP_ID:
-			priv->id = g_value_get_uint(value);
-			break;
-		case PROP_ENCODING_NAME:
-			g_free(priv->encoding_name);
-			priv->encoding_name = g_value_dup_string(value);
-			break;
-		case PROP_MEDIA_TYPE:
-			priv->media_type = g_value_get_flags(value);
-			break;
-		case PROP_CLOCK_RATE:
-			priv->clock_rate = g_value_get_uint(value);
-			break;
-		case PROP_CHANNELS:
-			priv->channels = g_value_get_uint(value);
-			break;
-		case PROP_OPTIONAL_PARAMS:
-			priv->optional_params = g_value_get_pointer(value);
-			break;
-		default:	
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(
-					object, prop_id, pspec);
-			break;
-	}
-}
-
-static void
-purple_media_codec_get_property (GObject *object, guint prop_id,
-		GValue *value, GParamSpec *pspec)
-{
-	PurpleMediaCodecPrivate *priv;
-	g_return_if_fail(PURPLE_IS_MEDIA_CODEC(object));
-	
-	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(object);
-
-	switch (prop_id) {
-		case PROP_ID:
-			g_value_set_uint(value, priv->id);
-			break;
-		case PROP_ENCODING_NAME:
-			g_value_set_string(value, priv->encoding_name);
-			break;
-		case PROP_MEDIA_TYPE:
-			g_value_set_flags(value, priv->media_type);
-			break;
-		case PROP_CLOCK_RATE:
-			g_value_set_uint(value, priv->clock_rate);
-			break;
-		case PROP_CHANNELS:
-			g_value_set_uint(value, priv->channels);
-			break;
-		case PROP_OPTIONAL_PARAMS:
-			g_value_set_pointer(value, priv->optional_params);
-			break;
-		default:	
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(
-					object, prop_id, pspec);
-			break;
-	}
-}
-
-static void
-purple_media_codec_class_init(PurpleMediaCodecClass *klass)
-{
-	GObjectClass *gobject_class = (GObjectClass*)klass;
-	
-	gobject_class->finalize = purple_media_codec_finalize;
-	gobject_class->set_property = purple_media_codec_set_property;
-	gobject_class->get_property = purple_media_codec_get_property;
-
-	g_object_class_install_property(gobject_class, PROP_ID,
-			g_param_spec_uint("id",
-			"ID",
-			"The numeric identifier of the codec.",
-			0, G_MAXUINT, 0,
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_ENCODING_NAME,
-			g_param_spec_string("encoding-name",
-			"Encoding Name",
-			"The name of the codec.",
-			NULL,
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_MEDIA_TYPE,
-			g_param_spec_flags("media-type",
-			"Media Type",
-			"Whether this is an audio of video codec.",
-			PURPLE_TYPE_MEDIA_SESSION_TYPE,
-			PURPLE_MEDIA_NONE,
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_CLOCK_RATE,
-			g_param_spec_uint("clock-rate",
-			"Create Callback",
-			"The function called to create this element.",
-			0, G_MAXUINT, 0,
-			G_PARAM_READWRITE));
-
-	g_object_class_install_property(gobject_class, PROP_CHANNELS,
-			g_param_spec_uint("channels",
-			"Channels",
-			"The number of channels in this codec.",
-			0, G_MAXUINT, 0,
-			G_PARAM_READWRITE));
-	g_object_class_install_property(gobject_class, PROP_OPTIONAL_PARAMS,
-			g_param_spec_pointer("optional-params",
-			"Optional Params",
-			"A list of optional parameters for the codec.",
-			G_PARAM_READWRITE));
-
-	g_type_class_add_private(klass, sizeof(PurpleMediaCodecPrivate));
-}
-
-G_DEFINE_TYPE(PurpleMediaCodec,
-		purple_media_codec, G_TYPE_OBJECT);
-#else
-GType
-purple_media_codec_get_type()
-{
-	return G_TYPE_NONE;
-}
-#endif
-
-guint
-purple_media_codec_get_id(PurpleMediaCodec *codec)
-{
-	guint id;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0);
-	g_object_get(codec, "id", &id, NULL);
-	return id;
-}
-
-gchar *
-purple_media_codec_get_encoding_name(PurpleMediaCodec *codec)
-{
-	gchar *name;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), NULL);
-	g_object_get(codec, "encoding-name", &name, NULL);
-	return name;
-}
-
-guint
-purple_media_codec_get_clock_rate(PurpleMediaCodec *codec)
-{
-	guint clock_rate;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0);
-	g_object_get(codec, "clock-rate", &clock_rate, NULL);
-	return clock_rate;
-}
-
-guint
-purple_media_codec_get_channels(PurpleMediaCodec *codec)
-{
-	guint channels;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0);
-	g_object_get(codec, "channels", &channels, NULL);
-	return channels;
-}
-
-GList *
-purple_media_codec_get_optional_parameters(PurpleMediaCodec *codec)
-{
-	GList *optional_params;
-	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), NULL);
-	g_object_get(codec, "optional-params", &optional_params, NULL);
-	return optional_params;
-}
-
-void
-purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec,
-		const gchar *name, const gchar *value)
-{
-#ifdef USE_VV
-	PurpleMediaCodecPrivate *priv;
-	PurpleKeyValuePair *new_param;
-
-	g_return_if_fail(codec != NULL);
-	g_return_if_fail(name != NULL && value != NULL);
-
-	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
-
-	new_param = g_new0(PurpleKeyValuePair, 1);
-	new_param->key = g_strdup(name);
-	new_param->value = g_strdup(value);
-	priv->optional_params = g_list_append(
-			priv->optional_params, new_param);
-#endif
-}
-
-void
-purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec,
-		PurpleKeyValuePair *param)
-{
-#ifdef USE_VV
-	PurpleMediaCodecPrivate *priv;
-
-	g_return_if_fail(codec != NULL && param != NULL);
-
-	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
-
-	g_free(param->key);
-	g_free(param->value);
-	g_free(param);
-
-	priv->optional_params =
-			g_list_remove(priv->optional_params, param);
-#endif
-}
-
-PurpleKeyValuePair *
-purple_media_codec_get_optional_parameter(PurpleMediaCodec *codec,
-		const gchar *name, const gchar *value)
-{
-#ifdef USE_VV
-	PurpleMediaCodecPrivate *priv;
-	GList *iter;
-
-	g_return_val_if_fail(codec != NULL, NULL);
-	g_return_val_if_fail(name != NULL, NULL);
-
-	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
-
-	for (iter = priv->optional_params; iter; iter = g_list_next(iter)) {
-		PurpleKeyValuePair *param = iter->data;
-		if (!g_ascii_strcasecmp(param->key, name) &&
-				(value == NULL ||
-				!g_ascii_strcasecmp(param->value, value)))
-			return param;
-	}
-#endif
-
-	return NULL;
-}
-
-PurpleMediaCodec *
-purple_media_codec_new(int id, const char *encoding_name,
-		PurpleMediaSessionType media_type, guint clock_rate)
-{
-	PurpleMediaCodec *codec =
-			g_object_new(PURPLE_TYPE_MEDIA_CODEC,
-			"id", id,
-			"encoding_name", encoding_name,
-			"media_type", media_type,
-			"clock-rate", clock_rate, NULL);
-	return codec;
-}
-
-static PurpleMediaCodec *
-purple_media_codec_copy(PurpleMediaCodec *codec)
-{
-#ifdef USE_VV
-	PurpleMediaCodecPrivate *priv;
-	PurpleMediaCodec *new_codec;
-	GList *iter;
-
-	if (codec == NULL)
-		return NULL;
-
-	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
-
-	new_codec = purple_media_codec_new(priv->id, priv->encoding_name,
-			priv->media_type, priv->clock_rate);
-	g_object_set(codec, "channels", priv->channels, NULL);
-
-	for (iter = priv->optional_params; iter; iter = g_list_next(iter)) {
-		PurpleKeyValuePair *param =
-				(PurpleKeyValuePair*)iter->data;
-		purple_media_codec_add_optional_parameter(new_codec,
-				param->key, param->value);
-	}
-
-	return new_codec;
-#else
-	return NULL;
-#endif
-}
-
-#ifdef USE_VV
-static FsCodec *
-purple_media_codec_to_fs(const PurpleMediaCodec *codec)
-{
-	PurpleMediaCodecPrivate *priv;
-	FsCodec *new_codec;
-	GList *iter;
-
-	if (codec == NULL)
-		return NULL;
-
-	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
-
-	new_codec = fs_codec_new(priv->id, priv->encoding_name,
-			purple_media_to_fs_media_type(priv->media_type),
-			priv->clock_rate);
-	new_codec->channels = priv->channels;
-
-	for (iter = priv->optional_params; iter; iter = g_list_next(iter)) {
-		PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data;
-		fs_codec_add_optional_parameter(new_codec,
-				param->key, param->value);
-	}
-
-	return new_codec;
-}
-
-static PurpleMediaCodec *
-purple_media_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,
-			purple_media_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;
-}
-#endif
-
-gchar *
-purple_media_codec_to_string(const PurpleMediaCodec *codec)
-{
-#ifdef USE_VV
-	FsCodec *fscodec = purple_media_codec_to_fs(codec);
-	gchar *str = fs_codec_to_string(fscodec);
-	fs_codec_destroy(fscodec);
-	return str;
-#else
-	return g_strdup("");
-#endif
-}
-
-#ifdef USE_VV
-static GList *
-purple_media_codec_list_from_fs(GList *codecs)
-{
-	GList *new_list = NULL;
-
-	for (; codecs; codecs = g_list_next(codecs)) {
-		new_list = g_list_prepend(new_list,
-				purple_media_codec_from_fs(
-				codecs->data));
-	}
-
-	new_list = g_list_reverse(new_list);
-	return new_list;
-}
-
-static GList *
-purple_media_codec_list_to_fs(GList *codecs)
-{
-	GList *new_list = NULL;
-
-	for (; codecs; codecs = g_list_next(codecs)) {
-		new_list = g_list_prepend(new_list,
-				purple_media_codec_to_fs(
-				codecs->data));
-	}
-
-	new_list = g_list_reverse(new_list);
-	return new_list;
-}
-#endif
-
-GList *
-purple_media_codec_list_copy(GList *codecs)
-{
-	GList *new_list = NULL;
-
-	for (; codecs; codecs = g_list_next(codecs)) {
-		new_list = g_list_prepend(new_list,
-				purple_media_codec_copy(codecs->data));
-	}
-
-	new_list = g_list_reverse(new_list);
-	return new_list;
-}
-
-void
-purple_media_codec_list_free(GList *codecs)
-{
-	for (; codecs; codecs =
-			g_list_delete_link(codecs, codecs)) {
-		g_object_unref(codecs->data);
-	}
-}
-
-#ifdef USE_VV
-static PurpleMediaSession*
-purple_media_get_session(PurpleMedia *media, const gchar *sess_id)
-{
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	return (PurpleMediaSession*) (media->priv->sessions) ?
-			g_hash_table_lookup(media->priv->sessions, sess_id) : NULL;
-}
-
-static FsParticipant*
-purple_media_get_participant(PurpleMedia *media, const gchar *name)
-{
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	return (FsParticipant*) (media->priv->participants) ?
-			g_hash_table_lookup(media->priv->participants, name) : NULL;
-}
-
-static PurpleMediaStream*
-purple_media_get_stream(PurpleMedia *media, const gchar *session, const gchar *participant)
-{
-	GList *streams;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-
-	streams = media->priv->streams;
-
-	for (; streams; streams = g_list_next(streams)) {
-		PurpleMediaStream *stream = streams->data;
-		if (!strcmp(stream->session->id, session) &&
-				!strcmp(stream->participant, participant))
-			return stream;
-	}
-
-	return NULL;
-}
-
-static GList *
-purple_media_get_streams(PurpleMedia *media, const gchar *session,
-		const gchar *participant)
-{
-	GList *streams;
-	GList *ret = NULL;
-	
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-
-	streams = media->priv->streams;
-
-	for (; streams; streams = g_list_next(streams)) {
-		PurpleMediaStream *stream = streams->data;
-		if ((session == NULL ||
-				!strcmp(stream->session->id, session)) &&
-				(participant == NULL ||
-				!strcmp(stream->participant, participant)))
-			ret = g_list_append(ret, stream);
-	}
-
-	return ret;
-}
-
-static void
-purple_media_add_session(PurpleMedia *media, PurpleMediaSession *session)
-{
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-	g_return_if_fail(session != NULL);
-
-	if (!media->priv->sessions) {
-		purple_debug_info("media", "Creating hash table for sessions\n");
-		media->priv->sessions = g_hash_table_new(g_str_hash, g_str_equal);
-	}
-	g_hash_table_insert(media->priv->sessions, g_strdup(session->id), session);
-}
-
-static gboolean
-purple_media_remove_session(PurpleMedia *media, PurpleMediaSession *session)
-{
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-	return g_hash_table_remove(media->priv->sessions, session->id);
-}
-
-static FsParticipant *
-purple_media_add_participant(PurpleMedia *media, const gchar *name)
-{
-	FsParticipant *participant;
-	GError *err = NULL;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-
-	participant = purple_media_get_participant(media, name);
-
-	if (participant)
-		return participant;
-
-	participant = fs_conference_new_participant(media->priv->conference,
-						    (gchar*)name, &err);
-
-	if (err) {
-		purple_debug_error("media", "Error creating participant: %s\n",
-				   err->message);
-		g_error_free(err);
-		return NULL;
-	}
-
-	if (!media->priv->participants) {
-		purple_debug_info("media", "Creating hash table for participants\n");
-		media->priv->participants = g_hash_table_new_full(g_str_hash,
-				g_str_equal, g_free, NULL);
-	}
-
-	g_hash_table_insert(media->priv->participants, g_strdup(name), participant);
-
-	return participant;
-}
-
-static PurpleMediaStream *
-purple_media_insert_stream(PurpleMediaSession *session, const gchar *name, FsStream *stream)
-{
-	PurpleMediaStream *media_stream;
-	
-	g_return_val_if_fail(session != NULL, NULL);
-
-	media_stream = g_new0(PurpleMediaStream, 1);
-	media_stream->stream = stream;
-	media_stream->participant = g_strdup(name);
-	media_stream->session = session;
-
-	session->media->priv->streams =
-			g_list_append(session->media->priv->streams, media_stream);
-
-	return media_stream;
-}
-
-static void
-purple_media_insert_local_candidate(PurpleMediaSession *session, const gchar *name,
-				     FsCandidate *candidate)
-{
-	PurpleMediaStream *stream;
-
-	g_return_if_fail(session != NULL);
-
-	stream = purple_media_get_stream(session->media, session->id, name);
-	stream->local_candidates = g_list_append(stream->local_candidates, candidate);
-}
-#endif
-
-GList *
-purple_media_get_session_ids(PurpleMedia *media)
-{
-#ifdef USE_VV
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	return media->priv->sessions != NULL ?
-			g_hash_table_get_keys(media->priv->sessions) : NULL;
-#else
-	return NULL;
-#endif
-}
-
-#ifdef USE_VV
-static void 
-purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src)
-{
-	PurpleMediaSession *session;
-	GstPad *sinkpad;
-	GstPad *srcpad;
-
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-	g_return_if_fail(GST_IS_ELEMENT(src));
-
-	session = purple_media_get_session(media, sess_id);
-
-	if (session == NULL) {
-		purple_debug_warning("media", "purple_media_set_src: trying"
-				" to set src on non-existent session\n");
-		return;
-	}
-
-	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(session->media->priv->confbin), session->tee);
-
-	/* This supposedly isn't necessary, but it silences some warnings */
-	if (GST_ELEMENT_PARENT(session->media->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(session->media->priv->confbin, ghost);
-	}
-
-	gst_element_set_state(session->tee, GST_STATE_PLAYING);
-	gst_element_link(session->src, session->media->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(session->media->priv->confbin), volume);
-		gst_bin_add(GST_BIN(session->media->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("media", "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);
-}
-#endif
-
-#ifdef USE_GSTREAMER
-GstElement *
-purple_media_get_src(PurpleMedia *media, const gchar *sess_id)
-{
-#ifdef USE_VV
-	PurpleMediaSession *session;
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	session = purple_media_get_session(media, sess_id);
-	return (session != NULL) ? session->src : NULL;
-#else
-	return NULL;
-#endif
-}
-#endif /* USE_GSTREAMER */
-
-#ifdef USE_VV
-static PurpleMediaSession *
-purple_media_session_from_fs_stream(PurpleMedia *media, FsStream *stream)
-{
-	FsSession *fssession;
-	GList *values;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	g_return_val_if_fail(FS_IS_STREAM(stream), NULL);
-
-	g_object_get(stream, "session", &fssession, NULL);
-
-	values = g_hash_table_get_values(media->priv->sessions);
-
-	for (; values; values = g_list_delete_link(values, values)) {
-		PurpleMediaSession *session = values->data;
-
-		if (session->session == fssession) {
-			g_list_free(values);
-			g_object_unref(fssession);
-			return session;
-		}
-	}
-
-	g_object_unref(fssession);
-	return NULL;
-}
-
-static gboolean
-media_bus_call(GstBus *bus, GstMessage *msg, PurpleMedia *media)
-{
-	switch(GST_MESSAGE_TYPE(msg)) {
-		case GST_MESSAGE_ELEMENT: {
-			if (g_signal_has_handler_pending(media,
-					purple_media_signals[LEVEL], 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;
-				PurpleMediaSession *session = NULL;
-				gdouble rms_db;
-				gdouble percent;
-				const GValue *list;
-				const GValue *value;
-
-				if (!PURPLE_IS_MEDIA(media) ||
-						GST_ELEMENT_PARENT(src) !=
-						media->priv->confbin)
-					break;
-
-				name = gst_element_get_name(src);
-				if (!strncmp(name, "sendlevel_", 10)) {
-					session = purple_media_get_session(
-							media, name+10);
-				} else {
-					GList *iter = media->priv->streams;
-					for (; iter; iter = g_list_next(iter)) {
-						PurpleMediaStream *stream = iter->data;
-						if (stream->level == src) {
-							session = stream->session;
-							participant = stream->participant;
-							break;
-						}
-					}
-				}
-				g_free(name);
-				if (!session)
-					break;
-
-				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(media, purple_media_signals[LEVEL],
-						0, session->id, participant, percent);
-				break;
-			}
-			if (!FS_IS_CONFERENCE(GST_MESSAGE_SRC(msg)) ||
-					!PURPLE_IS_MEDIA(media) ||
-					media->priv->conference !=
-					FS_CONFERENCE(GST_MESSAGE_SRC(msg)))
-				break;
-
-			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(media, _("No codecs found. Install some GStreamer codecs found in GStreamer plugins packages."));
-						purple_media_end(media, NULL, NULL);
-						break;
-					case FS_ERROR_NO_CODECS_LEFT:
-						purple_media_error(media, _("No codecs left. Your codec preferences in fs-codecs.conf are too strict."));
-						purple_media_end(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("media", "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(media, _("A non-recoverable Farsight2 error has occurred."));
-					purple_media_end(media, NULL, NULL);
-				}
-			} else if (gst_structure_has_name(msg->structure,
-					"farsight-new-local-candidate")) {
-				FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream"));
-				FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "candidate"));
-				PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream);
-				purple_media_new_local_candidate_cb(stream, local_candidate, session);
-			} else if (gst_structure_has_name(msg->structure,
-					"farsight-local-candidates-prepared")) {
-				FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream"));
-				PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream);
-				purple_media_candidates_prepared_cb(stream, session);
-			} else if (gst_structure_has_name(msg->structure,
-					"farsight-new-active-candidate-pair")) {
-				FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream"));
-				FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "local-candidate"));
-				FsCandidate *remote_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "remote-candidate"));
-				PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream);
-				purple_media_candidate_pair_established_cb(stream, local_candidate, remote_candidate, session);
-			} else if (gst_structure_has_name(msg->structure,
-					"farsight-recv-codecs-changed")) {
-				GList *codecs = g_value_get_boxed(gst_structure_get_value(msg->structure, "codecs"));
-				FsCodec *codec = codecs->data;
-				purple_debug_info("media", "farsight-recv-codecs-changed: %s\n", codec->encoding_name);
-				
-			} else if (gst_structure_has_name(msg->structure,
-					"farsight-component-state-changed")) {
-				FsStreamState fsstate = g_value_get_enum(gst_structure_get_value(msg->structure, "state"));
-				guint component = g_value_get_uint(gst_structure_get_value(msg->structure, "component"));
-				const gchar *state;
-				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("media", "farsight-component-state-changed: component: %u state: %s\n", component, state);
-			} else if (gst_structure_has_name(msg->structure,
-					"farsight-send-codec-changed")) {
-				FsCodec *codec = g_value_get_boxed(gst_structure_get_value(msg->structure, "codec"));
-				gchar *codec_str = fs_codec_to_string(codec);
-				purple_debug_info("media", "farsight-send-codec-changed: codec: %s\n", codec_str);
-				g_free(codec_str);
-			} else if (gst_structure_has_name(msg->structure,
-					"farsight-codecs-changed")) {
-				GList *sessions = g_hash_table_get_values(PURPLE_MEDIA(media)->priv->sessions);
-				FsSession *fssession = g_value_get_object(gst_structure_get_value(msg->structure, "session"));
-				for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
-					PurpleMediaSession *session = sessions->data;
-					if (session->session == fssession) {
-						gchar *session_id = g_strdup(session->id);
-						g_signal_emit(media, purple_media_signals[CODECS_CHANGED], 0, session_id);
-						g_free(session_id);
-						g_list_free(sessions);
-						break;
-					}
-				}
-			}
-			break;
-		}
-		case GST_MESSAGE_ERROR: {
-			GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg));
-			GstElement *lastElement = NULL;
-			while (!GST_IS_PIPELINE(element)) {
-				if (element == media->priv->confbin) {
-					purple_media_error(media, _("Conference error"));
-					purple_media_end(media, NULL, NULL);
-					break;
-				}
-				lastElement = element;
-				element = GST_ELEMENT_PARENT(element);
-			}
-			if (GST_IS_PIPELINE(element)) {
-				GList *sessions = g_hash_table_get_values(media->priv->sessions);
-				for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
-					PurpleMediaSession *session = sessions->data;
-
-					if (session->src == lastElement) {
-						if (session->type & PURPLE_MEDIA_AUDIO)
-							purple_media_error(media, _("Error with your microphone"));
-						else
-							purple_media_error(media, _("Error with your webcam"));
-						purple_media_end(media, NULL, NULL);
-						break;
-					}
-				}
-				g_list_free(sessions);
-			}
-		}
-		default:
-			break;
-	}
-
-	return TRUE;
-}
-#endif
-
-PurpleAccount *
-purple_media_get_account(PurpleMedia *media)
-{
-#ifdef USE_VV
-	PurpleAccount *account;
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	g_object_get(G_OBJECT(media), "account", &account, NULL);
-	return account;
-#else
-	return NULL;
-#endif
-}
-
-gpointer
-purple_media_get_prpl_data(PurpleMedia *media)
-{
-#ifdef USE_VV
-	gpointer prpl_data;
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	g_object_get(G_OBJECT(media), "prpl-data", &prpl_data, NULL);
-	return prpl_data;
-#else
-	return NULL;
-#endif
-}
-
-void
-purple_media_set_prpl_data(PurpleMedia *media, gpointer prpl_data)
-{
-#ifdef USE_VV
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-	g_object_set(G_OBJECT(media), "prpl-data", prpl_data, NULL);
-#endif
-}
-
-void
-purple_media_error(PurpleMedia *media, const gchar *error, ...)
-{
-#ifdef USE_VV
-	va_list args;
-	gchar *message;
-
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-
-	va_start(args, error);
-	message = g_strdup_vprintf(error, args);
-	va_end(args);
-
-	purple_debug_error("media", "%s\n", message);
-	g_signal_emit(media, purple_media_signals[S_ERROR], 0, message);
-
-	g_free(message);
-#endif
-}
-
-void
-purple_media_end(PurpleMedia *media,
-		const gchar *session_id, const gchar *participant)
-{
-#ifdef USE_VV
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-	if (session_id == NULL && participant == NULL) {
-		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
-				0, PURPLE_MEDIA_STATE_END,
-				NULL, NULL);
-		g_object_unref(media);
-	}
-#endif
-}
-
-void
-purple_media_stream_info(PurpleMedia *media, PurpleMediaInfoType type,
-		const gchar *session_id, const gchar *participant,
-		gboolean local)
-{
-#ifdef USE_VV
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-
-	if (type == PURPLE_MEDIA_INFO_ACCEPT) {
-		GList *streams;
-
-		g_return_if_fail(PURPLE_IS_MEDIA(media));
-
-		streams = purple_media_get_streams(media,
-				session_id, participant);
-
-		for (; streams; streams =
-				g_list_delete_link(streams, streams)) {
-			PurpleMediaStream *stream = streams->data;
-			g_object_set(G_OBJECT(stream->stream), "direction",
-					purple_media_to_fs_stream_direction(
-					stream->session->type), NULL);
-			stream->accepted = TRUE;
-
-			if (stream->remote_candidates != NULL &&
-					stream->initiator == FALSE) {
-				GError *err = NULL;
-				fs_stream_set_remote_candidates(stream->stream,
-						stream->remote_candidates, &err);
-
-				if (err) {
-					purple_debug_error("media", "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)) {
-		GList *sessions;
-		gboolean active = (type == PURPLE_MEDIA_INFO_MUTE);
-
-		g_return_if_fail(PURPLE_IS_MEDIA(media));
-
-		if (session_id == NULL)
-			sessions = g_hash_table_get_values(
-					media->priv->sessions);
-		else
-			sessions = g_list_prepend(NULL,
-					purple_media_get_session(
-					media, session_id));
-
-		purple_debug_info("media", "Turning mute %s\n",
-				active ? "on" : "off");
-
-		for (; sessions; sessions = g_list_delete_link(
-				sessions, sessions)) {
-			PurpleMediaSession *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(session->media->
-						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 = purple_media_get_streams(media,
-				session_id, participant);
-		for (; streams; streams = g_list_delete_link(streams, streams)) {
-			PurpleMediaStream *stream = streams->data;
-			if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
-				stream->paused = active;
-
-				if (!stream->held)
-					g_object_set(stream->stream, "direction",
-							purple_media_to_fs_stream_direction(
-							stream->session->type & ((active) ?
-							~PURPLE_MEDIA_SEND_VIDEO :
-							PURPLE_MEDIA_VIDEO)), NULL);
-			}
-		}
-	} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
-			type == PURPLE_MEDIA_INFO_UNHOLD)) {
-		GList *streams;
-		gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
-
-		g_return_if_fail(PURPLE_IS_MEDIA(media));
-
-		streams = purple_media_get_streams(media,
-				session_id, participant);
-		for (; streams; streams = g_list_delete_link(streams, streams)) {
-			PurpleMediaStream *stream = streams->data;
-			stream->held = active;
-			if (stream->session->type & PURPLE_MEDIA_VIDEO) {
-				FsStreamDirection direction;
-
-				direction = ((active) ?
-						~PURPLE_MEDIA_VIDEO :
-						PURPLE_MEDIA_VIDEO);
-				if (!active && stream->paused)
-					direction &= ~PURPLE_MEDIA_SEND_VIDEO;
-
-				g_object_set(stream->stream, "direction",
-						purple_media_to_fs_stream_direction(
-						stream->session->type & direction), NULL);
-			} else if (stream->session->type & PURPLE_MEDIA_AUDIO) {
-				g_object_set(stream->stream, "direction",
-						purple_media_to_fs_stream_direction(
-						stream->session->type & ((active) ?
-						~PURPLE_MEDIA_AUDIO :
-						PURPLE_MEDIA_AUDIO)), NULL);
-			}
-		}
-	}
-
-	g_signal_emit(media, purple_media_signals[STREAM_INFO],
-			0, type, session_id, participant, local);
-
-	if (type == PURPLE_MEDIA_INFO_HANGUP ||
-			type == PURPLE_MEDIA_INFO_REJECT) {
-		purple_media_end(media, session_id, participant);
-	}
-#endif
-}
-
-#ifdef USE_VV
-static void
-purple_media_new_local_candidate_cb(FsStream *stream,
-				    FsCandidate *local_candidate,
-				    PurpleMediaSession *session)
-{
-	gchar *name;
-	FsParticipant *participant;
-	PurpleMediaCandidate *candidate;
-
-	g_return_if_fail(FS_IS_STREAM(stream));
-	g_return_if_fail(session != NULL);
-
-	purple_debug_info("media", "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);
-
-	purple_media_insert_local_candidate(session, name, fs_candidate_copy(local_candidate));
-
-	candidate = purple_media_candidate_from_fs(local_candidate);
-	g_signal_emit(session->media, purple_media_signals[NEW_CANDIDATE],
-		      0, session->id, name, candidate);
-	g_object_unref(candidate);
-
-	g_free(name);
-}
-
-static void
-purple_media_candidates_prepared_cb(FsStream *stream, PurpleMediaSession *session)
-{
-	gchar *name;
-	FsParticipant *participant;
-	PurpleMediaStream *stream_data;
-
-	g_return_if_fail(FS_IS_STREAM(stream));
-	g_return_if_fail(session != NULL);
-
-	g_object_get(stream, "participant", &participant, NULL);
-	g_object_get(participant, "cname", &name, NULL);
-	g_object_unref(participant);
-
-	stream_data = purple_media_get_stream(session->media, session->id, name);
-	stream_data->candidates_prepared = TRUE;
-
-	g_signal_emit(session->media,
-			purple_media_signals[CANDIDATES_PREPARED],
-			0, session->id, name);
-
-	g_free(name);
-}
-
-/* callback called when a pair of transport candidates (local and remote)
- * has been established */
-static void
-purple_media_candidate_pair_established_cb(FsStream *fsstream,
-					   FsCandidate *native_candidate,
-					   FsCandidate *remote_candidate,
-					   PurpleMediaSession *session)
-{
-	gchar *name;
-	FsParticipant *participant;
-	PurpleMediaStream *stream;
-	GList *iter;
-
-	g_return_if_fail(FS_IS_STREAM(fsstream));
-	g_return_if_fail(session != NULL);
-
-	g_object_get(fsstream, "participant", &participant, NULL);
-	g_object_get(participant, "cname", &name, NULL);
-	g_object_unref(participant);
-
-	stream = purple_media_get_stream(session->media, session->id, name);
-
-	iter = stream->active_local_candidates;
-	for(; iter; iter = g_list_next(iter)) {
-		FsCandidate *c = iter->data;
-		if (native_candidate->component_id == c->component_id) {
-			fs_candidate_destroy(c);
-			stream->active_local_candidates =
-					g_list_delete_link(iter, iter);
-			stream->active_local_candidates = g_list_prepend(
-					stream->active_local_candidates,
-					fs_candidate_copy(native_candidate));
-			break;
-		}
-	}
-	if (iter == NULL)
-		stream->active_local_candidates = g_list_prepend(
-				stream->active_local_candidates,
-				fs_candidate_copy(native_candidate));
-
-	iter = stream->active_remote_candidates;
-	for(; iter; iter = g_list_next(iter)) {
-		FsCandidate *c = iter->data;
-		if (native_candidate->component_id == c->component_id) {
-			fs_candidate_destroy(c);
-			stream->active_remote_candidates =
-					g_list_delete_link(iter, iter);
-			stream->active_remote_candidates = g_list_prepend(
-					stream->active_remote_candidates,
-					fs_candidate_copy(remote_candidate));
-			break;
-		}
-	}
-	if (iter == NULL)
-		stream->active_remote_candidates = g_list_prepend(
-				stream->active_remote_candidates,
-				fs_candidate_copy(remote_candidate));
-
-	purple_debug_info("media", "candidate pair established\n");
-}
-
-static gboolean
-purple_media_connected_cb(PurpleMediaStream *stream)
-{
-	g_return_val_if_fail(stream != NULL, FALSE);
-
-	stream->connected_cb_id = 0;
-
-	purple_media_manager_create_output_window(
-			stream->session->media->priv->manager,
-			stream->session->media,
-			stream->session->id, stream->participant);
-
-	g_signal_emit(stream->session->media,
-			purple_media_signals[STATE_CHANGED],
-			0, PURPLE_MEDIA_STATE_CONNECTED,
-			stream->session->id, stream->participant);
-	return FALSE;
-}
-
-static void
-purple_media_src_pad_added_cb(FsStream *fsstream, GstPad *srcpad,
-			      FsCodec *codec, PurpleMediaStream *stream)
-{
-	PurpleMediaPrivate *priv;
-	GstPad *sinkpad;
-
-	g_return_if_fail(FS_IS_STREAM(fsstream));
-	g_return_if_fail(stream != NULL);
-
-	priv = stream->session->media->priv;
-
-	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(priv->manager,
-					PURPLE_MEDIA_RECV_AUDIO,
-					stream->session->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)purple_media_connected_cb, stream);
-}
-
-static void
-purple_media_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);
-	}
-}
-#endif  /* USE_VV */
-
-gboolean
-purple_media_add_stream(PurpleMedia *media, const gchar *sess_id,
-		const gchar *who, PurpleMediaSessionType type,
-		gboolean initiator, const gchar *transmitter,
-		guint num_params, GParameter *params)
-{
-#ifdef USE_VV
-	PurpleMediaSession *session;
-	FsParticipant *participant = NULL;
-	PurpleMediaStream *stream = NULL;
-	FsMediaType media_type = purple_media_to_fs_media_type(type);
-	FsStreamDirection type_direction =
-			purple_media_to_fs_stream_direction(type);
-	gboolean is_nice = !strcmp(transmitter, "nice");
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-
-	session = purple_media_get_session(media, sess_id);
-
-	if (!session) {
-		GError *err = NULL;
-		GList *codec_conf = NULL, *iter = NULL;
-		gchar *filename = NULL;
-		PurpleMediaSessionType session_type;
-		GstElement *src = NULL;
-
-		session = g_new0(PurpleMediaSession, 1);
-
-		session->session = fs_conference_new_session(
-				media->priv->conference, media_type, &err);
-
-		if (err != NULL) {
-			purple_media_error(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("media", "Couldn't read "
-						"fs-codec.conf: %s\n",
-						err->message);
-			else
-				purple_debug_error("media", "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);
-
-		/*
-		 * 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(purple_media_element_added_cb),
-					stream);
-			fs_element_added_notifier_add(notifier,
-					GST_BIN(media->priv->conference));
-		}
-
-		fs_codec_list_destroy(codec_conf);
-
-		session->id = g_strdup(sess_id);
-		session->media = media;
-		session->type = type;
-		session->initiator = initiator;
-
-		purple_media_add_session(media, session);
-		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
-				0, PURPLE_MEDIA_STATE_NEW,
-				session->id, NULL);
-
-		if (type_direction & FS_DIRECTION_SEND) {
-			session_type = purple_media_from_fs(media_type,
-					FS_DIRECTION_SEND);
-			src = purple_media_manager_get_element(
-					media->priv->manager, session_type,
-					media, session->id, who);
-			if (!GST_IS_ELEMENT(src)) {
-				purple_debug_error("media",
-						"Error creating src for session %s\n",
-						session->id);
-				purple_media_end(media, session->id, NULL);
-				return FALSE;
-			}
-
-			purple_media_set_src(media, session->id, src);
-			gst_element_set_state(session->src, GST_STATE_PLAYING);
-			purple_media_manager_create_output_window(
-					media->priv->manager,
-					session->media,
-					session->id, NULL);
-		}
-	}
-
-	if (!(participant = purple_media_add_participant(media, who))) {
-		purple_media_remove_session(media, session);
-		g_free(session);
-		return FALSE;
-	} else {
-		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
-				0, PURPLE_MEDIA_STATE_NEW,
-				NULL, who);
-	}
-
-	stream = purple_media_get_stream(media, sess_id, who);
-
-	if (!stream) {
-		GError *err = NULL;
-		FsStream *fsstream = NULL;
-		const gchar *stun_ip = purple_network_get_stun_ip();
-		const gchar *turn_ip = purple_network_get_turn_ip();
-
-		if (stun_ip || turn_ip) {
-			guint new_num_params = 
-					(stun_ip && is_nice) && turn_ip ?
-					num_params + 2 : num_params + 1;
-			guint next_param_index = num_params;
-			GParameter *param = g_new0(GParameter, new_num_params);
-			memcpy(param, params, sizeof(GParameter) * num_params);
-
-			if (stun_ip) {
-				purple_debug_info("media", 
-					"setting property stun-ip on new stream: %s\n", stun_ip);
-
-				param[next_param_index].name = "stun-ip";
-				g_value_init(&param[next_param_index].value, G_TYPE_STRING);
-				g_value_set_string(&param[next_param_index].value, stun_ip);
-				next_param_index++;
-			}
-
-			if (turn_ip && is_nice) {
-				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) {
-					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("media",
-						"setting property relay-info on new stream\n");
-					param[next_param_index].name = "relay-info";
-					g_value_init(&param[next_param_index].value, 
-						G_TYPE_VALUE_ARRAY);
-					g_value_set_boxed(&param[next_param_index].value,
-						relay_info);
-					g_value_array_free(relay_info);
-				} else {
-					purple_debug_error("media", "Error relay info");
-					g_object_unref(participant);
-					g_hash_table_remove(media->priv->participants, who);
-					purple_media_remove_session(media, session);
-					g_free(session);
-					return FALSE;
-				}
-			}
-
-			fsstream = fs_session_new_stream(session->session,
-					participant, initiator == TRUE ?
-					type_direction : (type_direction &
-					FS_DIRECTION_RECV), transmitter,
-					new_num_params, param, &err);
-			g_free(param);
-		} else {
-			fsstream = fs_session_new_stream(session->session,
-					participant, initiator == TRUE ?
-					type_direction : (type_direction &
-					FS_DIRECTION_RECV), transmitter,
-					num_params, params, &err);
-		}
-
-		if (fsstream == NULL) {
-			purple_debug_error("media",
-					"Error creating stream: %s\n",
-					err && err->message ?
-					err->message : "NULL");
-			if (err)
-				g_error_free(err);
-			g_object_unref(participant);
-			g_hash_table_remove(media->priv->participants, who);
-			purple_media_remove_session(media, session);
-			g_free(session);
-			return FALSE;
-		}
-
-		stream = purple_media_insert_stream(session, who, fsstream);
-		stream->initiator = initiator;
-
-		/* callback for source pad added (new stream source ready) */
-		g_signal_connect(G_OBJECT(fsstream),
-				 "src-pad-added", G_CALLBACK(purple_media_src_pad_added_cb), stream);
-
-		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
-				0, PURPLE_MEDIA_STATE_NEW,
-				session->id, who);
-	} else {
-		if (purple_media_to_fs_stream_direction(stream->session->type)
-				!= type_direction) {
-			/* change direction */
-			g_object_set(stream->stream, "direction",
-					type_direction, NULL);
-		}
-	}
-
-	return TRUE;
-#else
-	return FALSE;
-#endif  /* USE_VV */
-}
-
-PurpleMediaManager *
-purple_media_get_manager(PurpleMedia *media)
-{
-	PurpleMediaManager *ret;
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	g_object_get(media, "manager", &ret, NULL);
-	return ret;
-}
-
-PurpleMediaSessionType
-purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id)
-{
-#ifdef USE_VV
-	PurpleMediaSession *session;
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), PURPLE_MEDIA_NONE);
-	session = purple_media_get_session(media, sess_id);
-	return session->type;
-#else
-	return PURPLE_MEDIA_NONE;
-#endif
-}
-/* XXX: Should wait until codecs-ready is TRUE before using this function */
-GList *
-purple_media_get_codecs(PurpleMedia *media, const gchar *sess_id)
-{
-#ifdef USE_VV
-	GList *fscodecs;
-	GList *codecs;
-	PurpleMediaSession *session;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-
-	session = purple_media_get_session(media, sess_id);
-
-	if (session == NULL)
-		return NULL;
-
-	g_object_get(G_OBJECT(session->session),
-		     "codecs", &fscodecs, NULL);
-	codecs = purple_media_codec_list_from_fs(fscodecs);
-	fs_codec_list_destroy(fscodecs);
-	return codecs;
-#else
-	return NULL;
-#endif
-}
-
-GList *
-purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id,
-                                  const gchar *participant)
-{
-#ifdef USE_VV
-	PurpleMediaStream *stream;
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	stream = purple_media_get_stream(media, sess_id, participant);
-	return stream ? purple_media_candidate_list_from_fs(
-			stream->local_candidates) : NULL;
-#else
-	return NULL;
-#endif
-}
-
-void
-purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id,
-                                   const gchar *participant,
-                                   GList *remote_candidates)
-{
-#ifdef USE_VV
-	PurpleMediaStream *stream;
-	GError *err = NULL;
-
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-	stream = purple_media_get_stream(media, sess_id, participant);
-
-	if (stream == NULL) {
-		purple_debug_error("media",
-				"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,
-			purple_media_candidate_list_to_fs(remote_candidates));
-
-	if (stream->initiator == TRUE || stream->accepted == TRUE) {
-		fs_stream_set_remote_candidates(stream->stream,
-				stream->remote_candidates, &err);
-
-		if (err) {
-			purple_debug_error("media", "Error adding remote"
-					" candidates: %s\n", err->message);
-			g_error_free(err);
-		}
-	}
-#endif
-}
-
-#if 0
-/*
- * These two functions aren't being used and I'd rather not lock in the API
- * until they are needed. If they ever are.
- */
-
-GList *
-purple_media_get_active_local_candidates(PurpleMedia *media,
-		const gchar *sess_id, const gchar *participant)
-{
-#ifdef USE_VV
-	PurpleMediaStream *stream;
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	stream = purple_media_get_stream(media, sess_id, participant);
-	return purple_media_candidate_list_from_fs(
-			stream->active_local_candidates);
-#else
-	return NULL;
-#endif
-}
-
-GList *
-purple_media_get_active_remote_candidates(PurpleMedia *media,
-		const gchar *sess_id, const gchar *participant)
-{
-#ifdef USE_VV
-	PurpleMediaStream *stream;
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-	stream = purple_media_get_stream(media, sess_id, participant);
-	return purple_media_candidate_list_from_fs(
-			stream->active_remote_candidates);
-#else
-	return NULL;
-#endif
-}
-#endif
-
-gboolean
-purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id,
-                               const gchar *participant, GList *codecs)
-{
-#ifdef USE_VV
-	PurpleMediaStream *stream;
-	FsStream *fsstream;
-	GList *fscodecs;
-	GError *err = NULL;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-	stream = purple_media_get_stream(media, sess_id, participant);
-
-	if (stream == NULL)
-		return FALSE;
-
-	fsstream = stream->stream;
-	fscodecs = purple_media_codec_list_to_fs(codecs);
-	fs_stream_set_remote_codecs(fsstream, fscodecs, &err);
-	fs_codec_list_destroy(fscodecs);
-
-	if (err) {
-		purple_debug_error("media", "Error setting remote codecs: %s\n",
-				   err->message);
-		g_error_free(err);
-		return FALSE;
-	}
-	return TRUE;
-#else
-	return FALSE;
-#endif
-}
-
-gboolean
-purple_media_candidates_prepared(PurpleMedia *media,
-		const gchar *session_id, const gchar *participant)
-{
-#ifdef USE_VV
-	GList *streams;
-	gboolean prepared = TRUE;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-
-	streams = purple_media_get_streams(media, session_id, participant);
-
-	for (; streams; streams = g_list_delete_link(streams, streams)) {
-		PurpleMediaStream *stream = streams->data;
-		if (stream->candidates_prepared == FALSE) {
-			g_list_free(streams);
-			prepared = FALSE;
-			break;
-		}
-	}
-
-	return prepared;
-#else
-	return FALSE;
-#endif
-}
-
-gboolean
-purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, PurpleMediaCodec *codec)
-{
-#ifdef USE_VV
-	PurpleMediaSession *session;
-	FsCodec *fscodec;
-	GError *err = NULL;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-
-	session = purple_media_get_session(media, sess_id);
-
-	if (session != NULL)
-		return FALSE;
-
-	fscodec = purple_media_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
-	return FALSE;
-#endif
-}
-
-gboolean
-purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id)
-{
-#ifdef USE_VV
-	gboolean ret;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-
-	if (sess_id != NULL) {
-		PurpleMediaSession *session;
-		session = purple_media_get_session(media, 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(media->priv->sessions);
-		for (; values; values = g_list_delete_link(values, values)) {
-			PurpleMediaSession *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;
-#else
-	return FALSE;
-#endif
-}
-
-gboolean
-purple_media_is_initiator(PurpleMedia *media,
-		const gchar *sess_id, const gchar *participant)
-{
-#ifdef USE_VV
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-
-	if (sess_id == NULL && participant == NULL)
-		return media->priv->initiator;
-	else if (sess_id != NULL && participant == NULL) {
-		PurpleMediaSession *session =
-				purple_media_get_session(media, sess_id);
-		return session != NULL ? session->initiator : FALSE;
-	} else if (sess_id != NULL && participant != NULL) {
-		PurpleMediaStream *stream = purple_media_get_stream(
-				media, sess_id, participant);
-		return stream != NULL ? stream->initiator : FALSE;
-	}
-#endif
-	return FALSE;
-}
-
-gboolean
-purple_media_accepted(PurpleMedia *media, const gchar *sess_id,
-		const gchar *participant)
-{
-#ifdef USE_VV
-	gboolean accepted = TRUE;
-
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-
-	if (sess_id == NULL && participant == NULL) {
-		GList *streams = media->priv->streams;
-
-		for (; streams; streams = g_list_next(streams)) {
-			PurpleMediaStream *stream = streams->data;
-			if (stream->accepted == FALSE) {
-				accepted = FALSE;
-				break;
-			}
-		}
-	} else if (sess_id != NULL && participant == NULL) {
-		GList *streams = purple_media_get_streams(
-				media, sess_id, NULL);
-		for (; streams; streams =
-				g_list_delete_link(streams, streams)) {
-			PurpleMediaStream *stream = streams->data;
-			if (stream->accepted == FALSE) {
-				g_list_free(streams);
-				accepted = FALSE;
-				break;
-			}
-		}
-	} else if (sess_id != NULL && participant != NULL) {
-		PurpleMediaStream *stream = purple_media_get_stream(
-				media, sess_id, participant);
-		if (stream == NULL || stream->accepted == FALSE)
-			accepted = FALSE;
-	}
-
-	return accepted;
-#else
-	return FALSE;
-#endif
-}
-
-void purple_media_set_input_volume(PurpleMedia *media,
-		const gchar *session_id, double level)
-{
-#ifdef USE_VV
-	GList *sessions;
-
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-
-	purple_prefs_set_int("/purple/media/audio/volume/input", level);
-
-	if (session_id == NULL)
-		sessions = g_hash_table_get_values(media->priv->sessions);
-	else
-		sessions = g_list_append(NULL,
-				purple_media_get_session(media, session_id));
-
-	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
-		PurpleMediaSession *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(session->media->priv->confbin),
-					name);
-			g_free(name);
-			g_object_set(volume, "volume", level/10.0, NULL);
-		}
-	}
-#endif
-}
-
-void purple_media_set_output_volume(PurpleMedia *media,
-		const gchar *session_id, const gchar *participant,
-		double level)
-{
-#ifdef USE_VV
-	GList *streams;
-
-	g_return_if_fail(PURPLE_IS_MEDIA(media));
-
-	purple_prefs_set_int("/purple/media/audio/volume/output", level);
-
-	streams = purple_media_get_streams(media,
-			session_id, participant);
-
-	for (; streams; streams = g_list_delete_link(streams, streams)) {
-		PurpleMediaStream *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
-}
-
-gulong
-purple_media_set_output_window(PurpleMedia *media, const gchar *session_id,
-		const gchar *participant, gulong window_id)
-{
-#ifdef USE_VV
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
-
-	return purple_media_manager_set_output_window(media->priv->manager,
-			media, session_id, participant, window_id);
-#else
-	return 0;
-#endif
-}
-
-void
-purple_media_remove_output_windows(PurpleMedia *media)
-{
-#ifdef USE_VV
-	GList *iter = media->priv->streams;
-	for (; iter; iter = g_list_next(iter)) {
-		PurpleMediaStream *stream = iter->data;
-		purple_media_manager_remove_output_windows(
-				media->priv->manager, media,
-				stream->session->id, stream->participant);
-	}
-
-	iter = purple_media_get_session_ids(media);
-	for (; iter; iter = g_list_delete_link(iter, iter)) {
-		gchar *session_name = iter->data;
-		purple_media_manager_remove_output_windows(
-				media->priv->manager, media,
-				session_name, NULL);
-	}
-#endif
-}
-
-#ifdef USE_GSTREAMER
-GstElement *
-purple_media_get_tee(PurpleMedia *media,
-		const gchar *session_id, const gchar *participant)
-{
-#ifdef USE_VV
-	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
-
-	if (session_id != NULL && participant == NULL) {
-		PurpleMediaSession *session =
-				purple_media_get_session(media, session_id);
-		return (session != NULL) ? session->tee : NULL;
-	} else if (session_id != NULL && participant != NULL) {
-		PurpleMediaStream *stream =
-				purple_media_get_stream(media,
-				session_id, participant);
-		return (stream != NULL) ? stream->tee : NULL;
-	}
-	g_return_val_if_reached(NULL);
-#else
-	return NULL;
-#endif
-}
-#endif /* USE_GSTREAMER */
-
--- a/libpurple/media.h	Wed Nov 11 02:47:50 2009 +0000
+++ b/libpurple/media.h	Wed Nov 11 03:07:21 2009 +0000
@@ -27,26 +27,15 @@
 #ifndef _PURPLE_MEDIA_H_
 #define _PURPLE_MEDIA_H_
 
+#include "media/candidate.h"
+#include "media/codec.h"
+#include "media/enum-types.h"
+
 #include <glib.h>
 #include <glib-object.h>
 
 G_BEGIN_DECLS
 
-#define PURPLE_TYPE_MEDIA_CANDIDATE           (purple_media_candidate_get_type())
-#define PURPLE_MEDIA_CANDIDATE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate))
-#define PURPLE_MEDIA_CANDIDATE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate))
-#define PURPLE_IS_MEDIA_CANDIDATE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_CANDIDATE))
-#define PURPLE_IS_MEDIA_CANDIDATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_CANDIDATE))
-#define PURPLE_MEDIA_CANDIDATE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate))
-
-#define PURPLE_TYPE_MEDIA_CODEC           (purple_media_codec_get_type())
-#define PURPLE_MEDIA_CODEC(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec))
-#define PURPLE_MEDIA_CODEC_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec))
-#define PURPLE_IS_MEDIA_CODEC(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_CODEC))
-#define PURPLE_IS_MEDIA_CODEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_CODEC))
-#define PURPLE_MEDIA_CODEC_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec))
-
-#define PURPLE_TYPE_MEDIA_SESSION_TYPE (purple_media_session_type_get_type())
 #define PURPLE_TYPE_MEDIA            (purple_media_get_type())
 #define PURPLE_MEDIA(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA, PurpleMedia))
 #define PURPLE_MEDIA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA, PurpleMediaClass))
@@ -54,80 +43,8 @@
 #define PURPLE_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA))
 #define PURPLE_MEDIA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA, PurpleMediaClass))
 
-#define PURPLE_TYPE_MEDIA_CANDIDATE_TYPE (purple_media_candidate_type_get_type())
-#define PURPLE_TYPE_MEDIA_NETWORK_PROTOCOL (purple_media_network_protocol_get_type())
-#define PURPLE_MEDIA_TYPE_STATE      (purple_media_state_changed_get_type())
-#define PURPLE_MEDIA_TYPE_INFO_TYPE	(purple_media_info_type_get_type())
-#define PURPLE_MEDIA_TYPE_CAPS	     (purple_media_caps_get_type())
-
 /** An opaque structure representing a media call. */
 typedef struct _PurpleMedia PurpleMedia;
-/** An opaque structure representing a network candidate (IP Address and port pair). */
-typedef struct _PurpleMediaCandidate PurpleMediaCandidate;
-/** An opaque structure representing an audio or video codec. */
-typedef struct _PurpleMediaCodec PurpleMediaCodec;
-
-/** Media caps */
-typedef enum {
-	PURPLE_MEDIA_CAPS_NONE = 0,
-	PURPLE_MEDIA_CAPS_AUDIO = 1,
-	PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION = 1 << 1,
-	PURPLE_MEDIA_CAPS_VIDEO = 1 << 2,
-	PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION = 1 << 3,
-	PURPLE_MEDIA_CAPS_AUDIO_VIDEO = 1 << 4,
-	PURPLE_MEDIA_CAPS_MODIFY_SESSION = 1 << 5,
-	PURPLE_MEDIA_CAPS_CHANGE_DIRECTION = 1 << 6,
-} PurpleMediaCaps;
-
-/** Media session types */
-typedef enum {
-	PURPLE_MEDIA_NONE	= 0,
-	PURPLE_MEDIA_RECV_AUDIO = 1 << 0,
-	PURPLE_MEDIA_SEND_AUDIO = 1 << 1,
-	PURPLE_MEDIA_RECV_VIDEO = 1 << 2,
-	PURPLE_MEDIA_SEND_VIDEO = 1 << 3,
-	PURPLE_MEDIA_AUDIO = PURPLE_MEDIA_RECV_AUDIO | PURPLE_MEDIA_SEND_AUDIO,
-	PURPLE_MEDIA_VIDEO = PURPLE_MEDIA_RECV_VIDEO | PURPLE_MEDIA_SEND_VIDEO
-} PurpleMediaSessionType;
-
-/** Media state-changed types */
-typedef enum {
-	PURPLE_MEDIA_STATE_NEW = 0,
-	PURPLE_MEDIA_STATE_CONNECTED,
-	PURPLE_MEDIA_STATE_END,
-} PurpleMediaState;
-
-/** Media info types */
-typedef enum {
-	PURPLE_MEDIA_INFO_HANGUP = 0,
-	PURPLE_MEDIA_INFO_ACCEPT,
-	PURPLE_MEDIA_INFO_REJECT,
-	PURPLE_MEDIA_INFO_MUTE,
-	PURPLE_MEDIA_INFO_UNMUTE,
-	PURPLE_MEDIA_INFO_PAUSE,
-	PURPLE_MEDIA_INFO_UNPAUSE,
-	PURPLE_MEDIA_INFO_HOLD,
-	PURPLE_MEDIA_INFO_UNHOLD,
-} PurpleMediaInfoType;
-
-typedef enum {
-	PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
-	PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX,
-	PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX,
-	PURPLE_MEDIA_CANDIDATE_TYPE_RELAY,
-	PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST,
-} PurpleMediaCandidateType;
-
-typedef enum {
-	PURPLE_MEDIA_COMPONENT_NONE = 0,
-	PURPLE_MEDIA_COMPONENT_RTP = 1,
-	PURPLE_MEDIA_COMPONENT_RTCP = 2,
-} PurpleMediaComponentType;
-
-typedef enum {
-	PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
-	PURPLE_MEDIA_NETWORK_PROTOCOL_TCP,
-} PurpleMediaNetworkProtocol;
 
 #include "signals.h"
 #include "util.h"
@@ -137,33 +54,6 @@
 #endif
 
 /**
- * Gets the media session type's GType
- *
- * @return The media session type's GType.
- *
- * @since 2.6.0
- */
-GType purple_media_session_type_get_type(void);
-
-/**
- * Gets the media candidate type's GType
- *
- * @return The media candidate type's GType.
- *
- * @since 2.6.0
- */
-GType purple_media_candidate_type_get_type(void);
-
-/**
- * Gets the media network protocol's GType
- *
- * @return The media network protocol's GType.
- *
- * @since 2.6.0
- */
-GType purple_media_network_protocol_get_type(void);
-
-/**
  * Gets the media class's GType
  *
  * @return The media class's GType.
@@ -173,196 +63,6 @@
 GType purple_media_get_type(void);
 
 /**
- * Gets the type of the state-changed enum
- *
- * @return The state-changed enum's GType
- *
- * @since 2.6.0
- */
-GType purple_media_state_changed_get_type(void);
-
-/**
- * Gets the type of the info type enum
- *
- * @return The info type enum's GType
- *
- * @since 2.6.0
- */
-GType purple_media_info_type_get_type(void);
-
-/**
- * Gets the type of the media caps flags
- *
- * @return The media caps flags' GType
- *
- * @since 2.7.0
- */
-GType purple_media_caps_get_type(void);
-
-/**
- * Gets the type of the media candidate structure.
- *
- * @return The media canditate's GType
- *
- * @since 2.6.0
- */
-GType purple_media_candidate_get_type(void);
-
-/**
- * Creates a PurpleMediaCandidate instance.
- *
- * @param foundation The foundation of the candidate.
- * @param component_id The component this candidate is for.
- * @param type The type of candidate.
- * @param proto The protocol this component is for.
- * @param ip The IP address of this component.
- * @param port The network port.
- *
- * @return The newly created PurpleMediaCandidate instance.
- *
- * @since 2.6.0
- */
-PurpleMediaCandidate *purple_media_candidate_new(
-		const gchar *foundation, guint component_id,
-		PurpleMediaCandidateType type,
-		PurpleMediaNetworkProtocol proto,
-		const gchar *ip, guint port);
-
-/**
- * Copies a GList of PurpleMediaCandidate and its contents.
- *
- * @param candidates The list of candidates to be copied.
- *
- * @return The copy of the GList.
- *
- * @since 2.6.0
- */
-GList *purple_media_candidate_list_copy(GList *candidates);
-
-/**
- * Frees a GList of PurpleMediaCandidate and its contents.
- *
- * @param candidates The list of candidates to be freed.
- *
- * @since 2.6.0
- */
-void purple_media_candidate_list_free(GList *candidates);
-
-gchar *purple_media_candidate_get_foundation(PurpleMediaCandidate *candidate);
-guint purple_media_candidate_get_component_id(PurpleMediaCandidate *candidate);
-gchar *purple_media_candidate_get_ip(PurpleMediaCandidate *candidate);
-guint16 purple_media_candidate_get_port(PurpleMediaCandidate *candidate);
-gchar *purple_media_candidate_get_base_ip(PurpleMediaCandidate *candidate);
-guint16 purple_media_candidate_get_base_port(PurpleMediaCandidate *candidate);
-PurpleMediaNetworkProtocol purple_media_candidate_get_protocol(
-		PurpleMediaCandidate *candidate);
-guint32 purple_media_candidate_get_priority(PurpleMediaCandidate *candidate);
-PurpleMediaCandidateType purple_media_candidate_get_candidate_type(
-		PurpleMediaCandidate *candidate);
-gchar *purple_media_candidate_get_username(PurpleMediaCandidate *candidate);
-gchar *purple_media_candidate_get_password(PurpleMediaCandidate *candidate);
-guint purple_media_candidate_get_ttl(PurpleMediaCandidate *candidate);
-
-/**
- * Gets the type of the media codec structure.
- *
- * @return The media codec's GType
- *
- * @since 2.6.0
- */
-GType purple_media_codec_get_type(void);
-
-/**
- * Creates a new PurpleMediaCodec instance.
- *
- * @param id Codec identifier.
- * @param encoding_name Name of the media type this encodes.
- * @param media_type PurpleMediaSessionType of this codec.
- * @param clock_rate The clock rate this codec encodes at, if applicable.
- *
- * @return The newly created PurpleMediaCodec.
- *
- * @since 2.6.0
- */
-PurpleMediaCodec *purple_media_codec_new(int id, const char *encoding_name,
-		PurpleMediaSessionType media_type, guint clock_rate);
-
-guint purple_media_codec_get_id(PurpleMediaCodec *codec);
-gchar *purple_media_codec_get_encoding_name(PurpleMediaCodec *codec);
-guint purple_media_codec_get_clock_rate(PurpleMediaCodec *codec);
-guint purple_media_codec_get_channels(PurpleMediaCodec *codec);
-GList *purple_media_codec_get_optional_parameters(PurpleMediaCodec *codec);
-
-/**
- * Creates a string representation of the codec.
- *
- * @param codec The codec to create the string of.
- *
- * @return The new string representation.
- *
- * @since 2.6.0
- */
-gchar *purple_media_codec_to_string(const PurpleMediaCodec *codec);
-
-/**
- * Adds an optional parameter to the codec.
- *
- * @param codec The codec to add the parameter to.
- * @param name The name of the parameter to add.
- * @param value The value of the parameter to add.
- *
- * @since 2.6.0
- */
-void purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec,
-		const gchar *name, const gchar *value);
-
-/**
- * Removes an optional parameter from the codec.
- *
- * @param codec The codec to remove the parameter from.
- * @param param A pointer to the parameter to remove.
- *
- * @since 2.6.0
- */
-void purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec,
-		PurpleKeyValuePair *param);
-
-/**
- * Gets an optional parameter based on the values given.
- *
- * @param codec The codec to find the parameter in.
- * @param name The name of the parameter to search for.
- * @param value The value to search for or NULL.
- *
- * @return The value found or NULL.
- *
- * @since 2.6.0
- */
-PurpleKeyValuePair *purple_media_codec_get_optional_parameter(
-		PurpleMediaCodec *codec, const gchar *name,
-		const gchar *value);
-
-/**
- * Copies a GList of PurpleMediaCodec and its contents.
- *
- * @param codecs The list of codecs to be copied.
- *
- * @return The copy of the GList.
- *
- * @since 2.6.0
- */
-GList *purple_media_codec_list_copy(GList *codecs);
-
-/**
- * Frees a GList of PurpleMediaCodec and its contents.
- *
- * @param codecs The list of codecs to be freed.
- *
- * @since 2.6.0
- */
-void purple_media_codec_list_free(GList *codecs);
-
-/**
  * Gets a list of session IDs.
  *
  * @param media The media session from which to retrieve session IDs.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/backend-fs2.c	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,2009 @@
+/**
+ * @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 "backend-fs2.h"
+
+#include "internal.h"
+
+#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)
+			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 + 2);
+	FsStreamDirection type_direction =
+			session_type_to_fs_stream_direction(type);
+	PurpleMediaBackendFs2Session *session;
+	PurpleMediaBackendFs2Stream *stream;
+	FsParticipant *participant;
+
+	memcpy(_params, params, sizeof(GParameter) * 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,
+			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_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;
+}
+
+GstElement *
+purple_media_backend_fs2_get_src(PurpleMediaBackendFs2 *self,
+		const gchar *sess_id)
+{
+	PurpleMediaBackendFs2Session *session = get_session(self, sess_id);
+	return session != NULL ? session->src : NULL;
+}
+
+GstElement *
+purple_media_backend_fs2_get_tee(PurpleMediaBackendFs2 *self,
+		const gchar *sess_id, const gchar *who)
+{
+	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;
+	}
+
+	g_return_val_if_reached(NULL);
+}
+
+void
+purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self,
+		const gchar *sess_id, double level)
+{
+	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);
+		}
+	}
+}
+
+void
+purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
+		const gchar *sess_id, const gchar *who, double level)
+{
+	
+	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);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/backend-fs2.h	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,77 @@
+/**
+ * @file backend-fs2.h 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
+ */
+
+/*
+ * This file should not yet be part of libpurple's API.
+ * It should remain internal only for now.
+ */
+
+#ifndef _MEDIA_BACKEND_FS2_H_
+#define _MEDIA_BACKEND_FS2_H_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA_BACKEND_FS2            (purple_media_backend_fs2_get_type())
+#define PURPLE_IS_MEDIA_BACKEND_FS2(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_BACKEND_FS2))
+#define PURPLE_IS_MEDIA_BACKEND_FS2_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_BACKEND_FS2))
+#define PURPLE_MEDIA_BACKEND_FS2(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_BACKEND_FS2, PurpleMediaBackendFs2))
+#define PURPLE_MEDIA_BACKEND_FS2_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_BACKEND_FS2, PurpleMediaBackendFs2))
+#define PURPLE_MEDIA_BACKEND_FS2_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_BACKEND_FS2, PurpleMediaBackendFs2))
+
+/** An opaque structure representing the Farsight 2 media backend. */
+typedef struct _PurpleMediaBackendFs2 PurpleMediaBackendFs2;
+
+/**
+ * Gets the type of the Farsight 2 media backend object.
+ *
+ * @return The Farsight 2 media backend's GType
+ *
+ * @since 2.7.0
+ */
+GType purple_media_backend_fs2_get_type(void);
+
+/*
+ * Temporary function in order to be able to test while
+ * integrating with PurpleMedia
+ */
+#include <gst/gst.h>
+GstElement *purple_media_backend_fs2_get_src(
+		PurpleMediaBackendFs2 *self,
+		const gchar *sess_id);
+GstElement *purple_media_backend_fs2_get_tee(
+		PurpleMediaBackendFs2 *self,
+		const gchar *sess_id, const gchar *who);
+void purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self,
+		const gchar *sess_id, double level);
+void purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
+		const gchar *sess_id, const gchar *who, double level);
+/* end tmp */
+
+G_END_DECLS
+
+#endif /* _MEDIA_BACKEND_FS2_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/backend-iface.c	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,194 @@
+/**
+ * @file backend-iface.c Interface for media backend
+ * @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 "backend-iface.h"
+
+#include "marshallers.h"
+
+enum {
+	S_ERROR,
+	CANDIDATES_PREPARED,
+	CODECS_CHANGED,
+	NEW_CANDIDATE,
+	ACTIVE_CANDIDATE_PAIR,
+	LAST_SIGNAL
+};
+
+static guint purple_media_backend_signals[LAST_SIGNAL] = {0};
+
+static void
+purple_media_backend_base_init(gpointer iface)
+{
+	static gboolean is_initialized = FALSE;
+
+	if (is_initialized)
+		return;
+
+	g_object_interface_install_property(iface,
+			g_param_spec_string("conference-type",
+			"Conference Type",
+			"The type of conference that this backend "
+			"has been created to provide.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+	g_object_interface_install_property(iface,
+			g_param_spec_object("media",
+			"Purple Media",
+			"The media object that this backend is bound to.",
+			PURPLE_TYPE_MEDIA,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	purple_media_backend_signals[S_ERROR] =
+			g_signal_new("error", G_TYPE_FROM_CLASS(iface),
+			G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+			g_cclosure_marshal_VOID__STRING,
+			G_TYPE_NONE, 1, G_TYPE_STRING);
+	purple_media_backend_signals[CANDIDATES_PREPARED] =
+			g_signal_new("candidates-prepared",
+			G_TYPE_FROM_CLASS(iface),
+			G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+			purple_smarshal_VOID__STRING_STRING,
+			G_TYPE_NONE, 2, G_TYPE_STRING,
+			G_TYPE_STRING);
+	purple_media_backend_signals[CODECS_CHANGED] =
+			g_signal_new("codecs-changed",
+			G_TYPE_FROM_CLASS(iface),
+			G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+			g_cclosure_marshal_VOID__STRING,
+			G_TYPE_NONE, 1, G_TYPE_STRING);
+	purple_media_backend_signals[NEW_CANDIDATE] =
+			g_signal_new("new-candidate",
+			G_TYPE_FROM_CLASS(iface),
+			G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+			purple_smarshal_VOID__POINTER_POINTER_OBJECT,
+			G_TYPE_NONE, 3, G_TYPE_POINTER,
+			G_TYPE_POINTER, PURPLE_TYPE_MEDIA_CANDIDATE);
+	purple_media_backend_signals[ACTIVE_CANDIDATE_PAIR] =
+			g_signal_new("active-candidate-pair",
+			G_TYPE_FROM_CLASS(iface),
+			G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+			purple_smarshal_VOID__STRING_STRING_OBJECT_OBJECT,
+			G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING,
+			PURPLE_TYPE_MEDIA_CANDIDATE,
+			PURPLE_TYPE_MEDIA_CANDIDATE);
+
+	is_initialized = TRUE;
+}
+
+GType
+purple_media_backend_get_type(void)
+{
+	static GType iface_type = 0;
+	if (iface_type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PurpleMediaBackendIface),
+			purple_media_backend_base_init,
+			NULL,
+			NULL,
+			NULL,
+			NULL,
+			0,
+			0,
+			NULL,
+			NULL
+		};
+
+		iface_type = g_type_register_static (G_TYPE_INTERFACE,
+				"PurpleMediaBackend", &info, 0);
+	}
+
+	return iface_type;
+}
+
+gboolean
+purple_media_backend_add_stream(PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *who,
+		PurpleMediaSessionType type, gboolean initiator,
+		const gchar *transmitter,
+		guint num_params, GParameter *params)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND(self), FALSE);
+	return PURPLE_MEDIA_BACKEND_GET_INTERFACE(self)->add_stream(self,
+			sess_id, who, type, initiator, transmitter,
+			num_params, params);
+}
+
+void
+purple_media_backend_add_remote_candidates(PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant,
+		GList *remote_candidates)
+{
+	g_return_if_fail(PURPLE_IS_MEDIA_BACKEND(self));
+	PURPLE_MEDIA_BACKEND_GET_INTERFACE(self)->add_remote_candidates(self,
+			sess_id, participant, remote_candidates);
+}
+
+gboolean
+purple_media_backend_codecs_ready(PurpleMediaBackend *self,
+		const gchar *sess_id)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND(self), FALSE);
+	return PURPLE_MEDIA_BACKEND_GET_INTERFACE(self)->codecs_ready(self,
+			sess_id);
+}
+
+GList *
+purple_media_backend_get_codecs(PurpleMediaBackend *self,
+		const gchar *sess_id)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND(self), NULL);
+	return PURPLE_MEDIA_BACKEND_GET_INTERFACE(self)->get_codecs(self,
+			sess_id);
+}
+
+GList *
+purple_media_backend_get_local_candidates(PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND(self), NULL);
+	return PURPLE_MEDIA_BACKEND_GET_INTERFACE(self)->
+			get_local_candidates(self,
+			sess_id, participant);
+}
+
+gboolean
+purple_media_backend_set_remote_codecs(PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant,
+		GList *codecs)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND(self), FALSE);
+	return PURPLE_MEDIA_BACKEND_GET_INTERFACE(self)->set_remote_codecs(
+			self, sess_id, participant, codecs);
+}
+
+gboolean
+purple_media_backend_set_send_codec(PurpleMediaBackend *self,
+		const gchar *sess_id, PurpleMediaCodec *codec)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND(self), FALSE);
+	return PURPLE_MEDIA_BACKEND_GET_INTERFACE(self)->set_send_codec(self,
+			sess_id, codec);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/backend-iface.h	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,94 @@
+/**
+ * @file backend-iface.h Interface for media backends
+ * @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
+ */
+
+#ifndef _MEDIA_BACKEND_IFACE_H_
+#define _MEDIA_BACKEND_IFACE_H_
+
+#include "codec.h"
+#include "enum-types.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA_BACKEND		(purple_media_backend_get_type())
+#define PURPLE_IS_MEDIA_BACKEND(obj)		(G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_BACKEND))
+#define PURPLE_MEDIA_BACKEND(obj)		(G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_BACKEND, PurpleMediaBackend))
+#define PURPLE_MEDIA_BACKEND_GET_INTERFACE(inst)(G_TYPE_INSTANCE_GET_INTERFACE((inst), PURPLE_TYPE_MEDIA_BACKEND, PurpleMediaBackendIface))
+
+typedef struct _PurpleMediaBackend PurpleMediaBackend;
+typedef struct _PurpleMediaBackendIface PurpleMediaBackendIface;
+
+struct _PurpleMediaBackendIface
+{
+	GTypeInterface parent_iface;
+
+	gboolean (*add_stream) (PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *who,
+		PurpleMediaSessionType type, gboolean initiator,
+		const gchar *transmitter,
+		guint num_params, GParameter *params);
+	void (*add_remote_candidates) (PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant,
+		GList *remote_candidates);
+	gboolean (*codecs_ready) (PurpleMediaBackend *self,
+		const gchar *sess_id);
+	GList *(*get_codecs) (PurpleMediaBackend *self,
+		const gchar *sess_id);
+	GList *(*get_local_candidates) (PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant);
+	gboolean (*set_remote_codecs) (PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant,
+		GList *codecs);
+	gboolean (*set_send_codec) (PurpleMediaBackend *self,
+		const gchar *sess_id, PurpleMediaCodec *codec);
+};
+
+GType purple_media_backend_get_type(void);
+
+gboolean purple_media_backend_add_stream(PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *who,
+		PurpleMediaSessionType type, gboolean initiator,
+		const gchar *transmitter,
+		guint num_params, GParameter *params);
+void purple_media_backend_add_remote_candidates(PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant,
+		GList *remote_candidates);
+gboolean purple_media_backend_codecs_ready(PurpleMediaBackend *self,
+		const gchar *sess_id);
+GList *purple_media_backend_get_codecs(PurpleMediaBackend *self,
+		const gchar *sess_id);
+GList *purple_media_backend_get_local_candidates(PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant);
+gboolean purple_media_backend_set_remote_codecs(PurpleMediaBackend *self,
+		const gchar *sess_id, const gchar *participant,
+		GList *codecs);
+gboolean purple_media_backend_set_send_codec(PurpleMediaBackend *self,
+		const gchar *sess_id, PurpleMediaCodec *codec);
+
+G_END_DECLS
+
+#endif /* _MEDIA_BACKEND_IFACE_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/candidate.c	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,495 @@
+/**
+ * @file candidate.c Candidate 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 "candidate.h"
+
+/** @copydoc _PurpleMediaCandidateClass */
+typedef struct _PurpleMediaCandidateClass PurpleMediaCandidateClass;
+/** @copydoc _PurpleMediaCandidatePrivate */
+typedef struct _PurpleMediaCandidatePrivate PurpleMediaCandidatePrivate;
+
+#define PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(obj) \
+		(G_TYPE_INSTANCE_GET_PRIVATE((obj), \
+		PURPLE_TYPE_MEDIA_CANDIDATE, \
+		PurpleMediaCandidatePrivate))
+
+
+struct _PurpleMediaCandidateClass
+{
+	GObjectClass parent_class;
+};
+
+struct _PurpleMediaCandidate
+{
+	GObject parent;
+};
+
+G_DEFINE_TYPE(PurpleMediaCandidate, purple_media_candidate, G_TYPE_OBJECT);
+
+struct _PurpleMediaCandidatePrivate
+{
+	gchar *foundation;
+	guint component_id;
+	gchar *ip;
+	guint16 port;
+	gchar *base_ip;
+	guint16 base_port;
+	PurpleMediaNetworkProtocol proto;
+	guint32 priority;
+	PurpleMediaCandidateType type;
+	gchar *username;
+	gchar *password;
+	guint ttl;
+};
+
+enum {
+	PROP_CANDIDATE_0,
+	PROP_FOUNDATION,
+	PROP_COMPONENT_ID,
+	PROP_IP,
+	PROP_PORT,
+	PROP_BASE_IP,
+	PROP_BASE_PORT,
+	PROP_PROTOCOL,
+	PROP_PRIORITY,
+	PROP_TYPE,
+	PROP_USERNAME,
+	PROP_PASSWORD,
+	PROP_TTL,
+};
+
+static void
+purple_media_candidate_init(PurpleMediaCandidate *info)
+{
+	PurpleMediaCandidatePrivate *priv =
+			PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(info);
+	priv->foundation = NULL;
+	priv->component_id = 0;
+	priv->ip = NULL;
+	priv->port = 0;
+	priv->base_ip = NULL;
+	priv->proto = PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
+	priv->priority = 0;
+	priv->type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
+	priv->username = NULL;
+	priv->password = NULL;
+	priv->ttl = 0;
+}
+
+static void
+purple_media_candidate_finalize(GObject *info)
+{
+	PurpleMediaCandidatePrivate *priv =
+			PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(info);
+
+	g_free(priv->foundation);
+	g_free(priv->ip);
+	g_free(priv->base_ip);
+	g_free(priv->username);
+	g_free(priv->password);
+}
+
+static void
+purple_media_candidate_set_property (GObject *object, guint prop_id,
+		const GValue *value, GParamSpec *pspec)
+{
+	PurpleMediaCandidatePrivate *priv;
+	g_return_if_fail(PURPLE_IS_MEDIA_CANDIDATE(object));
+
+	priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(object);
+
+	switch (prop_id) {
+		case PROP_FOUNDATION:
+			g_free(priv->foundation);
+			priv->foundation = g_value_dup_string(value);
+			break;
+		case PROP_COMPONENT_ID:
+			priv->component_id = g_value_get_uint(value);
+			break;
+		case PROP_IP:
+			g_free(priv->ip);
+			priv->ip = g_value_dup_string(value);
+			break;
+		case PROP_PORT:
+			priv->port = g_value_get_uint(value);
+			break;
+		case PROP_BASE_IP:
+			g_free(priv->base_ip);
+			priv->base_ip = g_value_dup_string(value);
+			break;
+		case PROP_BASE_PORT:
+			priv->base_port = g_value_get_uint(value);
+			break;
+		case PROP_PROTOCOL:
+			priv->proto = g_value_get_enum(value);
+			break;
+		case PROP_PRIORITY:
+			priv->priority = g_value_get_uint(value);
+			break;
+		case PROP_TYPE:
+			priv->type = g_value_get_enum(value);
+			break;
+		case PROP_USERNAME:
+			g_free(priv->username);
+			priv->username = g_value_dup_string(value);
+			break;
+		case PROP_PASSWORD:
+			g_free(priv->password);
+			priv->password = g_value_dup_string(value);
+			break;
+		case PROP_TTL:
+			priv->ttl = g_value_get_uint(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(
+					object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_media_candidate_get_property (GObject *object, guint prop_id,
+		GValue *value, GParamSpec *pspec)
+{
+	PurpleMediaCandidatePrivate *priv;
+	g_return_if_fail(PURPLE_IS_MEDIA_CANDIDATE(object));
+	
+	priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(object);
+
+	switch (prop_id) {
+		case PROP_FOUNDATION:
+			g_value_set_string(value, priv->foundation);
+			break;
+		case PROP_COMPONENT_ID:
+			g_value_set_uint(value, priv->component_id);
+			break;
+		case PROP_IP:
+			g_value_set_string(value, priv->ip);
+			break;
+		case PROP_PORT:
+			g_value_set_uint(value, priv->port);
+			break;
+		case PROP_BASE_IP:
+			g_value_set_string(value, priv->base_ip);
+			break;
+		case PROP_BASE_PORT:
+			g_value_set_uint(value, priv->base_port);
+			break;
+		case PROP_PROTOCOL:
+			g_value_set_enum(value, priv->proto);
+			break;
+		case PROP_PRIORITY:
+			g_value_set_uint(value, priv->priority);
+			break;
+		case PROP_TYPE:
+			g_value_set_enum(value, priv->type);
+			break;
+		case PROP_USERNAME:
+			g_value_set_string(value, priv->username);
+			break;
+		case PROP_PASSWORD:
+			g_value_set_string(value, priv->password);
+			break;
+		case PROP_TTL:
+			g_value_set_uint(value, priv->ttl);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(
+					object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_media_candidate_class_init(PurpleMediaCandidateClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	
+	gobject_class->finalize = purple_media_candidate_finalize;
+	gobject_class->set_property = purple_media_candidate_set_property;
+	gobject_class->get_property = purple_media_candidate_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_FOUNDATION,
+			g_param_spec_string("foundation",
+			"Foundation",
+			"The foundation of the candidate.",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_COMPONENT_ID,
+			g_param_spec_uint("component-id",
+			"Component ID",
+			"The component id of the candidate.",
+			0, G_MAXUINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_IP,
+			g_param_spec_string("ip",
+			"IP Address",
+			"The IP address of the candidate.",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_PORT,
+			g_param_spec_uint("port",
+			"Port",
+			"The port of the candidate.",
+			0, G_MAXUINT16, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_BASE_IP,
+			g_param_spec_string("base-ip",
+			"Base IP",
+			"The internal IP address of the candidate.",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_BASE_PORT,
+			g_param_spec_uint("base-port",
+			"Base Port",
+			"The internal port of the candidate.",
+			0, G_MAXUINT16, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_PROTOCOL,
+			g_param_spec_enum("protocol",
+			"Protocol",
+			"The protocol of the candidate.",
+			PURPLE_TYPE_MEDIA_NETWORK_PROTOCOL,
+			PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_PRIORITY,
+			g_param_spec_uint("priority",
+			"Priority",
+			"The priority of the candidate.",
+			0, G_MAXUINT32, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_TYPE,
+			g_param_spec_enum("type",
+			"Type",
+			"The type of the candidate.",
+			PURPLE_TYPE_MEDIA_CANDIDATE_TYPE,
+			PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_USERNAME,
+			g_param_spec_string("username",
+			"Username",
+			"The username used to connect to the candidate.",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_PASSWORD,
+			g_param_spec_string("password",
+			"Password",
+			"The password use to connect to the candidate.",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_TTL,
+			g_param_spec_uint("ttl",
+			"TTL",
+			"The TTL of the candidate.",
+			0, G_MAXUINT, 0,
+			G_PARAM_READWRITE));
+
+	g_type_class_add_private(klass, sizeof(PurpleMediaCandidatePrivate));
+}
+
+PurpleMediaCandidate *
+purple_media_candidate_new(const gchar *foundation, guint component_id,
+		PurpleMediaCandidateType type,
+		PurpleMediaNetworkProtocol proto,
+		const gchar *ip, guint port)
+{
+	return g_object_new(PURPLE_TYPE_MEDIA_CANDIDATE,
+			"foundation", foundation,
+			"component-id", component_id,
+			"type", type,
+			"protocol", proto,
+			"ip", ip,
+			"port", port, NULL);
+}
+
+PurpleMediaCandidate *
+purple_media_candidate_copy(PurpleMediaCandidate *candidate)
+{
+	PurpleMediaCandidatePrivate *priv;
+	PurpleMediaCandidate *new_candidate;
+
+	if (candidate == NULL)
+		return NULL;
+
+	priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(candidate);
+
+	new_candidate = purple_media_candidate_new(priv->foundation,
+			priv->component_id, priv->type, priv->proto,
+			priv->ip, priv->port);
+	g_object_set(new_candidate,
+			"base-ip", priv->base_ip,
+			"base-port", priv->base_port,
+			"priority", priv->priority,
+			"username", priv->username,
+			"password", priv->password,
+			"ttl", priv->ttl, NULL);
+	return new_candidate;
+}
+
+GList *
+purple_media_candidate_list_copy(GList *candidates)
+{
+	GList *new_list = NULL;
+
+	for (; candidates; candidates = g_list_next(candidates)) {
+		new_list = g_list_prepend(new_list,
+				purple_media_candidate_copy(candidates->data));
+	}
+
+	new_list = g_list_reverse(new_list);
+	return new_list;
+}
+
+void
+purple_media_candidate_list_free(GList *candidates)
+{
+	for (; candidates; candidates =
+			g_list_delete_link(candidates, candidates)) {
+		g_object_unref(candidates->data);
+	}
+}
+
+gchar *
+purple_media_candidate_get_foundation(PurpleMediaCandidate *candidate)
+{
+	gchar *foundation;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
+	g_object_get(candidate, "foundation", &foundation, NULL);
+	return foundation;
+}
+
+guint
+purple_media_candidate_get_component_id(PurpleMediaCandidate *candidate)
+{
+	guint component_id;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
+	g_object_get(candidate, "component-id", &component_id, NULL);
+	return component_id;
+}
+
+gchar *
+purple_media_candidate_get_ip(PurpleMediaCandidate *candidate)
+{
+	gchar *ip;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
+	g_object_get(candidate, "ip", &ip, NULL);
+	return ip;
+}
+
+guint16
+purple_media_candidate_get_port(PurpleMediaCandidate *candidate)
+{
+	guint port;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
+	g_object_get(candidate, "port", &port, NULL);
+	return port;
+}
+
+gchar *
+purple_media_candidate_get_base_ip(PurpleMediaCandidate *candidate)
+{
+	gchar *base_ip;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
+	g_object_get(candidate, "base-ip", &base_ip, NULL);
+	return base_ip;
+}
+
+guint16
+purple_media_candidate_get_base_port(PurpleMediaCandidate *candidate)
+{
+	guint base_port;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
+	g_object_get(candidate, "base_port", &base_port, NULL);
+	return base_port;
+}
+
+PurpleMediaNetworkProtocol
+purple_media_candidate_get_protocol(PurpleMediaCandidate *candidate)
+{
+	PurpleMediaNetworkProtocol protocol;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate),
+			PURPLE_MEDIA_NETWORK_PROTOCOL_UDP);
+	g_object_get(candidate, "protocol", &protocol, NULL);
+	return protocol;
+}
+
+guint32
+purple_media_candidate_get_priority(PurpleMediaCandidate *candidate)
+{
+	guint priority;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
+	g_object_get(candidate, "priority", &priority, NULL);
+	return priority;
+}
+
+PurpleMediaCandidateType
+purple_media_candidate_get_candidate_type(PurpleMediaCandidate *candidate)
+{
+	PurpleMediaCandidateType type;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate),
+			PURPLE_MEDIA_CANDIDATE_TYPE_HOST);
+	g_object_get(candidate, "type", &type, NULL);
+	return type;
+}
+
+gchar *
+purple_media_candidate_get_username(PurpleMediaCandidate *candidate)
+{
+	gchar *username;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
+	g_object_get(candidate, "username", &username, NULL);
+	return username;
+}
+
+gchar *
+purple_media_candidate_get_password(PurpleMediaCandidate *candidate)
+{
+	gchar *password;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL);
+	g_object_get(candidate, "password", &password, NULL);
+	return password;
+}
+
+guint
+purple_media_candidate_get_ttl(PurpleMediaCandidate *candidate)
+{
+	guint ttl;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0);
+	g_object_get(candidate, "ttl", &ttl, NULL);
+	return ttl;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/candidate.h	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,125 @@
+/**
+ * @file candidate.h Candidate 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
+ */
+
+#ifndef _PURPLE_MEDIA_CANDIDATE_H_
+#define _PURPLE_MEDIA_CANDIDATE_H_
+
+#include "enum-types.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA_CANDIDATE            (purple_media_candidate_get_type())
+#define PURPLE_IS_MEDIA_CANDIDATE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_CANDIDATE))
+#define PURPLE_IS_MEDIA_CANDIDATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_CANDIDATE))
+#define PURPLE_MEDIA_CANDIDATE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate))
+#define PURPLE_MEDIA_CANDIDATE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate))
+#define PURPLE_MEDIA_CANDIDATE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate))
+
+/** An opaque structure representing a network candidate (IP Address and port pair). */
+typedef struct _PurpleMediaCandidate PurpleMediaCandidate;
+
+/**
+ * Gets the type of the media candidate structure.
+ *
+ * @return The media canditate's GType
+ *
+ * @since 2.6.0
+ */
+GType purple_media_candidate_get_type(void);
+
+/**
+ * Creates a PurpleMediaCandidate instance.
+ *
+ * @param foundation The foundation of the candidate.
+ * @param component_id The component this candidate is for.
+ * @param type The type of candidate.
+ * @param proto The protocol this component is for.
+ * @param ip The IP address of this component.
+ * @param port The network port.
+ *
+ * @return The newly created PurpleMediaCandidate instance.
+ *
+ * @since 2.6.0
+ */
+PurpleMediaCandidate *purple_media_candidate_new(
+		const gchar *foundation, guint component_id,
+		PurpleMediaCandidateType type,
+		PurpleMediaNetworkProtocol proto,
+		const gchar *ip, guint port);
+
+/**
+ * Copies a PurpleMediaCandidate.
+ *
+ * @param candidate The candidate to copy.
+ *
+ * @return The copy of the PurpleMediaCandidate.
+ *
+ * @since 2.7.0
+ */
+PurpleMediaCandidate *purple_media_candidate_copy(
+		PurpleMediaCandidate *candidate);
+
+/**
+ * Copies a GList of PurpleMediaCandidate and its contents.
+ *
+ * @param candidates The list of candidates to be copied.
+ *
+ * @return The copy of the GList.
+ *
+ * @since 2.6.0
+ */
+GList *purple_media_candidate_list_copy(GList *candidates);
+
+/**
+ * Frees a GList of PurpleMediaCandidate and its contents.
+ *
+ * @param candidates The list of candidates to be freed.
+ *
+ * @since 2.6.0
+ */
+void purple_media_candidate_list_free(GList *candidates);
+
+gchar *purple_media_candidate_get_foundation(PurpleMediaCandidate *candidate);
+guint purple_media_candidate_get_component_id(PurpleMediaCandidate *candidate);
+gchar *purple_media_candidate_get_ip(PurpleMediaCandidate *candidate);
+guint16 purple_media_candidate_get_port(PurpleMediaCandidate *candidate);
+gchar *purple_media_candidate_get_base_ip(PurpleMediaCandidate *candidate);
+guint16 purple_media_candidate_get_base_port(PurpleMediaCandidate *candidate);
+PurpleMediaNetworkProtocol purple_media_candidate_get_protocol(
+		PurpleMediaCandidate *candidate);
+guint32 purple_media_candidate_get_priority(PurpleMediaCandidate *candidate);
+PurpleMediaCandidateType purple_media_candidate_get_candidate_type(
+		PurpleMediaCandidate *candidate);
+gchar *purple_media_candidate_get_username(PurpleMediaCandidate *candidate);
+gchar *purple_media_candidate_get_password(PurpleMediaCandidate *candidate);
+guint purple_media_candidate_get_ttl(PurpleMediaCandidate *candidate);
+
+G_END_DECLS
+
+#endif  /* _PURPLE_MEDIA_CANDIDATE_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/codec.c	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,419 @@
+/**
+ * @file codec.c Codec 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 "codec.h"
+
+/** @copydoc _PurpleMediaCodecClass */
+typedef struct _PurpleMediaCodecClass PurpleMediaCodecClass;
+/** @copydoc _PurpleMediaCodecPrivate */
+typedef struct _PurpleMediaCodecPrivate PurpleMediaCodecPrivate;
+
+#define PURPLE_MEDIA_CODEC_GET_PRIVATE(obj) \
+		(G_TYPE_INSTANCE_GET_PRIVATE((obj), \
+		PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodecPrivate))
+
+struct _PurpleMediaCodecClass
+{
+	GObjectClass parent_class;
+};
+
+struct _PurpleMediaCodec
+{
+	GObject parent;
+};
+
+G_DEFINE_TYPE(PurpleMediaCodec, purple_media_codec, G_TYPE_OBJECT);
+
+struct _PurpleMediaCodecPrivate
+{
+	gint id;
+	char *encoding_name;
+	PurpleMediaSessionType media_type;
+	guint clock_rate;
+	guint channels;
+	GList *optional_params;
+};
+
+enum {
+	PROP_CODEC_0,
+	PROP_ID,
+	PROP_ENCODING_NAME,
+	PROP_MEDIA_TYPE,
+	PROP_CLOCK_RATE,
+	PROP_CHANNELS,
+	PROP_OPTIONAL_PARAMS,
+};
+
+static void
+purple_media_codec_init(PurpleMediaCodec *info)
+{
+	PurpleMediaCodecPrivate *priv =
+			PURPLE_MEDIA_CODEC_GET_PRIVATE(info);
+	priv->encoding_name = NULL;
+	priv->optional_params = NULL;
+}
+
+static void
+purple_media_codec_finalize(GObject *info)
+{
+	PurpleMediaCodecPrivate *priv =
+			PURPLE_MEDIA_CODEC_GET_PRIVATE(info);
+	g_free(priv->encoding_name);
+	for (; priv->optional_params; priv->optional_params =
+			g_list_delete_link(priv->optional_params,
+			priv->optional_params)) {
+		g_free(priv->optional_params->data);
+	}
+}
+
+static void
+purple_media_codec_set_property (GObject *object, guint prop_id,
+		const GValue *value, GParamSpec *pspec)
+{
+	PurpleMediaCodecPrivate *priv;
+	g_return_if_fail(PURPLE_IS_MEDIA_CODEC(object));
+
+	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(object);
+
+	switch (prop_id) {
+		case PROP_ID:
+			priv->id = g_value_get_uint(value);
+			break;
+		case PROP_ENCODING_NAME:
+			g_free(priv->encoding_name);
+			priv->encoding_name = g_value_dup_string(value);
+			break;
+		case PROP_MEDIA_TYPE:
+			priv->media_type = g_value_get_flags(value);
+			break;
+		case PROP_CLOCK_RATE:
+			priv->clock_rate = g_value_get_uint(value);
+			break;
+		case PROP_CHANNELS:
+			priv->channels = g_value_get_uint(value);
+			break;
+		case PROP_OPTIONAL_PARAMS:
+			priv->optional_params = g_value_get_pointer(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(
+					object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_media_codec_get_property (GObject *object, guint prop_id,
+		GValue *value, GParamSpec *pspec)
+{
+	PurpleMediaCodecPrivate *priv;
+	g_return_if_fail(PURPLE_IS_MEDIA_CODEC(object));
+	
+	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(object);
+
+	switch (prop_id) {
+		case PROP_ID:
+			g_value_set_uint(value, priv->id);
+			break;
+		case PROP_ENCODING_NAME:
+			g_value_set_string(value, priv->encoding_name);
+			break;
+		case PROP_MEDIA_TYPE:
+			g_value_set_flags(value, priv->media_type);
+			break;
+		case PROP_CLOCK_RATE:
+			g_value_set_uint(value, priv->clock_rate);
+			break;
+		case PROP_CHANNELS:
+			g_value_set_uint(value, priv->channels);
+			break;
+		case PROP_OPTIONAL_PARAMS:
+			g_value_set_pointer(value, priv->optional_params);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(
+					object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_media_codec_class_init(PurpleMediaCodecClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	
+	gobject_class->finalize = purple_media_codec_finalize;
+	gobject_class->set_property = purple_media_codec_set_property;
+	gobject_class->get_property = purple_media_codec_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_ID,
+			g_param_spec_uint("id",
+			"ID",
+			"The numeric identifier of the codec.",
+			0, G_MAXUINT, 0,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_ENCODING_NAME,
+			g_param_spec_string("encoding-name",
+			"Encoding Name",
+			"The name of the codec.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_MEDIA_TYPE,
+			g_param_spec_flags("media-type",
+			"Media Type",
+			"Whether this is an audio of video codec.",
+			PURPLE_TYPE_MEDIA_SESSION_TYPE,
+			PURPLE_MEDIA_NONE,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_CLOCK_RATE,
+			g_param_spec_uint("clock-rate",
+			"Create Callback",
+			"The function called to create this element.",
+			0, G_MAXUINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_CHANNELS,
+			g_param_spec_uint("channels",
+			"Channels",
+			"The number of channels in this codec.",
+			0, G_MAXUINT, 0,
+			G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_OPTIONAL_PARAMS,
+			g_param_spec_pointer("optional-params",
+			"Optional Params",
+			"A list of optional parameters for the codec.",
+			G_PARAM_READWRITE));
+
+	g_type_class_add_private(klass, sizeof(PurpleMediaCodecPrivate));
+}
+
+PurpleMediaCodec *
+purple_media_codec_new(int id, const char *encoding_name,
+		PurpleMediaSessionType media_type, guint clock_rate)
+{
+	PurpleMediaCodec *codec =
+			g_object_new(PURPLE_TYPE_MEDIA_CODEC,
+			"id", id,
+			"encoding_name", encoding_name,
+			"media_type", media_type,
+			"clock-rate", clock_rate, NULL);
+	return codec;
+}
+
+guint
+purple_media_codec_get_id(PurpleMediaCodec *codec)
+{
+	guint id;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0);
+	g_object_get(codec, "id", &id, NULL);
+	return id;
+}
+
+gchar *
+purple_media_codec_get_encoding_name(PurpleMediaCodec *codec)
+{
+	gchar *name;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), NULL);
+	g_object_get(codec, "encoding-name", &name, NULL);
+	return name;
+}
+
+guint
+purple_media_codec_get_clock_rate(PurpleMediaCodec *codec)
+{
+	guint clock_rate;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0);
+	g_object_get(codec, "clock-rate", &clock_rate, NULL);
+	return clock_rate;
+}
+
+guint
+purple_media_codec_get_channels(PurpleMediaCodec *codec)
+{
+	guint channels;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0);
+	g_object_get(codec, "channels", &channels, NULL);
+	return channels;
+}
+
+GList *
+purple_media_codec_get_optional_parameters(PurpleMediaCodec *codec)
+{
+	GList *optional_params;
+	g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), NULL);
+	g_object_get(codec, "optional-params", &optional_params, NULL);
+	return optional_params;
+}
+
+void
+purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec,
+		const gchar *name, const gchar *value)
+{
+	PurpleMediaCodecPrivate *priv;
+	PurpleKeyValuePair *new_param;
+
+	g_return_if_fail(codec != NULL);
+	g_return_if_fail(name != NULL && value != NULL);
+
+	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
+
+	new_param = g_new0(PurpleKeyValuePair, 1);
+	new_param->key = g_strdup(name);
+	new_param->value = g_strdup(value);
+	priv->optional_params = g_list_append(
+			priv->optional_params, new_param);
+}
+
+void
+purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec,
+		PurpleKeyValuePair *param)
+{
+	PurpleMediaCodecPrivate *priv;
+
+	g_return_if_fail(codec != NULL && param != NULL);
+
+	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
+
+	g_free(param->key);
+	g_free(param->value);
+	g_free(param);
+
+	priv->optional_params =
+			g_list_remove(priv->optional_params, param);
+}
+
+PurpleKeyValuePair *
+purple_media_codec_get_optional_parameter(PurpleMediaCodec *codec,
+		const gchar *name, const gchar *value)
+{
+	PurpleMediaCodecPrivate *priv;
+	GList *iter;
+
+	g_return_val_if_fail(codec != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+
+	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
+
+	for (iter = priv->optional_params; iter; iter = g_list_next(iter)) {
+		PurpleKeyValuePair *param = iter->data;
+		if (!g_ascii_strcasecmp(param->key, name) &&
+				(value == NULL ||
+				!g_ascii_strcasecmp(param->value, value)))
+			return param;
+	}
+
+	return NULL;
+}
+
+PurpleMediaCodec *
+purple_media_codec_copy(PurpleMediaCodec *codec)
+{
+	PurpleMediaCodecPrivate *priv;
+	PurpleMediaCodec *new_codec;
+	GList *iter;
+
+	if (codec == NULL)
+		return NULL;
+
+	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
+
+	new_codec = purple_media_codec_new(priv->id, priv->encoding_name,
+			priv->media_type, priv->clock_rate);
+	g_object_set(codec, "channels", priv->channels, NULL);
+
+	for (iter = priv->optional_params; iter; iter = g_list_next(iter)) {
+		PurpleKeyValuePair *param =
+				(PurpleKeyValuePair*)iter->data;
+		purple_media_codec_add_optional_parameter(new_codec,
+				param->key, param->value);
+	}
+
+	return new_codec;
+}
+
+GList *
+purple_media_codec_list_copy(GList *codecs)
+{
+	GList *new_list = NULL;
+
+	for (; codecs; codecs = g_list_next(codecs)) {
+		new_list = g_list_prepend(new_list,
+				purple_media_codec_copy(codecs->data));
+	}
+
+	new_list = g_list_reverse(new_list);
+	return new_list;
+}
+
+void
+purple_media_codec_list_free(GList *codecs)
+{
+	for (; codecs; codecs =
+			g_list_delete_link(codecs, codecs)) {
+		g_object_unref(codecs->data);
+	}
+}
+
+gchar *
+purple_media_codec_to_string(const PurpleMediaCodec *codec)
+{
+	PurpleMediaCodecPrivate *priv;
+	GString *string = NULL;
+	GList *item;
+	gchar *charstring;
+	const gchar *media_type_str = NULL;
+
+	if (codec == NULL)
+		return g_strdup("(NULL)");
+
+	priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec);
+
+	string = g_string_new("");
+
+	if (priv->media_type & PURPLE_MEDIA_AUDIO)
+		media_type_str = "audio";
+	else if (priv->media_type & PURPLE_MEDIA_VIDEO)
+		media_type_str = "video";
+
+	g_string_printf(string, "%d: %s %s clock:%d channels:%d", priv->id,
+			media_type_str, priv->encoding_name,
+			priv->clock_rate, priv->channels);
+
+	for (item = priv->optional_params; item; item = g_list_next (item)) {
+		PurpleKeyValuePair *param = item->data;
+		g_string_append_printf (string, " %s=%s",
+				param->key, (gchar *)param->value);
+	}
+
+	charstring = string->str;
+	g_string_free (string, FALSE);
+
+	return charstring;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/codec.h	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,162 @@
+/**
+ * @file codec.h Codec 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
+ */
+
+#ifndef _PURPLE_MEDIA_CODEC_H_
+#define _PURPLE_MEDIA_CODEC_H_
+
+#include "enum-types.h"
+
+/** An opaque structure representing an audio or video codec. */
+typedef struct _PurpleMediaCodec PurpleMediaCodec;
+
+#include "util.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA_CODEC            (purple_media_codec_get_type())
+#define PURPLE_IS_MEDIA_CODEC(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_CODEC))
+#define PURPLE_IS_MEDIA_CODEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_CODEC))
+#define PURPLE_MEDIA_CODEC(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec))
+#define PURPLE_MEDIA_CODEC_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec))
+#define PURPLE_MEDIA_CODEC_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec))
+
+
+/**
+ * Gets the type of the media codec structure.
+ *
+ * @return The media codec's GType
+ *
+ * @since 2.6.0
+ */
+GType purple_media_codec_get_type(void);
+
+/**
+ * Creates a new PurpleMediaCodec instance.
+ *
+ * @param id Codec identifier.
+ * @param encoding_name Name of the media type this encodes.
+ * @param media_type PurpleMediaSessionType of this codec.
+ * @param clock_rate The clock rate this codec encodes at, if applicable.
+ *
+ * @return The newly created PurpleMediaCodec.
+ *
+ * @since 2.6.0
+ */
+PurpleMediaCodec *purple_media_codec_new(int id, const char *encoding_name,
+		PurpleMediaSessionType media_type, guint clock_rate);
+
+guint purple_media_codec_get_id(PurpleMediaCodec *codec);
+gchar *purple_media_codec_get_encoding_name(PurpleMediaCodec *codec);
+guint purple_media_codec_get_clock_rate(PurpleMediaCodec *codec);
+guint purple_media_codec_get_channels(PurpleMediaCodec *codec);
+GList *purple_media_codec_get_optional_parameters(PurpleMediaCodec *codec);
+
+/**
+ * Adds an optional parameter to the codec.
+ *
+ * @param codec The codec to add the parameter to.
+ * @param name The name of the parameter to add.
+ * @param value The value of the parameter to add.
+ *
+ * @since 2.6.0
+ */
+void purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec,
+		const gchar *name, const gchar *value);
+
+/**
+ * Removes an optional parameter from the codec.
+ *
+ * @param codec The codec to remove the parameter from.
+ * @param param A pointer to the parameter to remove.
+ *
+ * @since 2.6.0
+ */
+void purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec,
+		PurpleKeyValuePair *param);
+
+/**
+ * Gets an optional parameter based on the values given.
+ *
+ * @param codec The codec to find the parameter in.
+ * @param name The name of the parameter to search for.
+ * @param value The value to search for or NULL.
+ *
+ * @return The value found or NULL.
+ *
+ * @since 2.6.0
+ */
+PurpleKeyValuePair *purple_media_codec_get_optional_parameter(
+		PurpleMediaCodec *codec, const gchar *name,
+		const gchar *value);
+
+/**
+ * Copies a PurpleMediaCodec object.
+ *
+ * @param codec The codec to copy.
+ *
+ * @return The copy of the codec.
+ *
+ * @since 2.7.0
+ */
+PurpleMediaCodec *purple_media_codec_copy(PurpleMediaCodec *codec);
+
+/**
+ * Copies a GList of PurpleMediaCodec and its contents.
+ *
+ * @param codecs The list of codecs to be copied.
+ *
+ * @return The copy of the GList.
+ *
+ * @since 2.6.0
+ */
+GList *purple_media_codec_list_copy(GList *codecs);
+
+/**
+ * Frees a GList of PurpleMediaCodec and its contents.
+ *
+ * @param codecs The list of codecs to be freed.
+ *
+ * @since 2.6.0
+ */
+void purple_media_codec_list_free(GList *codecs);
+
+/**
+ * Creates a string representation of the codec.
+ *
+ * @param codec The codec to create the string of.
+ *
+ * @return The new string representation.
+ *
+ * @since 2.6.0
+ */
+gchar *purple_media_codec_to_string(const PurpleMediaCodec *codec);
+
+G_END_DECLS
+
+#endif  /* _PURPLE_MEDIA_CODEC_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/enum-types.c	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,213 @@
+/**
+ * @file enum-types.c Enum types 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 "enum-types.h"
+
+/*
+ * PurpleMediaCandidateType
+ */
+
+GType
+purple_media_candidate_type_get_type()
+{
+	static GType type = 0;
+	if (type == 0) {
+		static const GEnumValue values[] = {
+			{ PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
+					"PURPLE_MEDIA_CANDIDATE_TYPE_HOST",
+					"host" },
+			{ PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX,
+					"PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX",
+					"srflx" },
+			{ PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX,
+					"PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX",
+					"prflx" },
+			{ PURPLE_MEDIA_CANDIDATE_TYPE_RELAY,
+					"PURPLE_MEDIA_CANDIDATE_TYPE_RELAY",
+					"relay" },
+			{ PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST,
+					"PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST",
+					"multicast" },
+			{ 0, NULL, NULL }
+		};
+		type = g_enum_register_static("PurpleMediaCandidateType",
+				values);
+	}
+	return type;
+}
+
+/*
+ * PurpleMediaCaps
+ */
+
+GType
+purple_media_caps_get_type()
+{
+	static GType type = 0;
+	if (type == 0) {
+		static const GEnumValue values[] = {
+			{ PURPLE_MEDIA_CAPS_NONE,
+					"PURPLE_MEDIA_CAPS_NONE", "none" },
+			{ PURPLE_MEDIA_CAPS_AUDIO,
+					"PURPLE_MEDIA_CAPS_AUDIO", "audio" },
+			{ PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION,
+					"PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION",
+					"audio-single-direction" },
+			{ PURPLE_MEDIA_CAPS_VIDEO,
+					"PURPLE_MEDIA_CAPS_VIDEO", "video" },
+			{ PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION,
+					"PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION",
+					"video-single-direction" },
+			{ PURPLE_MEDIA_CAPS_AUDIO_VIDEO,
+					"PURPLE_MEDIA_CAPS_AUDIO_VIDEO",
+					"audio-video" },
+			{ PURPLE_MEDIA_CAPS_MODIFY_SESSION,
+					"PURPLE_MEDIA_CAPS_MODIFY_SESSION",
+					"modify-session" },
+			{ PURPLE_MEDIA_CAPS_CHANGE_DIRECTION,
+					"PURPLE_MEDIA_CAPS_CHANGE_DIRECTION",
+					"change-direction" },
+			{ 0, NULL, NULL }
+		};
+		type = g_enum_register_static("PurpleMediaCaps", values);
+	}
+	return type;
+}
+
+/*
+ * PurpleMediaInfoType
+ */
+
+GType
+purple_media_info_type_get_type()
+{
+	static GType type = 0;
+	if (type == 0) {
+		static const GEnumValue values[] = {
+			{ PURPLE_MEDIA_INFO_HANGUP,
+					"PURPLE_MEDIA_INFO_HANGUP", "hangup" },
+			{ PURPLE_MEDIA_INFO_ACCEPT,
+					"PURPLE_MEDIA_INFO_ACCEPT", "accept" },
+			{ PURPLE_MEDIA_INFO_REJECT,
+					"PURPLE_MEDIA_INFO_REJECT", "reject" },
+			{ PURPLE_MEDIA_INFO_MUTE,
+					"PURPLE_MEDIA_INFO_MUTE", "mute" },
+			{ PURPLE_MEDIA_INFO_UNMUTE,
+					"PURPLE_MEDIA_INFO_UNMUTE", "unmute" },
+			{ PURPLE_MEDIA_INFO_PAUSE,
+					"PURPLE_MEDIA_INFO_PAUSE", "pause" },
+			{ PURPLE_MEDIA_INFO_UNPAUSE,
+					"PURPLE_MEDIA_INFO_UNPAUSE", "unpause" },
+			{ PURPLE_MEDIA_INFO_HOLD,
+					"PURPLE_MEDIA_INFO_HOLD", "hold" },
+			{ PURPLE_MEDIA_INFO_UNHOLD,
+					"PURPLE_MEDIA_INFO_HOLD", "unhold" },
+			{ 0, NULL, NULL }
+		};
+		type = g_enum_register_static("PurpleMediaInfoType", values);
+	}
+	return type;
+}
+
+/*
+ * PurpleMediaNetworkProtocol
+ */
+
+GType
+purple_media_network_protocol_get_type()
+{
+	static GType type = 0;
+	if (type == 0) {
+		static const GEnumValue values[] = {
+			{ PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
+					"PURPLE_MEDIA_NETWORK_PROTOCOL_UDP",
+					"udp" },
+			{ PURPLE_MEDIA_NETWORK_PROTOCOL_TCP,
+					"PURPLE_MEDIA_NETWORK_PROTOCOL_TCP",
+					"tcp" },
+			{ 0, NULL, NULL }
+		};
+		type = g_enum_register_static("PurpleMediaNetworkProtocol",
+				values);
+	}
+	return type;
+}
+
+/*
+ * PurpleMediaSessionType
+ */
+
+GType
+purple_media_session_type_get_type()
+{
+	static GType type = 0;
+	if (type == 0) {
+		static const GFlagsValue values[] = {
+			{ PURPLE_MEDIA_NONE,
+				"PURPLE_MEDIA_NONE", "none" },
+			{ PURPLE_MEDIA_RECV_AUDIO,
+				"PURPLE_MEDIA_RECV_AUDIO", "recv-audio" },
+			{ PURPLE_MEDIA_SEND_AUDIO,
+				"PURPLE_MEDIA_SEND_AUDIO", "send-audio" },
+			{ PURPLE_MEDIA_RECV_VIDEO,
+				"PURPLE_MEDIA_RECV_VIDEO", "recv-video" },
+			{ PURPLE_MEDIA_SEND_VIDEO,
+				"PURPLE_MEDIA_SEND_VIDEO", "send-audio" },
+			{ PURPLE_MEDIA_AUDIO,
+				"PURPLE_MEDIA_AUDIO", "audio" },
+			{ PURPLE_MEDIA_VIDEO,
+				"PURPLE_MEDIA_VIDEO", "video" },
+			{ 0, NULL, NULL }
+		};
+		type = g_flags_register_static(
+				"PurpleMediaSessionType", values);
+	}
+	return type;
+}
+
+/*
+ * PurpleMediaState
+ */
+
+GType
+purple_media_state_changed_get_type()
+{
+	static GType type = 0;
+	if (type == 0) {
+		static const GEnumValue values[] = {
+			{ PURPLE_MEDIA_STATE_NEW,
+				"PURPLE_MEDIA_STATE_NEW", "new" },
+			{ PURPLE_MEDIA_STATE_CONNECTED,
+				"PURPLE_MEDIA_STATE_CONNECTED", "connected" },
+			{ PURPLE_MEDIA_STATE_END,
+				"PURPLE_MEDIA_STATE_END", "end" },
+			{ 0, NULL, NULL }
+		};
+		type = g_enum_register_static("PurpleMediaState", values);
+	}
+	return type;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/enum-types.h	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,162 @@
+/**
+ * @file enum-types.h Enum types 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
+ */
+
+#ifndef _PURPLE_MEDIA_ENUM_TYPES_H_
+#define _PURPLE_MEDIA_ENUM_TYPES_H_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA_CANDIDATE_TYPE   (purple_media_candidate_type_get_type())
+#define PURPLE_MEDIA_TYPE_CAPS	           (purple_media_caps_get_type())
+#define PURPLE_MEDIA_TYPE_INFO_TYPE	   (purple_media_info_type_get_type())
+#define PURPLE_TYPE_MEDIA_NETWORK_PROTOCOL (purple_media_network_protocol_get_type())
+#define PURPLE_TYPE_MEDIA_SESSION_TYPE     (purple_media_session_type_get_type())
+#define PURPLE_MEDIA_TYPE_STATE            (purple_media_state_changed_get_type())
+
+/** Media candidate types */
+typedef enum {
+	PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
+	PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX,
+	PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX,
+	PURPLE_MEDIA_CANDIDATE_TYPE_RELAY,
+	PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST,
+} PurpleMediaCandidateType;
+
+/** Media caps */
+typedef enum {
+	PURPLE_MEDIA_CAPS_NONE = 0,
+	PURPLE_MEDIA_CAPS_AUDIO = 1,
+	PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION = 1 << 1,
+	PURPLE_MEDIA_CAPS_VIDEO = 1 << 2,
+	PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION = 1 << 3,
+	PURPLE_MEDIA_CAPS_AUDIO_VIDEO = 1 << 4,
+	PURPLE_MEDIA_CAPS_MODIFY_SESSION = 1 << 5,
+	PURPLE_MEDIA_CAPS_CHANGE_DIRECTION = 1 << 6,
+} PurpleMediaCaps;
+
+/** Media component types */
+typedef enum {
+	PURPLE_MEDIA_COMPONENT_NONE = 0,
+	PURPLE_MEDIA_COMPONENT_RTP = 1,
+	PURPLE_MEDIA_COMPONENT_RTCP = 2,
+} PurpleMediaComponentType;
+
+/** Media info types */
+typedef enum {
+	PURPLE_MEDIA_INFO_HANGUP = 0,
+	PURPLE_MEDIA_INFO_ACCEPT,
+	PURPLE_MEDIA_INFO_REJECT,
+	PURPLE_MEDIA_INFO_MUTE,
+	PURPLE_MEDIA_INFO_UNMUTE,
+	PURPLE_MEDIA_INFO_PAUSE,
+	PURPLE_MEDIA_INFO_UNPAUSE,
+	PURPLE_MEDIA_INFO_HOLD,
+	PURPLE_MEDIA_INFO_UNHOLD,
+} PurpleMediaInfoType;
+
+/** Media network protocols */
+typedef enum {
+	PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
+	PURPLE_MEDIA_NETWORK_PROTOCOL_TCP,
+} PurpleMediaNetworkProtocol;
+
+/** Media session types */
+typedef enum {
+	PURPLE_MEDIA_NONE	= 0,
+	PURPLE_MEDIA_RECV_AUDIO = 1 << 0,
+	PURPLE_MEDIA_SEND_AUDIO = 1 << 1,
+	PURPLE_MEDIA_RECV_VIDEO = 1 << 2,
+	PURPLE_MEDIA_SEND_VIDEO = 1 << 3,
+	PURPLE_MEDIA_AUDIO = PURPLE_MEDIA_RECV_AUDIO | PURPLE_MEDIA_SEND_AUDIO,
+	PURPLE_MEDIA_VIDEO = PURPLE_MEDIA_RECV_VIDEO | PURPLE_MEDIA_SEND_VIDEO
+} PurpleMediaSessionType;
+
+/** Media state-changed types */
+typedef enum {
+	PURPLE_MEDIA_STATE_NEW = 0,
+	PURPLE_MEDIA_STATE_CONNECTED,
+	PURPLE_MEDIA_STATE_END,
+} PurpleMediaState;
+
+/**
+ * Gets the media candidate type's GType
+ *
+ * @return The media candidate type's GType.
+ *
+ * @since 2.6.0
+ */
+GType purple_media_candidate_type_get_type(void);
+
+/**
+ * Gets the type of the media caps flags
+ *
+ * @return The media caps flags' GType
+ *
+ * @since 2.7.0
+ */
+GType purple_media_caps_get_type(void);
+
+/**
+ * Gets the type of the info type enum
+ *
+ * @return The info type enum's GType
+ *
+ * @since 2.6.0
+ */
+GType purple_media_info_type_get_type(void);
+
+/**
+ * Gets the media network protocol's GType
+ *
+ * @return The media network protocol's GType.
+ *
+ * @since 2.6.0
+ */
+GType purple_media_network_protocol_get_type(void);
+
+/**
+ * Gets the media session type's GType
+ *
+ * @return The media session type's GType.
+ *
+ * @since 2.6.0
+ */
+GType purple_media_session_type_get_type(void);
+
+/**
+ * Gets the type of the state-changed enum
+ *
+ * @return The state-changed enum's GType
+ *
+ * @since 2.6.0
+ */
+GType purple_media_state_changed_get_type(void);
+
+G_END_DECLS
+
+#endif  /* _PURPLE_MEDIA_ENUM_TYPES_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media/media.c	Wed Nov 11 03:07:21 2009 +0000
@@ -0,0 +1,1408 @@
+/**
+ * @file media.c 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 "account.h"
+#include "media.h"
+#include "media/backend-fs2.h"
+#include "media/backend-iface.h"
+#include "mediamanager.h"
+
+#include "debug.h"
+
+#ifdef USE_GSTREAMER
+#include "marshallers.h"
+#include "media-gst.h"
+#endif
+
+#ifdef USE_VV
+
+/** @copydoc _PurpleMediaSession */
+typedef struct _PurpleMediaSession PurpleMediaSession;
+/** @copydoc _PurpleMediaStream */
+typedef struct _PurpleMediaStream PurpleMediaStream;
+/** @copydoc _PurpleMediaClass */
+typedef struct _PurpleMediaClass PurpleMediaClass;
+/** @copydoc _PurpleMediaPrivate */
+typedef struct _PurpleMediaPrivate PurpleMediaPrivate;
+
+/** The media class */
+struct _PurpleMediaClass
+{
+	GObjectClass parent_class;     /**< The parent class. */
+};
+
+/** The media class's private data */
+struct _PurpleMedia
+{
+	GObject parent;                /**< The parent of this object. */
+	PurpleMediaPrivate *priv;      /**< The private data of this object. */
+};
+
+struct _PurpleMediaSession
+{
+	gchar *id;
+	PurpleMedia *media;
+	PurpleMediaSessionType type;
+	gboolean initiator;
+};
+
+struct _PurpleMediaStream
+{
+	PurpleMediaSession *session;
+	gchar *participant;
+
+	GList *local_candidates;
+	GList *remote_candidates;
+
+	gboolean initiator;
+	gboolean accepted;
+	gboolean candidates_prepared;
+
+	GList *active_local_candidates;
+	GList *active_remote_candidates;
+};
+#endif
+
+struct _PurpleMediaPrivate
+{
+#ifdef USE_VV
+	PurpleMediaManager *manager;
+	PurpleAccount *account;
+	PurpleMediaBackend *backend;
+	gchar *conference_type;
+	gboolean initiator;
+	gpointer prpl_data;
+
+	GHashTable *sessions;	/* PurpleMediaSession table */
+	GList *participants;
+	GList *streams;		/* PurpleMediaStream table */
+#else
+	gpointer dummy;
+#endif
+};
+
+#ifdef USE_VV
+#define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate))
+
+static void purple_media_class_init (PurpleMediaClass *klass);
+static void purple_media_init (PurpleMedia *media);
+static void purple_media_dispose (GObject *object);
+static void purple_media_finalize (GObject *object);
+static void purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+
+static void purple_media_new_local_candidate_cb(PurpleMediaBackend *backend,
+		const gchar *sess_id, const gchar *participant,
+		PurpleMediaCandidate *candidate, PurpleMedia *media);
+static void purple_media_candidates_prepared_cb(PurpleMediaBackend *backend,
+		const gchar *sess_id, const gchar *name, PurpleMedia *media);
+static void purple_media_candidate_pair_established_cb(
+		PurpleMediaBackend *backend,
+		const gchar *sess_id, const gchar *name,
+		PurpleMediaCandidate *local_candidate,
+		PurpleMediaCandidate *remote_candidate,
+		PurpleMedia *media);
+static void purple_media_codecs_changed_cb(PurpleMediaBackend *backend,
+		const gchar *sess_id, PurpleMedia *media);
+
+static GObjectClass *parent_class = NULL;
+
+
+
+enum {
+	S_ERROR,
+	CANDIDATES_PREPARED,
+	CODECS_CHANGED,
+	LEVEL,
+	NEW_CANDIDATE,
+	STATE_CHANGED,
+	STREAM_INFO,
+	LAST_SIGNAL
+};
+static guint purple_media_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_MANAGER,
+	PROP_BACKEND,
+	PROP_ACCOUNT,
+	PROP_CONFERENCE_TYPE,
+	PROP_INITIATOR,
+	PROP_PRPL_DATA,
+};
+#endif
+
+
+GType
+purple_media_get_type()
+{
+#ifdef USE_VV
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PurpleMediaClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) purple_media_class_init,
+			NULL,
+			NULL,
+			sizeof(PurpleMedia),
+			0,
+			(GInstanceInitFunc) purple_media_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "PurpleMedia", &info, 0);
+	}
+	return type;
+#else
+	return G_TYPE_NONE;
+#endif
+}
+
+#ifdef USE_VV
+static void
+purple_media_class_init (PurpleMediaClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+	
+	gobject_class->dispose = purple_media_dispose;
+	gobject_class->finalize = purple_media_finalize;
+	gobject_class->set_property = purple_media_set_property;
+	gobject_class->get_property = purple_media_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_MANAGER,
+			g_param_spec_object("manager",
+			"Purple Media Manager",
+			"The media manager that contains this media session.",
+			PURPLE_TYPE_MEDIA_MANAGER,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	/*
+	 * This one should be PURPLE_TYPE_MEDIA_BACKEND, but it doesn't
+	 * like interfaces because they "aren't GObjects"
+	 */
+	g_object_class_install_property(gobject_class, PROP_BACKEND,
+			g_param_spec_object("backend",
+			"Purple Media Backend",
+			"The backend object this media object uses.",
+			G_TYPE_OBJECT,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(gobject_class, PROP_ACCOUNT,
+			g_param_spec_pointer("account",
+			"PurpleAccount",
+			"The account this media session is on.",
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_CONFERENCE_TYPE,
+			g_param_spec_string("conference-type",
+			"Conference Type",
+			"The type of conference that this media object "
+			"has been created to provide.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_INITIATOR,
+			g_param_spec_boolean("initiator",
+			"initiator",
+			"If the local user initiated the conference.",
+			FALSE,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_PRPL_DATA,
+			g_param_spec_pointer("prpl-data",
+			"gpointer",
+			"Data the prpl plugin set on the media session.",
+			G_PARAM_READWRITE));
+
+	purple_media_signals[S_ERROR] = g_signal_new("error", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__STRING,
+					 G_TYPE_NONE, 1, G_TYPE_STRING);
+	purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__STRING_STRING,
+					 G_TYPE_NONE, 2, G_TYPE_STRING,
+					 G_TYPE_STRING);
+	purple_media_signals[CODECS_CHANGED] = g_signal_new("codecs-changed", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__STRING,
+					 G_TYPE_NONE, 1, G_TYPE_STRING);
+	purple_media_signals[LEVEL] = g_signal_new("level", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__STRING_STRING_DOUBLE,
+					 G_TYPE_NONE, 3, G_TYPE_STRING,
+					 G_TYPE_STRING, G_TYPE_DOUBLE);
+	purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__POINTER_POINTER_OBJECT,
+					 G_TYPE_NONE, 3, G_TYPE_POINTER,
+					 G_TYPE_POINTER, PURPLE_TYPE_MEDIA_CANDIDATE);
+	purple_media_signals[STATE_CHANGED] = g_signal_new("state-changed", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__ENUM_STRING_STRING,
+					 G_TYPE_NONE, 3, PURPLE_MEDIA_TYPE_STATE,
+					 G_TYPE_STRING, G_TYPE_STRING);
+	purple_media_signals[STREAM_INFO] = g_signal_new("stream-info", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__ENUM_STRING_STRING_BOOLEAN,
+					 G_TYPE_NONE, 4, PURPLE_MEDIA_TYPE_INFO_TYPE,
+					 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+	g_type_class_add_private(klass, sizeof(PurpleMediaPrivate));
+}
+
+
+static void
+purple_media_init (PurpleMedia *media)
+{
+	media->priv = PURPLE_MEDIA_GET_PRIVATE(media);
+	memset(media->priv, 0, sizeof(*media->priv));
+}
+
+static void
+purple_media_stream_free(PurpleMediaStream *stream)
+{
+	if (stream == NULL)
+		return;
+
+	g_free(stream->participant);
+
+	if (stream->local_candidates)
+		purple_media_candidate_list_free(stream->local_candidates);
+	if (stream->remote_candidates)
+		purple_media_candidate_list_free(stream->remote_candidates);
+
+	if (stream->active_local_candidates)
+		purple_media_candidate_list_free(
+				stream->active_local_candidates);
+	if (stream->active_remote_candidates)
+		purple_media_candidate_list_free(
+				stream->active_remote_candidates);
+
+	g_free(stream);
+}
+
+static void
+purple_media_session_free(PurpleMediaSession *session)
+{
+	if (session == NULL)
+		return;
+
+	g_free(session->id);
+	g_free(session);
+}
+
+static void
+purple_media_dispose(GObject *media)
+{
+	PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media);
+
+	purple_debug_info("media","purple_media_dispose\n");
+
+	purple_media_manager_remove_media(priv->manager, PURPLE_MEDIA(media));
+
+	if (priv->backend) {
+		g_object_unref(priv->backend);
+		priv->backend = NULL;
+	}
+
+	if (priv->manager) {
+		g_object_unref(priv->manager);
+		priv->manager = NULL;
+	}
+
+	G_OBJECT_CLASS(parent_class)->dispose(media);
+}
+
+static void
+purple_media_finalize(GObject *media)
+{
+	PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media);
+	purple_debug_info("media","purple_media_finalize\n");
+
+	for (; priv->streams; priv->streams = g_list_delete_link(priv->streams, priv->streams))
+		purple_media_stream_free(priv->streams->data);
+
+	for (; priv->participants; priv->participants = g_list_delete_link(
+			priv->participants, priv->participants))
+		g_free(priv->participants->data);
+
+	if (priv->sessions) {
+		GList *sessions = g_hash_table_get_values(priv->sessions);
+		for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+			purple_media_session_free(sessions->data);
+		}
+		g_hash_table_destroy(priv->sessions);
+	}
+
+	G_OBJECT_CLASS(parent_class)->finalize(media);
+}
+
+static void
+purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	PurpleMedia *media;
+	g_return_if_fail(PURPLE_IS_MEDIA(object));
+
+	media = PURPLE_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_MANAGER:
+			media->priv->manager = g_value_dup_object(value);
+			break;
+		case PROP_ACCOUNT:
+			media->priv->account = g_value_get_pointer(value);
+			break;
+		case PROP_CONFERENCE_TYPE:
+			media->priv->conference_type =
+					g_value_dup_string(value);
+			media->priv->backend = g_object_new(
+					purple_media_manager_get_backend_type(
+					purple_media_manager_get()),
+					"conference-type",
+					media->priv->conference_type,
+					"media", media,
+					NULL);
+			g_signal_connect(media->priv->backend,
+					"active-candidate-pair",
+					G_CALLBACK(
+					purple_media_candidate_pair_established_cb),
+					media);
+			g_signal_connect(media->priv->backend,
+					"candidates-prepared",
+					G_CALLBACK(
+					purple_media_candidates_prepared_cb),
+					media);
+			g_signal_connect(media->priv->backend,
+					"codecs-changed",
+					G_CALLBACK(
+					purple_media_codecs_changed_cb),
+					media);
+			g_signal_connect(media->priv->backend,
+					"new-candidate",
+					G_CALLBACK(
+					purple_media_new_local_candidate_cb),
+					media);
+			break;
+		case PROP_INITIATOR:
+			media->priv->initiator = g_value_get_boolean(value);
+			break;
+		case PROP_PRPL_DATA:
+			media->priv->prpl_data = g_value_get_pointer(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	PurpleMedia *media;
+	g_return_if_fail(PURPLE_IS_MEDIA(object));
+	
+	media = PURPLE_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_MANAGER:
+			g_value_set_object(value, media->priv->manager);
+			break;
+		case PROP_BACKEND:
+			g_value_set_object(value, media->priv->backend);
+			break;
+		case PROP_ACCOUNT:
+			g_value_set_pointer(value, media->priv->account);
+			break;
+		case PROP_CONFERENCE_TYPE:
+			g_value_set_string(value,
+					media->priv->conference_type);
+			break;
+		case PROP_INITIATOR:
+			g_value_set_boolean(value, media->priv->initiator);
+			break;
+		case PROP_PRPL_DATA:
+			g_value_set_pointer(value, media->priv->prpl_data);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+
+}
+
+static PurpleMediaSession*
+purple_media_get_session(PurpleMedia *media, const gchar *sess_id)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	return (PurpleMediaSession*) (media->priv->sessions) ?
+			g_hash_table_lookup(media->priv->sessions, sess_id) : NULL;
+}
+
+static PurpleMediaStream*
+purple_media_get_stream(PurpleMedia *media, const gchar *session, const gchar *participant)
+{
+	GList *streams;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	streams = media->priv->streams;
+
+	for (; streams; streams = g_list_next(streams)) {
+		PurpleMediaStream *stream = streams->data;
+		if (!strcmp(stream->session->id, session) &&
+				!strcmp(stream->participant, participant))
+			return stream;
+	}
+
+	return NULL;
+}
+
+static GList *
+purple_media_get_streams(PurpleMedia *media, const gchar *session,
+		const gchar *participant)
+{
+	GList *streams;
+	GList *ret = NULL;
+	
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	streams = media->priv->streams;
+
+	for (; streams; streams = g_list_next(streams)) {
+		PurpleMediaStream *stream = streams->data;
+		if ((session == NULL ||
+				!strcmp(stream->session->id, session)) &&
+				(participant == NULL ||
+				!strcmp(stream->participant, participant)))
+			ret = g_list_append(ret, stream);
+	}
+
+	return ret;
+}
+
+static void
+purple_media_add_session(PurpleMedia *media, PurpleMediaSession *session)
+{
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	g_return_if_fail(session != NULL);
+
+	if (!media->priv->sessions) {
+		purple_debug_info("media", "Creating hash table for sessions\n");
+		media->priv->sessions = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	g_hash_table_insert(media->priv->sessions, g_strdup(session->id), session);
+}
+
+#if 0
+static gboolean
+purple_media_remove_session(PurpleMedia *media, PurpleMediaSession *session)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+	return g_hash_table_remove(media->priv->sessions, session->id);
+}
+#endif
+
+static PurpleMediaStream *
+purple_media_insert_stream(PurpleMediaSession *session,
+		const gchar *name, gboolean initiator)
+{
+	PurpleMediaStream *media_stream;
+	
+	g_return_val_if_fail(session != NULL, NULL);
+
+	media_stream = g_new0(PurpleMediaStream, 1);
+	media_stream->participant = g_strdup(name);
+	media_stream->session = session;
+	media_stream->initiator = initiator;
+
+	session->media->priv->streams =
+			g_list_append(session->media->priv->streams, media_stream);
+
+	return media_stream;
+}
+
+static void
+purple_media_insert_local_candidate(PurpleMediaSession *session, const gchar *name,
+				     PurpleMediaCandidate *candidate)
+{
+	PurpleMediaStream *stream;
+
+	g_return_if_fail(session != NULL);
+
+	stream = purple_media_get_stream(session->media, session->id, name);
+	stream->local_candidates = g_list_append(stream->local_candidates, candidate);
+}
+#endif
+
+GList *
+purple_media_get_session_ids(PurpleMedia *media)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	return media->priv->sessions != NULL ?
+			g_hash_table_get_keys(media->priv->sessions) : NULL;
+#else
+	return NULL;
+#endif
+}
+
+#ifdef USE_GSTREAMER
+GstElement *
+purple_media_get_src(PurpleMedia *media, const gchar *sess_id)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	if (PURPLE_IS_MEDIA_BACKEND_FS2(media->priv->backend))
+		return purple_media_backend_fs2_get_src(
+				PURPLE_MEDIA_BACKEND_FS2(
+				media->priv->backend), sess_id);
+
+	g_return_val_if_reached(NULL);
+#else
+	return NULL;
+#endif
+}
+#endif /* USE_GSTREAMER */
+
+PurpleAccount *
+purple_media_get_account(PurpleMedia *media)
+{
+#ifdef USE_VV
+	PurpleAccount *account;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	g_object_get(G_OBJECT(media), "account", &account, NULL);
+	return account;
+#else
+	return NULL;
+#endif
+}
+
+gpointer
+purple_media_get_prpl_data(PurpleMedia *media)
+{
+#ifdef USE_VV
+	gpointer prpl_data;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	g_object_get(G_OBJECT(media), "prpl-data", &prpl_data, NULL);
+	return prpl_data;
+#else
+	return NULL;
+#endif
+}
+
+void
+purple_media_set_prpl_data(PurpleMedia *media, gpointer prpl_data)
+{
+#ifdef USE_VV
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	g_object_set(G_OBJECT(media), "prpl-data", prpl_data, NULL);
+#endif
+}
+
+void
+purple_media_error(PurpleMedia *media, const gchar *error, ...)
+{
+#ifdef USE_VV
+	va_list args;
+	gchar *message;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	va_start(args, error);
+	message = g_strdup_vprintf(error, args);
+	va_end(args);
+
+	purple_debug_error("media", "%s\n", message);
+	g_signal_emit(media, purple_media_signals[S_ERROR], 0, message);
+
+	g_free(message);
+#endif
+}
+
+void
+purple_media_end(PurpleMedia *media,
+		const gchar *session_id, const gchar *participant)
+{
+#ifdef USE_VV
+	GList *iter, *sessions = NULL, *participants = NULL;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	iter = purple_media_get_streams(media, session_id, participant);
+
+	/* Free matching streams */
+	for (; iter; iter = g_list_delete_link(iter, iter)) {
+		PurpleMediaStream *stream = iter->data;
+
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_END,
+				stream->session->id, stream->participant);
+
+		media->priv->streams =
+				g_list_remove(media->priv->streams, stream);
+
+		if (g_list_find(sessions, stream->session) == NULL)
+			sessions = g_list_prepend(sessions, stream->session);
+
+		if (g_list_find_custom(participants, stream->participant,
+				(GCompareFunc)strcmp) == NULL)
+			participants = g_list_prepend(participants,
+					g_strdup(stream->participant));
+
+		purple_media_stream_free(stream);
+	}
+
+	iter = media->priv->streams;
+
+	/* Reduce to list of sessions to remove */
+	for (; iter; iter = g_list_next(iter)) {
+		PurpleMediaStream *stream = iter->data;
+
+		sessions = g_list_remove(sessions, stream->session);
+	}
+
+	/* Free sessions with no streams left */
+	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+		PurpleMediaSession *session = sessions->data;
+
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_END,
+				session->id, NULL);
+
+		g_hash_table_remove(media->priv->sessions, session->id);
+		purple_media_session_free(session);
+	}
+
+	iter = media->priv->streams;
+
+	/* Reduce to list of participants to remove */
+	for (; iter; iter = g_list_next(iter)) {
+		PurpleMediaStream *stream = iter->data;
+		GList *tmp;
+
+		tmp = g_list_find_custom(participants,
+				stream->participant, (GCompareFunc)strcmp);
+
+		if (tmp != NULL) {
+			g_free(tmp->data);
+			participants = g_list_delete_link(participants,	tmp);
+		}
+	}
+
+	/* Remove participants with no streams left (just emit the signal) */
+	for (; participants; participants =
+			g_list_delete_link(participants, participants)) {
+		gchar *participant = participants->data;
+		GList *link = g_list_find_custom(media->priv->participants,
+				participant, (GCompareFunc)strcmp);
+
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_END,
+				NULL, participant);
+
+		if (link != NULL) {
+			g_free(link->data);
+			media->priv->participants = g_list_delete_link(
+					media->priv->participants, link);
+		}
+
+		g_free(participant);
+	}
+
+	/* Free the conference if no sessions left */
+	if (media->priv->sessions != NULL &&
+			g_hash_table_size(media->priv->sessions) == 0) {
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_END,
+				NULL, NULL);
+		g_object_unref(media);
+		return;
+	}
+#endif
+}
+
+void
+purple_media_stream_info(PurpleMedia *media, PurpleMediaInfoType type,
+		const gchar *session_id, const gchar *participant,
+		gboolean local)
+{
+#ifdef USE_VV
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	if (type == PURPLE_MEDIA_INFO_ACCEPT) {
+		GList *streams, *sessions = NULL, *participants = NULL;
+
+		g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+		streams = purple_media_get_streams(media,
+				session_id, participant);
+
+		/* Emit stream acceptance */
+		for (; streams; streams =
+				g_list_delete_link(streams, streams)) {
+			PurpleMediaStream *stream = streams->data;
+
+			stream->accepted = TRUE;
+
+			g_signal_emit(media,
+					purple_media_signals[STREAM_INFO],
+					0, type, stream->session->id,
+					stream->participant, local);
+
+			if (g_list_find(sessions, stream->session) == NULL)
+				sessions = g_list_prepend(sessions,
+						stream->session);
+
+			if (g_list_find_custom(participants,
+					stream->participant,
+					(GCompareFunc)strcmp) == NULL)
+				participants = g_list_prepend(participants,
+						g_strdup(stream->participant));
+		}
+
+		/* Emit session acceptance */
+		for (; sessions; sessions =
+				g_list_delete_link(sessions, sessions)) {
+			PurpleMediaSession *session = sessions->data;
+
+			if (purple_media_accepted(media, session->id, NULL))
+				g_signal_emit(media, purple_media_signals[
+						STREAM_INFO], 0,
+						PURPLE_MEDIA_INFO_ACCEPT,
+						session->id, NULL, local);
+		}
+
+		/* Emit participant acceptance */
+		for (; participants; participants = g_list_delete_link(
+				participants, participants)) {
+			gchar *participant = participants->data;
+
+			if (purple_media_accepted(media, NULL, participant))
+				g_signal_emit(media, purple_media_signals[
+						STREAM_INFO], 0,
+						PURPLE_MEDIA_INFO_ACCEPT,
+						NULL, participant, local);
+
+			g_free(participant);
+		}
+
+		/* Emit conference acceptance */
+		if (purple_media_accepted(media, NULL, NULL))
+			g_signal_emit(media,
+					purple_media_signals[STREAM_INFO],
+					0, PURPLE_MEDIA_INFO_ACCEPT,
+					NULL, NULL, local);
+
+		return;
+	} else if (type == PURPLE_MEDIA_INFO_HANGUP ||
+			type == PURPLE_MEDIA_INFO_REJECT) {
+		GList *streams;
+
+		g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+		streams = purple_media_get_streams(media,
+				session_id, participant);
+
+		/* Emit for stream */
+		for (; streams; streams =
+				g_list_delete_link(streams, streams)) {
+			PurpleMediaStream *stream = streams->data;
+
+			g_signal_emit(media,
+					purple_media_signals[STREAM_INFO],
+					0, type, stream->session->id,
+					stream->participant, local);
+		}
+
+		if (session_id != NULL && participant != NULL) {
+			/* Everything that needs to be emitted has been */
+		} else if (session_id == NULL && participant == NULL) {
+			/* Emit for everything in the conference */
+			GList *sessions = NULL;
+			GList *participants = media->priv->participants;
+
+			if (media->priv->sessions != NULL)
+				sessions = g_hash_table_get_values(
+					media->priv->sessions);
+
+			/* Emit for sessions */
+			for (; sessions; sessions = g_list_delete_link(
+					sessions, sessions)) {
+				PurpleMediaSession *session = sessions->data;
+
+				g_signal_emit(media, purple_media_signals[
+						STREAM_INFO], 0, type,
+						session->id, NULL, local);
+			}
+
+			/* Emit for participants */
+			for (; participants; participants =
+					g_list_next(participants)) {
+				gchar *participant = participants->data;
+
+				g_signal_emit(media, purple_media_signals[
+						STREAM_INFO], 0, type,
+						NULL, participant, local);
+			}
+
+			/* Emit for conference */
+			g_signal_emit(media,
+					purple_media_signals[STREAM_INFO],
+					0, type, NULL, NULL, local);
+		} else if (session_id != NULL) {
+			/* Emit just the specific session */
+			PurpleMediaSession *session =
+					purple_media_get_session(
+					media, session_id);
+
+			if (session == NULL) {
+				purple_debug_warning("media",
+						"Couldn't find session"
+						" to hangup/reject.\n");
+			} else {
+				g_signal_emit(media, purple_media_signals[
+						STREAM_INFO], 0, type,
+						session->id, NULL, local);
+			}
+		} else if (participant != NULL) {
+			/* Emit just the specific participant */
+			if (!g_list_find_custom(media->priv->participants,
+					participant, (GCompareFunc)strcmp)) {
+				purple_debug_warning("media",
+						"Couldn't find participant"
+						" to hangup/reject.\n");
+			} else {
+				g_signal_emit(media, purple_media_signals[
+						STREAM_INFO], 0, type, NULL,
+						participant, local);
+			}
+		}
+
+		purple_media_end(media, session_id, participant);
+		return;
+	}
+
+	g_signal_emit(media, purple_media_signals[STREAM_INFO],
+			0, type, session_id, participant, local);
+#endif
+}
+
+#ifdef USE_VV
+static void
+purple_media_new_local_candidate_cb(PurpleMediaBackend *backend,
+		const gchar *sess_id, const gchar *participant,
+		PurpleMediaCandidate *candidate, PurpleMedia *media)
+{
+	PurpleMediaSession *session =
+			purple_media_get_session(media, sess_id);
+
+	purple_media_insert_local_candidate(session, participant,
+			purple_media_candidate_copy(candidate));
+
+	g_signal_emit(session->media, purple_media_signals[NEW_CANDIDATE],
+		      0, session->id, participant, candidate);
+}
+
+static void
+purple_media_candidates_prepared_cb(PurpleMediaBackend *backend,
+		const gchar *sess_id, const gchar *name, PurpleMedia *media)
+{
+	PurpleMediaStream *stream_data;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	stream_data = purple_media_get_stream(media, sess_id, name);
+	stream_data->candidates_prepared = TRUE;
+
+	g_signal_emit(media, purple_media_signals[CANDIDATES_PREPARED],
+			0, sess_id, name);
+}
+
+/* callback called when a pair of transport candidates (local and remote)
+ * has been established */
+static void
+purple_media_candidate_pair_established_cb(PurpleMediaBackend *backend,
+		const gchar *sess_id, const gchar *name,
+		PurpleMediaCandidate *local_candidate,
+		PurpleMediaCandidate *remote_candidate,
+		PurpleMedia *media)
+{
+	PurpleMediaStream *stream;
+	GList *iter;
+	guint id;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	stream = purple_media_get_stream(media, sess_id, name);
+	id = purple_media_candidate_get_component_id(local_candidate);
+
+	iter = stream->active_local_candidates;
+	for(; iter; iter = g_list_next(iter)) {
+		PurpleMediaCandidate *c = iter->data;
+		if (id == purple_media_candidate_get_component_id(c)) {
+			g_object_unref(c);
+			stream->active_local_candidates =
+					g_list_delete_link(iter, iter);
+			stream->active_local_candidates = g_list_prepend(
+					stream->active_local_candidates,
+					purple_media_candidate_copy(
+					local_candidate));
+			break;
+		}
+	}
+	if (iter == NULL)
+		stream->active_local_candidates = g_list_prepend(
+				stream->active_local_candidates,
+				purple_media_candidate_copy(
+				local_candidate));
+
+	id = purple_media_candidate_get_component_id(local_candidate);
+
+	iter = stream->active_remote_candidates;
+	for(; iter; iter = g_list_next(iter)) {
+		PurpleMediaCandidate *c = iter->data;
+		if (id == purple_media_candidate_get_component_id(c)) {
+			g_object_unref(c);
+			stream->active_remote_candidates =
+					g_list_delete_link(iter, iter);
+			stream->active_remote_candidates = g_list_prepend(
+					stream->active_remote_candidates,
+					purple_media_candidate_copy(
+					remote_candidate));
+			break;
+		}
+	}
+	if (iter == NULL)
+		stream->active_remote_candidates = g_list_prepend(
+				stream->active_remote_candidates,
+				purple_media_candidate_copy(
+				remote_candidate));
+
+	purple_debug_info("media", "candidate pair established\n");
+}
+
+static void
+purple_media_codecs_changed_cb(PurpleMediaBackend *backend,
+		const gchar *sess_id, PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[CODECS_CHANGED], 0, sess_id);
+}
+#endif  /* USE_VV */
+
+gboolean
+purple_media_add_stream(PurpleMedia *media, const gchar *sess_id,
+		const gchar *who, PurpleMediaSessionType type,
+		gboolean initiator, const gchar *transmitter,
+		guint num_params, GParameter *params)
+{
+#ifdef USE_VV
+	PurpleMediaSession *session;
+	PurpleMediaStream *stream = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	if (!purple_media_backend_add_stream(media->priv->backend,
+			sess_id, who, type, initiator, transmitter,
+			num_params, params)) {
+		purple_debug_error("media", "Error adding stream.\n");
+		return FALSE;
+	}
+
+	session = purple_media_get_session(media, sess_id);
+
+	if (!session) {
+		session = g_new0(PurpleMediaSession, 1);
+		session->id = g_strdup(sess_id);
+		session->media = media;
+		session->type = type;
+		session->initiator = initiator;
+
+		purple_media_add_session(media, session);
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_NEW,
+				session->id, NULL);
+	}
+
+	if (!g_list_find_custom(media->priv->participants,
+			who, (GCompareFunc)strcmp)) {
+		media->priv->participants = g_list_prepend(
+				media->priv->participants, g_strdup(who));
+
+		g_signal_emit_by_name(media, "state-changed",
+				PURPLE_MEDIA_STATE_NEW, NULL, who);
+	}
+
+	if (purple_media_get_stream(media, sess_id, who) == NULL) {
+		stream = purple_media_insert_stream(session, who, initiator);
+
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_NEW,
+				session->id, who);
+	}
+
+	return TRUE;
+#else
+	return FALSE;
+#endif  /* USE_VV */
+}
+
+PurpleMediaManager *
+purple_media_get_manager(PurpleMedia *media)
+{
+	PurpleMediaManager *ret;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	g_object_get(media, "manager", &ret, NULL);
+	return ret;
+}
+
+PurpleMediaSessionType
+purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id)
+{
+#ifdef USE_VV
+	PurpleMediaSession *session;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), PURPLE_MEDIA_NONE);
+	session = purple_media_get_session(media, sess_id);
+	return session->type;
+#else
+	return PURPLE_MEDIA_NONE;
+#endif
+}
+/* XXX: Should wait until codecs-ready is TRUE before using this function */
+GList *
+purple_media_get_codecs(PurpleMedia *media, const gchar *sess_id)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	return purple_media_backend_get_codecs(media->priv->backend, sess_id);
+#else
+	return NULL;
+#endif
+}
+
+GList *
+purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id,
+                                  const gchar *participant)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	return purple_media_backend_get_local_candidates(media->priv->backend,
+			sess_id, participant);
+#else
+	return NULL;
+#endif
+}
+
+void
+purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id,
+                                   const gchar *participant,
+                                   GList *remote_candidates)
+{
+#ifdef USE_VV
+	PurpleMediaStream *stream;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	stream = purple_media_get_stream(media, sess_id, participant);
+
+	if (stream == NULL) {
+		purple_debug_error("media",
+				"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,
+			purple_media_candidate_list_copy(remote_candidates));
+
+	purple_media_backend_add_remote_candidates(media->priv->backend,
+			sess_id, participant, remote_candidates);
+#endif
+}
+
+#if 0
+/*
+ * These two functions aren't being used and I'd rather not lock in the API
+ * until they are needed. If they ever are.
+ */
+
+GList *
+purple_media_get_active_local_candidates(PurpleMedia *media,
+		const gchar *sess_id, const gchar *participant)
+{
+#ifdef USE_VV
+	PurpleMediaStream *stream;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	stream = purple_media_get_stream(media, sess_id, participant);
+	return purple_media_candidate_list_copy(
+			stream->active_local_candidates);
+#else
+	return NULL;
+#endif
+}
+
+GList *
+purple_media_get_active_remote_candidates(PurpleMedia *media,
+		const gchar *sess_id, const gchar *participant)
+{
+#ifdef USE_VV
+	PurpleMediaStream *stream;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	stream = purple_media_get_stream(media, sess_id, participant);
+	return purple_media_candidate_list_copy(
+			stream->active_remote_candidates);
+#else
+	return NULL;
+#endif
+}
+#endif
+
+gboolean
+purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id,
+                               const gchar *participant, GList *codecs)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	return purple_media_backend_set_remote_codecs(media->priv->backend,
+			sess_id, participant, codecs);
+#else
+	return FALSE;
+#endif
+}
+
+gboolean
+purple_media_candidates_prepared(PurpleMedia *media,
+		const gchar *session_id, const gchar *participant)
+{
+#ifdef USE_VV
+	GList *streams;
+	gboolean prepared = TRUE;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	streams = purple_media_get_streams(media, session_id, participant);
+
+	for (; streams; streams = g_list_delete_link(streams, streams)) {
+		PurpleMediaStream *stream = streams->data;
+		if (stream->candidates_prepared == FALSE) {
+			g_list_free(streams);
+			prepared = FALSE;
+			break;
+		}
+	}
+
+	return prepared;
+#else
+	return FALSE;
+#endif
+}
+
+gboolean
+purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, PurpleMediaCodec *codec)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	return purple_media_backend_set_send_codec(
+			media->priv->backend, sess_id, codec);
+#else
+	return FALSE;
+#endif
+}
+
+gboolean
+purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	return purple_media_backend_codecs_ready(
+			media->priv->backend, sess_id);
+#else
+	return FALSE;
+#endif
+}
+
+gboolean
+purple_media_is_initiator(PurpleMedia *media,
+		const gchar *sess_id, const gchar *participant)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	if (sess_id == NULL && participant == NULL)
+		return media->priv->initiator;
+	else if (sess_id != NULL && participant == NULL) {
+		PurpleMediaSession *session =
+				purple_media_get_session(media, sess_id);
+		return session != NULL ? session->initiator : FALSE;
+	} else if (sess_id != NULL && participant != NULL) {
+		PurpleMediaStream *stream = purple_media_get_stream(
+				media, sess_id, participant);
+		return stream != NULL ? stream->initiator : FALSE;
+	}
+#endif
+	return FALSE;
+}
+
+gboolean
+purple_media_accepted(PurpleMedia *media, const gchar *sess_id,
+		const gchar *participant)
+{
+#ifdef USE_VV
+	gboolean accepted = TRUE;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	if (sess_id == NULL && participant == NULL) {
+		GList *streams = media->priv->streams;
+
+		for (; streams; streams = g_list_next(streams)) {
+			PurpleMediaStream *stream = streams->data;
+			if (stream->accepted == FALSE) {
+				accepted = FALSE;
+				break;
+			}
+		}
+	} else if (sess_id != NULL && participant == NULL) {
+		GList *streams = purple_media_get_streams(
+				media, sess_id, NULL);
+		for (; streams; streams =
+				g_list_delete_link(streams, streams)) {
+			PurpleMediaStream *stream = streams->data;
+			if (stream->accepted == FALSE) {
+				g_list_free(streams);
+				accepted = FALSE;
+				break;
+			}
+		}
+	} else if (sess_id != NULL && participant != NULL) {
+		PurpleMediaStream *stream = purple_media_get_stream(
+				media, sess_id, participant);
+		if (stream == NULL || stream->accepted == FALSE)
+			accepted = FALSE;
+	}
+
+	return accepted;
+#else
+	return FALSE;
+#endif
+}
+
+void purple_media_set_input_volume(PurpleMedia *media,
+		const gchar *session_id, double level)
+{
+#ifdef USE_VV
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(media->priv->backend));
+
+	purple_media_backend_fs2_set_input_volume(
+			PURPLE_MEDIA_BACKEND_FS2(
+			media->priv->backend),
+			session_id, level);
+#endif
+}
+
+void purple_media_set_output_volume(PurpleMedia *media,
+		const gchar *session_id, const gchar *participant,
+		double level)
+{
+#ifdef USE_VV
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(media->priv->backend));
+
+	purple_media_backend_fs2_set_output_volume(
+			PURPLE_MEDIA_BACKEND_FS2(
+			media->priv->backend),
+			session_id, participant, level);
+#endif
+}
+
+gulong
+purple_media_set_output_window(PurpleMedia *media, const gchar *session_id,
+		const gchar *participant, gulong window_id)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	return purple_media_manager_set_output_window(media->priv->manager,
+			media, session_id, participant, window_id);
+#else
+	return 0;
+#endif
+}
+
+void
+purple_media_remove_output_windows(PurpleMedia *media)
+{
+#ifdef USE_VV
+	GList *iter = media->priv->streams;
+	for (; iter; iter = g_list_next(iter)) {
+		PurpleMediaStream *stream = iter->data;
+		purple_media_manager_remove_output_windows(
+				media->priv->manager, media,
+				stream->session->id, stream->participant);
+	}
+
+	iter = purple_media_get_session_ids(media);
+	for (; iter; iter = g_list_delete_link(iter, iter)) {
+		gchar *session_name = iter->data;
+		purple_media_manager_remove_output_windows(
+				media->priv->manager, media,
+				session_name, NULL);
+	}
+#endif
+}
+
+#ifdef USE_GSTREAMER
+GstElement *
+purple_media_get_tee(PurpleMedia *media,
+		const gchar *session_id, const gchar *participant)
+{
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	if (PURPLE_IS_MEDIA_BACKEND_FS2(media->priv->backend))
+		return purple_media_backend_fs2_get_tee(
+				PURPLE_MEDIA_BACKEND_FS2(
+				media->priv->backend),
+				session_id, participant);
+	g_return_val_if_reached(NULL);
+#else
+	return NULL;
+#endif
+}
+#endif /* USE_GSTREAMER */
+
--- a/libpurple/mediamanager.c	Wed Nov 11 02:47:50 2009 +0000
+++ b/libpurple/mediamanager.c	Wed Nov 11 03:07:21 2009 +0000
@@ -37,8 +37,8 @@
 #endif
 
 #ifdef USE_VV
+#include <media/backend-fs2.h>
 
-#include <gst/farsight/fs-conference-iface.h>
 #include <gst/farsight/fs-element-added-notifier.h>
 #include <gst/interfaces/xoverlay.h>
 
@@ -80,6 +80,7 @@
 	GList *elements;
 	GList *output_windows;
 	gulong next_output_window_id;
+	GType backend_type;
 
 	PurpleMediaElementInfo *video_src;
 	PurpleMediaElementInfo *video_sink;
@@ -167,6 +168,9 @@
 	media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
 	media->priv->medias = NULL;
 	media->priv->next_output_window_id = 1;
+#ifdef USE_VV
+	media->priv->backend_type = PURPLE_TYPE_MEDIA_BACKEND_FS2;
+#endif
 
 	purple_prefs_add_none("/purple/media");
 	purple_prefs_add_none("/purple/media/audio");
@@ -314,34 +318,15 @@
 {
 #ifdef USE_VV
 	PurpleMedia *media;
-	FsConference *conference = FS_CONFERENCE(gst_element_factory_make(conference_type, NULL));
-	GstStateChangeReturn ret;
 	gboolean signal_ret;
 
-	if (conference == NULL) {
-		purple_conv_present_error(remote_user, account,
-					  _("Error creating conference."));
-		purple_debug_error("media", "Conference == NULL\n");
-		return NULL;
-	}
-
 	media = PURPLE_MEDIA(g_object_new(purple_media_get_type(),
 			     "manager", manager,
 			     "account", account,
-			     "conference", conference,
+			     "conference-type", conference_type,
 			     "initiator", initiator,
 			     NULL));
 
-	ret = gst_element_set_state(GST_ELEMENT(conference), GST_STATE_PLAYING);
-
-	if (ret == GST_STATE_CHANGE_FAILURE) {
-		purple_conv_present_error(remote_user, account,
-					  _("Error creating conference."));
-		purple_debug_error("media", "Failed to start conference.\n");
-		g_object_unref(media);
-		return NULL;
-	}
-
 	g_signal_emit(manager, purple_media_manager_signals[INIT_MEDIA], 0,
 			media, account, remote_user, &signal_ret);
 
@@ -930,6 +915,30 @@
 #endif
 }
 
+void
+purple_media_manager_set_backend_type(PurpleMediaManager *manager,
+		GType backend_type)
+{
+#ifdef USE_VV
+	g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager));
+
+	manager->priv->backend_type = backend_type;
+#endif
+}
+
+GType
+purple_media_manager_get_backend_type(PurpleMediaManager *manager)
+{	
+#ifdef USE_VV
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager),
+			PURPLE_MEDIA_CAPS_NONE);
+
+	return manager->priv->backend_type;
+#else
+	return G_TYPE_NONE;
+#endif
+}
+
 #ifdef USE_GSTREAMER
 
 /*
--- a/libpurple/mediamanager.h	Wed Nov 11 02:47:50 2009 +0000
+++ b/libpurple/mediamanager.h	Wed Nov 11 03:07:21 2009 +0000
@@ -213,6 +213,28 @@
  */
 PurpleMediaCaps purple_media_manager_get_ui_caps(PurpleMediaManager *manager);
 
+/**
+ * Sets which media backend type media objects will use.
+ *
+ * @param manager The manager to set the caps on.
+ * @param backend_type The media backend type to use.
+ *
+ * @since 2.7.0
+ */
+void purple_media_manager_set_backend_type(PurpleMediaManager *manager,
+		GType backend_type);
+
+/**
+ * Gets which media backend type media objects will use.
+ *
+ * @param manager The manager to get the media backend type from.
+ *
+ * @return The type of media backend type media objects will use.
+ *
+ * @since 2.7.0
+ */
+GType purple_media_manager_get_backend_type(PurpleMediaManager *manager);
+
 /*}@*/
 
 #ifdef __cplusplus
--- a/libpurple/protocols/jabber/jingle/rtp.c	Wed Nov 11 02:47:50 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.c	Wed Nov 11 03:07:21 2009 +0000
@@ -468,13 +468,22 @@
 
 	g_return_if_fail(JINGLE_IS_SESSION(session));
 
-	if (type == PURPLE_MEDIA_INFO_HANGUP) {
+	if (type == PURPLE_MEDIA_INFO_HANGUP ||
+			type == PURPLE_MEDIA_INFO_REJECT) {
 		jabber_iq_send(jingle_session_terminate_packet(
-				session, "success"));
-		g_object_unref(session);
-	} else if (type == PURPLE_MEDIA_INFO_REJECT) {
-		jabber_iq_send(jingle_session_terminate_packet(
-				session, "decline"));
+				session, type == PURPLE_MEDIA_INFO_HANGUP ?
+				"success" : "decline"));
+
+		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
+				G_CALLBACK(jingle_rtp_state_changed_cb),
+				session);
+		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
+				G_CALLBACK(jingle_rtp_stream_info_cb),
+				session);
+		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
+				G_CALLBACK(jingle_rtp_new_candidate_cb),
+				session);
+
 		g_object_unref(session);
 	} else if (type == PURPLE_MEDIA_INFO_ACCEPT &&
 			jingle_session_is_initiator(session) == FALSE) {
--- a/libpurple/util.h	Wed Nov 11 02:47:50 2009 +0000
+++ b/libpurple/util.h	Wed Nov 11 03:07:21 2009 +0000
@@ -42,6 +42,7 @@
 typedef struct _PurpleKeyValuePair PurpleKeyValuePair;
 
 #include "account.h"
+#include "signals.h"
 #include "xmlnode.h"
 #include "notify.h"
 
--- a/po/POTFILES.in	Wed Nov 11 02:47:50 2009 +0000
+++ b/po/POTFILES.in	Wed Nov 11 03:07:21 2009 +0000
@@ -50,7 +50,7 @@
 libpurple/ft.c
 libpurple/gconf/purple.schemas.in
 libpurple/log.c
-libpurple/media.c
+libpurple/media/media.c
 libpurple/mediamanager.c
 libpurple/plugin.c
 libpurple/plugins/autoaccept.c