changeset 25548:70cdff43ec76

You can actually receive Google Talk voice calls with crappy UI now
author Sean Egan <seanegan@gmail.com>
date Wed, 19 Sep 2007 22:55:19 +0000
parents 2fda71133800
children de4914b5e45c 8067ba21fdd0
files libpurple/media.c libpurple/media.h libpurple/mediamanager.c libpurple/mediamanager.h libpurple/protocols/jabber/google.c pidgin/Makefile.am pidgin/gtkconv.c pidgin/gtkconv.h
diffstat 8 files changed, 465 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/media.c	Wed Sep 05 01:55:16 2007 +0000
+++ b/libpurple/media.c	Wed Sep 19 22:55:19 2007 +0000
@@ -23,6 +23,8 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#include <string.h>
+
 #include "connection.h"
 #include "media.h"
 
@@ -36,6 +38,13 @@
 
 	char *name;
 	PurpleConnection *connection;
+	GstElement *audio_src;
+	GstElement *audio_sink;
+	GstElement *video_src;
+	GstElement *video_sink;
+
+	FarsightStream *audio_stream;
+	FarsightStream *video_stream;
 };
 
 #define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate))
@@ -51,7 +60,11 @@
 
 
 enum {
-	STATE_CHANGE,
+	READY,
+	ACCEPTED,
+	HANGUP,
+	REJECT,
+	GOT_HANGUP,
 	LAST_SIGNAL
 };
 static guint purple_media_signals[LAST_SIGNAL] = {0};
@@ -61,8 +74,12 @@
 	PROP_FARSIGHT_SESSION,
 	PROP_NAME,
 	PROP_CONNECTION,
-	PROP_MIC_ELEMENT,
-	PROP_SPEAKER_ELEMENT,
+	PROP_AUDIO_SRC,
+	PROP_AUDIO_SINK,
+	PROP_VIDEO_SRC,
+	PROP_VIDEO_SINK,
+	PROP_VIDEO_STREAM,
+	PROP_AUDIO_STREAM
 };
 
 GType
