summaryrefslogtreecommitdiff
path: root/telepathy-farstream
diff options
context:
space:
mode:
Diffstat (limited to 'telepathy-farstream')
-rw-r--r--telepathy-farstream/Makefile.am106
-rw-r--r--telepathy-farstream/NEWS157
-rw-r--r--telepathy-farstream/call-channel.c668
-rw-r--r--telepathy-farstream/call-channel.h118
-rw-r--r--telepathy-farstream/call-content.c2553
-rw-r--r--telepathy-farstream/call-content.h109
-rw-r--r--telepathy-farstream/call-priv.h33
-rw-r--r--telepathy-farstream/call-stream.c1742
-rw-r--r--telepathy-farstream/call-stream.h134
-rw-r--r--telepathy-farstream/channel-priv.h28
-rw-r--r--telepathy-farstream/channel.c594
-rw-r--r--telepathy-farstream/channel.h65
-rw-r--r--telepathy-farstream/content-priv.h48
-rw-r--r--telepathy-farstream/content.c551
-rw-r--r--telepathy-farstream/content.h72
-rw-r--r--telepathy-farstream/examples/Makefile.am24
-rw-r--r--telepathy-farstream/examples/call-handler.c646
-rw-r--r--telepathy-farstream/examples/python/Makefile.am7
-rw-r--r--telepathy-farstream/examples/python/README5
-rw-r--r--telepathy-farstream/examples/python/callchannel.py180
-rw-r--r--telepathy-farstream/examples/python/callhandler.py116
-rw-r--r--telepathy-farstream/examples/python/callui.py285
-rw-r--r--telepathy-farstream/examples/python/constants.py66
-rw-r--r--telepathy-farstream/examples/python/element-properties62
-rw-r--r--telepathy-farstream/examples/python/util.py40
-rw-r--r--telepathy-farstream/telepathy-farstream-1.pc.in11
-rw-r--r--telepathy-farstream/telepathy-farstream-uninstalled-1.pc.in11
-rw-r--r--telepathy-farstream/telepathy-farstream.h27
-rw-r--r--telepathy-farstream/utils.h62
29 files changed, 8520 insertions, 0 deletions
diff --git a/telepathy-farstream/Makefile.am b/telepathy-farstream/Makefile.am
new file mode 100644
index 000000000..06d3392aa
--- /dev/null
+++ b/telepathy-farstream/Makefile.am
@@ -0,0 +1,106 @@
+tfincludedir = $(includedir)/telepathy-farstream-1/telepathy-farstream
+tfinclude_HEADERS = \
+ telepathy-farstream.h \
+ channel.h \
+ content.h
+
+apisources = \
+ content.c \
+ channel.c
+
+libtelepathy_farstream_1_la_SOURCES = \
+ $(tfinclude_HEADERS) \
+ $(apisources) \
+ content-priv.h \
+ channel-priv.h \
+ call-channel.c \
+ call-channel.h \
+ call-content.h \
+ call-content.c \
+ call-stream.h \
+ call-stream.c \
+ call-priv.h \
+ utils.h
+
+nodist_libtelepathy_farstream_1_la_SOURCES = $(BUILT_SOURCES)
+
+lib_LTLIBRARIES = libtelepathy-farstream-1.la
+
+pkgconfigdir = ${libdir}/pkgconfig
+pkgconfig_DATA = telepathy-farstream-1.pc
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(FARSTREAM_CFLAGS) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -DG_LOG_DOMAIN=\"tp-fs\" \
+ $(ERROR_CFLAGS)
+
+libtelepathy_farstream_1_la_LIBADD = \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(GST_LIBS) \
+ $(FARSTREAM_LIBS) \
+ $(NULL)
+
+libtelepathy_farstream_1_la_LDFLAGS = -no-undefined \
+ -export-symbols-regex "^tf_(init|content_|channel_).*" \
+ -version-info "$(TPFS_LT_CURRENT)":"$(TPFS_LT_REVISION)":"$(TPFS_LT_AGE)"
+
+-include $(INTROSPECTION_MAKEFILE)
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = \
+ --add-include-path=$(srcdir) \
+ --add-include-path=$(top_builddir)/telepathy-glib \
+ $(NULL)
+INTROSPECTION_COMPILER_ARGS = \
+ --includedir=$(srcdir) \
+ --includedir=$(top_builddir)/telepathy-glib \
+ $(NULL)
+
+if HAVE_INTROSPECTION
+introspection_sources = $(tfinclude_HEADERS) $(apisources)
+
+TelepathyFarstream-1.gir: libtelepathy-farstream-1.la
+TelepathyFarstream_1_gir_INCLUDES = \
+ GObject-2.0 \
+ Gst-1.0 \
+ Farstream-0.2 \
+ TelepathyGLib-1 \
+ $(NULL)
+TelepathyFarstream_1_gir_PACKAGES = \
+ glib-2.0 \
+ gobject-2.0 \
+ gio-2.0 \
+ telepathy-glib-1 \
+ $(NULL)
+TelepathyFarstream_1_gir_EXPORT_PACKAGES = \
+ telepathy-farstream-1 \
+ $(NULL)
+TelepathyFarstream_1_gir_CFLAGS = $(AM_CPPFLAGS)
+TelepathyFarstream_1_gir_LIBS = libtelepathy-farstream-1.la
+TelepathyFarstream_1_gir_FILES = $(introspection_sources)
+TelepathyFarstream_1_gir_SCANNERFLAGS = --identifier-prefix=tf_ --identifier-prefix=Tf
+INTROSPECTION_GIRS += TelepathyFarstream-1.gir
+INTROSPECTION_SCANNER_ENV = \
+ PKG_CONFIG_PATH=$(builddir):$(top_builddir)/telepathy-glib:$${PKG_CONFIG_PATH}
+
+girdir = $(datadir)/gir-1.0
+gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(libdir)/girepository-1.0
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES = $(gir_DATA) $(typelib_DATA)
+endif
+
+SUBDIRS = . examples
diff --git a/telepathy-farstream/NEWS b/telepathy-farstream/NEWS
new file mode 100644
index 000000000..7363226e7
--- /dev/null
+++ b/telepathy-farstream/NEWS
@@ -0,0 +1,157 @@
+See the top-level NEWS file for changes that took place after
+telepathy-farstream was merged into telepathy-glib.
+
+telepathy-farstream 0.6.0 (25 Sep 2012)
+=======================================
+
+- Port to the final Farstream 0.2 API
+
+telepathy-farstream 0.5.0 (13 Sep 2012)
+=======================================
+
+- Port to GStreamer 1.0 and Farstream 0.2
+- Set RemoteContact when accepting & updating media descriptions
+
+telepathy-farstream 0.4.0 (4 Apr 2012)
+======================================
+
+This is the start of a new stable branch.
+
+There have (basically) been no changes since 0.2.3.
+
+There are too many changes since the last stable release. For more
+details, look at the git log.
+
+telepathy-farstream 0.2.3 (20 Mar 2012)
+=======================================
+- Fix various bugs
+- Improve debug messages
+- Improve GI annotations
+- Use the generic marshallers
+
+telepathy-farstream 0.2.2 (08 Mar 2012)
+=======================================
+
+- Allow an Endpoint to be removed so as it work with Rakia call transfers
+- Ignore port 2.26 deprecations
+- Added a tf_channel_new_finish() function
+- Misc bug fixes
+
+telepathy-farstream 0.2.1 (20 Feb 2012)
+=======================================
+
+- Now use Call1 as well as Streamed Media
+- Now requires Farstream and telepathy-glib 0.17.5
+
+telepathy-farstream 0.1.2 (18 Nov 2011)
+=======================================
+
+- Fix linking with ld --as-needed (bigon)
+- Add AudioControl support (mike, sjoerd)
+- respond to farsight-negotiate events (olivier)
+
+telepathy-farstream 0.1.1 (14 Jul 2011)
+=======================================
+- Fix the python bindings
+- Make the VideoControl interface actually work
+
+telepathy-farstream 0.1.0 (27 Jun 2011)
+=======================================
+- Rename from telepathy-farsight to telepathy-farstream
+- Implement Call API
+- Also implement Streamed Media API under the same C api
+
+telepathy-farsight 0.0.16 (22 Dec 2010)
+=======================================
+- Emit the NewActiveTransportPair signal
+- Emit CodecsUpdated more often
+- Various bug fixes
+
+telepathy-farsight 0.0.15 (30 Sep 2010)
+=======================================
+- Release sending resource when SetStreamSending(False) is called
+
+telepathy-farsight 0.0.14 (26 May 2010)
+=======================================
+- Add properties to get the Farsight2 session and stream
+- Recognize the shm transmitter
+- Ignore invalidly empty strings in tp properties
+- Fix -Wshadow warnings
+- Use silent rules if automake >= 1.11
+
+telepathy-farsight 0.0.13 (5 Jan 2010)
+======================================
+- Export held resource in a property
+- Transfer the ptime/maxptime
+
+telepathy-farsight 0.0.12 (15 Oct 2009)
+=======================================
+- Fix mixup between GSlice and malloc
+- Fix potential race between src-pad-added and dispose
+- The connected state in farsight is a lie, ignore it
+
+telepathy-farsight 0.0.11 (10 Sep 2009)
+=======================================
+- Fix double free
+- Fix more leaks
+
+telepathy-farsight 0.0.10 (08 Sep 2009)
+======================================
+- Fix some leaks
+- Fix possible deadlocks
+- Emit different error codes depending on the error
+- Emit stream state changes when the stream state changes according to ICE
+
+telepathy-farsight 0.0.9 (03 Aug 2009)
+======================================
+- Emit session-invalidated on channel dispose (prevents leak)
+- Fix ICE priority mangling (so not all candidates get 0)
+- Use new error numbers from the the 0.17.27 spec
+
+telepathy-farsight 0.0.8 (03 Aug 2009)
+======================================
+- Set ToS property on streams
+- Set ICE controlling according to the CreatedLocally property
+- Work around bug in the dbus-glib 0.80 of dbus properties
+- Fix bugs
+
+telepathy-farsight 0.0.7 (06 May 2009)
+======================================
+- Remove pygtk requirement
+- Print errors in its own domain
+- Update tp-glib dependency to 0.7.26 and fs2 dependency to 0.0.7
+- Make it more resilient in case of errors from other layers
+
+telepathy-farsight 0.0.6 (17 March 2009)
+========================================
+- Add support of the RelayInfo property
+
+telepathy-farsight 0.0.5 (16 March 2009)
+========================================
+- Recognize ice-udp
+- Improve error handling
+- Support the new CodecsUpdated method
+
+telepathy-farsight 0.0.4 (14 January 2009)
+==========================================
+- Add python bindings for tpfarsight
+- Fix hold
+- Make the "request-resource" signal optional, assumes
+ the resource is always there if there is no handler.
+
+telepathy-farsight 0.0.3 (21 November 2008)
+===========================================
+- Fix small brown-paper bug in last release
+- Rename tf_channel_new_from_proxy to tf_channel_new,
+ and leave the proxy creation to the client
+
+telepathy-farsight 0.0.2 (21 November 2008)
+===========================================
+
+- Added various makefile niceties from telepathy-glib
+
+telepathy-farsight 0.0.1 (21 November 2008)
+===========================================
+
+- Initial version
+- Split from stream-engine after 0.5.3
diff --git a/telepathy-farstream/call-channel.c b/telepathy-farstream/call-channel.c
new file mode 100644
index 000000000..4b9e46c2d
--- /dev/null
+++ b/telepathy-farstream/call-channel.c
@@ -0,0 +1,668 @@
+/*
+ * call-channel.c - Source for TfCallChannel
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:call-channel
+
+ * @short_description: Handle the Call interface on a Channel
+ *
+ * This class handles the
+ * org.freedesktop.Telepathy.Channel.Interface.Call on a
+ * channel using Farstream.
+ */
+
+#include "config.h"
+
+#include "call-channel.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <farstream/fs-conference.h>
+
+#include "call-content.h"
+#include "call-priv.h"
+
+
+static void call_channel_async_initable_init (GAsyncInitableIface *asynciface);
+
+G_DEFINE_TYPE_WITH_CODE (TfCallChannel, tf_call_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+ call_channel_async_initable_init))
+
+enum
+{
+ PROP_FS_CONFERENCES = 1
+};
+
+
+enum
+{
+ SIGNAL_FS_CONFERENCE_ADDED,
+ SIGNAL_FS_CONFERENCE_REMOVED,
+ SIGNAL_CONTENT_ADDED,
+ SIGNAL_CONTENT_REMOVED,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+struct CallConference {
+ gint use_count;
+ gchar *conference_type;
+ FsConference *fsconference;
+};
+
+struct CallParticipant {
+ gint use_count;
+ guint handle;
+ FsConference *fsconference;
+ FsParticipant *fsparticipant;
+};
+
+static void
+tf_call_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void tf_call_channel_dispose (GObject *object);
+
+
+static void tf_call_channel_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+static gboolean tf_call_channel_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error);
+
+static void content_added (TpCallChannel *proxy,
+ TpCallContent *context_proxy, TfCallChannel *self);
+static void content_removed (TpCallChannel *proxy,
+ TpCallContent *content_proxy, TpCallStateReason *reason,
+ TfCallChannel *self);
+static void channel_prepared (GObject *proxy, GAsyncResult *prepare_res,
+ gpointer user_data);
+
+
+
+static void
+tf_call_channel_class_init (TfCallChannelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = tf_call_channel_dispose;
+ object_class->get_property = tf_call_channel_get_property;
+
+ g_object_class_install_property (object_class, PROP_FS_CONFERENCES,
+ g_param_spec_boxed ("fs-conferences",
+ "Farstream FsConference object",
+ "GPtrArray of Farstream FsConferences for this channel",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ signals[SIGNAL_FS_CONFERENCE_ADDED] = g_signal_new ("fs-conference-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+ signals[SIGNAL_FS_CONFERENCE_REMOVED] = g_signal_new ("fs-conference-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+ signals[SIGNAL_CONTENT_ADDED] = g_signal_new ("content-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, TF_TYPE_CALL_CONTENT);
+
+ signals[SIGNAL_CONTENT_REMOVED] = g_signal_new ("content-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, TF_TYPE_CALL_CONTENT);
+}
+
+
+static void
+call_channel_async_initable_init (GAsyncInitableIface *asynciface)
+{
+ asynciface->init_async = tf_call_channel_init_async;
+ asynciface->init_finish = tf_call_channel_init_finish;
+}
+
+static void
+free_call_conference (gpointer data)
+{
+ struct CallConference *cc = data;
+
+ gst_object_unref (cc->fsconference);
+ g_slice_free (struct CallConference, data);
+}
+
+static void
+free_participant (gpointer data)
+{
+ struct CallParticipant *cp = data;
+
+ g_object_unref (cp->fsparticipant);
+ gst_object_unref (cp->fsconference);
+ g_slice_free (struct CallParticipant, cp);
+}
+
+static void
+tf_call_channel_init (TfCallChannel *self)
+{
+ self->fsconferences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ free_call_conference);
+
+ self->participants = g_ptr_array_new_with_free_func (free_participant);
+}
+
+
+static void
+tf_call_channel_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (initable);
+ GSimpleAsyncResult *res;
+
+ if (cancellable != NULL)
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+ "TfCallChannel initialisation does not support cancellation");
+ return;
+ }
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tf_call_channel_init_async);
+
+ tp_g_signal_connect_object (self->proxy, "content-added",
+ G_CALLBACK (content_added), self, 0);
+ tp_g_signal_connect_object (self->proxy, "content-removed",
+ G_CALLBACK (content_removed), self, 0);
+
+ tp_proxy_prepare_async (self->proxy, NULL, channel_prepared, res);
+}
+
+static gboolean
+tf_call_channel_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple_res;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (res,
+ G_OBJECT (initable), tf_call_channel_init_async), FALSE);
+ simple_res = G_SIMPLE_ASYNC_RESULT (res);
+
+ if (g_simple_async_result_propagate_error (simple_res, error))
+ return FALSE;
+
+ return g_simple_async_result_get_op_res_gboolean (simple_res);
+}
+
+
+static void
+tf_call_channel_dispose (GObject *object)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (object);
+
+ g_debug (G_STRFUNC);
+
+ /* Some of the contents may have more than our ref - if they're in the
+ middle of an async op, they're reffed by the async result.
+ In this case, unreffing them (implicitely) through destruction of
+ the hash table they're in will not dispose them just yet.
+ However, they keep an unreffed pointer to the call channel, and will,
+ when eventually disposed of, call upon the call channel to put their
+ conference back. Since that call channel will then be disposed of,
+ I think we can all agree that this is a bit unfortunate.
+ So we force dispose the contents as other objects already do, and
+ add checks to the content routines to bail out when the object has
+ already been disposed of. */
+ if (self->contents)
+ {
+ g_ptr_array_free (self->contents, TRUE);
+ }
+ self->contents = NULL;
+
+ if (self->participants)
+ g_ptr_array_unref (self->participants);
+ self->participants = NULL;
+
+ if (self->fsconferences)
+ g_hash_table_unref (self->fsconferences);
+ self->fsconferences = NULL;
+
+ if (self->proxy)
+ g_object_unref (self->proxy);
+ self->proxy = NULL;
+
+ if (G_OBJECT_CLASS (tf_call_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_call_channel_parent_class)->dispose (object);
+}
+
+static void
+conf_into_ptr_array (gpointer key, gpointer value, gpointer data)
+{
+ struct CallConference *cc = value;
+ GPtrArray *array = data;
+
+ g_ptr_array_add (array, cc->fsconference);
+}
+
+static void
+tf_call_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_FS_CONFERENCES:
+ {
+ GPtrArray *array = g_ptr_array_sized_new (
+ g_hash_table_size (self->fsconferences));
+
+ g_ptr_array_set_free_func (array, gst_object_unref);
+ g_hash_table_foreach (self->fsconferences, conf_into_ptr_array, array);
+ g_value_take_boxed (value, array);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+content_ready (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (user_data);
+ TfCallContent *content = TF_CALL_CONTENT (object);
+
+ if (g_async_initable_init_finish (G_ASYNC_INITABLE (object), res, NULL))
+ {
+ g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, content);
+ }
+ else
+ {
+ g_ptr_array_remove_fast (self->contents, content);
+ }
+
+ g_object_unref (self);
+}
+
+static gboolean
+add_content (TfCallChannel *self, TpCallContent *content_proxy)
+{
+ GError *error = NULL;
+ TfCallContent *content;
+ guint i;
+
+ /* Check if content already added */
+ if (!self->contents)
+ return FALSE;
+
+ for (i = 0; i < self->contents->len; i++)
+ {
+ if (tf_call_content_get_proxy (g_ptr_array_index (self->contents, i)) ==
+ content_proxy)
+ return TRUE;
+ }
+
+ content = tf_call_content_new_async (self, content_proxy,
+ &error, content_ready, g_object_ref (self));
+
+ if (error)
+ {
+ /* Error was already transmitted to the CM by TfCallContent */
+ g_clear_error (&error);
+ g_object_unref (self);
+ return FALSE;
+ }
+
+ g_ptr_array_add (self->contents, content);
+
+ return TRUE;
+}
+
+static void
+content_added (TpCallChannel *proxy,
+ TpCallContent *content_proxy,
+ TfCallChannel *self)
+{
+ /* Ignore signals before we got the "Contents" property to avoid races that
+ * could cause the same content to be added twice
+ */
+
+ if (!self->contents)
+ return;
+
+ add_content (self, content_proxy);
+}
+
+static void
+content_removed (TpCallChannel *proxy,
+ TpCallContent *content_proxy,
+ TpCallStateReason *reason,
+ TfCallChannel *self)
+{
+ guint i;
+ if (!self->contents)
+ return;
+
+ for (i = 0; i < self->contents->len; i++)
+ {
+
+ if (tf_call_content_get_proxy (g_ptr_array_index (self->contents, i)) ==
+ content_proxy)
+ {
+ TfCallContent *content = g_ptr_array_index (self->contents, i);
+
+ g_object_ref (content);
+ g_ptr_array_remove_index_fast (self->contents, i);
+ g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content);
+ g_object_unref (content);
+ return;
+ }
+ }
+}
+
+static void
+free_content (gpointer data)
+{
+ TfCallContent *content = data;
+
+ _tf_call_content_destroy (content);
+ g_object_unref (content);
+}
+
+static void
+channel_prepared (GObject *proxy, GAsyncResult *prepare_res, gpointer user_data)
+{
+ GSimpleAsyncResult *res = user_data;
+ TfCallChannel *self =
+ TF_CALL_CHANNEL (g_async_result_get_source_object (G_ASYNC_RESULT (res)));
+ GError *error = NULL;
+ GPtrArray *contents;
+ guint i;
+
+ if (!tp_proxy_prepare_finish (proxy, prepare_res, &error))
+ {
+ g_warning ("Preparing the channel: %s",
+ error->message);
+ g_simple_async_result_take_error (res, error);
+ goto out;
+ }
+
+ if (tp_call_channel_has_hardware_streaming (TP_CALL_CHANNEL (proxy)))
+ {
+ g_warning ("Hardware streaming property is TRUE, ignoring");
+
+ g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_NOT_CAPABLE,
+ "This channel does hardware streaming, not handled here");
+ goto out;
+ }
+
+ contents = tp_call_channel_get_contents (TP_CALL_CHANNEL (proxy));
+
+ self->contents = g_ptr_array_new_with_free_func (free_content);
+
+ for (i = 0; i < contents->len; i++)
+ if (!add_content (self, g_ptr_array_index (contents, i)))
+ break;
+
+ g_simple_async_result_set_op_res_gboolean (res, TRUE);
+
+out:
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ g_object_unref (self);
+}
+
+void
+tf_call_channel_new_async (TpChannel *channel,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TfCallChannel *self = g_object_new (TF_TYPE_CALL_CHANNEL, NULL);
+
+ self->proxy = g_object_ref (channel);
+ g_async_initable_init_async (G_ASYNC_INITABLE (self), 0, NULL, callback,
+ user_data);
+
+ /* Ownership passed to async call */
+ g_object_unref (self);
+}
+
+
+static gboolean
+find_conf_func (gpointer key, gpointer value, gpointer data)
+{
+ FsConference *conf = data;
+ struct CallConference *cc = value;
+
+ if (cc->fsconference == conf)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static struct CallConference *
+find_call_conference_by_conference (TfCallChannel *channel,
+ GstObject *conference)
+{
+ return g_hash_table_find (channel->fsconferences, find_conf_func,
+ conference);
+}
+
+gboolean
+tf_call_channel_bus_message (TfCallChannel *channel,
+ GstMessage *message)
+{
+ GError *error = NULL;
+ gchar *debug;
+ struct CallConference *cc;
+ guint i;
+
+ cc = find_call_conference_by_conference (channel, GST_MESSAGE_SRC (message));
+ if (!cc)
+ return FALSE;
+
+ switch (GST_MESSAGE_TYPE (message))
+ {
+ case GST_MESSAGE_WARNING:
+ gst_message_parse_warning (message, &error, &debug);
+
+ g_warning ("session: %s (%s)", error->message, debug);
+
+ g_error_free (error);
+ g_free (debug);
+ return TRUE;
+ case GST_MESSAGE_ERROR:
+ gst_message_parse_error (message, &error, &debug);
+
+ g_warning ("session ERROR: %s (%s)", error->message, debug);
+
+ tf_call_channel_error (channel);
+
+ g_error_free (error);
+ g_free (debug);
+ return TRUE;
+ default:
+ break;
+ }
+
+ for (i = 0; i < channel->contents->len; i++)
+ if (tf_call_content_bus_message (g_ptr_array_index (channel->contents, i),
+ message))
+ return TRUE;
+
+ return FALSE;
+}
+
+void
+tf_call_channel_error (TfCallChannel *channel)
+{
+ tp_call_channel_hangup_async (TP_CALL_CHANNEL (channel->proxy),
+ TP_CALL_STATE_CHANGE_REASON_UNKNOWN, "", "", NULL, NULL);
+}
+
+
+/* This always returns a reference, one should use _put_conference to unref it
+ */
+FsConference *
+_tf_call_channel_get_conference (TfCallChannel *channel,
+ const gchar *conference_type)
+{
+ gchar *tmp;
+ struct CallConference *cc;
+
+ cc = g_hash_table_lookup (channel->fsconferences, conference_type);
+
+ if (cc)
+ {
+ cc->use_count++;
+ gst_object_ref (cc->fsconference);
+ return cc->fsconference;
+ }
+
+ cc = g_slice_new (struct CallConference);
+ cc->use_count = 1;
+ cc->conference_type = g_strdup (conference_type);
+
+ tmp = g_strdup_printf ("fs%sconference", conference_type);
+ cc->fsconference = FS_CONFERENCE (gst_element_factory_make (tmp, NULL));
+ g_free (tmp);
+
+ if (cc->fsconference == NULL)
+ {
+ g_slice_free (struct CallConference, cc);
+ return NULL;
+ }
+
+ /* Take ownership of the conference */
+ gst_object_ref_sink (cc->fsconference);
+ g_hash_table_insert (channel->fsconferences, cc->conference_type, cc);
+
+ g_signal_emit (channel, signals[SIGNAL_FS_CONFERENCE_ADDED], 0,
+ cc->fsconference);
+ g_object_notify (G_OBJECT (channel), "fs-conferences");
+
+ gst_object_ref (cc->fsconference);
+
+ return cc->fsconference;
+}
+
+void
+_tf_call_channel_put_conference (TfCallChannel *channel,
+ FsConference *conference)
+{
+ struct CallConference *cc;
+
+ cc = find_call_conference_by_conference (channel, GST_OBJECT (conference));
+ if (!cc)
+ {
+ g_warning ("Trying to put conference that does not exist");
+ return;
+ }
+
+ cc->use_count--;
+
+ if (cc->use_count <= 0)
+ {
+ g_signal_emit (channel, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0,
+ cc->fsconference);
+ g_hash_table_remove (channel->fsconferences, cc->conference_type);
+ g_object_notify (G_OBJECT (channel), "fs-conferences");
+ }
+
+ gst_object_unref (conference);
+}
+
+
+FsParticipant *
+_tf_call_channel_get_participant (TfCallChannel *channel,
+ FsConference *fsconference,
+ guint contact_handle,
+ GError **error)
+{
+ guint i;
+ struct CallParticipant *cp;
+ FsParticipant *p;
+
+ for (i = 0; i < channel->participants->len; i++)
+ {
+ cp = g_ptr_array_index (channel->participants, i);
+
+ if (cp->fsconference == fsconference &&
+ cp->handle == contact_handle)
+ {
+ cp->use_count++;
+ return g_object_ref (cp->fsparticipant);
+ }
+ }
+
+ p = fs_conference_new_participant (fsconference, error);
+ if (!p)
+ return NULL;
+
+ cp = g_slice_new (struct CallParticipant);
+ cp->use_count = 1;
+ cp->handle = contact_handle;
+ cp->fsconference = gst_object_ref (fsconference);
+ cp->fsparticipant = p;
+ g_ptr_array_add (channel->participants, cp);
+
+ return p;
+}
+
+
+void
+_tf_call_channel_put_participant (TfCallChannel *channel,
+ FsParticipant *participant)
+{
+ guint i;
+
+ for (i = 0; i < channel->participants->len; i++)
+ {
+ struct CallParticipant *cp = g_ptr_array_index (channel->participants, i);
+
+ if (cp->fsparticipant == participant)
+ {
+ cp->use_count--;
+ if (cp->use_count <= 0)
+ g_ptr_array_remove_index_fast (channel->participants, i);
+ else
+ gst_object_unref (cp->fsparticipant);
+ return;
+ }
+ }
+}
diff --git a/telepathy-farstream/call-channel.h b/telepathy-farstream/call-channel.h
new file mode 100644
index 000000000..525e12f0f
--- /dev/null
+++ b/telepathy-farstream/call-channel.h
@@ -0,0 +1,118 @@
+/*
+ * call-channel.c - Source for TfCallChannel
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_CALL_CHANNEL_H__
+#define __TF_CALL_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <gst/gst.h>
+#include <farstream/fs-conference.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CALL_CHANNEL tf_call_channel_get_type()
+
+#define TF_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CALL_CHANNEL, TfCallChannel))
+
+#define TF_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CALL_CHANNEL, TfCallChannelClass))
+
+#define TF_IS_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_CHANNEL))
+
+#define TF_IS_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_CHANNEL))
+
+#define TF_CALL_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CALL_CHANNEL, TfCallChannelClass))
+
+typedef struct _TfCallChannelPrivate TfCallChannelPrivate;
+
+/**
+ * TfCallChannel:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfCallChannel TfCallChannel;
+
+/**
+ * TfCallChannelClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfCallChannelClass TfCallChannelClass;
+
+
+
+struct _TfCallChannel {
+ GObject parent;
+
+ TpChannel *proxy;
+
+ GHashTable *fsconferences;
+
+ GPtrArray *contents; /* NULL before getting the first contents */
+
+ GPtrArray *participants;
+};
+
+struct _TfCallChannelClass{
+ GObjectClass parent_class;
+};
+
+
+GType tf_call_channel_get_type (void);
+
+void tf_call_channel_new_async (TpChannel *channel_proxy,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void tf_call_channel_error (TfCallChannel *channel);
+
+gboolean tf_call_channel_bus_message (TfCallChannel *channel,
+ GstMessage *message);
+
+/* Private methods, only to be used inside TP-FS */
+
+FsConference *_tf_call_channel_get_conference (TfCallChannel *channel,
+ const gchar *conference_type);
+void _tf_call_channel_put_conference (TfCallChannel *channel,
+ FsConference *conference);
+
+
+FsParticipant *_tf_call_channel_get_participant (TfCallChannel *channel,
+ FsConference *fsconference,
+ guint contact_handle,
+ GError **error);
+void _tf_call_channel_put_participant (TfCallChannel *channel,
+ FsParticipant *participant);
+
+G_END_DECLS
+
+#endif /* __TF_CALL_CHANNEL_H__ */
+
diff --git a/telepathy-farstream/call-content.c b/telepathy-farstream/call-content.c
new file mode 100644
index 000000000..db1e3c68e
--- /dev/null
+++ b/telepathy-farstream/call-content.c
@@ -0,0 +1,2553 @@
+/*
+ * call-content.c - Source for TfCallContent
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:call-content
+
+ * @short_description: Handle the Content objects on a Call channel
+ *
+ * This class handles the org.freedesktop.Telepathy.Call1.Content,
+ * org.freedesktop.Telepathy.Call1.Content.Interface.Media,
+ * org.freedesktop.Telepathy.Call1.Content.Interface.VideoControl,
+ * org.freedesktop.Telepathy.Call1.Content.MediaDescription,
+ * prg.freedesktop.Telepathy.Call1.Content.MediaDescription.Interface.RTPHeaderExtensions
+ * and org.freedesktop.Telepathy.Call1.Content.MediaDescription.Interface.RTCPFeedback interfaces
+ *
+ */
+
+/* TODO:
+ *
+ * In MediaDescription:
+ * - SSRCs
+ */
+
+#include "config.h"
+
+#include "call-content.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+#include <telepathy-glib/proxy-subclass.h>
+#include <farstream/fs-conference.h>
+#include <farstream/fs-utils.h>
+#include <farstream/fs-rtp.h>
+#include <farstream/fs-element-added-notifier.h>
+
+#include <stdarg.h>
+#include <string.h>
+
+
+#include "call-stream.h"
+#include "call-priv.h"
+#include "utils.h"
+
+#define DTMF_TONE_VOLUME (8)
+
+struct _TfCallContent {
+ TfContent parent;
+
+ TfCallChannel *call_channel;
+ FsConference *fsconference;
+
+ TpCallContent *proxy;
+
+ FsSession *fssession;
+
+ TpProxy *current_media_description;
+ guint current_md_contact_handle;
+ GList *current_md_fscodecs;
+ GList *current_md_rtp_hdrext;
+
+ gboolean current_has_rtp_hdrext;
+ gboolean current_has_rtcp_fb;
+ gboolean has_rtp_hdrext;
+ gboolean has_rtcp_fb;
+
+ GList *last_sent_codecs;
+
+ GPtrArray *streams; /* NULL before getting the first streams */
+ /* Streams for which we don't have a session yet*/
+ GList *outstanding_streams;
+
+ GMutex mutex;
+
+ gboolean remote_codecs_set;
+
+ TpSendingState dtmf_sending_state;
+ guint current_dtmf_event;
+
+ /* Content protected by the Mutex */
+ GPtrArray *fsstreams;
+ guint fsstreams_cookie;
+
+ gboolean got_media_description_property;
+
+ /* AudioControl API */
+ gint requested_input_volume;
+ gint requested_output_volume;
+ gint reported_input_volume;
+ gint reported_output_volume;
+
+ /* VideoControl API */
+ FsElementAddedNotifier *notifier;
+
+ volatile gint bitrate;
+ volatile gint mtu;
+ gboolean manual_keyframes;
+
+ guint framerate;
+ guint width;
+ guint height;
+};
+
+struct _TfCallContentClass {
+ TfContentClass parent_class;
+};
+
+
+static void call_content_async_initable_init (GAsyncInitableIface *asynciface);
+
+G_DEFINE_TYPE_WITH_CODE (TfCallContent, tf_call_content, TF_TYPE_CONTENT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+ call_content_async_initable_init))
+
+#define TF_CALL_CONTENT_LOCK(self) g_mutex_lock (&(self)->mutex)
+#define TF_CALL_CONTENT_UNLOCK(self) g_mutex_unlock (&(self)->mutex)
+
+
+enum
+{
+ PROP_TF_CHANNEL = 1,
+ PROP_FS_CONFERENCE,
+ PROP_FS_SESSION,
+ PROP_SINK_PAD,
+ PROP_MEDIA_TYPE,
+ PROP_OBJECT_PATH,
+ PROP_REQUESTED_INPUT_VOLUME,
+ PROP_REQUESTED_OUTPUT_VOLUME,
+ PROP_REPORTED_INPUT_VOLUME,
+ PROP_REPORTED_OUTPUT_VOLUME,
+ PROP_FRAMERATE,
+ PROP_WIDTH,
+ PROP_HEIGHT
+};
+
+enum
+{
+ RESOLUTION_CHANGED = 0,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+
+struct CallFsStream {
+ TfCallChannel *parent_channel;
+ guint use_count;
+ guint contact_handle;
+ FsParticipant *fsparticipant;
+ FsStream *fsstream;
+};
+
+static void
+tf_call_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void
+tf_call_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static void tf_call_content_dispose (GObject *object);
+static void tf_call_content_finalize (GObject *object);
+
+static void tf_call_content_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+static gboolean tf_call_content_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error);
+
+
+static void tf_call_content_try_sending_codecs (TfCallContent *self);
+static FsStream * tf_call_content_get_existing_fsstream_by_handle (
+ TfCallContent *content, guint contact_handle);
+
+static void src_pad_added (FsStream *fsstream, GstPad *pad, FsCodec *codec,
+ TfCallContent *content);
+static GstIterator * tf_call_content_iterate_src_pads (TfContent *content,
+ guint *handles, guint handle_count);
+
+static void tf_call_content_error (TfCallContent *self,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message_format,
+ ...) G_GNUC_PRINTF (4, 5);
+static void tf_call_content_error_literal (TfCallContent *self,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message);
+
+static void tf_call_content_error_impl (TfContent *content,
+ const gchar *message);
+static void tf_call_content_sending_failed (TfContent *content,
+ const gchar *message);
+static void tf_call_content_receiving_failed (TfContent *content,
+ guint *handles, guint handle_count,
+ const gchar *message);
+
+static void
+tf_call_content_class_init (TfCallContentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TfContentClass *content_class = TF_CONTENT_CLASS (klass);
+
+ content_class->iterate_src_pads = tf_call_content_iterate_src_pads;
+
+ content_class->content_error = tf_call_content_error_impl;
+ content_class->sending_failed = tf_call_content_sending_failed;
+ content_class->receiving_failed = tf_call_content_receiving_failed;
+
+ object_class->dispose = tf_call_content_dispose;
+ object_class->finalize = tf_call_content_finalize;
+ object_class->get_property = tf_call_content_get_property;
+ object_class->set_property = tf_call_content_set_property;
+
+ g_object_class_override_property (object_class, PROP_TF_CHANNEL,
+ "tf-channel");
+ g_object_class_override_property (object_class, PROP_FS_CONFERENCE,
+ "fs-conference");
+ g_object_class_override_property (object_class, PROP_FS_SESSION,
+ "fs-session");
+ g_object_class_override_property (object_class, PROP_SINK_PAD,
+ "sink-pad");
+ g_object_class_override_property (object_class, PROP_MEDIA_TYPE,
+ "media-type");
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+
+ g_object_class_install_property (object_class, PROP_FRAMERATE,
+ g_param_spec_uint ("framerate",
+ "Framerate",
+ "The framerate as indicated by the VideoControl interface"
+ "or the media layer",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_REQUESTED_INPUT_VOLUME,
+ g_param_spec_int ("requested-input-volume",
+ "Requested input volume",
+ "The requested input volume indicated by the AudioControl interface",
+ -1, 255, -1,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_REQUESTED_OUTPUT_VOLUME,
+ g_param_spec_int ("requested-output-volume",
+ "Requested output volume",
+ "The requested output volume indicated by the AudioControl interface",
+ -1, 255, -1,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_REPORTED_INPUT_VOLUME,
+ g_param_spec_int ("reported-input-volume",
+ "Reported input volume",
+ "The input volume indicated by or the media layer",
+ -1, 255, -1,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_REPORTED_OUTPUT_VOLUME,
+ g_param_spec_int ("reported-output-volume",
+ "Output volume",
+ "The output volume indicated by the the media layer",
+ -1, 255, -1,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_uint ("width",
+ "Width",
+ "The video width indicated by the VideoControl interface"
+ "or the media layer",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_uint ("height",
+ "Height",
+ "The video height as indicated by the VideoControl interface"
+ "or the media layer",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ signals[RESOLUTION_CHANGED] = g_signal_new ("resolution-changed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
+}
+
+static void
+call_content_async_initable_init (GAsyncInitableIface *asynciface)
+{
+ asynciface->init_async = tf_call_content_init_async;
+ asynciface->init_finish = tf_call_content_init_finish;
+}
+
+
+static void
+free_content_fsstream (gpointer data)
+{
+ struct CallFsStream *cfs = data;
+
+ fs_stream_destroy (cfs->fsstream);
+ g_object_unref (cfs->fsstream);
+ _tf_call_channel_put_participant (cfs->parent_channel, cfs->fsparticipant);
+ g_slice_free (struct CallFsStream, cfs);
+}
+
+static void
+tf_call_content_init (TfCallContent *self)
+{
+ self->fsstreams = g_ptr_array_new ();
+ self->dtmf_sending_state = TP_SENDING_STATE_NONE;
+
+ g_mutex_init (&self->mutex);
+ self->requested_input_volume = -1;
+ self->requested_output_volume = -1;
+
+ self->reported_input_volume = -1;
+ self->reported_output_volume = -1;
+}
+
+void
+_tf_call_content_destroy (TfCallContent *self)
+{
+ if (self->streams)
+ {
+ g_ptr_array_free (self->streams, TRUE);
+ }
+ self->streams = NULL;
+
+ if (self->fssession)
+ {
+ fs_session_destroy (self->fssession);
+ g_object_unref (self->fssession);
+ }
+ self->fssession = NULL;
+
+ if (self->fsstreams)
+ {
+ while (self->fsstreams->len)
+ free_content_fsstream (
+ g_ptr_array_remove_index_fast (self->fsstreams, 0));
+ g_ptr_array_unref (self->fsstreams);
+ }
+ self->fsstreams = NULL;
+
+ if (self->notifier)
+ g_object_unref (self->notifier);
+ self->notifier = NULL;
+
+ if (self->fsconference)
+ _tf_call_channel_put_conference (self->call_channel,
+ self->fsconference);
+ self->fsconference = NULL;
+
+ /* We do not hold a ref to the call channel, and use it as a flag to ensure
+ * we will bail out when disposed */
+ self->call_channel = NULL;
+}
+
+static void
+tf_call_content_dispose (GObject *object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (object);
+
+ g_debug (G_STRFUNC);
+
+ _tf_call_content_destroy (self);
+
+ if (self->proxy)
+ g_object_unref (self->proxy);
+ self->proxy = NULL;
+
+ if (G_OBJECT_CLASS (tf_call_content_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_call_content_parent_class)->dispose (object);
+}
+
+
+static void
+tf_call_content_finalize (GObject *object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (object);
+
+ fs_codec_list_destroy (self->last_sent_codecs);
+ self->last_sent_codecs = NULL;
+
+ g_mutex_clear (&self->mutex);
+
+ if (G_OBJECT_CLASS (tf_call_content_parent_class)->finalize)
+ G_OBJECT_CLASS (tf_call_content_parent_class)->finalize (object);
+}
+
+
+static void
+tf_call_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfCallContent *self = TF_CALL_CONTENT (object);
+
+ switch (property_id)
+ {
+ case PROP_TF_CHANNEL:
+ if (self->call_channel)
+ g_value_set_object (value, self->call_channel);
+ break;
+ case PROP_FS_CONFERENCE:
+ if (self->fsconference)
+ g_value_set_object (value, self->fsconference);
+ break;
+ case PROP_FS_SESSION:
+ if (self->fssession)
+ g_value_set_object (value, self->fssession);
+ break;
+ case PROP_SINK_PAD:
+ if (self->fssession)
+ g_object_get_property (G_OBJECT (self->fssession), "sink-pad", value);
+ break;
+ case PROP_MEDIA_TYPE:
+ g_value_set_enum (value, tf_call_content_get_fs_media_type (self));
+ break;
+ case PROP_OBJECT_PATH:
+ g_object_get_property (G_OBJECT (self->proxy), "object-path", value);
+ break;
+ case PROP_REQUESTED_INPUT_VOLUME:
+ g_value_set_int (value, self->requested_input_volume);
+ break;
+ case PROP_REQUESTED_OUTPUT_VOLUME:
+ g_value_set_int (value, self->requested_output_volume);
+ break;
+ case PROP_REPORTED_INPUT_VOLUME:
+ g_value_set_int (value, self->reported_input_volume);
+ break;
+ case PROP_REPORTED_OUTPUT_VOLUME:
+ g_value_set_int (value, self->reported_output_volume);
+ break;
+ case PROP_FRAMERATE:
+ g_value_set_uint (value, self->framerate);
+ break;
+ case PROP_WIDTH:
+ g_value_set_uint (value, self->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_uint (value, self->height);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tf_call_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TfCallContent *self = TF_CALL_CONTENT (object);
+
+ switch (property_id)
+ {
+ case PROP_REPORTED_INPUT_VOLUME:
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ break;
+
+ self->reported_input_volume = g_value_get_int (value);
+ tp_cli_call1_content_interface_audio_control1_call_report_input_volume (
+ self->proxy, -1, self->reported_input_volume,
+ NULL, NULL, NULL, NULL);
+
+ break;
+ case PROP_REPORTED_OUTPUT_VOLUME:
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ break;
+
+ self->reported_output_volume = g_value_get_int (value);
+ tp_cli_call1_content_interface_audio_control1_call_report_output_volume (
+ self->proxy, -1, self->reported_output_volume,
+ NULL, NULL, NULL, NULL);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+add_stream (TfCallContent *self, TpCallStream *stream_proxy)
+{
+ g_ptr_array_add (self->streams,
+ tf_call_stream_new (self, stream_proxy));
+}
+
+static void
+add_initial_streams (TfCallContent *self)
+{
+ GPtrArray *streams;
+ guint i;
+
+ g_assert (self->fsconference);
+ g_assert (self->streams->len == 0);
+
+ streams = tp_call_content_get_streams (self->proxy);
+
+ for (i = 0; i < streams->len; i++)
+ add_stream (self, g_ptr_array_index (streams, i));
+}
+
+static void
+tpparam_to_fsparam (gpointer key, gpointer value, gpointer user_data)
+{
+ gchar *name = key;
+ gchar *val = value;
+ FsCodec *fscodec = user_data;
+
+ fs_codec_add_optional_parameter (fscodec, name, val);
+}
+
+static GList *
+tpcodecs_to_fscodecs (FsMediaType fsmediatype, const GPtrArray *tpcodecs,
+ gboolean does_avpf, GHashTable *rtcp_fb)
+{
+ GList *fscodecs = NULL;
+ guint i;
+
+ for (i = 0; i < tpcodecs->len; i++)
+ {
+ GValueArray *tpcodec = g_ptr_array_index (tpcodecs, i);
+ guint pt;
+ gchar *name;
+ guint clock_rate;
+ guint channels;
+ GHashTable *params;
+ FsCodec *fscodec;
+ gchar *tmp;
+ GValueArray *feedback_params = NULL;
+ gboolean updated;
+
+ tp_value_array_unpack (tpcodec, 6, &pt, &name, &clock_rate, &channels,
+ &updated, &params);
+
+ fscodec = fs_codec_new (pt, name, fsmediatype, clock_rate);
+ fscodec->channels = channels;
+
+ g_hash_table_foreach (params, tpparam_to_fsparam, fscodec);
+
+ if (does_avpf)
+ fscodec->minimum_reporting_interval = 0;
+
+ if (rtcp_fb)
+ feedback_params = g_hash_table_lookup (rtcp_fb, GUINT_TO_POINTER (pt));
+
+ if (feedback_params)
+ {
+ guint rtcp_minimum_interval;
+ GPtrArray *messages;
+ guint j;
+
+ tp_value_array_unpack (feedback_params, 2, &rtcp_minimum_interval,
+ &messages);
+ if (rtcp_minimum_interval != G_MAXUINT)
+ fscodec->minimum_reporting_interval = rtcp_minimum_interval;
+
+ for (j = 0; j < messages->len ; j++)
+ {
+ GValueArray *message = g_ptr_array_index (messages, j);
+ const gchar *type, *subtype, *extra_params;
+
+ tp_value_array_unpack (message, 3, &type, &subtype,
+ &extra_params);
+
+ fs_codec_add_feedback_parameter (fscodec, type, subtype,
+ extra_params);
+ }
+ }
+
+ tmp = fs_codec_to_string (fscodec);
+ g_debug ("%s", tmp);
+ g_free (tmp);
+ fscodecs = g_list_prepend (fscodecs, fscodec);
+ }
+
+ fscodecs = g_list_reverse (fscodecs);
+
+ return fscodecs;
+}
+
+static GList *
+tprtphdrext_to_fsrtphdrext (GPtrArray *rtp_hdrext)
+{
+ GQueue ret = G_QUEUE_INIT;
+ guint i;
+
+ if (!rtp_hdrext)
+ return NULL;
+
+ for (i = 0; i < rtp_hdrext->len; i++)
+ {
+ GValueArray *extension = g_ptr_array_index (rtp_hdrext, i);
+ guint id;
+ TpMediaStreamDirection direction;
+ const char *uri;
+ const gchar *parameters;
+ FsRtpHeaderExtension *ext;
+
+ tp_value_array_unpack (extension, 4, &id, &direction, &uri, &parameters);
+
+ ext = fs_rtp_header_extension_new (id,
+ tpdirection_to_fsdirection (direction), uri);
+
+ g_debug ("hdrext: " FS_RTP_HEADER_EXTENSION_FORMAT,
+ FS_RTP_HEADER_EXTENSION_ARGS (ext));
+
+ g_queue_push_tail (&ret, ext);
+ }
+
+ return ret.head;
+}
+
+static gboolean
+object_has_property (GObject *object, const gchar *property)
+{
+ return g_object_class_find_property (G_OBJECT_GET_CLASS (object),
+ property) != NULL;
+}
+
+static void
+on_content_dtmf_change_requested (TpCallContent *proxy,
+ guchar arg_Event,
+ guint arg_State,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Ignore the signal until we've got the original properties and codecs */
+ if (!self->fssession || !self->remote_codecs_set) {
+ self->dtmf_sending_state = arg_State;
+ self->current_dtmf_event = arg_Event;
+ return;
+ }
+
+ switch (arg_State)
+ {
+ case TP_SENDING_STATE_PENDING_STOP_SENDING:
+ if (self->dtmf_sending_state != TP_SENDING_STATE_SENDING)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Tried to stop a %u DTMF event while state is %d",
+ arg_Event, self->dtmf_sending_state);
+ }
+
+ if (fs_session_stop_telephony_event (self->fssession))
+ {
+ self->dtmf_sending_state = TP_SENDING_STATE_PENDING_STOP_SENDING;
+ }
+ else
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Could not stop DTMF event %d", arg_Event);
+ tp_cli_call1_content_interface_media_call_acknowledge_dtmf_change (
+ self->proxy, -1, arg_Event, TP_SENDING_STATE_SENDING,
+ NULL, NULL, NULL, NULL);
+ }
+ break;
+ case TP_SENDING_STATE_PENDING_SEND:
+ if (self->dtmf_sending_state != TP_SENDING_STATE_NONE)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Tried to start a new DTMF event %u while %d is already playing",
+ arg_Event, self->current_dtmf_event);
+ fs_session_stop_telephony_event (self->fssession);
+ }
+
+ if (fs_session_start_telephony_event (self->fssession,
+ arg_Event, DTMF_TONE_VOLUME))
+ {
+ self->current_dtmf_event = arg_Event;
+ self->dtmf_sending_state = TP_SENDING_STATE_PENDING_SEND;
+ }
+ else
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Could not start DTMF event %d", arg_Event);
+ tp_cli_call1_content_interface_media_call_acknowledge_dtmf_change (
+ self->proxy, -1, arg_Event, TP_SENDING_STATE_NONE,
+ NULL, NULL, NULL, NULL);
+ }
+ break;
+ default:
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Invalid State %d in DTMFChangeRequested signal for event %d",
+ arg_State, arg_Event);
+ break;
+ }
+}
+
+
+static void
+process_media_description_try_codecs (TfCallContent *self, FsStream *fsstream,
+ TpProxy *media_description, GList *fscodecs, GList *rtp_hdrext)
+{
+ gboolean success = TRUE;
+ GError *error = NULL;
+
+ if (fscodecs != NULL)
+ {
+ GList *old_rtp_hdrext = NULL;
+
+ if (object_has_property (G_OBJECT (fsstream), "rtp-header-extensions"))
+ {
+ g_object_get (fsstream, "rtp-header-extensions", &old_rtp_hdrext,
+ NULL);
+ g_object_set (fsstream, "rtp-header-extensions", rtp_hdrext, NULL);
+ }
+
+ success = fs_stream_set_remote_codecs (fsstream, fscodecs, &error);
+
+ if (success)
+ {
+ if (!self->remote_codecs_set)
+ on_content_dtmf_change_requested (NULL, self->current_dtmf_event,
+ self->dtmf_sending_state, NULL, G_OBJECT (self));
+ self->remote_codecs_set = TRUE;
+ }
+
+ if (!success &&
+ object_has_property (G_OBJECT (fsstream), "rtp-header-extensions"))
+ g_object_set (fsstream, "rtp-header-extensions", old_rtp_hdrext, NULL);
+
+ fs_rtp_header_extension_list_destroy (old_rtp_hdrext);
+ }
+
+ fs_rtp_header_extension_list_destroy (rtp_hdrext);
+ fs_codec_list_destroy (fscodecs);
+
+ if (success)
+ {
+ self->current_media_description = media_description;
+ tf_call_content_try_sending_codecs (self);
+ }
+ else
+ {
+ GValueArray *reason = tp_value_array_build (4,
+ G_TYPE_UINT, 0,
+ G_TYPE_UINT, TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR,
+ G_TYPE_STRING, TP_ERROR_STR_MEDIA_CODECS_INCOMPATIBLE,
+ G_TYPE_STRING,
+ "Remote codecs are not compatible with the local ones",
+ G_TYPE_INVALID);
+
+ g_debug ("Rejecting Media Description");
+ tp_cli_call1_content_media_description_call_reject (media_description,
+ -1, reason, NULL, NULL, NULL, NULL);
+ tp_value_array_free (reason);
+ g_object_unref (media_description);
+ }
+ g_clear_error (&error);
+}
+
+static void
+process_media_description (TfCallContent *self,
+ const gchar *media_description_objpath,
+ const GHashTable *properties)
+{
+ TpProxy *proxy;
+ GError *error = NULL;
+ FsStream *fsstream;
+ GPtrArray *codecs;
+ GPtrArray *rtp_hdrext = NULL;
+ GHashTable *rtcp_fb = NULL;
+ gboolean does_avpf = FALSE;
+ GList *fscodecs;
+ const gchar * const *interfaces;
+ guint i;
+ GList *fsrtp_hdrext;
+ guint contact_handle;
+ gboolean valid;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ if (!tp_dbus_check_valid_object_path (media_description_objpath, &error))
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, TP_ERROR_STR_CONFUSED,
+ "Invalid MediaDescription path: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ contact_handle = tp_asv_get_uint32 (properties,
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_REMOTE_CONTACT, &valid);
+ if (!valid)
+ {
+ tf_call_content_error_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, TP_ERROR_STR_CONFUSED,
+ "MediaDescription does not contain a valid contact handle");
+ g_clear_error (&error);
+ return;
+ }
+
+ codecs = tp_asv_get_boxed (properties,
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_CODECS, TP_ARRAY_TYPE_CODEC_LIST);
+
+ if (!codecs)
+ {
+ tf_call_content_error_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, TP_ERROR_STR_CONFUSED,
+ "MediaDescription does not contain codecs");
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_call_content_media_description_init_known_interfaces ();
+ proxy = g_object_new (TP_TYPE_PROXY,
+ "dbus-daemon", tp_proxy_get_dbus_daemon (self->proxy),
+ "bus-name", tp_proxy_get_bus_name (self->proxy),
+ "object-path", media_description_objpath,
+ NULL);
+ tp_proxy_add_interface_by_id (TP_PROXY (proxy),
+ TP_IFACE_QUARK_CALL1_CONTENT_MEDIA_DESCRIPTION);
+
+ interfaces = tp_asv_get_strv (properties,
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACES);
+
+
+ self->current_has_rtcp_fb = FALSE;
+ self->current_has_rtp_hdrext = FALSE;
+ for (i = 0; interfaces[i]; i++)
+ {
+ if (!strcmp (interfaces[i],
+ TP_IFACE_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1))
+ {
+ self->current_has_rtcp_fb = TRUE;
+ rtcp_fb = tp_asv_get_boxed (properties,
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1_FEEDBACK_MESSAGES,
+ TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP);
+ does_avpf = tp_asv_get_boolean (properties,
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1_DOES_AVPF, NULL);
+ }
+ else if (!strcmp (interfaces[i],
+ TP_IFACE_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1))
+ {
+ self->current_has_rtp_hdrext = TRUE;
+ rtp_hdrext = tp_asv_get_boxed (properties,
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1_HEADER_EXTENSIONS,
+ TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST);
+ }
+ }
+
+
+ g_debug ("Got MediaDescription %s", media_description_objpath);
+ fscodecs = tpcodecs_to_fscodecs (tf_call_content_get_fs_media_type (self),
+ codecs, does_avpf, rtcp_fb);
+
+ fsrtp_hdrext = tprtphdrext_to_fsrtphdrext (rtp_hdrext);
+
+ fsstream = tf_call_content_get_existing_fsstream_by_handle (self,
+ contact_handle);
+ self->current_md_contact_handle = contact_handle;
+
+ if (!fsstream)
+ {
+ g_debug ("Delaying codec media_description processing");
+ self->current_media_description = proxy;
+ self->current_md_fscodecs = fscodecs;
+ self->current_md_rtp_hdrext = fsrtp_hdrext;
+ return;
+ }
+
+ process_media_description_try_codecs (self, fsstream, proxy, fscodecs,
+ fsrtp_hdrext);
+}
+
+static void
+on_content_video_keyframe_requested (TpCallContent *proxy,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ GstPad *pad;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ /* In case there is no session, ignore the request a new session should start
+ * with sending a KeyFrame in any case */
+ if (self->fssession == NULL)
+ return;
+
+ g_object_get (self->fssession, "sink-pad", &pad, NULL);
+
+ if (pad == NULL)
+ {
+ g_warning ("Failed to get a pad for the keyframe request");
+ return;
+ }
+
+ g_message ("Sending out a keyframe request");
+ gst_pad_send_event (pad,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
+ gst_structure_new ("GstForceKeyUnit",
+ "all-headers", G_TYPE_BOOLEAN, TRUE,
+ NULL)));
+
+ g_object_unref (pad);
+}
+
+static void
+on_content_video_resolution_changed (TpCallContent *proxy,
+ const GValueArray *resolution,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ guint width, height;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ tp_value_array_unpack ((GValueArray *)resolution, 2,
+ &width, &height, NULL);
+
+ /* Can be 0 in the initial property dump, shouldn't be at any other time */
+ if (width == 0 || height == 0)
+ return;
+
+ self->width = width;
+ self->height = height;
+
+ g_signal_emit (self, signals[RESOLUTION_CHANGED], 0, width, height);
+ g_signal_emit_by_name (self, "restart-source");
+
+ g_message ("requested video resolution: %dx%d", width, height);
+}
+
+static void
+on_content_video_bitrate_changed (TpCallContent *proxy,
+ guint bitrate,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ g_message ("Setting bitrate to %d bits/s", bitrate);
+ self->bitrate = bitrate;
+
+ if (self->fssession != NULL && self->bitrate > 0)
+ g_object_set (self->fssession, "send-bitrate", self->bitrate, NULL);
+}
+
+static void
+on_content_video_framerate_changed (TpCallContent *proxy,
+ guint framerate,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ g_message ("updated framerate requested: %d", framerate);
+
+ self->framerate = framerate;
+ g_object_notify (G_OBJECT (self), "framerate");
+ g_signal_emit_by_name (self, "restart-source");
+}
+
+static void
+on_content_video_mtu_changed (TpCallContent *proxy,
+ guint mtu,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ g_atomic_int_set (&self->mtu, mtu);
+
+ if (self->fsconference != NULL)
+ {
+ fs_element_added_notifier_remove (self->notifier,
+ GST_BIN (self->fsconference));
+
+ if (mtu > 0 || self->manual_keyframes)
+ fs_element_added_notifier_add (self->notifier,
+ GST_BIN (self->fsconference));
+ }
+}
+
+static void
+streams_added (TpCallContent *proxy,
+ GPtrArray *streams,
+ TfCallContent *self)
+{
+ guint i;
+
+ /* Ignore signals before we got the "Contents" property to avoid races that
+ * could cause the same content to be added twice
+ */
+
+ if (!self->streams)
+ return;
+
+ for (i = 0; i < streams->len; i++)
+ add_stream (self, g_ptr_array_index (streams, i));
+}
+
+static void
+streams_removed (TpCallContent *proxy,
+ const GPtrArray *streams,
+ TpCallStateReason *reason,
+ TfCallContent *self)
+{
+ guint i, j;
+
+ if (!self->streams)
+ return;
+
+ for (i = 0; i < streams->len; i++)
+ for (j = 0; j < self->streams->len; j++)
+ if (g_ptr_array_index (streams, i) ==
+ tf_call_stream_get_proxy (g_ptr_array_index (self->streams, j)))
+ {
+ g_ptr_array_remove_index_fast (self->streams, j);
+ break;
+ }
+}
+
+static void
+got_content_media_properties (TpProxy *proxy, GHashTable *properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ GSimpleAsyncResult *res = user_data;
+ GValueArray *gva;
+ const gchar *media_description_objpath = NULL;
+ GHashTable *media_description_properties;
+ GError *myerror = NULL;
+ guint32 packetization;
+ const gchar *conference_type;
+ gboolean valid;
+ GList *codec_prefs;
+ guchar dtmf_event;
+ guint dtmf_state;
+ const GValue *dtmf_event_value;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ goto disposed;
+
+ if (error != NULL)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Error getting the Content's media properties: %s", error->message);
+ g_simple_async_result_set_from_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ packetization = tp_asv_get_uint32 (properties, "Packetization", &valid);
+
+ if (!valid)
+ goto invalid_property;
+
+ g_assert (self->fssession == NULL);
+
+ switch (packetization)
+ {
+ case TP_CALL_CONTENT_PACKETIZATION_TYPE_RTP:
+ conference_type = "rtp";
+ break;
+ case TP_CALL_CONTENT_PACKETIZATION_TYPE_RAW:
+ conference_type = "raw";
+ break;
+ default:
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR,
+ TP_ERROR_STR_MEDIA_UNSUPPORTED_TYPE,
+ "Could not create FsConference for type %d", packetization);
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Could not create FsConference for type %d", packetization);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ self->fsconference = _tf_call_channel_get_conference (self->call_channel,
+ conference_type);
+ if (!self->fsconference)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR,
+ TP_ERROR_STR_MEDIA_UNSUPPORTED_TYPE,
+ "Could not create FsConference for type %s", conference_type);
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the Content's properties: invalid type");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ self->fssession = fs_conference_new_session (self->fsconference,
+ tf_call_content_get_fs_media_type (self), &myerror);
+
+ if (!self->fssession)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR,
+ TP_ERROR_STR_MEDIA_UNSUPPORTED_TYPE,
+ "Could not create FsSession: %s", myerror != NULL ? myerror->message : "(unknown error)");
+ g_simple_async_result_set_from_error (res, myerror);
+ g_simple_async_result_complete (res);
+ g_clear_error (&myerror);
+ g_object_unref (res);
+ return;
+ }
+
+ if (self->notifier != NULL)
+ fs_element_added_notifier_add (self->notifier,
+ GST_BIN (self->fsconference));
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ goto disposed;
+
+ gva = tp_asv_get_boxed (properties, "MediaDescriptionOffer",
+ TP_STRUCT_TYPE_MEDIA_DESCRIPTION_OFFER);
+ if (gva == NULL)
+ {
+ goto invalid_property;
+ }
+
+ codec_prefs = fs_utils_get_default_codec_preferences (
+ GST_ELEMENT (self->fsconference));
+
+ if (codec_prefs)
+ {
+ if (!fs_session_set_codec_preferences (self->fssession, codec_prefs,
+ &myerror))
+ {
+ g_warning ("Could not set codec preference: %s", myerror->message);
+ g_clear_error (&myerror);
+ }
+ }
+
+ /* First complete so we get signalled and the preferences can be set, then
+ * start looking at the media_description. We only unref the result later, to avoid
+ * self possibly being disposed early */
+ g_simple_async_result_set_op_res_gboolean (res, TRUE);
+ g_simple_async_result_complete (res);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ goto disposed_already_completed;
+
+ /* Now process outstanding streams */
+ add_initial_streams (self);
+
+ tp_g_signal_connect_object (self->proxy, "streams-added",
+ G_CALLBACK (streams_added), self, 0);
+ tp_g_signal_connect_object (self->proxy, "streams-removed",
+ G_CALLBACK (streams_removed), self, 0);
+
+ tp_value_array_unpack (gva, 2, &media_description_objpath,
+ &media_description_properties);
+
+ if (strcmp (media_description_objpath, "/"))
+ {
+ process_media_description (self, media_description_objpath,
+ media_description_properties);
+ }
+ self->got_media_description_property = TRUE;
+
+
+ dtmf_state = tp_asv_get_uint32 (properties, "CurrentDTMFState", &valid);
+ if (!valid)
+ goto invalid_property;
+
+ dtmf_event_value = tp_asv_lookup (properties, "CurrentDTMFEvent");
+ if (!dtmf_event_value || !G_VALUE_HOLDS_UCHAR (dtmf_event_value))
+ goto invalid_property;
+ dtmf_event = g_value_get_uchar (dtmf_event_value);
+
+ on_content_dtmf_change_requested (NULL, dtmf_event, dtmf_state, NULL,
+ G_OBJECT (self));
+
+ /* The async result holds a ref to self which may be the last one, so this
+ * comes after we're done with self */
+ g_object_unref (res);
+ return;
+
+ invalid_property:
+ tf_call_content_error_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Content's properties: invalid type");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the Content's properties: invalid type");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+
+ disposed:
+
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Call content has been disposed of");
+ g_simple_async_result_complete (res);
+
+ /* fallthrough */
+ disposed_already_completed:
+ g_object_unref (res);
+ return;
+}
+
+static void
+setup_content_media_properties (TfCallContent *self, GSimpleAsyncResult *res)
+{
+ GError *error = NULL;
+
+
+ if (tp_cli_call1_content_interface_media_connect_to_dtmf_change_requested (
+ self->proxy, on_content_dtmf_change_requested,
+ NULL, NULL, G_OBJECT (self), &error) == NULL)
+ goto connect_failed;
+
+ tp_cli_dbus_properties_call_get_all (TP_PROXY (self->proxy), -1,
+ TP_IFACE_CALL1_CONTENT_INTERFACE_MEDIA,
+ got_content_media_properties, res, NULL, G_OBJECT (self));
+
+ return;
+connect_failed:
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Could not connect to the DTMFChangeRequested signal: %s",
+ error->message);
+ g_simple_async_result_take_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+}
+
+static void
+update_audio_control (TfCallContent *self, GHashTable *properties)
+{
+ gint32 input_volume, output_volume;
+ gboolean valid;
+
+ input_volume = tp_asv_get_uint32 (properties, "RequestedInputVolume", &valid);
+ if (valid)
+ {
+ self->requested_input_volume = input_volume;
+ g_object_notify (G_OBJECT (self), "requested-input-volume");
+ }
+
+ output_volume = tp_asv_get_uint32 (properties, "RequestedOutputVolume", &valid);
+ if (valid)
+ {
+ self->requested_output_volume = output_volume;
+ g_object_notify (G_OBJECT (self), "requested-output-volume");
+ }
+
+}
+
+static void
+on_content_audio_control_properties_changed (TpProxy *proxy,
+ const gchar *interface_name,
+ GHashTable *changed,
+ const gchar **invalidated,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ if (tp_strdiff (interface_name,
+ TP_IFACE_CALL1_CONTENT_INTERFACE_AUDIO_CONTROL1))
+ return;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ update_audio_control (self, changed);
+}
+
+static void
+got_content_audio_control_properties (TpProxy *proxy, GHashTable *properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ GSimpleAsyncResult *res = user_data;
+
+ if (error)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Content's AudioControl properties: %s",
+ error->message);
+ g_simple_async_result_set_from_error (res, error);
+ goto error;
+ }
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ {
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Call content has been disposed of");
+ goto error;
+ }
+
+ if (properties == NULL)
+ {
+ tf_call_content_error_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Content's AudioControl properties: "
+ "there are none");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the AudioControl Content's properties: "
+ "there are none");
+ goto error;
+ }
+
+ update_audio_control (self, properties);
+
+ setup_content_media_properties (self, res);
+ return;
+
+error:
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+}
+
+static void
+setup_content_audio_control (TfCallContent *self,
+ GSimpleAsyncResult *res)
+{
+ GError *error = NULL;
+
+ if (tp_cli_dbus_properties_connect_to_properties_changed (self->proxy,
+ on_content_audio_control_properties_changed,
+ NULL, NULL, G_OBJECT (self), &error) == NULL)
+ goto connect_failed;
+
+ tp_cli_dbus_properties_call_get_all (self->proxy, -1,
+ TP_IFACE_CALL1_CONTENT_INTERFACE_AUDIO_CONTROL1,
+ got_content_audio_control_properties, res, NULL, G_OBJECT (self));
+
+ return;
+
+connect_failed:
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Content's VideoControl properties: %s",
+ error->message);
+ g_simple_async_result_take_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+}
+
+static void
+content_video_element_added (FsElementAddedNotifier *notifier,
+ GstBin *conference,
+ GstElement *element,
+ TfCallContent *self)
+{
+ gint mtu = g_atomic_int_get (&self->mtu);
+
+ if (G_UNLIKELY (mtu == 0 && !self->manual_keyframes))
+ return;
+
+ if (G_UNLIKELY (mtu > 0 && object_has_property (G_OBJECT (element), "mtu")))
+ {
+ g_message ("Setting %d as mtu on payloader", mtu);
+ g_object_set (element, "mtu", mtu, NULL);
+ }
+
+ if (G_UNLIKELY (self->manual_keyframes))
+ {
+ if (object_has_property (G_OBJECT (element), "key-int-max"))
+ {
+ g_message ("Setting key-int-max to max uint");
+ g_object_set (element, "key-int-max", G_MAXINT, NULL);
+ }
+
+ if (object_has_property (G_OBJECT (element), "intra-period"))
+ {
+ g_message ("Setting intra-period to 0");
+ g_object_set (element, "intra-period", 0, NULL);
+ }
+ }
+}
+
+static void
+got_content_video_control_properties (TpProxy *proxy, GHashTable *properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ GSimpleAsyncResult *res = user_data;
+ GValueArray *array;
+ guint32 bitrate, mtu;
+ gboolean valid;
+ gboolean manual_keyframes;
+
+ if (error)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Content's VideoControl properties: %s",
+ error->message);
+ g_simple_async_result_set_from_error (res, error);
+ goto error;
+ }
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ {
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Call content has been disposed of");
+ goto error;
+ }
+
+ if (properties == NULL)
+ {
+ tf_call_content_error_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Content's VideoControl properties: "
+ "there are none");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the VideoControl Content's properties: "
+ "there are none");
+ goto error;
+ }
+
+
+ /* Only get the various variables, we will not have an FsSession untill the
+ * media properties are retrieved so no need to act just yet */
+ bitrate = tp_asv_get_uint32 (properties, "Bitrate", &valid);
+ if (valid)
+ self->bitrate = bitrate;
+
+ mtu = tp_asv_get_uint32 (properties, "MTU", &valid);
+ if (valid)
+ self->mtu = mtu;
+
+ manual_keyframes = tp_asv_get_boolean (properties,
+ "ManualKeyFrames", &valid);
+ if (valid)
+ self->manual_keyframes = manual_keyframes;
+
+ array = tp_asv_get_boxed (properties, "VideoResolution",
+ TP_STRUCT_TYPE_VIDEO_RESOLUTION);
+ if (array)
+ on_content_video_resolution_changed (TP_CALL_CONTENT (proxy), array,
+ NULL, G_OBJECT (self));
+
+ self->notifier = fs_element_added_notifier_new ();
+ g_signal_connect (self->notifier, "element-added",
+ G_CALLBACK (content_video_element_added), self);
+
+ setup_content_media_properties (self, res);
+ return;
+
+error:
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+}
+
+
+static void
+setup_content_video_control (TfCallContent *self, GSimpleAsyncResult *res)
+{
+ GError *error = NULL;
+
+ if (tp_cli_call1_content_interface_video_control1_connect_to_key_frame_requested (
+ self->proxy, on_content_video_keyframe_requested,
+ NULL, NULL, G_OBJECT (self), &error) == NULL)
+ goto connect_failed;
+
+ if (tp_cli_call1_content_interface_video_control1_connect_to_video_resolution_changed (
+ self->proxy, on_content_video_resolution_changed,
+ NULL, NULL, G_OBJECT (self), &error) == NULL)
+ goto connect_failed;
+
+ if (tp_cli_call1_content_interface_video_control1_connect_to_bitrate_changed (
+ self->proxy, on_content_video_bitrate_changed,
+ NULL, NULL, G_OBJECT (self), NULL) == NULL)
+ goto connect_failed;
+
+ if (tp_cli_call1_content_interface_video_control1_connect_to_framerate_changed (
+ self->proxy, on_content_video_framerate_changed,
+ NULL, NULL, G_OBJECT (self), NULL) == NULL)
+ goto connect_failed;
+
+ if (tp_cli_call1_content_interface_video_control1_connect_to_mtu_changed (
+ self->proxy, on_content_video_mtu_changed,
+ NULL, NULL, G_OBJECT (self), NULL) == NULL)
+ goto connect_failed;
+
+ tp_cli_dbus_properties_call_get_all (TP_PROXY (self->proxy), -1,
+ TP_IFACE_CALL1_CONTENT_INTERFACE_VIDEO_CONTROL1,
+ got_content_video_control_properties, res, NULL, G_OBJECT (self));
+
+ return;
+
+connect_failed:
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Content's VideoControl properties: %s",
+ error->message);
+ g_simple_async_result_take_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+}
+
+static void
+new_media_description_offer (TpCallContent *proxy,
+ const gchar *arg_Media_Description,
+ GHashTable *arg_Properties,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ /* Ignore signals before we get the first codec MediaDescription property */
+ if (!self->got_media_description_property)
+ return;
+
+ if (self->current_media_description) {
+ g_object_unref (self->current_media_description);
+ fs_codec_list_destroy (self->current_md_fscodecs);
+ fs_rtp_header_extension_list_destroy (self->current_md_rtp_hdrext);
+ self->current_media_description = NULL;
+ self->current_md_fscodecs = NULL;
+ self->current_md_rtp_hdrext = NULL;
+ }
+
+ process_media_description (self, arg_Media_Description, arg_Properties);
+}
+
+
+static void
+free_stream (gpointer data)
+{
+ TfCallStream *stream = data;
+
+ _tf_call_stream_destroy (stream);
+ g_object_unref (stream);
+}
+
+
+static void
+content_prepared (GObject *src, GAsyncResult *prepare_res,
+ gpointer user_data)
+{
+ TpCallContent *proxy = TP_CALL_CONTENT (src);
+ GSimpleAsyncResult *res = user_data;
+ TfCallContent *self =
+ TF_CALL_CONTENT (g_async_result_get_source_object (G_ASYNC_RESULT (res)));
+ GError *error = NULL;
+
+ if (!tp_proxy_prepare_finish (proxy, prepare_res, &error))
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Content's properties: %s", error->message);
+ g_simple_async_result_take_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ {
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Call content has been disposed of");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ if (!tp_proxy_has_interface_by_id (proxy,
+ TP_IFACE_QUARK_CALL1_CONTENT_INTERFACE_MEDIA))
+ {
+ tf_call_content_error_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Content does not have the media interface,"
+ " but HardwareStreaming was NOT true");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Content does not have the media interface,"
+ " but HardwareStreaming was NOT true");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ self->streams = g_ptr_array_new_with_free_func (free_stream);
+
+ tp_cli_call1_content_interface_media_connect_to_new_media_description_offer (
+ self->proxy, new_media_description_offer, NULL, NULL,
+ G_OBJECT (self), &error);
+
+ if (error)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error connecting to NewCodecMediaDescription signal: %s",
+ error->message);
+ g_simple_async_result_set_from_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (tp_proxy_has_interface_by_id (proxy,
+ TP_IFACE_QUARK_CALL1_CONTENT_INTERFACE_AUDIO_CONTROL1))
+ setup_content_audio_control (self, res);
+ else if (tp_proxy_has_interface_by_id (proxy,
+ TP_IFACE_QUARK_CALL1_CONTENT_INTERFACE_VIDEO_CONTROL1))
+ setup_content_video_control (self, res);
+ else
+ setup_content_media_properties (self, res);
+
+ return;
+}
+
+
+static void
+tf_call_content_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TfCallContent *self = TF_CALL_CONTENT (initable);
+ GSimpleAsyncResult *res;
+
+ if (cancellable != NULL)
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+ "TfCallChannel initialisation does not support cancellation");
+ return;
+ }
+
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tf_call_content_init_async);
+
+ tp_proxy_prepare_async (self->proxy, NULL, content_prepared, res);
+}
+
+static gboolean
+tf_call_content_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple_res;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (res,
+ G_OBJECT (initable), tf_call_content_init_async), FALSE);
+ simple_res = G_SIMPLE_ASYNC_RESULT (res);
+
+ if (g_simple_async_result_propagate_error (simple_res, error))
+ return FALSE;
+
+ return g_simple_async_result_get_op_res_gboolean (simple_res);
+}
+
+TfCallContent *
+tf_call_content_new_async (TfCallChannel *call_channel,
+ TpCallContent *content_proxy, GError **error,
+ GAsyncReadyCallback callback, gpointer user_data)
+{
+ TfCallContent *self;
+
+ g_return_val_if_fail (call_channel != NULL, NULL);
+ g_return_val_if_fail (content_proxy != NULL, NULL);
+
+ self = g_object_new (TF_TYPE_CALL_CONTENT, NULL);
+
+ self->call_channel = call_channel;
+ self->proxy = g_object_ref (content_proxy);
+
+ g_async_initable_init_async (G_ASYNC_INITABLE (self), 0, NULL,
+ callback, user_data);
+
+ return self;
+}
+
+static gboolean
+find_codec (GList *codecs, FsCodec *codec)
+{
+ GList *item;
+
+ for (item = codecs; item ; item = item->next)
+ if (fs_codec_are_equal (item->data, codec))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static GHashTable *
+fscodecs_to_media_descriptions (TfCallContent *self, GList *codecs)
+{
+ GPtrArray *tpcodecs = g_ptr_array_new ();
+ GList *item;
+ GList *resend_codecs = NULL;
+ GHashTable *retval;
+ GPtrArray *rtp_hdrext = NULL;
+ GHashTable *rtcp_fb = NULL;
+ GPtrArray *interfaces;
+
+ if (self->last_sent_codecs)
+ resend_codecs = fs_session_codecs_need_resend (self->fssession,
+ self->last_sent_codecs, codecs);
+
+ if (!self->current_media_description && !resend_codecs)
+ return NULL;
+
+ if ((self->current_media_description && self->current_has_rtp_hdrext)
+ || self->has_rtp_hdrext)
+ rtp_hdrext = dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST);
+
+ if ((self->current_media_description && self->current_has_rtcp_fb)
+ || self->has_rtcp_fb)
+ rtcp_fb = dbus_g_type_specialized_construct (
+ TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP);
+
+ g_debug ("Local codecs:");
+
+ for (item = codecs; item; item = item->next)
+ {
+ FsCodec *fscodec = item->data;
+ GValue tpcodec = { 0, };
+ GHashTable *params;
+ GList *param_item;
+ gboolean updated;
+ gchar *tmp;
+
+ tmp = fs_codec_to_string (fscodec);
+ g_debug ("%s", tmp);
+ g_free (tmp);
+
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ for (param_item = fscodec->optional_params;
+ param_item;
+ param_item = param_item->next)
+ {
+ FsCodecParameter *param = (FsCodecParameter *) param_item->data;
+
+ g_hash_table_insert (params, g_strdup (param->name),
+ g_strdup (param->value));
+ }
+
+ updated = find_codec (resend_codecs, fscodec);
+
+ g_value_init (&tpcodec, TP_STRUCT_TYPE_CODEC);
+ g_value_take_boxed (&tpcodec,
+ dbus_g_type_specialized_construct (TP_STRUCT_TYPE_CODEC));
+
+ dbus_g_type_struct_set (&tpcodec,
+ 0, fscodec->id,
+ 1, fscodec->encoding_name,
+ 2, fscodec->clock_rate,
+ 3, fscodec->channels,
+ 4, updated,
+ 5, params,
+ G_MAXUINT);
+
+
+ g_hash_table_destroy (params);
+
+ g_ptr_array_add (tpcodecs, g_value_get_boxed (&tpcodec));
+
+ if (rtcp_fb != NULL &&
+ (fscodec->minimum_reporting_interval != G_MAXUINT ||
+ fscodec->feedback_params))
+ {
+ GPtrArray *messages = g_ptr_array_new ();
+ GList *item2;
+
+ for (item2 = fscodec->feedback_params; item2; item2 = item2->next)
+ {
+ FsFeedbackParameter *fb = item2->data;
+
+ g_ptr_array_add (messages, tp_value_array_build (3,
+ G_TYPE_STRING, fb->type,
+ G_TYPE_STRING, fb->subtype,
+ G_TYPE_STRING, fb->extra_params,
+ G_TYPE_INVALID));
+ }
+
+ g_hash_table_insert (rtcp_fb, GUINT_TO_POINTER (fscodec->id),
+ tp_value_array_build (2,
+ G_TYPE_UINT,
+ fscodec->minimum_reporting_interval != G_MAXUINT ?
+ fscodec->minimum_reporting_interval : 5000,
+ TP_ARRAY_TYPE_RTCP_FEEDBACK_MESSAGE_LIST, messages,
+ G_TYPE_INVALID));
+
+ g_boxed_free (TP_ARRAY_TYPE_RTCP_FEEDBACK_MESSAGE_LIST, messages);
+ }
+ }
+
+ fs_codec_list_destroy (resend_codecs);
+
+
+ if (rtp_hdrext)
+ {
+ GList *fs_rtp_hdrexts;
+
+ g_object_get (self->fssession, "rtp-header-extensions", &fs_rtp_hdrexts,
+ NULL);
+
+ for (item = fs_rtp_hdrexts; item; item = item->next)
+ {
+ FsRtpHeaderExtension *hdrext = item->data;
+
+ g_debug (FS_RTP_HEADER_EXTENSION_FORMAT,
+ FS_RTP_HEADER_EXTENSION_ARGS (hdrext));
+
+ g_ptr_array_add (rtp_hdrext, tp_value_array_build (4,
+ G_TYPE_UINT, hdrext->id,
+ G_TYPE_UINT, fsdirection_to_tpdirection (hdrext->direction),
+ G_TYPE_STRING, hdrext->uri,
+ G_TYPE_STRING, "",
+ G_TYPE_INVALID));
+ }
+
+ fs_rtp_header_extension_list_destroy (fs_rtp_hdrexts);
+ }
+
+ retval = tp_asv_new (
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_CODECS,
+ TP_ARRAY_TYPE_CODEC_LIST, tpcodecs,
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_FURTHER_NEGOTIATION_REQUIRED,
+ G_TYPE_BOOLEAN, !!resend_codecs,
+ NULL);
+
+ interfaces = g_ptr_array_new ();
+
+ if (rtp_hdrext)
+ {
+ tp_asv_take_boxed (retval, TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1_HEADER_EXTENSIONS,
+ TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST,
+ rtp_hdrext);
+ g_ptr_array_add (interfaces,
+ g_strdup (TP_IFACE_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1));
+ }
+
+ if (rtcp_fb)
+ {
+ tp_asv_set_boolean (retval, TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1_DOES_AVPF, g_hash_table_size (rtcp_fb));
+ tp_asv_take_boxed (retval, TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1_FEEDBACK_MESSAGES,
+ TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, rtcp_fb);
+ g_ptr_array_add (interfaces,
+ g_strdup (TP_IFACE_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1));
+ }
+
+ g_ptr_array_add (interfaces, NULL);
+ tp_asv_take_boxed (retval, TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACES,
+ G_TYPE_STRV, interfaces->pdata);
+ g_ptr_array_free (interfaces, FALSE);
+
+ return retval;
+}
+
+static void
+media_description_updated_cb (TpCallContent *proxy,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ if (error == NULL)
+ g_debug ("Local media description set");
+ else
+ g_debug ("Local media description error: %s", error->message);
+
+}
+
+static void
+tf_call_content_try_sending_codecs (TfCallContent *self)
+{
+ GList *codecs;
+ GHashTable *media_description;
+ const gchar *codecs_prop = NULL;
+ guint i;
+
+ if (self->current_md_fscodecs != NULL)
+ {
+ g_debug ("Ignoring updated codecs unprocessed media description"
+ " outstanding");
+ return;
+ }
+
+ g_debug ("updating local codecs: %d", TF_CONTENT (self)->sending_count);
+
+ if (TF_CONTENT (self)->sending_count == 0)
+ codecs_prop = "codecs-without-config";
+ else
+ codecs_prop = "codecs";
+
+ g_object_get (self->fssession, codecs_prop, &codecs, NULL);
+
+ if (!codecs)
+ return;
+
+ media_description = fscodecs_to_media_descriptions (self, codecs);
+ if (!media_description)
+ {
+ fs_codec_list_destroy (codecs);
+ return;
+ }
+
+ TF_CALL_CONTENT_LOCK (self);
+
+ for (i = 0; i < self->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (self->fsstreams, i);
+
+ tp_asv_set_uint32 (media_description,
+ TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_REMOTE_CONTACT,
+ cfs->contact_handle);
+
+ if (self->current_media_description &&
+ self->current_md_contact_handle == cfs->contact_handle)
+ {
+ g_debug ("Accepting Media Description for contact: %u",
+ cfs->contact_handle);
+
+ tp_cli_call1_content_media_description_call_accept (
+ self->current_media_description, -1, media_description,
+ NULL, NULL, NULL, NULL);
+
+ g_object_unref (self->current_media_description);
+ self->current_media_description = NULL;
+ }
+ else
+ {
+ g_debug ("Updating local Media Description for contact %u",
+ cfs->contact_handle);
+
+ tp_cli_call1_content_interface_media_call_update_local_media_description (
+ self->proxy, -1, media_description,
+ media_description_updated_cb, NULL, NULL, NULL);
+ }
+ }
+
+ TF_CALL_CONTENT_UNLOCK (self);
+
+ if (media_description)
+ {
+ fs_codec_list_destroy (self->last_sent_codecs);
+ self->last_sent_codecs = codecs;
+ self->has_rtcp_fb = self->current_has_rtcp_fb;
+ self->has_rtp_hdrext = self->current_has_rtp_hdrext;
+
+ g_boxed_free (TP_HASH_TYPE_MEDIA_DESCRIPTION_PROPERTIES, media_description);
+ }
+}
+
+static void
+tf_call_content_dtmf_started (TfCallContent *self, FsDTMFMethod method,
+ FsDTMFEvent event, guint8 volume)
+{
+ if (volume != DTMF_TONE_VOLUME)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "DTMF volume is %d, while we use %d", volume, DTMF_TONE_VOLUME);
+ return;
+ }
+
+ if (self->dtmf_sending_state != TP_SENDING_STATE_PENDING_SEND)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Farstream started a DTMFevent, but we were in the %d state",
+ self->dtmf_sending_state);
+ return;
+ }
+
+ if (self->current_dtmf_event != event)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Farstream started the wrong dtmf event, got %d but "
+ "expected %d", event, self->current_dtmf_event);
+ return;
+ }
+
+ tp_cli_call1_content_interface_media_call_acknowledge_dtmf_change (
+ self->proxy, -1, event, TP_SENDING_STATE_SENDING,
+ NULL, NULL, NULL, NULL);
+ self->dtmf_sending_state = TP_SENDING_STATE_SENDING;
+}
+
+static void
+tf_call_content_dtmf_stopped (TfCallContent *self, FsDTMFMethod method)
+{
+ if (self->dtmf_sending_state != TP_SENDING_STATE_PENDING_STOP_SENDING)
+ {
+ tf_call_content_error (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Farstream stopped a DTMFevent, but we were in the %d state",
+ self->dtmf_sending_state);
+ return;
+ }
+
+ tp_cli_call1_content_interface_media_call_acknowledge_dtmf_change (
+ self->proxy, -1, self->current_dtmf_event, TP_SENDING_STATE_NONE,
+ NULL, NULL, NULL, NULL);
+ self->dtmf_sending_state = TP_SENDING_STATE_NONE;
+}
+
+
+gboolean
+tf_call_content_bus_message (TfCallContent *content,
+ GstMessage *message)
+{
+ gboolean ret = TRUE;
+ FsDTMFMethod method;
+ FsDTMFEvent event;
+ guint8 volume;
+ FsCodec *codec;
+ GList *secondary_codecs;
+ FsError error_no;
+ const gchar *error_msg;
+ guint i;
+
+
+ /* Guard against early disposal */
+ if (content->call_channel == NULL)
+ return FALSE;
+
+ if (!content->fssession)
+ return FALSE;
+
+ if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
+ return FALSE;
+
+
+ if (fs_parse_error (G_OBJECT (content->fssession), message, &error_no,
+ &error_msg))
+ {
+ GEnumClass *enumclass;
+ GEnumValue *enumvalue;
+
+ enumclass = g_type_class_ref (FS_TYPE_ERROR);
+ enumvalue = g_enum_get_value (enumclass, error_no);
+ g_warning ("error (%s (%d)): %s",
+ enumvalue->value_nick, error_no, error_msg);
+ g_type_class_unref (enumclass);
+
+ tf_call_content_error_literal (content,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR, error_msg);
+ }
+ else if (fs_session_parse_codecs_changed (content->fssession, message))
+ {
+ g_debug ("Codecs changed");
+
+ tf_call_content_try_sending_codecs (content);
+ }
+ else if (fs_session_parse_telephony_event_started (content->fssession,
+ message, &method, &event, &volume))
+ {
+ g_debug ("DTMF started: method: %d event: %u volume: %u",
+ method, event, volume);
+
+ tf_call_content_dtmf_started (content, method, event, volume);
+ }
+ else if (fs_session_parse_telephony_event_stopped (content->fssession,
+ message, &method))
+ {
+ g_debug ("DTMF stopped: method: %d", method);
+
+ tf_call_content_dtmf_stopped (content, method);
+ }
+ else if (fs_session_parse_send_codec_changed (content->fssession, message,
+ &codec, &secondary_codecs))
+ {
+ gchar *tmp;
+ guint j = 1;
+
+ tmp = fs_codec_to_string (codec);
+ g_debug ("Send codec changed: %s", tmp);
+ g_free (tmp);
+
+ while (secondary_codecs)
+ {
+ tmp = fs_codec_to_string (secondary_codecs->data);
+ g_debug ("Secondary send codec %u changed: %s", j++, tmp);
+ g_free (tmp);
+ secondary_codecs = secondary_codecs->next;
+ }
+ }
+ else
+ {
+ ret = FALSE;
+ }
+
+ for (i = 0; i < content->streams->len; i++)
+ if (tf_call_stream_bus_message (g_ptr_array_index (content->streams, i),
+ message))
+ return TRUE;
+
+ return ret;
+}
+
+
+static void
+tf_call_content_error (TfCallContent *self,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message_format,
+ ...)
+{
+ gchar *message;
+ va_list valist;
+
+ va_start (valist, message_format);
+ message = g_strdup_vprintf (message_format, valist);
+ va_end (valist);
+
+ tf_call_content_error_literal (self, reason, detailed_reason, message);
+ g_free (message);
+}
+
+
+static void
+tf_call_content_error_literal (TfCallContent *self,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message)
+{
+ g_debug ("Content error: %s", message);
+ tp_cli_call1_content_interface_media_call_fail (
+ self->proxy, -1,
+ tp_value_array_build (4,
+ G_TYPE_UINT, 0,
+ G_TYPE_UINT, reason,
+ G_TYPE_STRING, detailed_reason,
+ G_TYPE_STRING, message,
+ G_TYPE_INVALID),
+ NULL, NULL, NULL, NULL);
+}
+
+
+static void
+tf_call_content_error_impl (TfContent *content,
+ const gchar *message)
+{
+ tf_call_content_error_literal (TF_CALL_CONTENT (content),
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR, message);
+}
+
+
+static FsStream *
+tf_call_content_get_existing_fsstream_by_handle (TfCallContent *content,
+ guint contact_handle)
+{
+ guint i;
+
+ TF_CALL_CONTENT_LOCK (content);
+
+ for (i = 0; i < content->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i);
+
+ if (cfs->contact_handle == contact_handle)
+ {
+ cfs->use_count++;
+ TF_CALL_CONTENT_UNLOCK (content);
+ return cfs->fsstream;
+ }
+ }
+
+ TF_CALL_CONTENT_UNLOCK (content);
+
+ return NULL;
+}
+
+
+FsStream *
+_tf_call_content_get_fsstream_by_handle (TfCallContent *content,
+ guint contact_handle,
+ FsStreamDirection dir,
+ const gchar *transmitter,
+ guint stream_transmitter_n_parameters,
+ GParameter *stream_transmitter_parameters,
+ GError **error)
+{
+ struct CallFsStream *cfs;
+ FsParticipant *p;
+ FsStream *s;
+
+ s = tf_call_content_get_existing_fsstream_by_handle (content,
+ contact_handle);
+ if (s)
+ return s;
+
+ p = _tf_call_channel_get_participant (content->call_channel,
+ content->fsconference, contact_handle, error);
+ if (!p)
+ return NULL;
+
+ s = fs_session_new_stream (content->fssession, p, dir, error);
+ if (!s)
+ {
+ _tf_call_channel_put_participant (content->call_channel, p);
+ return NULL;
+ }
+
+ if (!fs_stream_set_transmitter (s, transmitter,
+ stream_transmitter_parameters, stream_transmitter_n_parameters,
+ error))
+ {
+ g_object_unref (s);
+ _tf_call_channel_put_participant (content->call_channel, p);
+ return NULL;
+ }
+
+ cfs = g_slice_new (struct CallFsStream);
+ cfs->use_count = 1;
+ cfs->contact_handle = contact_handle;
+ cfs->parent_channel = content->call_channel;
+ cfs->fsparticipant = p;
+ cfs->fsstream = s;
+
+ tp_g_signal_connect_object (s, "src-pad-added",
+ G_CALLBACK (src_pad_added), content, 0);
+
+ g_ptr_array_add (content->fsstreams, cfs);
+ content->fsstreams_cookie ++;
+ if (content->current_media_description != NULL
+ && content->current_md_contact_handle == contact_handle)
+ {
+ GList *codecs = content->current_md_fscodecs;
+ TpProxy *current_media_description = content->current_media_description;
+ GList *rtp_hdrext = content->current_md_rtp_hdrext;
+
+ content->current_md_fscodecs = NULL;
+ content->current_media_description = NULL;
+ content->current_md_rtp_hdrext = NULL;
+
+ /* ownership transfers to try_codecs */
+ process_media_description_try_codecs (content, s,
+ current_media_description, codecs, rtp_hdrext);
+ }
+
+ return s;
+}
+
+void
+_tf_call_content_put_fsstream (TfCallContent *content, FsStream *fsstream)
+{
+ guint i;
+ struct CallFsStream *fs_cfs = NULL;
+
+ TF_CALL_CONTENT_LOCK (content);
+ for (i = 0; i < content->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i);
+
+ if (cfs->fsstream == fsstream)
+ {
+ cfs->use_count--;
+ if (cfs->use_count <= 0)
+ {
+ fs_cfs = g_ptr_array_remove_index_fast (content->fsstreams, i);
+ content->fsstreams_cookie++;
+ }
+ break;
+ }
+ }
+ TF_CALL_CONTENT_UNLOCK (content);
+
+ if (fs_cfs)
+ free_content_fsstream (fs_cfs);
+}
+
+FsMediaType
+tf_call_content_get_fs_media_type (TfCallContent *content)
+{
+ return tp_media_type_to_fs (tp_call_content_get_media_type (content->proxy));
+}
+
+static void
+src_pad_added (FsStream *fsstream, GstPad *pad, FsCodec *codec,
+ TfCallContent *content)
+{
+ guint handle = 0;
+ guint i;
+
+ TF_CALL_CONTENT_LOCK (content);
+
+ if (!content->fsstreams)
+ {
+ TF_CALL_CONTENT_UNLOCK (content);
+ return;
+ }
+
+ for (i = 0; i < content->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i);
+ if (cfs->fsstream == fsstream)
+ {
+ handle = cfs->contact_handle;
+ break;
+ }
+ }
+
+ TF_CALL_CONTENT_UNLOCK (content);
+
+ _tf_content_emit_src_pad_added (TF_CONTENT (content), handle,
+ fsstream, pad, codec);
+}
+
+struct StreamSrcPadIterator {
+ GstIterator iterator;
+
+ GArray *handles;
+ GArray *handles_backup;
+
+ TfCallContent *self;
+};
+
+
+static void
+streams_src_pads_iter_copy (const GstIterator *orig, GstIterator *copy)
+{
+ const struct StreamSrcPadIterator *iter_orig =
+ (const struct StreamSrcPadIterator *) orig;
+ struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) copy;
+
+ iter->handles = g_array_sized_new (TRUE, FALSE, sizeof(guint),
+ iter_orig->handles->len );
+ iter->handles_backup = g_array_sized_new (TRUE, FALSE, sizeof(guint),
+ iter_orig->handles_backup->len );
+ g_array_append_vals (iter->handles, iter_orig->handles->data,
+ iter_orig->handles->len);
+ g_array_append_vals (iter->handles_backup, iter_orig->handles_backup->data,
+ iter_orig->handles_backup->len);
+ iter->self = g_object_ref (iter_orig->self);
+}
+
+
+static GstIteratorResult
+streams_src_pads_iter_next (GstIterator *it, GValue *result)
+{
+ struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it;
+
+ guint i;
+
+ if (iter->handles->len == 0)
+ return GST_ITERATOR_DONE;
+
+ for (i = 0; i < iter->self->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (iter->self->fsstreams, i);
+
+ if (cfs->contact_handle == g_array_index (iter->handles, guint, 0))
+ {
+ g_array_remove_index_fast (iter->handles, 0);
+ g_value_unset (result);
+ g_value_init (result, G_TYPE_POINTER);
+ g_value_set_pointer (result, cfs);
+ return GST_ITERATOR_OK;
+ }
+ }
+
+ return GST_ITERATOR_ERROR;
+
+}
+
+static GstIteratorItem
+streams_src_pads_iter_item (GstIterator *it, const GValue *item)
+{
+ struct CallFsStream *cfs = g_value_get_pointer (item);
+
+ g_value_unset ((GValue*) item);
+
+ gst_iterator_push (it, fs_stream_iterate_src_pads (cfs->fsstream));
+
+ return GST_ITERATOR_ITEM_SKIP;
+}
+
+static void
+streams_src_pads_iter_resync (GstIterator *it)
+{
+ struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it;
+
+ g_array_set_size (iter->handles, iter->handles_backup->len);
+ memcpy (iter->handles->data, iter->handles_backup->data,
+ iter->handles_backup->len * sizeof(guint));
+}
+
+static void
+streams_src_pads_iter_free (GstIterator *it)
+{
+ struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it;
+
+ g_array_unref (iter->handles);
+ g_array_unref (iter->handles_backup);
+ g_object_unref (iter->self);
+}
+
+static GstIterator *
+tf_call_content_iterate_src_pads (TfContent *content, guint *handles,
+ guint handle_count)
+{
+ TfCallContent *self = TF_CALL_CONTENT (content);
+ struct StreamSrcPadIterator *iter;
+
+ iter = (struct StreamSrcPadIterator *) gst_iterator_new (
+ sizeof (struct StreamSrcPadIterator), GST_TYPE_PAD,
+ &self->mutex, &self->fsstreams_cookie,
+ streams_src_pads_iter_copy,
+ streams_src_pads_iter_next,
+ streams_src_pads_iter_item,
+ streams_src_pads_iter_resync,
+ streams_src_pads_iter_free);
+
+ iter->handles = g_array_sized_new (TRUE, FALSE, sizeof(guint), handle_count);
+ iter->handles_backup = g_array_sized_new (TRUE, FALSE, sizeof(guint),
+ handle_count);
+ g_array_append_vals (iter->handles, handles, handle_count);
+ g_array_append_vals (iter->handles_backup, handles, handle_count);
+ iter->self = g_object_ref (self);
+
+ return (GstIterator *) iter;
+}
+
+static void
+tf_call_content_sending_failed (TfContent *content,
+ const gchar *message)
+{
+ TfCallContent *self = TF_CALL_CONTENT (content);
+ guint i;
+
+ if (!self->streams)
+ {
+ g_warning ("Too early, ignoring sending error");
+ return;
+ }
+
+ for (i = 0; i < self->streams->len; i++)
+ tf_call_stream_sending_failed (g_ptr_array_index (self->streams, i),
+ message);
+}
+
+
+static void
+tf_call_content_receiving_failed (TfContent *content,
+ guint *handles, guint handle_count,
+ const gchar *message)
+{
+ TfCallContent *self = TF_CALL_CONTENT (content);
+ guint i;
+
+ if (!self->streams)
+ {
+ g_warning ("Too early, ignoring sending error");
+ return;
+ }
+
+ for (i = 0; i < self->streams->len; i++)
+ tf_call_stream_receiving_failed (g_ptr_array_index (self->streams, i),
+ handles, handle_count, message);
+}
+
+
+TpCallContent *
+tf_call_content_get_proxy (TfCallContent *content)
+{
+ g_return_val_if_fail (TF_IS_CALL_CONTENT (content), NULL);
+
+ return content->proxy;
+}
diff --git a/telepathy-farstream/call-content.h b/telepathy-farstream/call-content.h
new file mode 100644
index 000000000..499eead17
--- /dev/null
+++ b/telepathy-farstream/call-content.h
@@ -0,0 +1,109 @@
+/*
+ * call-content.h - Source for TfCallContent
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_CALL_CONTENT_H__
+#define __TF_CALL_CONTENT_H__
+
+#include <glib-object.h>
+
+#include <gst/gst.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "call-channel.h"
+#include "content.h"
+#include "content-priv.h"
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CALL_CONTENT tf_call_content_get_type()
+
+#define TF_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CALL_CONTENT, TfCallContent))
+
+#define TF_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CALL_CONTENT, TfCallContentClass))
+
+#define TF_IS_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_CONTENT))
+
+#define TF_IS_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_CONTENT))
+
+#define TF_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CALL_CONTENT, TfCallContentClass))
+
+typedef struct _TfCallContentPrivate TfCallContentPrivate;
+
+/**
+ * TfCallContent:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfCallContent TfCallContent;
+
+/**
+ * TfCallContentClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfCallContentClass TfCallContentClass;
+
+GType tf_call_content_get_type (void);
+
+TfCallContent *tf_call_content_new_async (
+ TfCallChannel *call_channel,
+ TpCallContent *content_proxy,
+ GError **error,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tf_call_content_bus_message (TfCallContent *content,
+ GstMessage *message);
+
+
+/* Private */
+FsStream *_tf_call_content_get_fsstream_by_handle (TfCallContent *content,
+ guint contact_handle,
+ FsStreamDirection dir,
+ const gchar *transmitter,
+ guint stream_transmitter_n_parameters,
+ GParameter *stream_transmitter_parameters,
+ GError **error);
+void _tf_call_content_put_fsstream (TfCallContent *content, FsStream *fsstream);
+
+FsMediaType
+tf_call_content_get_fs_media_type (TfCallContent *content);
+
+
+gboolean
+tf_call_content_bus_message (TfCallContent *content, GstMessage *message);
+
+TpCallContent *
+tf_call_content_get_proxy (TfCallContent *content);
+
+G_END_DECLS
+
+#endif /* __TF_CALL_CONTENT_H__ */
+
diff --git a/telepathy-farstream/call-priv.h b/telepathy-farstream/call-priv.h
new file mode 100644
index 000000000..c89c056f4
--- /dev/null
+++ b/telepathy-farstream/call-priv.h
@@ -0,0 +1,33 @@
+/*
+ * call-priv.h
+ * Copyright (C) 2012 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#ifndef __TF_CALL_PRIV_H__
+#define __TF_CALL_PRIV_H__
+
+#include "call-stream.h"
+
+G_BEGIN_DECLS
+
+void _tf_call_content_destroy (TfCallContent *self);
+void _tf_call_stream_destroy (TfCallStream *self);
+
+G_END_DECLS
+
+#endif /* __TF_CALL_PRIV_H__ */
diff --git a/telepathy-farstream/call-stream.c b/telepathy-farstream/call-stream.c
new file mode 100644
index 000000000..4052c2481
--- /dev/null
+++ b/telepathy-farstream/call-stream.c
@@ -0,0 +1,1742 @@
+/*
+ * call-stream.c - Source for TfCallStream
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:tfcallstream
+ *
+ * @short_description: Handle the Stream objects for a Call1 channel
+ *
+ * This class handles the org.freedesktop.Telepathy.Call1.Stream,
+ * org.freedesktop.Telepathy.Call1.Stream.Interface.Media and
+ * org.freedesktop.Telepathy.Call1.Stream.Endpoint interfaces.
+ */
+
+/*
+ * TODO:
+ * - Support multiple handles
+ * - Allow app to fail sending or receiving during call
+ *
+ * Endpoints:
+ * - Support multiple Endpoints (ie SIP forking with ICE)
+ * - Call SetControlling
+ * - Listen to CandidatePairSelected and call AcceptSelectedCandidatePair/RejectSelectedCandidatePair
+ * - Support IsICELite
+ */
+
+#include "config.h"
+
+#include "call-stream.h"
+#include "call-priv.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+#include <telepathy-glib/proxy-subclass.h>
+#include <farstream/fs-conference.h>
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "utils.h"
+
+
+G_DEFINE_TYPE (TfCallStream, tf_call_stream, G_TYPE_OBJECT);
+
+static void tf_call_stream_dispose (GObject *object);
+static void tf_call_stream_finalize (GObject *object);
+
+static void tf_call_stream_fail_literal (TfCallStream *self,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message);
+
+static void tf_call_stream_fail (TfCallStream *self,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message_format,
+ ...);
+
+static void _tf_call_stream_remove_endpoint (TfCallStream *self);
+
+
+static void
+tf_call_stream_class_init (TfCallStreamClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = tf_call_stream_dispose;
+ object_class->finalize = tf_call_stream_finalize;
+}
+
+static void
+tf_call_stream_init (TfCallStream *self)
+{
+ self->sending_state = TP_STREAM_FLOW_STATE_STOPPED;
+ self->receiving_state = TP_STREAM_FLOW_STATE_STOPPED;
+}
+
+void
+_tf_call_stream_destroy (TfCallStream *self)
+{
+ if (self->proxy)
+ g_object_unref (self->proxy);
+ self->proxy = NULL;
+
+ if (self->fsstream)
+ {
+ g_object_set (self->fsstream, "direction", FS_DIRECTION_NONE, NULL);
+
+ if (self->has_send_resource)
+ _tf_content_stop_sending (TF_CONTENT (self->call_content));
+ self->has_send_resource = FALSE;
+
+ if (self->has_receive_resource)
+ _tf_content_stop_receiving (TF_CONTENT (self->call_content),
+ &self->contact_handle, 1);
+ self->has_receive_resource = FALSE;
+ _tf_call_content_put_fsstream (self->call_content, self->fsstream);
+ }
+
+ self->fsstream = NULL;
+
+ if (self->endpoint)
+ _tf_call_stream_remove_endpoint (self);
+
+ self->call_content = NULL;
+}
+
+static void
+tf_call_stream_dispose (GObject *object)
+{
+ TfCallStream *self = TF_CALL_STREAM (object);
+
+ g_debug (G_STRFUNC);
+
+ _tf_call_stream_destroy (self);
+
+ if (G_OBJECT_CLASS (tf_call_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_call_stream_parent_class)->dispose (object);
+}
+
+static void
+tf_call_stream_finalize (GObject *object)
+{
+ TfCallStream *self = TF_CALL_STREAM (object);
+
+ g_debug (G_STRFUNC);
+
+ if (self->stun_servers)
+ g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, self->stun_servers);
+ self->stun_servers = NULL;
+
+ if (self->relay_info)
+ g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, self->relay_info);
+ self->relay_info = NULL;
+
+ if (G_OBJECT_CLASS (tf_call_stream_parent_class)->finalize)
+ G_OBJECT_CLASS (tf_call_stream_parent_class)->finalize (object);
+}
+
+
+static void
+tf_call_stream_update_sending_state (TfCallStream *self)
+{
+ gboolean sending = FALSE;
+ FsStreamDirection dir;
+
+ if (self->fsstream == NULL)
+ return;
+
+ if (self->endpoint == NULL)
+ goto done;
+
+ switch (self->sending_state)
+ {
+ case TP_STREAM_FLOW_STATE_PENDING_START:
+ if (self->has_send_resource)
+ sending = TRUE;
+ break;
+ case TP_STREAM_FLOW_STATE_STARTED:
+ sending = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ done:
+ g_object_get (self->fsstream, "direction", &dir, NULL);
+ if (sending)
+ g_object_set (self->fsstream, "direction", dir | FS_DIRECTION_SEND, NULL);
+ else
+ g_object_set (self->fsstream, "direction", dir & ~FS_DIRECTION_SEND, NULL);
+}
+
+static void
+sending_state_changed (TpCallStream *proxy,
+ guint arg_State,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ self->sending_state = arg_State;
+
+ if (!self->fsstream)
+ return;
+
+ switch (arg_State)
+ {
+ case TP_STREAM_FLOW_STATE_PENDING_START:
+ if (self->has_send_resource ||
+ _tf_content_start_sending (TF_CONTENT (self->call_content)))
+ {
+ self->has_send_resource = TRUE;
+
+ tp_cli_call1_stream_interface_media_call_complete_sending_state_change (
+ proxy, -1, TP_STREAM_FLOW_STATE_STARTED,
+ NULL, NULL, NULL, NULL);
+ tf_call_stream_update_sending_state (self);
+ }
+ else
+ {
+ tp_cli_call1_stream_interface_media_call_report_sending_failure (
+ proxy, -1, TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Could not start sending", NULL, NULL, NULL, NULL);
+ return;
+ }
+ break;
+ case TP_STREAM_FLOW_STATE_PENDING_STOP:
+ tf_call_stream_update_sending_state (self);
+ if (self->has_send_resource)
+ {
+ _tf_content_stop_sending (TF_CONTENT (self->call_content));
+
+ self->has_send_resource = FALSE;
+ }
+ tp_cli_call1_stream_interface_media_call_complete_sending_state_change (
+ proxy, -1, TP_STREAM_FLOW_STATE_STOPPED, NULL, NULL, NULL, NULL);
+ break;
+ default:
+ break;
+ }
+}
+
+static gboolean
+tf_call_stream_start_receiving (TfCallStream *self, FsStreamDirection dir)
+{
+ if (self->has_receive_resource ||
+ _tf_content_start_receiving (TF_CONTENT (self->call_content),
+ &self->contact_handle, 1))
+ {
+ self->has_receive_resource = TRUE;
+ if (self->fsstream)
+ g_object_set (self->fsstream,
+ "direction", dir | FS_DIRECTION_RECV, NULL);
+ tp_cli_call1_stream_interface_media_call_complete_receiving_state_change (
+ self->proxy, -1, TP_STREAM_FLOW_STATE_STARTED,
+ NULL, NULL, NULL, NULL);
+ return TRUE;
+ }
+ else
+ {
+ tp_cli_call1_stream_interface_media_call_report_receiving_failure (
+ self->proxy, -1, TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Could not start receiving", NULL, NULL, NULL, NULL);
+ return FALSE;
+ }
+}
+
+static void
+receiving_state_changed (TpCallStream *proxy,
+ guint arg_State,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+ FsStreamDirection dir;
+
+ self->receiving_state = arg_State;
+
+ if (!self->fsstream)
+ return;
+
+ g_object_get (self->fsstream, "direction", &dir, NULL);
+
+ switch (arg_State)
+ {
+ case TP_STREAM_FLOW_STATE_PENDING_START:
+ tf_call_stream_start_receiving (self, dir);
+ break;
+ case TP_STREAM_FLOW_STATE_PENDING_STOP:
+ g_object_set (self->fsstream,
+ "direction", dir & ~FS_DIRECTION_RECV, NULL);
+ if (self->has_receive_resource)
+ {
+ _tf_content_stop_receiving (TF_CONTENT (self->call_content),
+ &self->contact_handle, 1);
+
+ self->has_receive_resource = FALSE;
+ }
+ tp_cli_call1_stream_interface_media_call_complete_receiving_state_change (
+ proxy, -1, TP_STREAM_FLOW_STATE_STOPPED, NULL, NULL, NULL, NULL);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+_tf_call_stream_push_remote_candidates (TfCallStream *self,
+ GList *fscandidates)
+{
+ gboolean ret;
+ GError *error = NULL;
+
+ switch (self->transport_type)
+ {
+ case TP_STREAM_TRANSPORT_TYPE_RAW_UDP:
+ case TP_STREAM_TRANSPORT_TYPE_SHM:
+ case TP_STREAM_TRANSPORT_TYPE_MULTICAST:
+ ret = fs_stream_force_remote_candidates (self->fsstream,
+ fscandidates, &error);
+ break;
+ case TP_STREAM_TRANSPORT_TYPE_ICE:
+ case TP_STREAM_TRANSPORT_TYPE_GTALK_P2P:
+ case TP_STREAM_TRANSPORT_TYPE_WLM_2009:
+ ret = fs_stream_add_remote_candidates (self->fsstream, fscandidates,
+ &error);
+ break;
+ default:
+ ret = FALSE;
+ }
+
+ if (!ret)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Error setting the remote candidates: %s", error->message);
+ g_clear_error (&error);
+ }
+ fs_candidate_list_destroy (fscandidates);
+}
+
+static void
+tf_call_stream_try_adding_fsstream (TfCallStream *self)
+{
+ gchar *transmitter;
+ GError *error = NULL;
+ guint n_params = 0;
+ GParameter params[6] = { {NULL,} };
+ GList *preferred_local_candidates = NULL;
+ guint i;
+ FsStreamDirection dir = FS_DIRECTION_NONE;
+
+ memset (params, 0, sizeof(params));
+
+ if (!self->server_info_retrieved ||
+ !self->has_contact ||
+ !self->has_media_properties)
+ return;
+
+ switch (self->transport_type)
+ {
+ case TP_STREAM_TRANSPORT_TYPE_RAW_UDP:
+ transmitter = "rawudp";
+
+ g_debug ("Transmitter: rawudp");
+
+ switch (tf_call_content_get_fs_media_type (self->call_content))
+ {
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ preferred_local_candidates = g_list_prepend (NULL,
+ fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST,
+ FS_NETWORK_PROTOCOL_UDP, NULL, 9078));
+ break;
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ preferred_local_candidates = g_list_prepend (NULL,
+ fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST,
+ FS_NETWORK_PROTOCOL_UDP, NULL, 7078));
+ default:
+ break;
+ }
+
+ if (preferred_local_candidates)
+ {
+ params[n_params].name = "preferred-local-candidates";
+ g_value_init (&params[n_params].value, FS_TYPE_CANDIDATE_LIST);
+ g_value_take_boxed (&params[n_params].value,
+ preferred_local_candidates);
+ n_params++;
+ }
+ break;
+ case TP_STREAM_TRANSPORT_TYPE_ICE:
+ case TP_STREAM_TRANSPORT_TYPE_GTALK_P2P:
+ case TP_STREAM_TRANSPORT_TYPE_WLM_2009:
+ transmitter = "nice";
+
+ params[n_params].name = "controlling-mode";
+ g_value_init (&params[n_params].value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&params[n_params].value, self->controlling);
+ n_params++;
+
+ params[n_params].name = "compatibility-mode";
+ g_value_init (&params[n_params].value, G_TYPE_UINT);
+ switch (self->transport_type)
+ {
+ case TP_STREAM_TRANSPORT_TYPE_ICE:
+ g_value_set_uint (&params[n_params].value, 0);
+ break;
+ case TP_STREAM_TRANSPORT_TYPE_GTALK_P2P:
+ g_value_set_uint (&params[n_params].value, 1);
+ self->multiple_usernames = TRUE;
+ break;
+ case TP_STREAM_TRANSPORT_TYPE_WLM_2009:
+ g_value_set_uint (&params[n_params].value, 3);
+ break;
+ default:
+ break;
+ }
+
+ g_debug ("Transmitter: nice: TpTransportType:%d controlling:%d",
+ self->transport_type, self->controlling);
+
+ n_params++;
+ break;
+ case TP_STREAM_TRANSPORT_TYPE_SHM:
+ transmitter = "shm";
+ params[n_params].name = "create-local-candidates";
+ g_value_init (&params[n_params].value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&params[n_params].value, TRUE);
+ n_params++;
+ g_debug ("Transmitter: shm");
+ break;
+ default:
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, TP_ERROR_STR_CONFUSED,
+ "Unknown transport type %d", self->transport_type);
+ return;
+ }
+
+ if (self->stun_servers->len)
+ {
+ GValueArray *gva = g_ptr_array_index (self->stun_servers, 0);
+ gchar *ip;
+ guint port;
+ gchar *conn_timeout_str;
+
+ /* We only use the first STUN server if there are many */
+
+ tp_value_array_unpack (gva, 2, &ip, &port);
+
+ params[n_params].name = "stun-ip";
+ g_value_init (&params[n_params].value, G_TYPE_STRING);
+ g_value_set_string (&params[n_params].value, ip);
+ n_params++;
+
+ params[n_params].name = "stun-port";
+ g_value_init (&params[n_params].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_params].value, port);
+ n_params++;
+
+ conn_timeout_str = getenv ("FS_CONN_TIMEOUT");
+ if (conn_timeout_str)
+ {
+ gint conn_timeout = strtol (conn_timeout_str, NULL, 10);
+
+ params[n_params].name = "stun-timeout";
+ g_value_init (&params[n_params].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_params].value, conn_timeout);
+ n_params++;
+ }
+ }
+
+ if (self->relay_info->len)
+ {
+ GPtrArray *fs_relay_info = NULL;
+
+ for (i = 0; i < self->relay_info->len; i++)
+ {
+ GHashTable *one_relay = g_ptr_array_index(self->relay_info, i);
+ const gchar *type = NULL;
+ const gchar *ip;
+ guint32 port;
+ const gchar *username;
+ const gchar *password;
+ guint component;
+ GstStructure *s;
+
+ ip = tp_asv_get_string (one_relay, "ip");
+ port = tp_asv_get_uint32 (one_relay, "port", NULL);
+ type = tp_asv_get_string (one_relay, "type");
+ username = tp_asv_get_string (one_relay, "username");
+ password = tp_asv_get_string (one_relay, "password");
+ component = tp_asv_get_uint32 (one_relay, "component", NULL);
+
+ if (!ip || !port || !username || !password)
+ continue;
+
+ if (!type)
+ type = "udp";
+
+ s = gst_structure_new ("relay-info",
+ "ip", G_TYPE_STRING, ip,
+ "port", G_TYPE_UINT, port,
+ "username", G_TYPE_STRING, username,
+ "password", G_TYPE_STRING, password,
+ "type", G_TYPE_STRING, type,
+ NULL);
+
+ if (component)
+ gst_structure_set (s, "component", G_TYPE_UINT, component, NULL);
+
+
+ if (!fs_relay_info)
+ fs_relay_info = g_ptr_array_new_with_free_func (
+ (GDestroyNotify)gst_structure_free);
+
+ g_ptr_array_add (fs_relay_info, s);
+ }
+
+ if (fs_relay_info)
+ {
+ params[n_params].name = "relay-info";
+ g_value_init (&params[n_params].value, G_TYPE_PTR_ARRAY);
+ g_value_take_boxed (&params[n_params].value, fs_relay_info);
+ n_params++;
+ }
+ }
+
+ if (self->receiving_state == TP_STREAM_FLOW_STATE_PENDING_START)
+ {
+ if (tf_call_stream_start_receiving (self, FS_DIRECTION_NONE))
+ dir = FS_DIRECTION_RECV;
+ }
+
+ self->fsstream = _tf_call_content_get_fsstream_by_handle (self->call_content,
+ self->contact_handle,
+ dir,
+ transmitter,
+ n_params,
+ params,
+ &error);
+
+ for (i = 0; i < n_params; i++)
+ g_value_unset (&params[i].value);
+
+ if (!self->fsstream)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Could not create FsStream: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ _tf_call_stream_push_remote_candidates (self, self->stored_remote_candidates);
+ self->stored_remote_candidates = NULL;
+
+ if (self->sending_state == TP_STREAM_FLOW_STATE_PENDING_START)
+ sending_state_changed (self->proxy,
+ self->sending_state, NULL, (GObject *) self);
+}
+
+static void
+server_info_retrieved (TpCallStream *proxy,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if (self->proxy == NULL)
+ return;
+
+ self->server_info_retrieved = TRUE;
+
+ tf_call_stream_try_adding_fsstream (self);
+}
+
+static void
+relay_info_changed (TpCallStream *proxy,
+ const GPtrArray *arg_Relay_Info,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if (self->proxy == NULL)
+ return;
+
+ if (self->server_info_retrieved)
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_NOT_IMPLEMENTED,
+ "Changing relay servers after ServerInfoRetrived is not implemented");
+ return;
+ }
+
+ /* Ignore signals that come before the basic info has been retrived */
+ if (!self->relay_info)
+ return;
+
+ g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, self->relay_info);
+ self->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST,
+ arg_Relay_Info);
+}
+
+static void
+stun_servers_changed (TpCallStream *proxy,
+ const GPtrArray *arg_Servers,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if (self->proxy == NULL)
+ return;
+
+ if (self->server_info_retrieved)
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_NOT_IMPLEMENTED,
+ "Changing STUN servers after ServerInfoRetrived is not implemented");
+ return;
+ }
+
+ /* Ignore signals that come before the basic info has been retrived */
+ if (!self->stun_servers)
+ return;
+
+ g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, self->stun_servers);
+ self->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST,
+ arg_Servers);
+}
+
+static FsCandidateType
+tpcandidate_type_to_fs (TpCallStreamCandidateType type)
+{
+ switch(type)
+ {
+ case TP_CALL_STREAM_CANDIDATE_TYPE_NONE:
+ g_warning ("Candidate type NONE, assigning to HOST");
+ /* fallthrough */
+ case TP_CALL_STREAM_CANDIDATE_TYPE_HOST:
+ return FS_CANDIDATE_TYPE_HOST;
+ case TP_CALL_STREAM_CANDIDATE_TYPE_SERVER_REFLEXIVE:
+ return FS_CANDIDATE_TYPE_SRFLX;
+ case TP_CALL_STREAM_CANDIDATE_TYPE_PEER_REFLEXIVE:
+ return FS_CANDIDATE_TYPE_PRFLX;
+ case TP_CALL_STREAM_CANDIDATE_TYPE_RELAY:
+ return FS_CANDIDATE_TYPE_RELAY;
+ case TP_CALL_STREAM_CANDIDATE_TYPE_MULTICAST:
+ return FS_CANDIDATE_TYPE_MULTICAST;
+ default:
+ g_warning ("Candidate type %d unknown, assigning to HOST", type);
+ return FS_CANDIDATE_TYPE_HOST;
+ }
+}
+
+static FsNetworkProtocol
+tpnetworkproto_to_fs (TpMediaStreamBaseProto proto)
+{
+ switch(proto)
+ {
+ case TP_MEDIA_STREAM_BASE_PROTO_UDP:
+ return FS_NETWORK_PROTOCOL_UDP;
+ case TP_MEDIA_STREAM_BASE_PROTO_TCP:
+ return FS_NETWORK_PROTOCOL_TCP;
+ default:
+ g_debug ("Network protocol %d unknown, assigning to UDP", proto);
+ return FS_NETWORK_PROTOCOL_UDP;
+ }
+}
+
+static void
+tf_call_stream_add_remote_candidates (TfCallStream *self,
+ const GPtrArray *candidates)
+{
+ GList *fscandidates = NULL;
+ guint i;
+
+ /* No candidates to add, ignore. This could either be caused by the CM
+ * accidentally emitting an empty RemoteCandidatesAdded or when there are no
+ * remote candidates on the endpoint yet when we query it */
+ if (candidates->len == 0)
+ return;
+
+ for (i = 0; i < candidates->len; i++)
+ {
+ GValueArray *tpcandidate = g_ptr_array_index (candidates, i);
+ guint component;
+ gchar *ip;
+ guint port;
+ GHashTable *extra_info;
+ const gchar *foundation;
+ guint priority;
+ const gchar *username;
+ const gchar *password;
+ gboolean valid;
+ FsCandidate *cand;
+ guint type;
+ guint protocol;
+ guint ttl;
+ const gchar *base_ip;
+ guint base_port;
+
+ tp_value_array_unpack (tpcandidate, 4, &component, &ip, &port,
+ &extra_info);
+
+ foundation = tp_asv_get_string (extra_info, "foundation");
+ if (!foundation)
+ foundation = "";
+ priority = tp_asv_get_uint32 (extra_info, "priority", &valid);
+ if (!valid)
+ priority = 0;
+
+ username = tp_asv_get_string (extra_info, "username");
+ if (!username)
+ username = self->creds_username;
+
+ password = tp_asv_get_string (extra_info, "password");
+ if (!password)
+ password = self->creds_password;
+
+ type = tp_asv_get_uint32 (extra_info, "type", &valid);
+ if (!valid)
+ type = TP_CALL_STREAM_CANDIDATE_TYPE_HOST;
+
+ protocol = tp_asv_get_uint32 (extra_info, "protocol", &valid);
+ if (!valid)
+ protocol = TP_MEDIA_STREAM_BASE_PROTO_UDP;
+
+ base_ip = tp_asv_get_string (extra_info, "base-ip");
+ base_port = tp_asv_get_uint32 (extra_info, "base-port", &valid);
+ if (!valid)
+ base_port = 0;
+
+
+ ttl = tp_asv_get_uint32 (extra_info, "ttl", &valid);
+ if (!valid)
+ ttl = 0;
+
+ g_debug ("Remote Candidate: %s c:%d tptype:%d tpproto: %d ip:%s port:%u prio:%d u/p:%s/%s ttl:%d base_ip:%s base_port:%d",
+ foundation, component, type, protocol, ip, port, priority,
+ username, password, ttl, base_ip, base_port);
+
+ cand = fs_candidate_new (foundation, component,
+ tpcandidate_type_to_fs (type), tpnetworkproto_to_fs (protocol),
+ ip, port);
+ cand->priority = priority;
+ cand->username = g_strdup (username);
+ cand->password = g_strdup (password);
+ cand->ttl = ttl;
+ cand->base_ip = g_strdup (base_ip);
+ cand->base_port = base_port;
+
+ fscandidates = g_list_append (fscandidates, cand);
+ }
+
+ if (self->fsstream)
+ {
+ _tf_call_stream_push_remote_candidates (self, fscandidates);
+ }
+ else
+ {
+ self->stored_remote_candidates =
+ g_list_concat (self->stored_remote_candidates, fscandidates);
+ }
+}
+
+static void
+remote_candidates_added (TpProxy *proxy,
+ const GPtrArray *arg_Candidates,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if (self->proxy == NULL)
+ return;
+
+ if (!self->has_endpoint_properties)
+ return;
+
+ if (self->endpoint != proxy)
+ return;
+
+ tf_call_stream_add_remote_candidates (self, arg_Candidates);
+}
+
+static void
+remote_credentials_set (TpProxy *proxy,
+ const gchar *arg_Username,
+ const gchar *arg_Password,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if (self->proxy == NULL)
+ return;
+
+ if (self->endpoint != proxy)
+ return;
+
+ if ((self->creds_username && strcmp (self->creds_username, arg_Username)) ||
+ (self->creds_password && strcmp (self->creds_password, arg_Password)))
+ {
+ g_debug ("Remote credentials changed,"
+ " remote is doing an ICE restart");
+ /* Remote credentials changed, this will perform a ICE restart, so
+ * clear old remote candidates */
+ fs_candidate_list_destroy (self->stored_remote_candidates);
+ self->stored_remote_candidates = NULL;
+ }
+
+ g_free (self->creds_username);
+ g_free (self->creds_password);
+ self->creds_username = g_strdup (arg_Username);
+ self->creds_password = g_strdup (arg_Password);
+
+ g_debug ("Credentials set: %s / %s", arg_Username, arg_Password);
+}
+
+
+static void
+got_endpoint_properties (TpProxy *proxy, GHashTable *out_Properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+ GValueArray *credentials;
+ gchar *username, *password;
+ GPtrArray *candidates;
+ gboolean valid = FALSE;
+ guint transport_type;
+
+ if (self->endpoint != proxy)
+ return;
+
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Streams's media properties: %s", error->message);
+ return;
+ }
+
+ if (!out_Properties)
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Stream's media properties: there are none");
+ return;
+ }
+
+ g_debug ("Got Endpoint Properties");
+
+
+ credentials = tp_asv_get_boxed (out_Properties, "RemoteCredentials",
+ TP_STRUCT_TYPE_STREAM_CREDENTIALS);
+ if (!credentials)
+ goto invalid_property;
+ tp_value_array_unpack (credentials, 2, &username, &password);
+ if (username && username[0])
+ self->creds_username = g_strdup (username);
+ if (password && password[0])
+ self->creds_password = g_strdup (password);
+
+ if (self->creds_username || self->creds_password)
+ g_debug ("Credentials set: %s / %s", username, password);
+
+ candidates = tp_asv_get_boxed (out_Properties, "RemoteCandidates",
+ TP_ARRAY_TYPE_CANDIDATE_LIST);
+ if (!candidates)
+ goto invalid_property;
+
+ transport_type = tp_asv_get_uint32 (out_Properties, "Transport", &valid);
+ if (!valid)
+ {
+ g_warning ("No valid transport");
+ goto invalid_property;
+ }
+
+ if (transport_type != self->transport_type)
+ {
+ if (transport_type != TP_STREAM_TRANSPORT_TYPE_RAW_UDP)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_INVALID_ARGUMENT,
+ "The Transport of a Endpoint can only be changed to rawudp: %d invalid", transport_type);
+ return;
+ }
+ self->transport_type = transport_type;
+ }
+
+ self->has_endpoint_properties = TRUE;
+
+ tf_call_stream_add_remote_candidates (self, candidates);
+
+ tf_call_stream_update_sending_state (self);
+
+ return;
+
+ invalid_property:
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Endpoint's properties: invalid type");
+}
+
+static void
+tf_call_stream_add_endpoint (TfCallStream *self, const gchar *obj_path)
+{
+ GError *error = NULL;
+
+ self->endpoint_objpath = g_strdup (obj_path);
+
+ tp_call_stream_endpoint_init_known_interfaces ();
+ self->endpoint = g_object_new (TP_TYPE_PROXY,
+ "dbus-daemon", tp_proxy_get_dbus_daemon (self->proxy),
+ "bus-name", tp_proxy_get_bus_name (self->proxy),
+ "object-path", self->endpoint_objpath,
+ NULL);
+ tp_proxy_add_interface_by_id (TP_PROXY (self->endpoint),
+ TP_IFACE_QUARK_CALL1_STREAM_ENDPOINT);
+
+ tp_cli_call1_stream_endpoint_connect_to_remote_credentials_set (
+ TP_PROXY (self->endpoint), remote_credentials_set, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error connecting to RemoteCredentialsSet signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_cli_call1_stream_endpoint_connect_to_remote_candidates_added (
+ TP_PROXY (self->endpoint), remote_candidates_added, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error connecting to RemoteCandidatesAdded signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_cli_dbus_properties_call_get_all (self->endpoint, -1,
+ TP_IFACE_CALL1_STREAM_ENDPOINT,
+ got_endpoint_properties, NULL, NULL, G_OBJECT (self));
+}
+
+static void
+_tf_call_stream_remove_endpoint (TfCallStream *self)
+{
+ g_clear_object (&self->endpoint);
+
+ self->has_endpoint_properties = FALSE;
+ self->multiple_usernames = FALSE;
+ self->controlling = FALSE;
+
+ fs_candidate_list_destroy (self->stored_remote_candidates);
+ self->stored_remote_candidates = NULL;
+
+ g_free (self->creds_username);
+ self->creds_username = NULL;
+
+ g_free (self->creds_password);
+ self->creds_password = NULL;
+
+ g_free (self->endpoint_objpath);
+ self->endpoint_objpath = NULL;
+
+ tf_call_stream_update_sending_state (self);
+}
+
+static void
+endpoints_changed (TpCallStream *proxy,
+ const GPtrArray *arg_Endpoints_Added,
+ const GPtrArray *arg_Endpoints_Removed,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if (self->proxy == NULL)
+ return;
+
+ /* Ignore signals before getting the properties to avoid races */
+ if (!self->has_media_properties)
+ return;
+
+ if (arg_Endpoints_Removed->len == 1)
+ {
+ if (self->endpoint_objpath == NULL ||
+ strcmp (self->endpoint_objpath,
+ g_ptr_array_index (arg_Endpoints_Removed, 0)))
+
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Can not remove endpoint that has not been previously added");
+ return;
+ }
+ _tf_call_stream_remove_endpoint (self);
+ }
+ else if (arg_Endpoints_Removed->len > 1)
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_NOT_IMPLEMENTED,
+ "Having more than one endpoint is not implemented");
+ return;
+ }
+
+ /* Nothing added, it's over */
+ if (arg_Endpoints_Added->len == 0)
+ return;
+
+ if (arg_Endpoints_Added->len > 1)
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_NOT_IMPLEMENTED,
+ "Having more than one endpoint is not implemented");
+ return;
+ }
+
+ if (self->endpoint_objpath)
+ {
+ if (strcmp (g_ptr_array_index (arg_Endpoints_Added, 0),
+ self->endpoint_objpath))
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_NOT_IMPLEMENTED,
+ "Having more than one endpoint is not implemented");
+ return;
+ }
+
+ tf_call_stream_add_endpoint (self,
+ g_ptr_array_index (arg_Endpoints_Added, 0));
+}
+
+
+static void
+got_stream_media_properties (TpProxy *proxy, GHashTable *out_Properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+ GPtrArray *stun_servers;
+ GPtrArray *relay_info;
+ GPtrArray *endpoints;
+ gboolean valid;
+
+ if (self->proxy == NULL)
+ return;
+
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error getting the Streams's media properties: %s",
+ error->message);
+ return;
+ }
+
+ if (!out_Properties)
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_INVALID_ARGUMENT,
+ "Error getting the Stream's media properties: there are none");
+ return;
+ }
+
+ if (self->proxy == NULL)
+ return;
+
+ self->transport_type =
+ tp_asv_get_uint32 (out_Properties, "Transport", &valid);
+ if (!valid)
+ {
+ g_warning ("No valid transport");
+ goto invalid_property;
+ }
+
+ stun_servers = tp_asv_get_boxed (out_Properties, "STUNServers",
+ TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST);
+ if (!stun_servers)
+ {
+ g_warning ("No valid STUN servers");
+ goto invalid_property;
+ }
+
+ relay_info = tp_asv_get_boxed (out_Properties, "RelayInfo",
+ TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST);
+ if (!relay_info)
+ {
+ g_warning ("No valid RelayInfo");
+ goto invalid_property;
+ }
+
+ self->server_info_retrieved = tp_asv_get_boolean (out_Properties,
+ "HasServerInfo", &valid);
+ if (!valid)
+ {
+ g_warning ("No valid server info");
+ goto invalid_property;
+ }
+
+ self->sending_state = tp_asv_get_uint32 (out_Properties, "SendingState",
+ &valid);
+ if (!valid)
+ {
+ g_warning ("No valid sending state");
+ goto invalid_property;
+ }
+
+ self->receiving_state = tp_asv_get_uint32 (out_Properties,
+ "ReceivingState", &valid);
+ if (!valid)
+ {
+ g_warning ("No valid receiving state");
+ goto invalid_property;
+ }
+
+/* FIXME: controlling is on the endpoint
+ self->controlling = tp_asv_get_boolean (out_Properties,
+ "Controlling", &valid);
+ if (!valid)
+ {
+ g_warning ("No Controlling property");
+ goto invalid_property;
+ }
+*/
+ self->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST,
+ stun_servers);
+ self->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST,
+ relay_info);
+
+ endpoints = tp_asv_get_boxed (out_Properties, "Endpoints",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST);
+
+ if (endpoints->len > 1)
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_NOT_IMPLEMENTED,
+ "Having more than one endpoint is not implemented");
+ return;
+ }
+
+ if (endpoints->len == 1)
+ {
+ tf_call_stream_add_endpoint (self, g_ptr_array_index (endpoints, 0));
+ }
+
+ self->has_media_properties = TRUE;
+
+ tf_call_stream_try_adding_fsstream (self);
+
+ return;
+ invalid_property:
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_INVALID_ARGUMENT,
+ "Error getting the Stream's properties: invalid type");
+ return;
+}
+
+static void
+ice_restart_requested (TpCallStream *proxy,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+ GError *myerror = NULL;
+
+ if (!self->fsstream)
+ return;
+
+ if (self->multiple_usernames)
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_INVALID_ARGUMENT,
+ "CM tried to ICE restart an ICE-6 or Google compatible connection");
+ return;
+ }
+
+ g_debug ("Restarting ICE");
+
+ if (fs_stream_add_remote_candidates (self->fsstream, NULL, &myerror))
+ {
+ g_free (self->last_local_username);
+ g_free (self->last_local_password);
+ self->last_local_username = NULL;
+ self->last_local_password = NULL;
+ }
+ else
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ "Error restarting the ICE process: %s", myerror->message);
+ g_clear_error (&myerror);
+ }
+}
+
+static void
+stream_prepared (GObject *src_object, GAsyncResult *res, gpointer user_data)
+{
+ TfCallStream *self = TF_CALL_STREAM (user_data);
+ TpProxy *proxy = TP_PROXY (src_object);
+ GError *error = NULL;
+ GHashTable *members;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (self->proxy == NULL)
+ return;
+
+ if (!tp_proxy_prepare_finish (src_object, res, &error))
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_CONFUSED,
+ "Error preparing the stream Streams: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (!tp_proxy_has_interface_by_id (proxy,
+ TP_IFACE_QUARK_CALL1_STREAM_INTERFACE_MEDIA))
+ {
+ tf_call_stream_fail_literal (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_INVALID_ARGUMENT,
+ "Stream does not have the media interface,"
+ " but HardwareStreaming was NOT true");
+ return;
+ }
+
+ members = tp_call_stream_get_remote_members (self->proxy);
+
+ if (g_hash_table_size (members) != 1)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_NOT_IMPLEMENTED,
+ "Only one Member per Stream is supported, there are %d",
+ g_hash_table_size (members));
+ return;
+ }
+
+ g_hash_table_iter_init (&iter, members);
+ if (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ self->has_contact = TRUE;
+ self->contact_handle = tp_contact_get_handle (key);
+ }
+
+ tp_cli_call1_stream_interface_media_connect_to_sending_state_changed (
+ TP_CALL_STREAM (proxy), sending_state_changed, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "",
+ "Error connecting to SendingStateChanged signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+
+ tp_cli_call1_stream_interface_media_connect_to_receiving_state_changed (
+ TP_CALL_STREAM (proxy), receiving_state_changed, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "",
+ "Error connecting to ReceivingStateChanged signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_cli_call1_stream_interface_media_connect_to_server_info_retrieved (
+ TP_CALL_STREAM (proxy), server_info_retrieved, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "",
+ "Error connecting to ServerInfoRetrived signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_cli_call1_stream_interface_media_connect_to_stun_servers_changed (
+ TP_CALL_STREAM (proxy), stun_servers_changed, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "",
+ "Error connecting to ServerInfoRetrived signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+
+ tp_cli_call1_stream_interface_media_connect_to_relay_info_changed (
+ TP_CALL_STREAM (proxy), relay_info_changed, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "",
+ "Error connecting to ServerInfoRetrived signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+
+ tp_cli_call1_stream_interface_media_connect_to_endpoints_changed (
+ TP_CALL_STREAM (proxy), endpoints_changed, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "",
+ "Error connecting to EndpointsChanged signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+
+ tp_cli_call1_stream_interface_media_connect_to_ice_restart_requested (
+ TP_CALL_STREAM (proxy), ice_restart_requested, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_call_stream_fail (self,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "",
+ "Error connecting to ICERestartRequested signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_cli_dbus_properties_call_get_all (TP_PROXY (self->proxy), -1,
+ TP_IFACE_CALL1_STREAM_INTERFACE_MEDIA,
+ got_stream_media_properties, NULL, NULL, G_OBJECT (self));
+
+ return;
+}
+
+TfCallStream *
+tf_call_stream_new (TfCallContent *call_content,
+ TpCallStream *stream_proxy)
+{
+ TfCallStream *self;
+
+ g_assert (call_content != NULL);
+ g_assert (stream_proxy != NULL);
+
+ self = g_object_new (TF_TYPE_CALL_STREAM, NULL);
+
+ self->call_content = call_content;
+ self->proxy = g_object_ref (stream_proxy);
+
+ tp_proxy_prepare_async (self->proxy, NULL, stream_prepared,
+ g_object_ref (self));
+
+ return self;
+}
+
+static TpCallStreamCandidateType
+fscandidatetype_to_tp (FsCandidateType type)
+{
+ switch(type)
+ {
+ case FS_CANDIDATE_TYPE_HOST:
+ return TP_CALL_STREAM_CANDIDATE_TYPE_HOST;
+ case FS_CANDIDATE_TYPE_SRFLX:
+ return TP_CALL_STREAM_CANDIDATE_TYPE_SERVER_REFLEXIVE;
+ case FS_CANDIDATE_TYPE_PRFLX:
+ return TP_CALL_STREAM_CANDIDATE_TYPE_PEER_REFLEXIVE;
+ case FS_CANDIDATE_TYPE_RELAY:
+ return TP_CALL_STREAM_CANDIDATE_TYPE_RELAY;
+ case FS_CANDIDATE_TYPE_MULTICAST:
+ return TP_CALL_STREAM_CANDIDATE_TYPE_MULTICAST;
+ default:
+ g_warning ("Unkown candidate type, assigning type NONE");
+ return TP_CALL_STREAM_CANDIDATE_TYPE_NONE;
+ }
+}
+
+
+static TpMediaStreamBaseProto
+fs_network_proto_to_tp (FsNetworkProtocol proto)
+{
+ switch (proto)
+ {
+ case FS_NETWORK_PROTOCOL_UDP:
+ return TP_MEDIA_STREAM_BASE_PROTO_UDP;
+ case FS_NETWORK_PROTOCOL_TCP:
+ return TP_MEDIA_STREAM_BASE_PROTO_TCP;
+ default:
+ g_warning ("Invalid protocl, assigning to UDP");
+ return TP_MEDIA_STREAM_BASE_PROTO_UDP;
+ }
+}
+
+
+static GValueArray *
+fscandidate_to_tpcandidate (TfCallStream *stream, FsCandidate *candidate)
+{
+ GHashTable *extra_info;
+
+ extra_info = tp_asv_new (NULL, NULL);
+
+ tp_asv_set_uint32 (extra_info, "type",
+ fscandidatetype_to_tp (candidate->type));
+
+ if (candidate->foundation)
+ tp_asv_set_string (extra_info, "foundation", candidate->foundation);
+
+ tp_asv_set_uint32 (extra_info, "protocol",
+ fs_network_proto_to_tp (candidate->proto));
+
+ if (candidate->base_ip)
+ {
+ tp_asv_set_string (extra_info, "base-ip", candidate->base_ip);
+ tp_asv_set_uint32 (extra_info, "base-port", candidate->base_port);
+ }
+
+ if (candidate->priority)
+ tp_asv_set_uint32 (extra_info, "priority", candidate->priority);
+
+
+ if (candidate->type == FS_CANDIDATE_TYPE_MULTICAST)
+ tp_asv_set_uint32 (extra_info, "ttl", candidate->ttl);
+
+ if (stream->multiple_usernames)
+ {
+ if (candidate->username)
+ tp_asv_set_string (extra_info, "username", candidate->username);
+ if (candidate->password)
+ tp_asv_set_string (extra_info, "password", candidate->password);
+ }
+
+
+ return tp_value_array_build (4,
+ G_TYPE_UINT, candidate->component_id,
+ G_TYPE_STRING, candidate->ip,
+ G_TYPE_UINT, candidate->port,
+ TP_HASH_TYPE_CANDIDATE_INFO, extra_info,
+ G_TYPE_INVALID);
+}
+
+static void
+cb_fs_new_local_candidate (TfCallStream *stream, FsCandidate *candidate)
+{
+ GPtrArray *candidate_list;
+
+ if (!stream->multiple_usernames)
+ {
+ if ((!stream->last_local_username && candidate->username) ||
+ (!stream->last_local_password && candidate->password) ||
+ (stream->last_local_username &&
+ strcmp (candidate->username, stream->last_local_username)) ||
+ (stream->last_local_password &&
+ strcmp (candidate->password, stream->last_local_password)))
+ {
+ g_free (stream->last_local_username);
+ g_free (stream->last_local_password);
+ stream->last_local_username = g_strdup (candidate->username);
+ stream->last_local_password = g_strdup (candidate->password);
+
+ if (!stream->last_local_username)
+ stream->last_local_username = g_strdup ("");
+ if (!stream->last_local_password)
+ stream->last_local_password = g_strdup ("");
+
+ /* Add a callback to kill Call on errors */
+ tp_cli_call1_stream_interface_media_call_set_credentials (
+ stream->proxy, -1, stream->last_local_username,
+ stream->last_local_password, NULL, NULL, NULL, NULL);
+
+ }
+ }
+
+ g_debug ("Local Candidate: %s c:%d fstype:%d fsproto: %d ip:%s port:%u prio:%d u/p:%s/%s ttl:%d base_ip:%s base_port:%d",
+ candidate->foundation,candidate->component_id, candidate->type,
+ candidate->proto, candidate->ip, candidate->port,
+ candidate->priority, candidate->username, candidate->password,
+ candidate->ttl,candidate-> base_ip, candidate->base_port);
+
+
+ candidate_list = g_ptr_array_sized_new (1);
+ g_ptr_array_add (candidate_list,
+ fscandidate_to_tpcandidate (stream, candidate));
+
+ /* Should also check for errors */
+ tp_cli_call1_stream_interface_media_call_add_candidates (stream->proxy,
+ -1, candidate_list, NULL, NULL, NULL, NULL);
+
+
+ g_boxed_free (TP_ARRAY_TYPE_CANDIDATE_LIST, candidate_list);
+}
+
+static void
+cb_fs_local_candidates_prepared (TfCallStream *stream)
+{
+ g_debug ("Local candidates prepared");
+
+ tp_cli_call1_stream_interface_media_call_finish_initial_candidates (
+ stream->proxy, -1, NULL, NULL, NULL, NULL);
+}
+
+static void
+cb_fs_component_state_changed (TfCallStream *stream, guint component,
+ FsStreamState fsstate)
+{
+ TpStreamEndpointState state;
+
+ if (!stream->endpoint)
+ return;
+
+ switch (fsstate)
+ {
+ default:
+ g_warning ("Unknown Farstream state, returning ExhaustedCandidates");
+ /* fall through */
+ case FS_STREAM_STATE_FAILED:
+ state = TP_STREAM_ENDPOINT_STATE_EXHAUSTED_CANDIDATES;
+ break;
+ case FS_STREAM_STATE_DISCONNECTED:
+ case FS_STREAM_STATE_GATHERING:
+ case FS_STREAM_STATE_CONNECTING:
+ state = TP_STREAM_ENDPOINT_STATE_CONNECTING;
+ break;
+ case FS_STREAM_STATE_CONNECTED:
+ state = TP_STREAM_ENDPOINT_STATE_PROVISIONALLY_CONNECTED;
+ case FS_STREAM_STATE_READY:
+ state = TP_STREAM_ENDPOINT_STATE_FULLY_CONNECTED;
+ break;
+ }
+
+ g_debug ("Endpoint state for component %u changed to %d (fs: %d)",
+ component, state, fsstate);
+
+ tp_cli_call1_stream_endpoint_call_set_endpoint_state (stream->endpoint,
+ -1, component, state, NULL, NULL, NULL, NULL);
+}
+
+static void
+cb_fs_new_active_candidate_pair (TfCallStream *stream,
+ FsCandidate *local_candidate,
+ FsCandidate *remote_candidate)
+{
+ GValueArray *local_tp_candidate;
+ GValueArray *remote_tp_candidate;
+
+ g_debug ("new active candidate pair local: %s (%d) remote: %s (%d)",
+ local_candidate->ip, local_candidate->port,
+ remote_candidate->ip, remote_candidate->port);
+
+ if (!stream->endpoint)
+ return;
+
+ local_tp_candidate = fscandidate_to_tpcandidate (stream, local_candidate);
+ remote_tp_candidate = fscandidate_to_tpcandidate (stream, remote_candidate);
+
+ tp_cli_call1_stream_endpoint_call_set_selected_candidate_pair (
+ stream->endpoint, -1, local_tp_candidate, remote_tp_candidate,
+ NULL, NULL, NULL, NULL);
+
+ g_boxed_free (TP_STRUCT_TYPE_CANDIDATE, local_tp_candidate);
+ g_boxed_free (TP_STRUCT_TYPE_CANDIDATE, remote_tp_candidate);
+}
+
+gboolean
+tf_call_stream_bus_message (TfCallStream *stream, GstMessage *message)
+{
+ FsError errorno;
+ const gchar *msg;
+ FsCandidate *candidate;
+ guint component;
+ FsStreamState fsstate;
+ FsCandidate *local_candidate;
+ FsCandidate *remote_candidate;
+
+ if (!stream->fsstream)
+ return FALSE;
+
+ if (fs_parse_error (G_OBJECT (stream->fsstream), message, &errorno, &msg))
+ {
+ GEnumClass *enumclass;
+ GEnumValue *enumvalue;
+
+ enumclass = g_type_class_ref (FS_TYPE_ERROR);
+ enumvalue = g_enum_get_value (enumclass, errorno);
+ g_warning ("error (%s (%d)): %s",
+ enumvalue->value_nick, errorno, msg);
+ g_type_class_unref (enumclass);
+
+ tf_call_stream_fail_literal (stream,
+ TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR, msg);
+ }
+ else if (fs_stream_parse_new_local_candidate (stream->fsstream, message,
+ &candidate))
+ {
+ cb_fs_new_local_candidate (stream, candidate);
+ }
+ else if (fs_stream_parse_local_candidates_prepared (stream->fsstream,
+ message))
+ {
+ cb_fs_local_candidates_prepared (stream);
+ }
+ else if (fs_stream_parse_component_state_changed (stream->fsstream, message,
+ &component, &fsstate))
+ {
+ cb_fs_component_state_changed (stream, component, fsstate);
+ }
+ else if (fs_stream_parse_new_active_candidate_pair (stream->fsstream, message,
+ &local_candidate, &remote_candidate))
+ {
+ cb_fs_new_active_candidate_pair (stream, local_candidate,
+ remote_candidate);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+tf_call_stream_fail_literal (TfCallStream *self,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message)
+{
+ g_warning ("%s", message);
+
+ if (self->proxy == NULL)
+ return;
+
+ tp_cli_call1_stream_interface_media_call_fail (
+ self->proxy, -1,
+ tp_value_array_build (4,
+ G_TYPE_UINT, 0,
+ G_TYPE_UINT, reason,
+ G_TYPE_STRING, detailed_reason,
+ G_TYPE_STRING, message,
+ G_TYPE_INVALID),
+ NULL, NULL, NULL, NULL);
+}
+
+
+static void
+tf_call_stream_fail (TfCallStream *self,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message_format,
+ ...)
+{
+ gchar *message;
+ va_list valist;
+
+ va_start (valist, message_format);
+ message = g_strdup_vprintf (message_format, valist);
+ va_end (valist);
+
+ tf_call_stream_fail_literal (self, reason, detailed_reason, message);
+ g_free (message);
+}
+
+void
+tf_call_stream_sending_failed (TfCallStream *self, const gchar *message)
+{
+ g_warning ("Reporting sending failure: %s", message);
+
+ if (self->proxy == NULL)
+ return;
+
+ tp_cli_call1_stream_interface_media_call_report_sending_failure (
+ self->proxy, -1, TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ message, NULL, NULL, NULL, NULL);
+}
+
+
+void
+tf_call_stream_receiving_failed (TfCallStream *self,
+ guint *handles, guint handle_count,
+ const gchar *message)
+{
+ if (self->proxy == NULL)
+ return;
+
+ if (handle_count && handle_count > 0)
+ {
+ guint i;
+
+ for (i = 0; i < handle_count; i++)
+ if (handles[i] == self->contact_handle)
+ goto ok;
+ return;
+ }
+ ok:
+
+ g_warning ("Reporting receiving failure: %s", message);
+
+ tp_cli_call1_stream_interface_media_call_report_receiving_failure (
+ self->proxy, -1, TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR,
+ TP_ERROR_STR_MEDIA_STREAMING_ERROR,
+ message, NULL, NULL, NULL, NULL);
+}
+
+
+TpCallStream *
+tf_call_stream_get_proxy (TfCallStream *stream)
+{
+ g_return_val_if_fail (TF_IS_CALL_STREAM (stream), NULL);
+
+ return stream->proxy;
+}
diff --git a/telepathy-farstream/call-stream.h b/telepathy-farstream/call-stream.h
new file mode 100644
index 000000000..31961c1e8
--- /dev/null
+++ b/telepathy-farstream/call-stream.h
@@ -0,0 +1,134 @@
+/*
+ * call-stream.h - Source for TfCallStream
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_CALL_STREAM_H__
+#define __TF_CALL_STREAM_H__
+
+#include <glib-object.h>
+
+#include <gst/gst.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "call-channel.h"
+#include "call-content.h"
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CALL_STREAM tf_call_stream_get_type()
+
+#define TF_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CALL_STREAM, TfCallStream))
+
+#define TF_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CALL_STREAM, TfCallStreamClass))
+
+#define TF_IS_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_STREAM))
+
+#define TF_IS_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_STREAM))
+
+#define TF_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CALL_STREAM, TfCallStreamClass))
+
+typedef struct _TfCallStreamPrivate TfCallStreamPrivate;
+
+/**
+ * TfCallStream:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfCallStream TfCallStream;
+
+/**
+ * TfCallStreamClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfCallStreamClass TfCallStreamClass;
+
+
+struct _TfCallStream {
+ GObject parent;
+
+ TfCallContent *call_content;
+
+ TpCallStream *proxy;
+
+ gboolean has_endpoint_properties;
+ gchar *endpoint_objpath;
+ TpProxy *endpoint;
+ gchar *creds_username;
+ gchar *creds_password;
+ GList *stored_remote_candidates;
+ gboolean multiple_usernames;
+ gboolean controlling;
+
+ gchar *last_local_username;
+ gchar *last_local_password;
+
+ TpStreamFlowState sending_state;
+ gboolean has_send_resource;
+
+ TpStreamFlowState receiving_state;
+ gboolean has_receive_resource;
+
+ gboolean has_contact;
+ guint contact_handle;
+ FsStream *fsstream;
+
+ gboolean has_media_properties;
+ TpStreamTransportType transport_type;
+ gboolean server_info_retrieved;
+ GPtrArray *stun_servers;
+ GPtrArray *relay_info;
+};
+
+struct _TfCallStreamClass{
+ GObjectClass parent_class;
+};
+
+
+GType tf_call_stream_get_type (void);
+
+TfCallStream *tf_call_stream_new (
+ TfCallContent *content,
+ TpCallStream *stream_proxy);
+
+gboolean tf_call_stream_bus_message (TfCallStream *stream, GstMessage *message);
+
+void tf_call_stream_sending_failed (TfCallStream *stream, const gchar *message);
+
+void tf_call_stream_receiving_failed (TfCallStream *stream,
+ guint *handles, guint handle_count,
+ const gchar *message);
+
+TpCallStream *
+tf_call_stream_get_proxy (TfCallStream *stream);
+
+
+G_END_DECLS
+
+#endif /* __TF_CALL_STREAM_H__ */
diff --git a/telepathy-farstream/channel-priv.h b/telepathy-farstream/channel-priv.h
new file mode 100644
index 000000000..8fdee93a5
--- /dev/null
+++ b/telepathy-farstream/channel-priv.h
@@ -0,0 +1,28 @@
+#ifndef __TF_CHANNEL_PRIV_H__
+#define __TF_CHANNEL_PRIV_H__
+
+#include "channel.h"
+
+G_BEGIN_DECLS
+
+
+struct _TfChannel{
+ GObject parent;
+
+ /*< private >*/
+
+ TfChannelPrivate *priv;
+};
+
+struct _TfChannelClass{
+ GObjectClass parent_class;
+
+ /*< private >*/
+
+ gpointer unused[4];
+};
+
+
+G_END_DECLS
+
+#endif /* __TF_CHANNEL_PRIV_H__ */
diff --git a/telepathy-farstream/channel.c b/telepathy-farstream/channel.c
new file mode 100644
index 000000000..10107c04e
--- /dev/null
+++ b/telepathy-farstream/channel.c
@@ -0,0 +1,594 @@
+/*
+ * channel.c - Source for TfChannel
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * Copyright (C) 2006-2007 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:channel
+ * @short_description: Handle the Call media interfaces on a Channel
+ *
+ * This class handles the media part of the
+ * org.freedesktop.Telepathy.Channel.Type.Call that has HardwareStreaming=FALSE
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+
+#include <farstream/fs-conference.h>
+
+
+#include "channel.h"
+#include "channel-priv.h"
+#include "call-channel.h"
+#include "content.h"
+
+
+static void channel_async_initable_init (GAsyncInitableIface *asynciface);
+
+G_DEFINE_TYPE_WITH_CODE (TfChannel, tf_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, channel_async_initable_init));
+
+struct _TfChannelPrivate
+{
+ TpChannel *channel_proxy;
+
+ TfCallChannel *call_channel;
+
+ gulong channel_invalidated_handler;
+
+ gboolean closed;
+};
+
+enum
+{
+ SIGNAL_CLOSED,
+ SIGNAL_FS_CONFERENCE_ADDED,
+ SIGNAL_FS_CONFERENCE_REMOVED,
+ SIGNAL_CONTENT_ADDED,
+ SIGNAL_CONTENT_REMOVED,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+enum
+{
+ PROP_CHANNEL = 1,
+ PROP_OBJECT_PATH,
+ PROP_FS_CONFERENCES
+};
+
+static void shutdown_channel (TfChannel *self);
+
+static void channel_fs_conference_added (GObject *chan,
+ FsConference *conf, TfChannel *self);
+static void channel_fs_conference_removed (GObject *chan,
+ FsConference *conf, TfChannel *self);
+
+static void tf_channel_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+static gboolean tf_channel_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error);
+
+static void content_added (GObject *proxy,
+ TfContent *content,
+ TfChannel *self);
+static void content_removed (GObject *proxy,
+ TfContent *content,
+ TfChannel *self);
+
+
+static void
+tf_channel_init (TfChannel *self)
+{
+ TfChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TF_TYPE_CHANNEL, TfChannelPrivate);
+
+ self->priv = priv;
+}
+
+static void
+channel_async_initable_init (GAsyncInitableIface *asynciface)
+{
+ asynciface->init_async = tf_channel_init_async;
+ asynciface->init_finish = tf_channel_init_finish;
+}
+
+
+static void
+tf_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfChannel *self = TF_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_CHANNEL:
+ g_value_set_object (value, self->priv->channel_proxy);
+ break;
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value,
+ tp_proxy_get_object_path (self->priv->channel_proxy));
+ break;
+ case PROP_FS_CONFERENCES:
+ if (self->priv->call_channel)
+ {
+ g_object_get_property (G_OBJECT (self->priv->call_channel),
+ "fs-conferences", value);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tf_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TfChannel *self = TF_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_CHANNEL:
+ self->priv->channel_proxy = TP_CHANNEL (g_value_dup_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void channel_invalidated (TpChannel *channel_proxy,
+ guint domain, gint code, gchar *message, TfChannel *self);
+
+static void
+call_channel_ready (GObject *obj, GAsyncResult *call_res, gpointer user_data)
+{
+ GError *error = NULL;
+ GSimpleAsyncResult *res = user_data;
+ TfChannel *self = TF_CHANNEL (
+ g_async_result_get_source_object (G_ASYNC_RESULT (res)));
+
+ self->priv->call_channel = TF_CALL_CHANNEL (g_async_initable_new_finish (
+ G_ASYNC_INITABLE (obj), call_res, &error));
+
+ if (error)
+ {
+ shutdown_channel (self);
+ g_simple_async_result_set_op_res_gboolean (res, FALSE);
+ g_simple_async_result_set_from_error (res, error);
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gboolean (res, TRUE);
+
+ tp_g_signal_connect_object (self->priv->call_channel,
+ "fs-conference-added", G_CALLBACK (channel_fs_conference_added),
+ self, 0);
+ tp_g_signal_connect_object (self->priv->call_channel,
+ "fs-conference-removed", G_CALLBACK (channel_fs_conference_removed),
+ self, 0);
+
+ tp_g_signal_connect_object (self->priv->call_channel,
+ "content_added", G_CALLBACK (content_added),
+ self, 0);
+ tp_g_signal_connect_object (self->priv->call_channel,
+ "content_removed", G_CALLBACK (content_removed),
+ self, 0);
+ }
+
+
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ g_object_unref (self);
+}
+
+static void
+channel_prepared (GObject *obj,
+ GAsyncResult *proxy_res,
+ gpointer user_data)
+{
+ TpChannel *channel_proxy = TP_CHANNEL (obj);
+ TpProxy *as_proxy = (TpProxy *) channel_proxy;
+ GSimpleAsyncResult *res = user_data;
+ GError *error = NULL;
+ TfChannel *self = TF_CHANNEL (
+ g_async_result_get_source_object (G_ASYNC_RESULT (res)));
+
+ if (!tp_proxy_prepare_finish (channel_proxy, proxy_res, &error))
+ {
+ g_simple_async_result_propagate_error (res, &error);
+ shutdown_channel (self);
+ goto error;
+ }
+
+ if (self->priv->closed)
+ {
+ g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_CANCELLED,
+ "Channel already closed");
+ goto error;
+ }
+
+ if (tp_proxy_has_interface_by_id (as_proxy,
+ TP_IFACE_QUARK_CHANNEL_TYPE_CALL1))
+ {
+ if (!TP_IS_CALL_CHANNEL (channel_proxy))
+ {
+ g_simple_async_result_set_error (res, TP_ERROR,
+ TP_ERROR_INVALID_ARGUMENT,
+ "You must pass a TpCallChannel object if its a Call channel");
+ g_simple_async_result_set_op_res_gboolean (res, FALSE);
+ g_simple_async_result_complete (res);
+ }
+ else
+ {
+ tf_call_channel_new_async (channel_proxy, call_channel_ready, res);
+
+ self->priv->channel_invalidated_handler = g_signal_connect (
+ self->priv->channel_proxy,
+ "invalidated", G_CALLBACK (channel_invalidated), self);
+ }
+ }
+ else
+ {
+ g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
+ "Channel does not implement " TP_IFACE_CHANNEL_TYPE_CALL1);
+ goto error;
+ }
+
+ g_object_unref (self);
+ return;
+
+error:
+ g_simple_async_result_set_op_res_gboolean (res, FALSE);
+ g_simple_async_result_complete (res);
+
+ g_object_unref (res);
+ g_object_unref (self);
+}
+
+
+static void
+tf_channel_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TfChannel *self = TF_CHANNEL (initable);
+ GSimpleAsyncResult *res;
+
+ if (cancellable != NULL)
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+ "TfChannel initialisation does not support cancellation");
+ return;
+ }
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tf_channel_init_async);
+ tp_proxy_prepare_async (self->priv->channel_proxy, NULL,
+ channel_prepared, res);
+}
+
+static gboolean
+tf_channel_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple_res;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (res,
+ G_OBJECT (initable), tf_channel_init_async), FALSE);
+ simple_res = G_SIMPLE_ASYNC_RESULT (res);
+
+ g_simple_async_result_propagate_error (simple_res, error);
+
+ return g_simple_async_result_get_op_res_gboolean (simple_res);
+}
+
+
+static void
+tf_channel_dispose (GObject *object)
+{
+ TfChannel *self = TF_CHANNEL (object);
+
+ g_debug (G_STRFUNC);
+
+ tp_clear_object (&self->priv->call_channel);
+
+ if (self->priv->channel_proxy)
+ {
+ TpChannel *tmp;
+
+ if (self->priv->channel_invalidated_handler != 0)
+ g_signal_handler_disconnect (self->priv->channel_proxy,
+ self->priv->channel_invalidated_handler);
+
+ tmp = self->priv->channel_proxy;
+ self->priv->channel_proxy = NULL;
+ g_object_unref (tmp);
+ }
+
+ if (G_OBJECT_CLASS (tf_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_channel_parent_class)->dispose (object);
+}
+
+static void
+tf_channel_class_init (TfChannelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (TfChannelPrivate));
+
+ object_class->set_property = tf_channel_set_property;
+ object_class->get_property = tf_channel_get_property;
+
+ object_class->dispose = tf_channel_dispose;
+
+ g_object_class_install_property (object_class, PROP_CHANNEL,
+ g_param_spec_object ("channel",
+ "TpChannel object",
+ "Telepathy channel object which this media channel should operate on",
+ TP_TYPE_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path",
+ "channel object path",
+ "D-Bus object path of the Telepathy channel which this channel"
+ " operates on",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+
+ g_object_class_install_property (object_class, PROP_FS_CONFERENCES,
+ g_param_spec_boxed ("fs-conferences",
+ "Farstream FsConferences objects",
+ "GPtrArray of Farstream FsConferences for this channel",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * TfChannel::closed:
+ *
+ * This function is called after a channel is closed, either because
+ * it has been closed by the connection manager or because we had a locally
+ * generated error.
+ */
+
+ signals[SIGNAL_CLOSED] =
+ g_signal_new ("closed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * TfChannel::fs-conference-added:
+ * @tfchannel: the #TfChannel
+ * @conf: a #FsConference
+ *
+ * When this signal is emitted, the conference should be added to the
+ * application's pipeline.
+ */
+
+ signals[SIGNAL_FS_CONFERENCE_ADDED] = g_signal_new ("fs-conference-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+ /**
+ * TfChannel::fs-conference-removed:
+ * @tfchannel: the #TfChannel
+ * @conf: a #FsConference
+ *
+ * When this signal is emitted, the conference should be remove from the
+ * application's pipeline.
+ */
+
+ signals[SIGNAL_FS_CONFERENCE_REMOVED] = g_signal_new ("fs-conference-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+
+ /**
+ * TfChannel::content-added:
+ * @tfchannel: the #TfChannel
+ * @content: a #TfContent
+ *
+ * Tells the application that a content has been added. In the callback for
+ * this signal, the application should set its preferred codecs, and hook
+ * up to any signal from #TfContent it cares about. Special care should be
+ * made to connect #TfContent::src-pad-added as well
+ * as the #TfContent::start-sending and #TfContent::stop-sending signals.
+ */
+
+ signals[SIGNAL_CONTENT_ADDED] = g_signal_new ("content-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, TF_TYPE_CONTENT);
+
+ /**
+ * TfChannel::content-removed:
+ * @tfchannel: the #TfChannel
+ * @content: a #TfContent
+ *
+ * Tells the application that a content is being removed.
+ */
+
+ signals[SIGNAL_CONTENT_REMOVED] = g_signal_new ("content-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, TF_TYPE_CONTENT);
+}
+
+static void
+shutdown_channel (TfChannel *self)
+{
+ tp_clear_object (&self->priv->call_channel);
+
+ if (self->priv->channel_proxy != NULL)
+ {
+ if (self->priv->channel_invalidated_handler)
+ {
+ g_signal_handler_disconnect (
+ self->priv->channel_proxy, self->priv->channel_invalidated_handler);
+ self->priv->channel_invalidated_handler = 0;
+ }
+ }
+
+ g_signal_emit (self, signals[SIGNAL_CLOSED], 0);
+
+ self->priv->closed = TRUE;
+}
+
+static void
+channel_invalidated (TpChannel *channel_proxy,
+ guint domain,
+ gint code,
+ gchar *message,
+ TfChannel *self)
+{
+ shutdown_channel (self);
+}
+
+/**
+ * tf_channel_new_async:
+ * @channel_proxy: a #TpChannel proxy
+ * @callback: a #GAsyncReadyCallback to call when the channel is ready
+ * @user_data: the data to pass to callback function
+ *
+ * Creates a new #TfChannel from an existing channel proxy, the new
+ * TfChannel object will be return in the async callback.
+ *
+ * The user must call tf_channel_new_finish() in the callback
+ * to get the finished object.
+ */
+
+void
+tf_channel_new_async (TpChannel *channel_proxy,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (channel_proxy != NULL);
+ g_return_if_fail (callback != NULL);
+
+ return g_async_initable_new_async (TF_TYPE_CHANNEL,
+ 0, NULL, callback, user_data,
+ "channel", channel_proxy,
+ NULL);
+}
+
+/**
+ * tf_channel_bus_message:
+ * @channel: A #TfChannel
+ * @message: A #GstMessage received from the bus
+ *
+ * You must call this function on call messages received on the async bus.
+ * #GstMessages are not modified.
+ *
+ * Returns: %TRUE if the message has been handled, %FALSE otherwise
+ */
+
+gboolean
+tf_channel_bus_message (TfChannel *channel,
+ GstMessage *message)
+{
+ g_return_val_if_fail (channel != NULL, FALSE);
+ g_return_val_if_fail (message != NULL, FALSE);
+
+ if (channel->priv->call_channel)
+ return tf_call_channel_bus_message (channel->priv->call_channel,
+ message);
+
+ return FALSE;
+}
+
+static void
+channel_fs_conference_added (GObject *proxy, FsConference *conf,
+ TfChannel *self)
+{
+ g_object_notify (G_OBJECT (self), "fs-conferences");
+ g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_ADDED], 0,
+ conf);
+}
+
+static void
+channel_fs_conference_removed (GObject *proxy, FsConference *conf,
+ TfChannel *self)
+{
+ g_object_notify (G_OBJECT (self), "fs-conferences");
+ g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0,
+ conf);
+}
+
+static void
+content_added (GObject *proxy, TfContent *content, TfChannel *self)
+{
+ g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, content);
+}
+
+static void
+content_removed (GObject *proxy, TfContent *content, TfChannel *self)
+{
+ g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content);
+}
+
+/**
+ * tf_channel_new_finish:
+ * @object: The #GObject
+ * @result: a #GAsyncResult
+ * @error: the location of a #GError or %NULL to ignore it
+ *
+ * Completes the construction of a TfChannel.
+ *
+ * Returns: a #TfChannel or %NULL if there was an error
+ * Since: 0.2.2
+ */
+
+TfChannel *
+tf_channel_new_finish (GObject *object,
+ GAsyncResult *result,
+ GError **error)
+{
+ return (TfChannel *) g_async_initable_new_finish (G_ASYNC_INITABLE (object),
+ result, error);
+}
diff --git a/telepathy-farstream/channel.h b/telepathy-farstream/channel.h
new file mode 100644
index 000000000..77b3038be
--- /dev/null
+++ b/telepathy-farstream/channel.h
@@ -0,0 +1,65 @@
+#ifndef __TF_CHANNEL_H__
+#define __TF_CHANNEL_H__
+
+#include <glib-object.h>
+#include <gst/gst.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CHANNEL tf_channel_get_type()
+
+#define TF_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CHANNEL, TfChannel))
+
+#define TF_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CHANNEL, TfChannelClass))
+
+#define TF_IS_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CHANNEL))
+
+#define TF_IS_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CHANNEL))
+
+#define TF_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CHANNEL, TfChannelClass))
+
+typedef struct _TfChannelPrivate TfChannelPrivate;
+
+/**
+ * TfChannel:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfChannel TfChannel;
+
+/**
+ * TfChannelClass:
+ * @parent_class: the parent #GObjectClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfChannelClass TfChannelClass;
+
+GType tf_channel_get_type (void);
+
+void tf_channel_new_async (TpChannel *channel_proxy,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+TfChannel *tf_channel_new_finish (GObject *object,
+ GAsyncResult *result,
+ GError **error);
+
+
+gboolean tf_channel_bus_message (TfChannel *channel,
+ GstMessage *message);
+
+G_END_DECLS
+
+#endif /* __TF_CHANNEL_H__ */
diff --git a/telepathy-farstream/content-priv.h b/telepathy-farstream/content-priv.h
new file mode 100644
index 000000000..ab137da68
--- /dev/null
+++ b/telepathy-farstream/content-priv.h
@@ -0,0 +1,48 @@
+
+#ifndef __TF_CONTENT_PRIV_H__
+#define __TF_CONTENT_PRIV_H__
+
+#include <glib-object.h>
+
+#include <farstream/fs-conference.h>
+
+
+G_BEGIN_DECLS
+
+struct _TfContent {
+ GObject parent;
+
+ guint sending_count;
+};
+
+struct _TfContentClass{
+ GObjectClass parent_class;
+
+ void (*content_error) (TfContent *content,
+ const gchar *message);
+
+ void (*sending_failed) (TfContent *content,
+ const gchar *message);
+ void (*receiving_failed) (TfContent *content,
+ guint *handles, guint handle_count,
+ const gchar *message);
+
+ GstIterator * (*iterate_src_pads) (TfContent *content, guint *handle,
+ guint handle_count);
+};
+
+gboolean _tf_content_start_sending (TfContent *self);
+void _tf_content_stop_sending (TfContent *self);
+void _tf_content_mute_to_stop_sending (TfContent *self);
+
+void _tf_content_emit_src_pad_added (TfContent *self, guint handle,
+ FsStream *stream, GstPad *pad, FsCodec *codec);
+
+gboolean _tf_content_start_receiving (TfContent *self, guint *handles,
+ guint handle_count);
+void _tf_content_stop_receiving (TfContent *self,
+ guint *handles, guint handle_count);
+
+G_END_DECLS
+
+#endif /* __TF_CONTENT_PRIV_H__ */
diff --git a/telepathy-farstream/content.c b/telepathy-farstream/content.c
new file mode 100644
index 000000000..09989cd02
--- /dev/null
+++ b/telepathy-farstream/content.c
@@ -0,0 +1,551 @@
+#include "config.h"
+
+#include "content.h"
+#include "content-priv.h"
+
+#include <farstream/fs-conference.h>
+
+#include "channel.h"
+
+
+/**
+ * SECTION:content
+ * @short_description: Represent the Content of a channel handled by #TfChannel
+ *
+ * Objects of this class allow the user to handle the media side of a Telepathy
+ * channel handled by #TfChannel.
+ *
+ * This object is created by the #TfChannel and the user is notified
+ * of its creation by the #TfChannel::content-added signal. In the
+ * callback for this signal, the user should connect to the
+ * #TfContent::src-pad-added signal.
+ *
+ */
+
+
+G_DEFINE_ABSTRACT_TYPE (TfContent, tf_content, G_TYPE_OBJECT);
+
+
+enum
+{
+ PROP_TF_CHANNEL = 1,
+ PROP_FS_CONFERENCE,
+ PROP_FS_SESSION,
+ PROP_MEDIA_TYPE,
+ PROP_SINK_PAD,
+ PROP_OBJECT_PATH
+};
+
+enum
+{
+ SIGNAL_START_SENDING,
+ SIGNAL_STOP_SENDING,
+ SIGNAL_SRC_PAD_ADDED,
+ SIGNAL_START_RECEIVING,
+ SIGNAL_STOP_RECEIVING,
+ SIGNAL_RESTART_SOURCE,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+static void
+tf_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ /* Other properties need to be overwritten */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tf_content_class_init (TfContentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = tf_content_get_property;
+
+ g_object_class_install_property (object_class, PROP_TF_CHANNEL,
+ g_param_spec_object ("tf-channel",
+ "Parent TfChannel object ",
+ "The Telepathy-Farstream Channel for this object",
+ TF_TYPE_CHANNEL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FS_CONFERENCE,
+ g_param_spec_object ("fs-conference",
+ "Farstream FsConference used by the Content ",
+ "The Farstream conference for this content "
+ "(could be the same as other contents)",
+ FS_TYPE_CONFERENCE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FS_SESSION,
+ g_param_spec_object ("fs-session",
+ "Farstream FsSession ",
+ "The Farstream session for this content",
+ FS_TYPE_SESSION,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_SINK_PAD,
+ g_param_spec_object ("sink-pad",
+ "Sink Pad",
+ "Sink GstPad for this content",
+ GST_TYPE_PAD,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_MEDIA_TYPE,
+ g_param_spec_enum ("media-type",
+ "MediaType",
+ "The FsMediaType for this content",
+ FS_TYPE_MEDIA_TYPE,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path",
+ "content object path",
+ "D-Bus object path of the Telepathy content which this content"
+ " operates on",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+
+ /**
+ * TfContent::start-sending:
+ * @content: the #TfContent
+ *
+ * This signal is emitted when the connection manager ask to send media.
+ * For example, this can be used to open a camera, start recording from a
+ * microphone or play back a file. The application should start
+ * sending data on the #TfContent:sink-pad
+ *
+ * Returns: %TRUE if the application can start providing data or %FALSE
+ * otherwise
+ */
+
+ signals[SIGNAL_START_SENDING] =
+ g_signal_new ("start-sending",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled, NULL, NULL,
+ G_TYPE_BOOLEAN, 0);
+
+ /**
+ * TfContent::stop-sending:
+ * @content: the #TfContent
+ *
+ * This signal is emitted when the connection manager ask to stop
+ * sending media
+ */
+
+ signals[SIGNAL_STOP_SENDING] =
+ g_signal_new ("stop-sending",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * TfContent::src-pad-added:
+ * @content: the #TfContent
+ * @handle: the handle of the remote party producing the content on this pad
+ * or 0 if unknown
+ * @stream: the #FsStream for this pad
+ * @pad: a #GstPad
+ * @codec: the #FsCodec for this pad
+ *
+ * This signal is emitted when a data is coming on a new pad. This signal
+ * is not emitted on the main thread, so special care must be made to lock
+ * the relevant data. When the callback returns from this signal, data will
+ * start flowing through the pad, so the application MUST connect a sink.
+ */
+
+ signals[SIGNAL_SRC_PAD_ADDED] =
+ g_signal_new ("src-pad-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 4,
+ G_TYPE_UINT, FS_TYPE_STREAM, GST_TYPE_PAD, FS_TYPE_CODEC);
+
+ /**
+ * TfContent::start-receiving:
+ * @content: the #TfContent
+ * @handles: a 0-terminated array of #guint containing the handles
+ * @handle_count: The number of handles in the @handles array
+ *
+ * This signal is emitted when the connection managers requests that the
+ * application prepares itself to start receiving data again from certain
+ * handles.
+ *
+ * This signal will only be emitted after the #TfContent::stop-receiving
+ * signal has succeeded. It will not be emitted right after
+ * #TfContent::src-pad-added.
+ *
+ * Returns: %TRUE if the application can start receiving data or %FALSE
+ * otherwise
+ */
+
+ signals[SIGNAL_START_RECEIVING] =
+ g_signal_new ("start-receiving",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled, NULL, NULL,
+ G_TYPE_BOOLEAN, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+ /**
+ * TfContent::stop-receiving:
+ * @content: the #TfContent
+ * @handles: a 0-terminated array of #guint containing the handles
+ * @handle_count: The number of handles in the @handles array
+ *
+ * This signal is emitted when the connection manager wants to tell the
+ * application that it is now allowed to stop receiving.
+ */
+
+ signals[SIGNAL_STOP_RECEIVING] =
+ g_signal_new ("stop-receiving",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+ /**
+ * TfContent::restart-source:
+ * @content: the #TfContent
+ *
+ * This signal requests that the source be restarted so that the caps can
+ * be renegotiated with a new resolutions and framerate.
+ */
+
+ signals[SIGNAL_RESTART_SOURCE] =
+ g_signal_new ("restart-source",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+
+static void
+tf_content_init (TfContent *self)
+{
+}
+
+
+gboolean
+_tf_content_start_sending (TfContent *self)
+{
+ GValue instance = {0};
+ GValue sending_success_val = {0,};
+ gboolean sending_success;
+
+
+ if (self->sending_count)
+ {
+ self->sending_count ++;
+ return TRUE;
+ }
+
+ g_value_init (&sending_success_val, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&sending_success_val, TRUE);
+
+ g_value_init (&instance, TF_TYPE_CONTENT);
+ g_value_set_object (&instance, self);
+
+ g_debug ("Requesting that the application start sending");
+
+ g_signal_emitv (&instance, signals[SIGNAL_START_SENDING], 0,
+ &sending_success_val);
+ sending_success = g_value_get_boolean (&sending_success_val);
+
+ g_value_unset (&instance);
+
+ g_debug ("Request to start sending %s",
+ sending_success ? "succeeded" : "failed");
+
+ self->sending_count = 1;
+
+ return sending_success;
+}
+
+void
+_tf_content_stop_sending (TfContent *self)
+{
+ self->sending_count --;
+
+ if (self->sending_count == 0)
+ {
+ g_signal_emit (self, signals[SIGNAL_STOP_SENDING], 0);
+ }
+}
+
+
+void
+_tf_content_emit_src_pad_added (TfContent *self, guint handle,
+ FsStream *stream, GstPad *pad, FsCodec *codec)
+{
+ g_signal_emit (self, signals[SIGNAL_SRC_PAD_ADDED], 0, handle,
+ stream, pad, codec);
+}
+
+/**
+ * tf_content_error_literal:
+ * @content: a #TfContent
+ * @message: error Message
+ *
+ * Send a fatal streaming error to the Content to the CM, the effect is most
+ * likely that the content will be removed.
+ *
+ * Rename to: tf_content_error
+ */
+
+void
+tf_content_error_literal (TfContent *content,
+ const gchar *message)
+{
+ TfContentClass *klass = TF_CONTENT_GET_CLASS (content);
+
+ g_return_if_fail (content != NULL);
+ g_return_if_fail (message != NULL);
+
+ if (klass->content_error)
+ klass->content_error (content, message);
+ else
+ GST_WARNING ("content_error not defined in class: %s", message);
+}
+
+/**
+ * tf_content_error:
+ * @content: a #TfContent
+ * @message_format: error Message with printf style formatting
+ * @...: Parameters to insert into the @message_format string
+ *
+ * Send a fatal streaming error to the Content to the CM, the effect is most
+ * likely that the content will be removed.
+ */
+
+void
+tf_content_error (TfContent *content,
+ const gchar *message_format,
+ ...)
+{
+ gchar *message;
+ va_list valist;
+
+ g_return_if_fail (content != NULL);
+ g_return_if_fail (message_format != NULL);
+
+ va_start (valist, message_format);
+ message = g_strdup_vprintf (message_format, valist);
+ va_end (valist);
+
+ tf_content_error_literal (content, message);
+ g_free (message);
+}
+
+/**
+ * tf_content_iterate_src_pads:
+ * @content: a #TfContent
+ * @handles: a 0 terminated array of #guint representing Telepathy handles
+ * @handle_count: the numner of handles in @handles
+ *
+ * Provides a iterator that can be used to iterate through all of the src
+ * pads that are are used to receive from a group of Telepathy handles.
+ *
+ * Returns: a #GstIterator
+ */
+
+GstIterator *
+tf_content_iterate_src_pads (TfContent *content, guint *handles,
+ guint handle_count)
+{
+ TfContentClass *klass = TF_CONTENT_GET_CLASS (content);
+
+ g_return_val_if_fail (content != NULL, NULL);
+
+ if (klass->iterate_src_pads)
+ return klass->iterate_src_pads (content, handles, handle_count);
+ else
+ GST_WARNING ("iterate_src_pads not defined in class");
+
+ return NULL;
+}
+
+gboolean
+_tf_content_start_receiving (TfContent *self, guint *handles,
+ guint handle_count)
+{
+ GValue instance_and_params[3] = {{0} , {0}, {0}};
+ GValue receiving_success_val = {0,};
+ gboolean receiving_success;
+
+ g_value_init (&receiving_success_val, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&receiving_success_val, TRUE);
+
+ g_value_init (&instance_and_params[0], TF_TYPE_CONTENT);
+ g_value_set_object (&instance_and_params[0], self);
+
+ g_value_init (&instance_and_params[1], G_TYPE_POINTER);
+ g_value_set_pointer (&instance_and_params[1], handles);
+
+ g_value_init (&instance_and_params[2], G_TYPE_UINT);
+ g_value_set_uint (&instance_and_params[2], handle_count);
+
+ g_debug ("Requesting that the application start receiving");
+
+ g_signal_emitv (instance_and_params, signals[SIGNAL_START_RECEIVING], 0,
+ &receiving_success_val);
+ receiving_success = g_value_get_boolean (&receiving_success_val);
+
+ g_value_unset (&instance_and_params[0]);
+
+ g_debug ("Request to start receiving %s",
+ receiving_success ? "succeeded" : "failed");
+
+ return receiving_success;
+}
+
+void
+_tf_content_stop_receiving (TfContent *self, guint *handles,
+ guint handle_count)
+{
+ g_debug ("Requesting that the application stop receiving");
+ g_signal_emit (self, signals[SIGNAL_STOP_RECEIVING], 0, handles,
+ handle_count);
+}
+
+
+/**
+ * tf_content_sending_failed_literal:
+ * @content: a #TfContent
+ * @message: The error message
+ *
+ * Informs the Connection Manager that sending has failed for this
+ * content. This is a transient error and it may or not not end the Content
+ * and the call.
+ *
+ * Rename to: tf_content_sending_failed
+ */
+
+void
+tf_content_sending_failed_literal (TfContent *content,
+ const gchar *message)
+{
+ TfContentClass *klass = TF_CONTENT_GET_CLASS (content);
+
+ g_return_if_fail (content != NULL);
+ g_return_if_fail (message != NULL);
+
+ if (klass->content_error)
+ klass->sending_failed (content, message);
+ else
+ GST_WARNING ("sending_failed not defined in class, ignoring error: %s",
+ message);
+}
+
+/**
+ * tf_content_sending_failed:
+ * @content: a #TfContent
+ * @message_format: Message with printf style formatting
+ * @...: Parameters to insert into the @message_format string
+ *
+ * Informs the Connection Manager that sending has failed for this
+ * content. This is a transient error and it may or not not end the Content
+ * and the call.
+ */
+
+void
+tf_content_sending_failed (TfContent *content,
+ const gchar *message_format, ...)
+{
+ gchar *message;
+ va_list valist;
+
+ g_return_if_fail (content != NULL);
+ g_return_if_fail (message_format != NULL);
+
+ va_start (valist, message_format);
+ message = g_strdup_vprintf (message_format, valist);
+ va_end (valist);
+
+ tf_content_sending_failed_literal (content, message);
+ g_free (message);
+}
+
+/**
+ * tf_content_receiving_failed_literal:
+ * @content: a #TfContent
+ * @handles: an array of #guint representing Telepathy handles, may be %NULL
+ * @handle_count: the numner of handles in @handles
+ * @message: The error message
+ *
+ * Informs the Connection Manager that receiving has failed for this
+ * content. This is a transient error and it may or not not end the Content
+ * and the call.
+ *
+ * If handles are not specific, it assumes that it is valid for all handles.
+ *
+ * Rename to: tf_content_receiving_failed
+ */
+
+void
+tf_content_receiving_failed_literal (TfContent *content,
+ guint *handles, guint handle_count,
+ const gchar *message)
+{
+ TfContentClass *klass = TF_CONTENT_GET_CLASS (content);
+
+ g_return_if_fail (content != NULL);
+ g_return_if_fail (message != NULL);
+
+ if (klass->content_error)
+ klass->receiving_failed (content, handles, handle_count, message);
+ else
+ GST_WARNING ("receiving_failed not defined in class, ignoring error: %s",
+ message);
+}
+
+
+/**
+ * tf_content_receiving_failed:
+ * @content: a #TfContent
+ * @handles: an array of #guint representing Telepathy handles, may be %NULL
+ * @handle_count: the numner of handles in @handles
+ * @message_format: Message with printf style formatting
+ * @...: Parameters to insert into the @message_format string
+ *
+ * Informs the Connection Manager that receiving has failed for this
+ * content. This is a transient error and it may or not not end the Content
+ * and the call.
+ *
+ * If handles are not specific, it assumes that it is valid for all handles.
+ */
+
+void
+tf_content_receiving_failed (TfContent *content,
+ guint *handles, guint handle_count,
+ const gchar *message_format, ...)
+{
+ gchar *message;
+ va_list valist;
+
+ g_return_if_fail (content != NULL);
+ g_return_if_fail (message_format != NULL);
+
+ va_start (valist, message_format);
+ message = g_strdup_vprintf (message_format, valist);
+ va_end (valist);
+
+ tf_content_receiving_failed_literal (content, handles, handle_count, message);
+ g_free (message);
+}
diff --git a/telepathy-farstream/content.h b/telepathy-farstream/content.h
new file mode 100644
index 000000000..97c5c3a7a
--- /dev/null
+++ b/telepathy-farstream/content.h
@@ -0,0 +1,72 @@
+#ifndef __TF_CONTENT_H__
+#define __TF_CONTENT_H__
+
+#include <glib-object.h>
+#include <farstream/fs-conference.h>
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CONTENT tf_content_get_type()
+
+#define TF_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CONTENT, TfContent))
+
+#define TF_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CONTENT, TfContentClass))
+
+#define TF_IS_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CONTENT))
+
+#define TF_IS_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CONTENT))
+
+#define TF_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CONTENT, TfContentClass))
+
+typedef struct _TfContentPrivate TfContentPrivate;
+
+/**
+ * TfContent:
+ *
+ * This structure is private, this class is not subclassable.
+ */
+
+typedef struct _TfContent TfContent;
+
+/**
+ * TfContentClass:
+ *
+ * This structure is private, this class is not subclassable.
+ */
+
+typedef struct _TfContentClass TfContentClass;
+
+GType tf_content_get_type (void);
+
+void tf_content_error_literal (TfContent *content,
+ const gchar *message);
+void tf_content_error (TfContent *content,
+ const gchar *message_format, ...) G_GNUC_PRINTF (2, 3);
+
+
+void tf_content_sending_failed_literal (TfContent *content,
+ const gchar *message);
+void tf_content_sending_failed (TfContent *content,
+ const gchar *message_format, ...) G_GNUC_PRINTF (2, 3);
+
+void tf_content_receiving_failed_literal (TfContent *content,
+ guint *handles, guint handle_count,
+ const gchar *message);
+void tf_content_receiving_failed (TfContent *content,
+ guint *handles, guint handle_count,
+ const gchar *message_format, ...) G_GNUC_PRINTF (4, 5);
+
+GstIterator *tf_content_iterate_src_pads (TfContent *content,
+ guint *handles, guint handle_count);
+
+G_END_DECLS
+
+#endif /* __TF_CONTENT_H__ */
diff --git a/telepathy-farstream/examples/Makefile.am b/telepathy-farstream/examples/Makefile.am
new file mode 100644
index 000000000..bd2dc0d38
--- /dev/null
+++ b/telepathy-farstream/examples/Makefile.am
@@ -0,0 +1,24 @@
+SUBDIRS=python
+
+noinst_PROGRAMS = call-handler
+
+LDADD = \
+ $(top_builddir)/telepathy-farstream/libtelepathy-farstream-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(GST_LIBS) \
+ $(FARSTREAM_LIBS) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ $(ERROR_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(FARSTREAM_CFLAGS) \
+ $(NULL)
diff --git a/telepathy-farstream/examples/call-handler.c b/telepathy-farstream/examples/call-handler.c
new file mode 100644
index 000000000..d26aea411
--- /dev/null
+++ b/telepathy-farstream/examples/call-handler.c
@@ -0,0 +1,646 @@
+/*
+ * call-handler.c
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <gst/gst.h>
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+#include <farstream/fs-element-added-notifier.h>
+#include <farstream/fs-utils.h>
+#include <telepathy-farstream/telepathy-farstream.h>
+
+typedef struct {
+ GstElement *pipeline;
+ guint buswatch;
+ TpChannel *proxy;
+ TfChannel *channel;
+ GList *notifiers;
+
+ guint input_volume;
+ guint output_volume;
+
+ gboolean has_audio_src;
+ gboolean has_video_src;
+
+ GstElement *video_input;
+ GstElement *video_capsfilter;
+
+ guint width;
+ guint height;
+ guint framerate;
+} ChannelContext;
+
+GMainLoop *loop;
+
+static gboolean
+bus_watch_cb (GstBus *bus,
+ GstMessage *message,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ if (context->channel != NULL)
+ tf_channel_bus_message (context->channel, message);
+
+ if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR)
+ {
+ GError *error = NULL;
+ gchar *debug = NULL;
+ gst_message_parse_error (message, &error, &debug);
+ g_printerr ("ERROR from element %s: %s\n",
+ GST_OBJECT_NAME (message->src), error->message);
+ g_printerr ("Debugging info: %s\n", (debug) ? debug : "none");
+ g_error_free (error);
+ g_free (debug);
+ }
+
+ return TRUE;
+}
+
+static void
+on_audio_output_volume_changed (TfContent *content,
+ GParamSpec *spec,
+ GstElement *volume)
+{
+ guint output_volume = 0;
+
+ g_object_get (content, "requested-output-volume", &output_volume, NULL);
+
+ if (output_volume == 0)
+ return;
+
+ g_object_set (volume, "volume", (double)output_volume / 255.0, NULL);
+}
+
+static void
+src_pad_added_cb (TfContent *content,
+ TpHandle handle,
+ FsStream *stream,
+ GstPad *pad,
+ FsCodec *codec,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ gchar *cstr = fs_codec_to_string (codec);
+ FsMediaType mtype;
+ GstPad *sinkpad;
+ GstElement *element;
+ GstStateChangeReturn ret;
+
+ g_debug ("New src pad: %s", cstr);
+ g_object_get (content, "media-type", &mtype, NULL);
+
+ switch (mtype)
+ {
+ case FS_MEDIA_TYPE_AUDIO:
+ {
+ GstElement *volume = NULL;
+ gchar *tmp_str = g_strdup_printf ("audioconvert ! audioresample "
+ "! volume name=\"output_volume%s\" "
+ "! audioconvert ! autoaudiosink", cstr);
+ element = gst_parse_bin_from_description (tmp_str,
+ TRUE, NULL);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf ("output_volume%s", cstr);
+ volume = gst_bin_get_by_name (GST_BIN (element), tmp_str);
+ g_free (tmp_str);
+
+ tp_g_signal_connect_object (content, "notify::output-volume",
+ G_CALLBACK (on_audio_output_volume_changed),
+ volume, 0);
+
+ gst_object_unref (volume);
+
+ break;
+ }
+ case FS_MEDIA_TYPE_VIDEO:
+ element = gst_parse_bin_from_description (
+ "videoconvert ! videoscale ! autovideosink",
+ TRUE, NULL);
+ break;
+ default:
+ g_warning ("Unknown media type");
+ return;
+ }
+
+ gst_bin_add (GST_BIN (context->pipeline), element);
+ sinkpad = gst_element_get_static_pad (element, "sink");
+ ret = gst_element_set_state (element, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Failed to start sink pipeline !?");
+ return;
+ }
+
+ if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sinkpad)))
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Couldn't link sink pipeline !?");
+ return;
+ }
+
+ g_object_unref (sinkpad);
+}
+
+static void
+update_video_parameters (ChannelContext *context, gboolean restart)
+{
+ GstCaps *caps;
+ GstClock *clock;
+
+ if (restart)
+ {
+ /* Assuming the pipeline is in playing state */
+ gst_element_set_locked_state (context->video_input, TRUE);
+ gst_element_set_state (context->video_input, GST_STATE_NULL);
+ }
+
+ g_object_get (context->video_capsfilter, "caps", &caps, NULL);
+ caps = gst_caps_make_writable (caps);
+
+ gst_caps_set_simple (caps,
+ "framerate", GST_TYPE_FRACTION, context->framerate, 1,
+ "width", G_TYPE_INT, context->width,
+ "height", G_TYPE_INT, context->height,
+ NULL);
+
+ g_object_set (context->video_capsfilter, "caps", caps, NULL);
+
+ if (restart)
+ {
+ clock = gst_pipeline_get_clock (GST_PIPELINE (context->pipeline));
+ /* Need to reset the clock if we set the pipeline back to ready by hand */
+ if (clock != NULL)
+ {
+ gst_element_set_clock (context->video_input, clock);
+ g_object_unref (clock);
+ }
+
+ gst_element_set_locked_state (context->video_input, FALSE);
+ gst_element_sync_state_with_parent (context->video_input);
+ }
+}
+
+static void
+on_video_framerate_changed (TfContent *content,
+ GParamSpec *spec,
+ ChannelContext *context)
+{
+ guint framerate;
+
+ g_object_get (content, "framerate", &framerate, NULL);
+
+ if (framerate != 0)
+ context->framerate = framerate;
+
+ update_video_parameters (context, FALSE);
+}
+
+static void
+on_video_resolution_changed (TfContent *content,
+ guint width,
+ guint height,
+ ChannelContext *context)
+{
+ g_assert (width > 0 && height > 0);
+
+ context->width = width;
+ context->height = height;
+
+ update_video_parameters (context, TRUE);
+}
+
+static void
+on_audio_input_volume_changed (TfContent *content,
+ GParamSpec *spec,
+ ChannelContext *context)
+{
+ GstElement *volume;
+ guint input_volume = 0;
+
+ g_object_get (content, "requested-input-volume", &input_volume, NULL);
+
+ if (input_volume == 0)
+ return;
+
+ volume = gst_bin_get_by_name (GST_BIN (context->pipeline), "input_volume");
+ g_object_set (volume, "volume", (double)input_volume / 255.0, NULL);
+ gst_object_unref (volume);
+}
+
+static GstElement *
+setup_audio_source (ChannelContext *context, TfContent *content)
+{
+ GstElement *result;
+ GstElement *volume;
+ gint input_volume = 0;
+
+ result = gst_parse_bin_from_description (
+ "pulsesrc ! audio/x-raw, rate=8000 ! queue"
+ " ! audioconvert ! audioresample"
+ " ! volume name=input_volume ! audioconvert ",
+ TRUE, NULL);
+
+ /* FIXME Need to handle both requested/reported */
+ /* TODO Volume control should be handled in FsIo */
+ g_object_get (content,
+ "requested-input-volume", &input_volume,
+ NULL);
+
+ if (input_volume >= 0)
+ {
+ volume = gst_bin_get_by_name (GST_BIN (result), "input_volume");
+ g_debug ("Requested volume is: %i", input_volume);
+ g_object_set (volume, "volume", (double)input_volume / 255.0, NULL);
+ gst_object_unref (volume);
+ }
+
+ g_signal_connect (content, "notify::requested-input-volume",
+ G_CALLBACK (on_audio_input_volume_changed),
+ context);
+
+ return result;
+}
+
+static GstElement *
+setup_video_source (ChannelContext *context, TfContent *content)
+{
+ GstElement *result, *capsfilter;
+ GstCaps *caps;
+ guint framerate = 0, width = 0, height = 0;
+
+ result = gst_parse_bin_from_description_full (
+ "autovideosrc ! videorate drop-only=1 average-period=20000000000 ! videoscale ! videoconvert ! capsfilter name=c",
+ TRUE, NULL, GST_PARSE_FLAG_FATAL_ERRORS, NULL);
+
+ g_assert (result);
+ capsfilter = gst_bin_get_by_name (GST_BIN (result), "c");
+
+ g_object_get (content,
+ "framerate", &framerate,
+ "width", &width,
+ "height", &height,
+ NULL);
+
+ if (framerate == 0)
+ framerate = 15;
+
+ if (width == 0 || height == 0)
+ {
+ width = 320;
+ height = 240;
+ }
+
+ context->framerate = framerate;
+ context->width = width;
+ context->height = height;
+
+ caps = gst_caps_new_simple ("video/x-raw",
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ "framerate", GST_TYPE_FRACTION, framerate, 1,
+ NULL);
+
+ g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
+
+ gst_caps_unref (caps);
+
+ context->video_input = result;
+ context->video_capsfilter = capsfilter;
+
+ g_signal_connect (content, "notify::framerate",
+ G_CALLBACK (on_video_framerate_changed),
+ context);
+
+ g_signal_connect (content, "resolution-changed",
+ G_CALLBACK (on_video_resolution_changed),
+ context);
+
+ return result;
+}
+
+static gboolean
+start_sending_cb (TfContent *content, gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ GstPad *srcpad, *sinkpad;
+ FsMediaType mtype;
+ GstElement *element;
+ GstStateChangeReturn ret;
+ gboolean res = FALSE;
+
+ g_debug ("Start sending");
+
+ g_object_get (content,
+ "sink-pad", &sinkpad,
+ "media-type", &mtype,
+ NULL);
+
+ switch (mtype)
+ {
+ case FS_MEDIA_TYPE_AUDIO:
+ if (context->has_audio_src)
+ goto out;
+
+ element = setup_audio_source (context, content);
+ context->has_audio_src = TRUE;
+ break;
+ case FS_MEDIA_TYPE_VIDEO:
+ if (context->has_video_src)
+ goto out;
+
+ element = setup_video_source (context, content);
+ context->has_video_src = TRUE;
+ break;
+ default:
+ g_warning ("Unknown media type");
+ goto out;
+ }
+
+
+ gst_bin_add (GST_BIN (context->pipeline), element);
+ srcpad = gst_element_get_static_pad (element, "src");
+
+ if (GST_PAD_LINK_FAILED (gst_pad_link (srcpad, sinkpad)))
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Couldn't link source pipeline !?");
+ goto out2;
+ }
+
+ ret = gst_element_set_state (element, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("source pipeline failed to start!?");
+ goto out2;
+ }
+
+ res = TRUE;
+
+out2:
+ g_object_unref (srcpad);
+out:
+ g_object_unref (sinkpad);
+
+ return res;
+}
+
+static void
+content_added_cb (TfChannel *channel,
+ TfContent *content,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ g_debug ("Content added");
+
+ g_signal_connect (content, "src-pad-added",
+ G_CALLBACK (src_pad_added_cb), context);
+ g_signal_connect (content, "start-sending",
+ G_CALLBACK (start_sending_cb), context);
+}
+
+static void
+conference_added_cb (TfChannel *channel,
+ GstElement *conference,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ GKeyFile *keyfile;
+
+ g_debug ("Conference added");
+
+ /* Add notifier to set the various element properties as needed */
+ keyfile = fs_utils_get_default_element_properties (conference);
+ if (keyfile != NULL)
+ {
+ FsElementAddedNotifier *notifier;
+ g_debug ("Loaded default codecs for %s", GST_ELEMENT_NAME (conference));
+
+ notifier = fs_element_added_notifier_new ();
+ fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
+ fs_element_added_notifier_add (notifier, GST_BIN (context->pipeline));
+
+ context->notifiers = g_list_prepend (context->notifiers, notifier);
+ }
+
+
+ gst_bin_add (GST_BIN (context->pipeline), conference);
+ gst_element_set_state (conference, GST_STATE_PLAYING);
+}
+
+
+static void
+conference_removed_cb (TfChannel *channel,
+ GstElement *conference,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ gst_element_set_locked_state (conference, TRUE);
+ gst_element_set_state (conference, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (context->pipeline), conference);
+}
+
+static gboolean
+dump_pipeline_cb (gpointer data)
+{
+ ChannelContext *context = data;
+
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (context->pipeline),
+ GST_DEBUG_GRAPH_SHOW_ALL,
+ "call-handler");
+
+ return TRUE;
+}
+
+static void
+new_tf_channel_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ GError *error = NULL;
+
+ g_debug ("New TfChannel");
+
+ context->channel = tf_channel_new_finish (source, result, &error);
+
+ if (context->channel == NULL)
+ {
+ g_error ("Failed to create channel: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_debug ("Adding timeout");
+ g_timeout_add_seconds (5, dump_pipeline_cb, context);
+
+ g_signal_connect (context->channel, "fs-conference-added",
+ G_CALLBACK (conference_added_cb), context);
+
+
+ g_signal_connect (context->channel, "fs-conference-removed",
+ G_CALLBACK (conference_removed_cb), context);
+
+ g_signal_connect (context->channel, "content-added",
+ G_CALLBACK (content_added_cb), context);
+}
+
+static void
+proxy_invalidated_cb (TpProxy *proxy,
+ guint domain,
+ gint code,
+ gchar *message,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ g_debug ("Channel closed");
+ if (context->pipeline != NULL)
+ {
+ gst_element_set_state (context->pipeline, GST_STATE_NULL);
+ g_object_unref (context->pipeline);
+ }
+
+ if (context->channel != NULL)
+ g_object_unref (context->channel);
+
+ g_list_foreach (context->notifiers, (GFunc) g_object_unref, NULL);
+ g_list_free (context->notifiers);
+
+ g_object_unref (context->proxy);
+
+ g_slice_free (ChannelContext, context);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+new_call_channel_cb (TpSimpleHandler *handler,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ GList *requests_satisfied,
+ gint64 user_action_time,
+ TpHandleChannelsContext *handler_context,
+ gpointer user_data)
+{
+ ChannelContext *context;
+ TpChannel *proxy;
+ GstBus *bus;
+ GstElement *pipeline;
+ GstStateChangeReturn ret;
+
+ g_debug ("New channel");
+
+ proxy = channels->data;
+
+ pipeline = gst_pipeline_new (NULL);
+
+ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (proxy), NULL, NULL);
+ g_object_unref (pipeline);
+ g_warning ("Failed to start an empty pipeline !?");
+ return;
+ }
+
+ context = g_slice_new0 (ChannelContext);
+ context->pipeline = pipeline;
+
+ bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+ context->buswatch = gst_bus_add_watch (bus, bus_watch_cb, context);
+ g_object_unref (bus);
+
+ tf_channel_new_async (proxy, new_tf_channel_cb, context);
+
+ tp_handle_channels_context_accept (handler_context);
+
+ tp_call_channel_accept_async (TP_CALL_CHANNEL (proxy), NULL, NULL);
+
+ context->proxy = g_object_ref (proxy);
+ g_signal_connect (proxy, "invalidated",
+ G_CALLBACK (proxy_invalidated_cb),
+ context);
+}
+
+int
+main (int argc, char **argv)
+{
+ TpBaseClient *client;
+ TpAccountManager *am;
+
+ gst_init (&argc, &argv);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ am = tp_account_manager_dup ();
+
+ client = tp_simple_handler_new_with_am (am,
+ FALSE,
+ FALSE,
+ "TpFsCallHandlerDemo",
+ TRUE,
+ new_call_channel_cb,
+ NULL,
+ NULL);
+
+ tp_base_client_take_handler_filter (client,
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TP_IFACE_CHANNEL_TYPE_CALL1,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ TP_PROP_CHANNEL_TYPE_CALL1_INITIAL_AUDIO, G_TYPE_BOOLEAN,
+ TRUE,
+ NULL));
+
+ tp_base_client_take_handler_filter (client,
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TP_IFACE_CHANNEL_TYPE_CALL1,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ TP_PROP_CHANNEL_TYPE_CALL1_INITIAL_VIDEO, G_TYPE_BOOLEAN,
+ TRUE,
+ NULL));
+
+ tp_base_client_add_handler_capabilities_varargs (client,
+ TP_IFACE_CHANNEL_TYPE_CALL1 "/video/h264",
+ TP_TOKEN_CHANNEL_TYPE_CALL1_SHM,
+ TP_TOKEN_CHANNEL_TYPE_CALL1_ICE,
+ TP_TOKEN_CHANNEL_TYPE_CALL1_GTALK_P2P,
+ NULL);
+
+ tp_base_client_register (client, NULL);
+
+ g_main_loop_run (loop);
+
+ g_object_unref (am);
+ g_object_unref (client);
+ g_main_loop_unref (loop);
+
+ return 0;
+}
diff --git a/telepathy-farstream/examples/python/Makefile.am b/telepathy-farstream/examples/python/Makefile.am
new file mode 100644
index 000000000..7b89027c2
--- /dev/null
+++ b/telepathy-farstream/examples/python/Makefile.am
@@ -0,0 +1,7 @@
+EXTRA_DIST = \
+ README \
+ callchannel.py \
+ callhandler.py \
+ callui.py \
+ constants.py \
+ util.py
diff --git a/telepathy-farstream/examples/python/README b/telepathy-farstream/examples/python/README
new file mode 100644
index 000000000..8007df6b5
--- /dev/null
+++ b/telepathy-farstream/examples/python/README
@@ -0,0 +1,5 @@
+Simple python example using telepathy-farstream in most minimal way possible.
+Two programs are included:
+
+callui.py: Doesn't do anything with tp-fs, but allows the start of a Call call
+callhandler.py: Simple handler that handles calls and handles the media
diff --git a/telepathy-farstream/examples/python/callchannel.py b/telepathy-farstream/examples/python/callchannel.py
new file mode 100644
index 000000000..f37c7243b
--- /dev/null
+++ b/telepathy-farstream/examples/python/callchannel.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+#
+# callchannel.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import dbus
+import dbus.glib
+import gobject
+import sys
+from glib import GError
+
+import pygst
+pygst.require("0.10")
+import gst
+
+import tpfarstream
+import farstream
+from util import *
+import gc
+
+from telepathy.client.channel import Channel
+from telepathy.constants import (
+ CONNECTION_HANDLE_TYPE_NONE, CONNECTION_HANDLE_TYPE_CONTACT,
+ CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED,
+ MEDIA_STREAM_STATE_CONNECTED
+ )
+from telepathy.interfaces import (
+ CHANNEL_INTERFACE, CONN_INTERFACE,
+ CONNECTION_INTERFACE_REQUESTS,
+ CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ CLIENT)
+
+from constants import *
+
+class CallChannel:
+ def __init__ (self, bus, connection, object_path, properties):
+ self.bus = bus
+ self.conn = connection
+ self.tfchannel = None
+
+ self.obj = self.bus.get_object (self.conn.service_name, object_path)
+ self.obj.connect_to_signal ("CallStateChanged",
+ self.state_changed_cb, dbus_interface=CHANNEL_TYPE_CALL)
+
+ self.pipeline = gst.Pipeline()
+ self.pipeline.get_bus().add_watch(self.async_handler)
+
+ self.notifier = notifier = farstream.ElementAddedNotifier()
+ notifier.set_properties_from_file("element-properties")
+ notifier.add(self.pipeline)
+
+ tpfarstream.tf_channel_new_async (connection.service_name,
+ connection.object_path, object_path, self.tpfs_created)
+
+ def state_changed_cb(self, state, flags, reason, details):
+ print "* StateChanged:\n State: %s (%d)\n Flags: %s" % (
+ call_state_to_s (state), state, call_flags_to_s (flags))
+
+ print "\tReason: actor: %d reason: %d dbus_reason: '%s'" % (
+ reason[0], reason[1], reason[2])
+
+ print '\tDetails:'
+ for key, value in details.iteritems():
+ print "\t %s: %s" % (key, value)
+ else:
+ print '\t None'
+
+ if state == CALL_STATE_ENDED:
+ self.close()
+
+ def accept (self):
+ self.obj.Accept(dbus_interface=CHANNEL_TYPE_CALL)
+
+ def close (self):
+ print "Closing the channel"
+ # close and cleanup
+ self.obj.Close(dbus_interface=CHANNEL_INTERFACE)
+
+ self.pipeline.set_state (gst.STATE_NULL)
+ self.pipeline = None
+
+ self.tfchannel = None
+ self.notifier = None
+
+ def async_handler (self, bus, message):
+ if self.tfchannel != None:
+ self.tfchannel.bus_message(message)
+ return True
+
+ self.pipeline = gst.Pipeline()
+
+ def tpfs_created (self, source, result):
+ tfchannel = self.tfchannel = source.new_finish(result)
+ tfchannel.connect ("fs-conference-added", self.conference_added)
+ tfchannel.connect ("content-added", self.content_added)
+
+
+ def src_pad_added (self, content, handle, stream, pad, codec):
+ type = content.get_property ("media-type")
+ if type == farstream.MEDIA_TYPE_AUDIO:
+ sink = gst.parse_bin_from_description("audioconvert ! audioresample ! audioconvert ! autoaudiosink", True)
+ elif type == farstream.MEDIA_TYPE_VIDEO:
+ sink = gst.parse_bin_from_description("ffmpegcolorspace ! videoscale ! autovideosink", True)
+
+ self.pipeline.add(sink)
+ pad.link(sink.get_pad("sink"))
+ sink.set_state(gst.STATE_PLAYING)
+
+ def get_codec_config (self, media_type):
+ if media_type == farstream.MEDIA_TYPE_VIDEO:
+ codecs = [ farstream.Codec(farstream.CODEC_ID_ANY, "H264",
+ farstream.MEDIA_TYPE_VIDEO, 0) ]
+ if self.conn.GetProtocol() == "sip" :
+ codecs += [ farstream.Codec(farstream.CODEC_ID_DISABLE, "THEORA",
+ farstream.MEDIA_TYPE_VIDEO, 0) ]
+ else:
+ codecs += [ farstream.Codec(farstream.CODEC_ID_ANY, "THEORA",
+ farstream.MEDIA_TYPE_VIDEO, 0) ]
+ codecs += [
+ farstream.Codec(farstream.CODEC_ID_ANY, "H263",
+ farstream.MEDIA_TYPE_VIDEO, 0),
+ farstream.Codec(farstream.CODEC_ID_DISABLE, "DV",
+ farstream.MEDIA_TYPE_VIDEO, 0),
+ farstream.Codec(farstream.CODEC_ID_ANY, "JPEG",
+ farstream.MEDIA_TYPE_VIDEO, 0),
+ farstream.Codec(farstream.CODEC_ID_ANY, "MPV",
+ farstream.MEDIA_TYPE_VIDEO, 0),
+ ]
+
+ else:
+ codecs = [
+ farstream.Codec(farstream.CODEC_ID_ANY, "SPEEX",
+ farstream.MEDIA_TYPE_AUDIO, 16000 ),
+ farstream.Codec(farstream.CODEC_ID_ANY, "SPEEX",
+ farstream.MEDIA_TYPE_AUDIO, 8000 )
+ ]
+ return codecs
+
+ def content_added(self, channel, content):
+ sinkpad = content.get_property ("sink-pad")
+
+ mtype = content.get_property ("media-type")
+ prefs = self.get_codec_config (mtype)
+ if prefs != None:
+ try:
+ content.set_codec_preferences(prefs)
+ except GError, e:
+ print e.message
+
+ content.connect ("src-pad-added", self.src_pad_added)
+
+ if mtype == farstream.MEDIA_TYPE_AUDIO:
+ src = gst.parse_bin_from_description("audiotestsrc is-live=1 ! " \
+ "queue", True)
+ elif mtype == farstream.MEDIA_TYPE_VIDEO:
+ src = gst.parse_bin_from_description("videotestsrc is-live=1 ! " \
+ "capsfilter caps=video/x-raw-yuv,width=320,height=240", True)
+
+ self.pipeline.add(src)
+ src.get_pad("src").link(sinkpad)
+ src.set_state(gst.STATE_PLAYING)
+
+ def conference_added (self, channel, conference):
+ self.pipeline.add(conference)
+ self.pipeline.set_state(gst.STATE_PLAYING)
+
diff --git a/telepathy-farstream/examples/python/callhandler.py b/telepathy-farstream/examples/python/callhandler.py
new file mode 100644
index 000000000..71af24cd9
--- /dev/null
+++ b/telepathy-farstream/examples/python/callhandler.py
@@ -0,0 +1,116 @@
+# callhandler.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gobject
+# Need gio so GAsyncInitialbe is known
+import gio
+
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+
+from constants import *
+from telepathy.interfaces import CHANNEL_INTERFACE, CLIENT, CLIENT_HANDLER
+from telepathy.constants import CONNECTION_HANDLE_TYPE_CONTACT, CONNECTION_HANDLE_TYPE_ROOM
+import telepathy
+
+from callchannel import CallChannel
+
+class CallHandler(dbus.service.Object, telepathy.server.DBusProperties):
+ def __init__(self, bus, bus_name = None):
+ self.bus = bus
+ if bus_name == None:
+ self.bus_name = "org.freedesktop.Telepathy.Client.CallDemo" \
+ + bus.get_unique_name().replace(":", "_").replace(".","_")
+ else:
+ self.bus_name = bus_name
+ self.path = "/" + self.bus_name.replace(".", "/")
+ self._interfaces = set([CLIENT, CLIENT_HANDLER])
+ self._prop_getters = {}
+ self._prop_setters = {}
+
+ dbus.service.Object.__init__(self, bus, self.path)
+ telepathy.server.DBusProperties.__init__(self)
+
+ self._name = dbus.service.BusName (self.bus_name, bus)
+
+ self._implement_property_get (CLIENT,
+ { "Interfaces": self._get_interfaces } )
+ self._implement_property_get (CLIENT_HANDLER,
+ { "HandlerChannelFilter": self._get_filters } )
+ self._implement_property_get (CLIENT_HANDLER,
+ { "Capabilities": self._get_capabilities } )
+
+ def _get_interfaces(self):
+ return dbus.Array(self._interfaces, signature='s')
+
+ def _get_filters(self):
+ return dbus.Array ([
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType":
+ CONNECTION_HANDLE_TYPE_CONTACT,
+ CALL_INITIAL_AUDIO: True,
+ },
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType":
+ CONNECTION_HANDLE_TYPE_CONTACT,
+ CALL_INITIAL_VIDEO: True,
+ },
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType":
+ CONNECTION_HANDLE_TYPE_ROOM,
+ CALL_INITIAL_AUDIO: True,
+ },
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType":
+ CONNECTION_HANDLE_TYPE_ROOM,
+ CALL_INITIAL_VIDEO: True,
+ }
+ ],
+ signature='a{sv}')
+
+ def _get_capabilities(self):
+ return dbus.Array ([
+ CHANNEL_TYPE_CALL + '/gtalk-p2p',
+ CHANNEL_TYPE_CALL + '/ice-udp',
+ CHANNEL_TYPE_CALL + '/video/h264',
+ ], signature='s')
+
+ def do_handle_call_channel (self, requests, bus, conn, channel, properties):
+ cchannel = CallChannel(self.bus, conn, channel, properties)
+ cchannel.accept()
+
+ @dbus.service.method(dbus_interface=CLIENT_HANDLER,
+ in_signature='ooa(oa{sv})aota{sv}',
+ async_callbacks= ('_success', '_error'))
+ def HandleChannels(self, account, connection, channels,
+ requests, time, info, _success, _error):
+
+ conn = telepathy.client.Connection (connection[1:].replace('/','.'),
+ connection)
+ # Assume there can be only one
+ (channel, properties) = channels[0]
+
+ _success()
+ self.do_handle_call_channel (requests,
+ self.bus, conn, channel, properties);
+
+if __name__ == '__main__':
+ gobject.threads_init()
+ loop = gobject.MainLoop()
+ CallHandler(dbus.SessionBus())
+ loop.run()
diff --git a/telepathy-farstream/examples/python/callui.py b/telepathy-farstream/examples/python/callui.py
new file mode 100644
index 000000000..9e7558f6c
--- /dev/null
+++ b/telepathy-farstream/examples/python/callui.py
@@ -0,0 +1,285 @@
+#!/usr/bin/env python
+#
+# callui.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gobject
+gobject.threads_init()
+
+import pygtk
+import gtk
+
+gtk.gdk.threads_init()
+
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+
+import sys
+import time
+
+from telepathy.interfaces import *
+from telepathy.constants import *
+
+from constants import *
+
+class CallChannelRequest:
+ def __init__ (self, bus, account_path, contact,
+ preferred_handler = "", audio = True, video = False,
+ calltype = HANDLE_TYPE_CONTACT):
+ self.bus = bus
+ self.cd = bus.get_object (CHANNEL_DISPATCHER,
+ '/' + CHANNEL_DISPATCHER.replace('.', '/'))
+
+ props = {
+ CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType": calltype,
+ CHANNEL_INTERFACE + ".TargetID": contact,
+ }
+
+ if audio:
+ props[CHANNEL_TYPE_CALL + ".InitialAudio"] = True
+ if video:
+ props[CHANNEL_TYPE_CALL + ".InitialVideo"] = True
+
+ self.request_path = req_path = self.cd.CreateChannel(account_path,
+ props,
+ 0,
+ preferred_handler,
+ dbus_interface = CHANNEL_DISPATCHER)
+
+ self.req = self.bus.get_object (CHANNEL_DISPATCHER, req_path)
+ self.req.connect_to_signal("Failed", self.req_failed)
+ self.req.connect_to_signal("Succeeded", self.req_succeeded)
+ self.req.Proceed(dbus_interface = CHANNEL_REQUEST)
+
+ def req_failed(self, error, message):
+ print "FAILURE: %s (%s)"% (error, message)
+
+ def req_succeeded(self):
+ pass
+
+class Account:
+ CALL_CLASS = {
+ CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + '.TargetHandleType': HANDLE_TYPE_CONTACT
+ }
+
+ def __init__(self, bus, path):
+ self.bus = bus
+ self.path = path
+ self.obj = bus.get_object (ACCOUNT_MANAGER, path)
+ self.properties = self.obj.GetAll (ACCOUNT,
+ dbus_interface=dbus.PROPERTIES_IFACE)
+
+ def get_path(self):
+ return self.path
+
+ def name(self):
+ return self.properties["DisplayName"]
+
+ def has_connection(self):
+ return self.properties["Connection"] != "/"
+
+ def get_contacts(self):
+ path = self.properties["Connection"]
+ if path == "/":
+ return []
+
+ conn = self.bus.get_object (path[1:].replace("/","."), path)
+ yours, channel, properties = conn.EnsureChannel (
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CONTACT_LIST,
+ CHANNEL_INTERFACE + ".TargetHandleType": HANDLE_TYPE_LIST,
+ CHANNEL_INTERFACE + ".TargetID": "subscribe"
+ },
+ dbus_interface = CONNECTION_INTERFACE_REQUESTS
+ )
+
+ subscribe = self.bus.get_object (conn.bus_name, channel)
+ members = subscribe.Get(CHANNEL_INTERFACE_GROUP, "Members",
+ dbus_interface = dbus.PROPERTIES_IFACE)
+
+ caps = conn.GetContactCapabilities (members,
+ dbus_interface = CONNECTION_INTERFACE_CONTACT_CAPABILITIES)
+ members = caps.keys()
+
+ for k, v in caps.iteritems():
+ for c in v:
+ if c[0][CHANNEL_TYPE] == CHANNEL_TYPE_CALL:
+ break
+ else:
+ members.remove (k)
+
+ attributes = conn.GetContactAttributes (
+ dbus.Array(members, signature="u"),
+ dbus.Array([], signature="s"),
+ True)
+
+ return map (lambda v: v[CONNECTION + "/contact-id"],
+ attributes.itervalues())
+
+ def supports_calls(self):
+ path = self.properties["Connection"]
+ if path == "/":
+ return False
+
+ conn = self.bus.get_object (path[1:].replace("/","."), path)
+ classes = conn.Get (CONNECTION_INTERFACE_REQUESTS,
+ 'RequestableChannelClasses', dbus_interface=dbus.PROPERTIES_IFACE)
+
+ return len ([c for c in classes if c[0] == self.CALL_CLASS]) > 0
+
+class UI(gtk.Window):
+ WIDTH=240
+ HEIGHT=-1
+ def __init__ (self, bus):
+ gtk.Window.__init__(self)
+ self.connect('destroy', lambda x: gtk.main_quit())
+ self.set_resizable(False)
+ self.set_size_request(self.WIDTH, self.HEIGHT)
+
+ vbox = gtk.VBox(False, 3)
+ self.add(vbox)
+
+ # call type combo box
+ self.type_store = gtk.ListStore (
+ gobject.TYPE_STRING,
+ gobject.TYPE_UINT)
+
+ self.type_store.append (("1-to-1", CONNECTION_HANDLE_TYPE_CONTACT))
+ self.type_store.append (("Conference",
+ CONNECTION_HANDLE_TYPE_ROOM))
+
+ self.type_combo = combobox = gtk.ComboBox (self.type_store)
+ vbox.pack_start(combobox, False)
+
+ renderer = gtk.CellRendererText()
+ combobox.pack_start(renderer, True)
+ combobox.set_attributes(renderer, text=0)
+ combobox.set_active (0)
+
+ # account combo box
+ self.store = gtk.ListStore (gobject.TYPE_STRING,
+ gobject.TYPE_BOOLEAN,
+ gobject.TYPE_PYOBJECT)
+ self.store.set_sort_func(0,
+ (lambda m, i0, i1:
+ { True: -1, False: 1}[m.get(i0, 0) < m.get(i1, 0)] ))
+ self.store.set_sort_column_id(0, gtk.SORT_ASCENDING)
+
+ f = self.store.filter_new()
+ f.set_visible_func(self.filter_visible)
+ self.account_combo = combobox = gtk.ComboBox(f)
+ vbox.pack_start(combobox, False)
+
+ renderer = gtk.CellRendererText()
+ combobox.pack_start(renderer, True)
+ combobox.set_attributes(renderer, text=0)
+ combobox.connect('changed', self.account_selected)
+
+ # contact entry box
+ self.contact_store = gtk.ListStore(gobject.TYPE_STRING)
+
+ completion = gtk.EntryCompletion ()
+ completion.set_model(self.contact_store)
+ completion.set_text_column(0)
+
+ self.contact_store.set_sort_func(0, self.contact_sort)
+ self.contact_store.set_sort_column_id(0, gtk.SORT_ASCENDING)
+
+ self.contact_combo = combobox = gtk.ComboBoxEntry(self.contact_store)
+ combobox.get_child().set_completion(completion)
+
+ vbox.pack_start(combobox, False)
+
+ bbox = gtk.HButtonBox()
+ bbox.set_layout(gtk.BUTTONBOX_END)
+ vbox.pack_start(bbox, True, False, 3)
+
+ call = gtk.Button("Audio call")
+ call.connect("clicked", self.start_call)
+ bbox.add(call)
+
+ call = gtk.Button("Video call")
+ call.connect("clicked",
+ lambda button: self.start_call(button, video=True))
+ bbox.add(call)
+
+ self.show_all()
+
+ self.bus = bus
+ self.account_mgr = bus.get_object (ACCOUNT_MANAGER,
+ '/' + ACCOUNT_MANAGER.replace('.', '/'))
+ self.get_accounts()
+
+ def start_call(self, button, audio=True, video=False):
+ i = self.type_combo.get_active_iter()
+ (calltype, ) = self.type_combo.get_model().get(i, 1)
+
+ i = self.account_combo.get_active_iter()
+ (account, ) = self.account_combo.get_model().get(i, 2)
+
+ contact = self.contact_combo.get_active_text().strip()
+
+ print "* starting %s call" % ('video' if video else 'audio')
+ CallChannelRequest (self.bus, account.path, contact,
+ audio=audio, video=video, calltype=calltype)
+
+ def contact_sort (self, model, i0, i1):
+ if model.get(i0, 0)[0] < model.get(i1, 0)[0]:
+ return -1
+ else:
+ return 0
+
+ def filter_visible(self, model, titer):
+ return model.get(titer, 1)[0]
+
+ def account_selected (self, combobox):
+ iter = combobox.get_active_iter()
+ if iter == None:
+ return None
+
+ (account,) = combobox.get_model().get(iter, 2)
+
+ self.contact_store.clear()
+
+ map(lambda x: self.contact_store.insert (0, (x,)),
+ account.get_contacts())
+
+ def bail (self, *args):
+ print "BAILING"
+ print args
+ gtk.main_quit()
+
+ def got_accounts(self, accounts):
+ for x in accounts:
+ a = Account(self.bus, x)
+ if a.supports_calls():
+ self.store.insert(0, (a.name(), a.has_connection(), a))
+ self.account_combo.set_active(0)
+
+ def get_accounts (self):
+ self.account_mgr.Get(ACCOUNT_MANAGER, "ValidAccounts",
+ dbus_interface = dbus.PROPERTIES_IFACE,
+ reply_handler = self.got_accounts,
+ error_handler = self.bail)
+
+if __name__ == '__main__':
+ bus = dbus.SessionBus()
+
+ UI(bus)
+ gtk.main()
diff --git a/telepathy-farstream/examples/python/constants.py b/telepathy-farstream/examples/python/constants.py
new file mode 100644
index 000000000..43af8baf2
--- /dev/null
+++ b/telepathy-farstream/examples/python/constants.py
@@ -0,0 +1,66 @@
+# constants.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from telepathy.interfaces import CHANNEL_INTERFACE
+
+CHANNEL = CHANNEL_INTERFACE
+
+CHANNEL_TYPE = CHANNEL + ".ChannelType"
+CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call1"
+CALL_INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio'
+CALL_INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo'
+CALL_MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents'
+
+CALL_CONTENT = 'org.freedesktop.Telepathy.Call1.Content'
+CALL_CONTENT_IFACE_MEDIA = \
+ 'org.freedesktop.Telepathy.Call1.Content.Interface.Media'
+
+CALL_CONTENT_CODECOFFER = \
+ 'org.freedesktop.Telepathy.Call1.Content.CodecOffer'
+
+CALL_STREAM = 'org.freedesktop.Telepathy.Call1.Stream'
+CALL_STREAM_IFACE_MEDIA = \
+ 'org.freedesktop.Telepathy.Call1.Stream.Interface.Media'
+
+CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call1.Stream.Endpoint'
+
+STREAM_TRANSPORT_RAW_UDP = 1
+STREAM_TRANSPORT_ICE_UDP = 2
+STREAM_TRANSPORT_GTALK_P2P = 3
+STREAM_TRANSPORT_WLM_2009 = 4
+STREAM_TRANSPORT_SHM = 5
+STREAM_TRANSPORT_MULTICAST = 6
+STREAM_TRANSPOR_DUMMY = 0xff
+
+CALL_STATE_UNKNOWN = 0
+CALL_STATE_PENDING_INITIATOR = 1
+CALL_STATE_PENDING_RECEIVER = 2
+CALL_STATE_ACCEPTED = 3
+CALL_STATE_ENDED = 4
+
+CALL_FLAG_LOCALLY_RINGING = 1
+CALL_FLAG_QUEUED = 2
+CALL_FLAG_LOCALLY_HELD = 4
+CALL_FLAG_FORWARDED = 8
+CALL_FLAG_IN_PROGRESS = 16
+CALL_FLAG_CLEARING = 32
+
+CALL_STATE_CHANGE_REASON_UNKNOWN = 0
+CALL_STATE_CHANGE_REASON_REQUESTED = 1
+
+CONTENT_PACKETIZATION_RTP = 0
+CONTENT_PACKETIZATION_RAW = 1
diff --git a/telepathy-farstream/examples/python/element-properties b/telepathy-farstream/examples/python/element-properties
new file mode 100644
index 000000000..40f706d6e
--- /dev/null
+++ b/telepathy-farstream/examples/python/element-properties
@@ -0,0 +1,62 @@
+# Put the desired properties in the style of
+#
+# [element name]
+# prop1=val1
+
+[gstrtpbin]
+latency=100
+
+[x264enc]
+byte-stream=1
+bframes=0
+b-adapt=0
+cabac=0
+dct8x8=0
+bitrate=256
+# tuned for zero latency
+tune=0x4
+profile=1
+speed-preset=3
+sliced-threads=false
+
+[ffenc_h263]
+rtp-payload-size=1
+
+[theoraenc]
+bitrate=256
+
+[vp8enc]
+bitrate=256000
+max-latency=1
+speed=2
+error-resilient=true
+
+# Work around bug in the re-timestamp slaving method in
+# GStreamer (2 is skew)
+[alsasrc]
+slave-method=2
+
+[osssrc]
+slave-method=2
+
+[oss4src]
+slave-method=2
+
+[sunaudiosrc]
+slave-method=2
+
+[rtph264pay]
+config-interval=5
+
+[rtppcmupay]
+ptime-multiple=20000000
+
+[rtppcmapay]
+ptime-multiple=20000000
+
+[gstrtpjitterbuffer]
+do-lost=1
+
+[ewh264enc]
+profile=baseline
+quality=5
diff --git a/telepathy-farstream/examples/python/util.py b/telepathy-farstream/examples/python/util.py
new file mode 100644
index 000000000..bbad9c852
--- /dev/null
+++ b/telepathy-farstream/examples/python/util.py
@@ -0,0 +1,40 @@
+# util.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from constants import *
+
+def call_state_to_s(state):
+ return {
+ CALL_STATE_UNKNOWN: 'Unknown',
+ CALL_STATE_PENDING_INITIATOR: 'Pending Initiator',
+ CALL_STATE_PENDING_RECEIVER: 'Pending Receiver',
+ CALL_STATE_ACCEPTED: 'Accepted',
+ CALL_STATE_ENDED: 'Ended'
+ }[state]
+
+def call_flags_to_s(flags):
+ flag_strs = {
+ CALL_FLAG_LOCALLY_RINGING: 'Locally Ringing',
+ CALL_FLAG_QUEUED: 'Queued',
+ CALL_FLAG_LOCALLY_HELD: 'Locally Held',
+ CALL_FLAG_FORWARDED: 'Forwarded',
+ CALL_FLAG_IN_PROGRESS: 'In Progress',
+ CALL_FLAG_CLEARING: 'Clearing'
+ }
+
+ return ' | '.join([ '%s (%d)' % (flag_strs[i], i)
+ for i in flag_strs.keys() if flags & i ]) or 'None'
diff --git a/telepathy-farstream/telepathy-farstream-1.pc.in b/telepathy-farstream/telepathy-farstream-1.pc.in
new file mode 100644
index 000000000..ed500a699
--- /dev/null
+++ b/telepathy-farstream/telepathy-farstream-1.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Telepathy-Farstream-1
+Description: Library implementing the Telepathy Call API using Farstream
+Version: @VERSION@
+Requires.private: dbus-glib-1 >= 0.73, glib-2.0 >= 2.10, gobject-2.0 >= 2.10, farstream-0.2, telepathy-glib-1 >= 0.99.1, telepathy-glib-1-dbus >= 0.99.1, gstreamer-1.0
+Libs: -L${libdir} -ltelepathy-farstream-1
+Cflags: -I${includedir}/telepathy-farstream-1
diff --git a/telepathy-farstream/telepathy-farstream-uninstalled-1.pc.in b/telepathy-farstream/telepathy-farstream-uninstalled-1.pc.in
new file mode 100644
index 000000000..b6290e4d2
--- /dev/null
+++ b/telepathy-farstream/telepathy-farstream-uninstalled-1.pc.in
@@ -0,0 +1,11 @@
+prefix=
+exec_prefix=
+abs_top_srcdir=@abs_top_srcdir@
+abs_top_builddir=@abs_top_builddir@
+
+Name: Telepathy-Farstream-1
+Description: Library implementing the Telepathy Call API using Farstream
+Version: @VERSION@
+Requires.private: dbus-glib-1 >= 0.73, glib-2.0 >= 2.10, gobject-2.0 >= 2.10, farstream-0.2
+Libs: ${abs_top_builddir}/telepathy-farstream/libtelepathy-farstream-1.la
+Cflags: -I${abs_top_srcdir} -I${abs_top_builddir}
diff --git a/telepathy-farstream/telepathy-farstream.h b/telepathy-farstream/telepathy-farstream.h
new file mode 100644
index 000000000..4a24fb275
--- /dev/null
+++ b/telepathy-farstream/telepathy-farstream.h
@@ -0,0 +1,27 @@
+/*
+ * telepathy-farstream.h - Source for TfCallChannel
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_FARSTREAM_H_
+#define __TF_FARSTREAM_H_
+
+#include <telepathy-farstream/channel.h>
+#include <telepathy-farstream/content.h>
+
+#endif /* __TF_FARSTREAM_H__ */
+
diff --git a/telepathy-farstream/utils.h b/telepathy-farstream/utils.h
new file mode 100644
index 000000000..fbf5fcc5d
--- /dev/null
+++ b/telepathy-farstream/utils.h
@@ -0,0 +1,62 @@
+#ifndef __UTILS_H__
+#define __UTILS_H__
+
+/*
+ * tp_media_type_to_fs:
+ * @type: A Telepathy Media Stream Type
+ *
+ * Converts a Telepathy Media Stream Type to the Farstream equivalent
+ *
+ * Return: A Farstream Stream Type
+ */
+
+static inline FsMediaType
+tp_media_type_to_fs (TpMediaStreamType type)
+{
+ switch (type)
+ {
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ return FS_MEDIA_TYPE_AUDIO;
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ return FS_MEDIA_TYPE_VIDEO;
+ default:
+ g_return_val_if_reached(0);
+ }
+}
+
+static inline TpMediaStreamDirection
+fsdirection_to_tpdirection (FsStreamDirection dir)
+{
+ switch (dir) {
+ case FS_DIRECTION_NONE:
+ return TP_MEDIA_STREAM_DIRECTION_NONE;
+ case FS_DIRECTION_SEND:
+ return TP_MEDIA_STREAM_DIRECTION_SEND;
+ case FS_DIRECTION_RECV:
+ return TP_MEDIA_STREAM_DIRECTION_RECEIVE;
+ case FS_DIRECTION_BOTH:
+ return TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static inline FsStreamDirection
+tpdirection_to_fsdirection (TpMediaStreamDirection dir)
+{
+ switch (dir) {
+ case TP_MEDIA_STREAM_DIRECTION_NONE:
+ return FS_DIRECTION_NONE;
+ case TP_MEDIA_STREAM_DIRECTION_SEND:
+ return FS_DIRECTION_SEND;
+ case TP_MEDIA_STREAM_DIRECTION_RECEIVE:
+ return FS_DIRECTION_RECV;
+ case TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL:
+ return FS_DIRECTION_BOTH;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+
+#endif /* __UTILS_H__ */