@@ -103,26 +120,93 @@
 			"Farsight session",
 			"The FarsightSession associated with this media.",
 			FARSIGHT_TYPE_SESSION,
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READABLE));
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
 
 	g_object_class_install_property(gobject_class, PROP_NAME,
 			g_param_spec_string("screenname",
 			"Screenname",
 			"The screenname of the remote user",
 			NULL,
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READABLE));
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
 
 	g_object_class_install_property(gobject_class, PROP_CONNECTION,
 			g_param_spec_pointer("connection",
 			"Connection",
 			"The PurpleConnection associated with this session",
-			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READABLE));
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_AUDIO_SRC,
+			g_param_spec_object("audio-src",
+			"Audio source",
+			"The GstElement used to source audio",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_AUDIO_SINK,
+			g_param_spec_object("audio-sink",
+			"Audio sink",
+			"The GstElement used to sink audio",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_VIDEO_SRC,
+			g_param_spec_object("video-src",
+			"Video source",
+			"The GstElement used to source video",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_VIDEO_SINK,
+			g_param_spec_object("video-sink",
+			"Audio source",
+			"The GstElement used to sink video",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_VIDEO_STREAM,
+			g_param_spec_object("video-stream",
+			"Video stream",
+			"The FarsightStream used for video",
+			FARSIGHT_TYPE_STREAM,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_AUDIO_STREAM,
+			g_param_spec_object("audio-stream",
+			"Audio stream",
+			"The FarsightStream used for audio",
+			FARSIGHT_TYPE_STREAM,
+			G_PARAM_READWRITE));
+
+	purple_media_signals[READY] = g_signal_new("ready", G_TYPE_FROM_CLASS(klass),
+				 	 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[ACCEPTED] = g_signal_new("accepted", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[HANGUP] = g_signal_new("hangup", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[REJECT] = g_signal_new("reject", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[GOT_HANGUP] = g_signal_new("got-hangup", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+
+	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
@@ -141,14 +225,55 @@
 
 	switch (prop_id) {
 		case PROP_FARSIGHT_SESSION:
+			if (media->priv->farsight_session)
+				g_object_unref(media->priv->farsight_session);
 			media->priv->farsight_session = g_value_get_object(value);
+			g_object_ref(media->priv->farsight_session);
 			break;
 		case PROP_NAME:
-			media->priv->name = g_value_get_string(value);
+			g_free(media->priv->name);
+			media->priv->name = g_value_dup_string(value);
 			break;
 		case PROP_CONNECTION:
 			media->priv->connection = g_value_get_pointer(value);
 			break;
+		case PROP_AUDIO_SRC:
+			if (media->priv->audio_src)
+				gst_object_unref(media->priv->audio_src);
+			media->priv->audio_src = g_value_get_object(value);
+			gst_object_ref(media->priv->audio_src);
+			break;
+		case PROP_AUDIO_SINK:
+			if (media->priv->audio_sink)
+				gst_object_unref(media->priv->audio_sink);
+			media->priv->audio_sink = g_value_get_object(value);
+			gst_object_ref(media->priv->audio_sink);
+			break;
+		case PROP_VIDEO_SRC:
+			if (media->priv->video_src)
+				gst_object_unref(media->priv->video_src);
+			media->priv->video_src = g_value_get_object(value);
+			gst_object_ref(media->priv->video_src);
+			break;
+		case PROP_VIDEO_SINK:
+			if (media->priv->video_sink)
+				gst_object_unref(media->priv->video_sink);
+			media->priv->video_sink = g_value_get_object(value);
+			gst_object_ref(media->priv->video_sink);
+			break;
+		case PROP_VIDEO_STREAM:
+			if (media->priv->video_stream)
+				g_object_unref(media->priv->video_stream);
+			media->priv->video_stream = g_value_get_object(value);
+			gst_object_ref(media->priv->video_stream);
+			break;
+		case PROP_AUDIO_STREAM:
+			if (media->priv->audio_stream)
+				g_object_unref(media->priv->audio_stream);
+			media->priv->audio_stream = g_value_get_object(value);
+			gst_object_ref(media->priv->audio_stream);
+			break;
+
 		default:	
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 			break;
@@ -173,6 +298,25 @@
 		case PROP_CONNECTION:
 			g_value_set_pointer(value, media->priv->connection);
 			break;
+		case PROP_AUDIO_SRC:
+			g_value_set_object(value, media->priv->audio_src);
+			break;
+		case PROP_AUDIO_SINK:
+			g_value_set_object(value, media->priv->audio_sink);
+			break;
+		case PROP_VIDEO_SRC:
+			g_value_set_object(value, media->priv->video_src);
+			break;
+		case PROP_VIDEO_SINK:
+			g_value_set_object(value, media->priv->video_sink);
+			break;
+		case PROP_VIDEO_STREAM:
+			g_value_set_object(value, media->priv->video_stream);
+			break;
+		case PROP_AUDIO_STREAM:
+			g_value_set_object(value, media->priv->audio_stream);
+			break;
+
 		default:	
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
 			break;
@@ -180,4 +324,132 @@
 
 }
 
+void 
+purple_media_get_elements(PurpleMedia *media, GstElement **audio_src, GstElement **audio_sink,
+                                                  GstElement **video_src, GstElement **video_sink)
+{
+	 if (audio_src) 
+		g_object_get(G_OBJECT(media), "audio-src", *audio_src, NULL);
+	 if (audio_sink) 
+		g_object_get(G_OBJECT(media), "audio-sink", *audio_sink, NULL);
+	 if (video_src) 
+		g_object_get(G_OBJECT(media), "video-src", *video_src, NULL);
+	 if (video_sink) 
+		g_object_get(G_OBJECT(media), "video-sink", *video_sink, NULL);
+
+}
+
+void 
+purple_media_set_audio_src(PurpleMedia *media, GstElement *audio_src)
+{
+	g_object_set(G_OBJECT(media), "audio-src", audio_src, NULL);
+}
+
+void 
+purple_media_set_audio_sink(PurpleMedia *media, GstElement *audio_sink)
+{
+	g_object_set(G_OBJECT(media), "audio-sink", audio_sink, NULL);
+}
+
+void 
+purple_media_set_video_src(PurpleMedia *media, GstElement *video_src)
+{
+	g_object_set(G_OBJECT(media), "video-src", video_src, NULL);
+}
+
+void 
+purple_media_set_video_sink(PurpleMedia *media, GstElement *video_sink)
+{
+	g_object_set(G_OBJECT(media), "video-sink", video_sink, NULL);
+}
+
+GstElement *
+purple_media_get_audio_src(PurpleMedia *media)
+{
+	GstElement *ret;
+	g_object_get(G_OBJECT(media), "audio-src", &ret, NULL);
+	return ret;
+}
+
+GstElement *
+purple_media_get_audio_sink(PurpleMedia *media)
+{
+	GstElement *ret;
+	g_object_get(G_OBJECT(media), "audio-sink", &ret, NULL);
+	return ret;
+}
+
+GstElement *
+purple_media_get_video_src(PurpleMedia *media)
+{
+	GstElement *ret;
+	g_object_get(G_OBJECT(media), "video-src", &ret, NULL);
+	return ret;
+}
+
+GstElement *
+purple_media_get_video_sink(PurpleMedia *media)
+{
+	GstElement *ret;
+	g_object_get(G_OBJECT(media), "video-sink", &ret, NULL);
+	return ret;
+}
+
+GstElement *
+purple_media_get_audio_pipeline(PurpleMedia *media)
+{
+	FarsightStream *stream;
+	g_object_get(G_OBJECT(media), "audio-stream", &stream, NULL);
+printf("stream: %d\n\n\n", stream);
+GstElement *l = farsight_stream_get_pipeline(stream);
+printf("Element: %d\n", l);
+	return farsight_stream_get_pipeline(stream);
+}
+
+PurpleConnection *
+purple_media_get_connection(PurpleMedia *media)
+{
+	PurpleConnection *gc;
+	g_object_get(G_OBJECT(media), "connection", &gc, NULL);
+	return gc;
+}
+
+const char *
+purple_media_get_screenname(PurpleMedia *media)
+{
+	const char *ret;
+	g_object_get(G_OBJECT(media), "screenname", &ret, NULL);
+	return ret;
+}
+
+void
+purple_media_ready(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[READY], 0);
+}
+
+void
+purple_media_accept(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[ACCEPTED], 0);
+}
+
+void
+purple_media_hangup(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[HANGUP], 0);
+}
+
+void
+purple_media_reject(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[REJECT], 0);
+}
+
+void
+purple_media_got_hangup(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[GOT_HANGUP], 0);
+}
+
 #endif  /* USE_FARSIGHT */
--- a/libpurple/media.h	Wed Sep 05 01:55:16 2007 +0000
+++ b/libpurple/media.h	Wed Sep 19 22:55:19 2007 +0000
@@ -71,6 +71,28 @@
 
 GType purple_media_get_type();
 
+void purple_media_get_elements(PurpleMedia *media, GstElement **audio_src, GstElement **audio_sink,
+						  GstElement **video_src, GstElement **video_sink);
+
+void purple_media_set_audio_src(PurpleMedia *media, GstElement *video_src);
+void purple_media_set_audio_sink(PurpleMedia *media, GstElement *video_src);
+void purple_media_set_video_src(PurpleMedia *media, GstElement *video_src);
+void purple_media_set_video_sink(PurpleMedia *media, GstElement *video_src);
+
+GstElement *purple_media_get_audio_src(PurpleMedia *media);
+GstElement *purple_media_get_audio_sink(PurpleMedia *media);
+GstElement *purple_media_get_video_src(PurpleMedia *media);
+GstElement *purple_media_get_video_sink(PurpleMedia *media);
+
+GstElement *purple_media_get_audio_pipeline(PurpleMedia *media);
+
+PurpleConnection *purple_media_get_connection(PurpleMedia *media);
+const char *purple_media_get_screenname(media);
+void purple_media_ready(PurpleMedia *media);
+void purple_media_accept(PurpleMedia *media);
+void purple_media_reject(PurpleMedia *media);
+void purple_media_hangup(PurpleMedia *media);
+void purple_media_got_hangup(PurpleMedia *media);
 G_END_DECLS
 
 #endif  /* USE_FARSIGHT */
--- a/libpurple/mediamanager.c	Wed Sep 05 01:55:16 2007 +0000
+++ b/libpurple/mediamanager.c	Wed Sep 19 22:55:19 2007 +0000
@@ -47,7 +47,7 @@
 
 
 enum {
-	NEW_MEDIA,
+	INIT_MEDIA,
 	LAST_SIGNAL
 };
 static guint purple_media_manager_signals[LAST_SIGNAL] = {0};
@@ -92,7 +92,7 @@
 	
 	gobject_class->finalize = purple_media_manager_finalize;
 
-	purple_media_manager_signals[NEW_MEDIA] = g_signal_new ("new-media",
+	purple_media_manager_signals[INIT_MEDIA] = g_signal_new ("init-media",
 		G_TYPE_FROM_CLASS (klass),
 		G_SIGNAL_RUN_LAST,
 		0, NULL, NULL,
@@ -127,13 +127,17 @@
 PurpleMedia*
 purple_media_manager_create_media(PurpleMediaManager *manager, 
 				  PurpleConnection *gc,
-				  const char *screenname)
+				  const char *screenname,
+				  FarsightStream *audio_stream,
+				  FarsightStream *video_stream)
 {
 	PurpleMedia *media = PURPLE_MEDIA(g_object_new(purple_media_get_type(),
 					  "screenname", screenname,
-					  "connection", gc, NULL));
+					  "connection", gc, 
+					  "audio-stream", audio_stream,
+					  "video-stream", video_stream, NULL));
 	manager->priv->medias = g_list_append(manager->priv->medias, media);
-	g_signal_emit(manager, purple_media_manager_signals[NEW_MEDIA], 0, media);
+	g_signal_emit(manager, purple_media_manager_signals[INIT_MEDIA], 0, media);
 	return media;
 }
 
--- a/libpurple/mediamanager.h	Wed Sep 05 01:55:16 2007 +0000
+++ b/libpurple/mediamanager.h	Wed Sep 19 22:55:19 2007 +0000
@@ -68,7 +68,9 @@
 
 PurpleMedia *purple_media_manager_create_media(PurpleMediaManager *manager,
 					       PurpleConnection *gc,
-					       const char *screenname);
+					       const char *screenname,
+					       FarsightStream *audio_stream,
+					       FarsightStream *video_stream);
 
 G_END_DECLS
 
--- a/libpurple/protocols/jabber/google.c	Wed Sep 05 01:55:16 2007 +0000
+++ b/libpurple/protocols/jabber/google.c	Wed Sep 19 22:55:19 2007 +0000
@@ -54,6 +54,7 @@
 	char *remote_jid;
 } GoogleSession;
 
+GHashTable *sessions = NULL;
 
 static guint 
 google_session_id_hash(gconstpointer key) 
@@ -75,7 +76,17 @@
 	return !strcmp(c->id, d->id) && !strcmp(c->initiator, d->initiator);
 }
 
-GHashTable *sessions = NULL;
+static void
+google_session_destroy(GoogleSession *session)
+{
+	g_hash_table_remove(sessions, &(session->id));
+	g_free(session->id.id);
+	g_free(session->id.initiator);
+	g_free(session->remote_jid);
+	g_object_unref(session->media);
+	g_object_unref(session->stream);
+	g_free(session);
+}
 
 static xmlnode *
 google_session_create_xmlnode(GoogleSession *session, const char *type)
@@ -116,6 +127,39 @@
 	farsight_stream_start(session->stream);
 }
 
+static void
+google_session_send_terminate(GoogleSession *session)
+{
+	xmlnode *sess;
+	GList *codecs = farsight_stream_get_codec_intersection(session->stream);
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	sess = google_session_create_xmlnode(session, "terminate");
+	xmlnode_insert_child(iq->node, sess);
+	
+	jabber_iq_send(iq);
+	farsight_stream_stop(session->stream);
+	google_session_destroy(session);
+}
+
+static void
+google_session_send_reject(GoogleSession *session)
+{
+	xmlnode *sess;
+	GList *codecs = farsight_stream_get_codec_intersection(session->stream);
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	sess = google_session_create_xmlnode(session, "reject");
+	xmlnode_insert_child(iq->node, sess);
+	
+	jabber_iq_send(iq);
+	farsight_stream_stop(session->stream);
+	google_session_destroy(session);
+}
+
+
 static void 
 google_session_candidates_prepared (FarsightStream *stream, gchar *candidate_id, GoogleSession *session)
 {
@@ -159,10 +203,11 @@
 	jabber_iq_send(iq);
 }
 
-static gboolean
-google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet)
+static void
+google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
 {
 	PurpleMedia *media;
+	JabberIq *result;
 	FarsightSession *fs;
 	GList *codecs = NULL;
 	xmlnode *desc_element, *codec_element;
@@ -185,7 +230,7 @@
 
 	g_object_set(G_OBJECT(session->stream), "transmitter", "libjingle", NULL);
 	
-	desc_element = xmlnode_get_child(packet, "description");
+	desc_element = xmlnode_get_child(sess, "description");
 	
 	for (codec_element = xmlnode_get_child(desc_element, "payload-type"); 
 	     codec_element; 
@@ -198,30 +243,37 @@
 		farsight_codec_init(codec, atoi(id), encoding_name, FARSIGHT_MEDIA_TYPE_AUDIO, clock_rate ? atoi(clock_rate) : 0);
 		codecs = g_list_append(codecs, codec);
 	}
-	GstElement *e = gst_element_factory_make("alsasrc", "source");
-	farsight_stream_set_source(session->stream, e);
-	farsight_stream_set_source_filter(session->stream, gst_caps_new_simple("audio/x-raw-int", "rate",G_TYPE_INT,8000, NULL));
-	gst_object_unref(e);
+
+	session->media = media = purple_media_manager_create_media(purple_media_manager_get(), js->gc, session->remote_jid, session->stream, NULL);
+
+	g_signal_connect_swapped(G_OBJECT(media), "accepted", G_CALLBACK(google_session_send_accept), session);
+	g_signal_connect_swapped(G_OBJECT(media), "reject", G_CALLBACK(google_session_send_reject), session);
+	g_signal_connect_swapped(G_OBJECT(media), "hangup", G_CALLBACK(google_session_send_terminate), session);
 
-	e = gst_element_factory_make("alsasink", "fakes");
-	g_object_set(e, "sync", FALSE, NULL);
+	
+	GstElement *e = purple_media_get_audio_src(media);
+	farsight_stream_set_source(session->stream, e);	
+	
+	e = purple_media_get_audio_sink(media);
 	farsight_stream_set_sink(session->stream, e);
-	gst_object_unref(e);
-
+	
 	farsight_stream_prepare_transports(session->stream);
 	res = farsight_stream_set_remote_codecs(session->stream, codecs);
-
+	
+	purple_media_ready(media);
 
 	farsight_codec_list_destroy(codecs);
 	g_signal_connect(G_OBJECT(session->stream), "new-native-candidate", G_CALLBACK(google_session_candidates_prepared), session);
-google_session_send_accept(session);
-	media = purple_media_manager_create_media(purple_media_manager_get(), js->gc, session->remote_jid);
-	return res;	
+	result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", session->remote_jid);
+	jabber_iq_send(result);
 }
 
-static gboolean
-google_session_handle_candidates(JabberStream  *js, GoogleSession *session, xmlnode *sess)
+static void 
+google_session_handle_candidates(JabberStream  *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
 {
+	JabberIq *result;
 	GList *list = NULL;
 	xmlnode *cand;
 	static int name = 0;
@@ -246,31 +298,46 @@
 	farsight_stream_add_remote_candidate(session->stream, list);
 	g_list_foreach(list, g_free, NULL);
 	g_list_free(list);
-	return TRUE;
+
+	result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", session->remote_jid);
+	jabber_iq_send(result);
+}
+
+static void
+google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	farsight_stream_stop(session->stream);
+	purple_media_got_hangup(session->media);
+	
+	google_session_destroy(session);
+}
+
+static void
+google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	farsight_stream_stop(session->stream);
+	purple_media_got_hangup(session->media);
+
+	google_session_destroy(session);
 }
 
 static void
 google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *packet)
 {
-	JabberIq *result;
-	gboolean valid = TRUE;
 	xmlnode *sess = xmlnode_get_child(packet, "session");	
 	const char *type = xmlnode_get_attrib(sess, "type");
 
 	if (!strcmp(type, "initiate")) {
-		valid  = google_session_handle_initiate(js, session, sess);
+		google_session_handle_initiate(js, session, packet, sess);
 	} else if (!strcmp(type, "accept")) {
 	} else if (!strcmp(type, "reject")) {
+		google_session_handle_reject(js, session, packet, sess);
 	} else if (!strcmp(type, "terminate")) {
+		google_session_handle_terminate(js, session, packet, sess);
 	} else if (!strcmp(type, "candidates")) {
-		valid = google_session_handle_candidates(js, session, sess);
-	}
-	
-	if (valid) {
-		result = jabber_iq_new(js, JABBER_IQ_RESULT);
-		jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
-		xmlnode_set_attrib(result->node, "to", session->remote_jid);
-		jabber_iq_send(result);
+		google_session_handle_candidates(js, session, packet, sess);
 	}
 }
 
--- a/pidgin/Makefile.am	Wed Sep 05 01:55:16 2007 +0000
+++ b/pidgin/Makefile.am	Wed Sep 19 22:55:19 2007 +0000
@@ -96,6 +96,7 @@
 	gtkimhtmltoolbar.c \
 	gtklog.c \
 	gtkmain.c \
+	gtkmedia.c \
 	gtkmenutray.c \
 	gtknotify.c \
 	gtkplugin.c \
@@ -144,6 +145,7 @@
 	gtkimhtml.h \
 	gtkimhtmltoolbar.h \
 	gtklog.h \
+	gtkmedia.c \
 	gtkmenutray.h \
 	gtknickcolors.h \
 	gtknotify.h \
--- a/pidgin/gtkconv.c	Wed Sep 05 01:55:16 2007 +0000
+++ b/pidgin/gtkconv.c	Wed Sep 19 22:55:19 2007 +0000
@@ -60,6 +60,7 @@
 #include "gtkimhtml.h"
 #include "gtkimhtmltoolbar.h"
 #include "gtklog.h"
+#include "gtkmedia.h"
 #include "gtkmenutray.h"
 #include "gtkpounce.h"
 #include "gtkprefs.h"
@@ -4490,7 +4491,7 @@
 	gtk_widget_show(paned);
 
 	/* Setup the top part of the pane */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtkconv->topvbox = vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE);
 	gtk_widget_show(vbox);
 
@@ -7163,9 +7164,55 @@
 }
 
 static void
-pidgin_conv_new_media_cb(PurpleMedia *media, gpointer nul)
-{
-	purple_notify_info(pidgin_conversations_get_handle(), "Media!", "New Media!", "You got new media!\n");
+pidgin_gtkmedia_message_cb(PidginMedia *media, const char *msg, PurpleConversation *conv)
+{
+	purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL));
+}
+
+static void
+pidgin_conv_new_media_cb(PurpleMediaManager *manager, PurpleMedia *media, gpointer nul)
+{	
+	GstElement *sendbin, *src, *sendlevel;
+	GstElement *recvbin, *sink, *recvlevel;
+	GstPad *pad, *ghost;
+
+	GtkWidget *gtkmedia;
+	PurpleConversation *conv;
+	PidginConversation *gtkconv;
+
+	sendbin = gst_bin_new("sendbin");
+	src = gst_element_factory_make("alsasrc", "asrc");
+	sendlevel = gst_element_factory_make("level", "sendlevel");
+	gst_bin_add_many(GST_BIN(sendbin), src, sendlevel, NULL);
+	gst_element_link(src, sendlevel); //, gst_caps_new_simple("audio/x-raw-int", "rate", G_TYPE_INT, 8000, NULL));
+	pad = gst_element_get_pad(sendlevel, "src");
+	ghost = gst_ghost_pad_new("ghostsrc", pad);
+	gst_element_add_pad(sendbin, ghost);
+	g_object_set(G_OBJECT(sendlevel), "message", TRUE, NULL);
+
+	recvbin = gst_bin_new("pidginrecvbin");
+	sink = gst_element_factory_make("alsasink", "asink");
+	g_object_set(G_OBJECT(sink), "sync", FALSE, NULL);
+	recvlevel = gst_element_factory_make("level", "recvlevel");
+	gst_bin_add_many(GST_BIN(recvbin), sink, recvlevel, NULL);
+	gst_element_link(recvlevel, sink);
+	pad = gst_element_get_pad(recvlevel, "sink");
+	ghost = gst_ghost_pad_new("ghostsink", pad);
+	gst_element_add_pad(recvbin, ghost);
+	g_object_set(G_OBJECT(recvlevel), "message", TRUE, NULL);
+
+	purple_media_set_audio_src(media, sendbin);
+	purple_media_set_audio_sink(media, recvbin);
+
+	gtkmedia = pidgin_media_new(media, sendlevel, recvlevel);
+	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, 
+				       purple_connection_get_account(purple_media_get_connection(media)), 
+				       purple_media_get_screenname(media));
+	gtkconv = PIDGIN_CONVERSATION(conv);
+	gtk_box_pack_start(GTK_BOX(gtkconv->topvbox), gtkmedia, FALSE, FALSE, 0);
+	gtk_widget_show(gtkmedia);
+	g_signal_connect_swapped(G_OBJECT(media), "got-hangup", G_CALLBACK(gtk_widget_destroy), gtkmedia);
+	g_signal_connect(G_OBJECT(gtkmedia), "message", G_CALLBACK(pidgin_gtkmedia_message_cb), conv);
 }
 
 void *
@@ -7264,7 +7311,7 @@
 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/hide_new",
                                 hide_new_pref_cb, NULL);
 
-	g_signal_connect(G_OBJECT(purple_media_manager_get()), "new-media",
+	g_signal_connect(G_OBJECT(purple_media_manager_get()), "init-media",
 			 G_CALLBACK(pidgin_conv_new_media_cb), NULL);
 
 
--- a/pidgin/gtkconv.h	Wed Sep 05 01:55:16 2007 +0000
+++ b/pidgin/gtkconv.h	Wed Sep 19 22:55:19 2007 +0000
@@ -162,6 +162,7 @@
 	GtkWidget *infopane;
 	GtkListStore *infopane_model;
 	GtkTreeIter infopane_iter;
+	GtkWidget *topvbox;
 };
 
 /*@}*/