pidgin/pidgin

Increase timeout for initial XDP portal session to 2s
release-2.x.y
2020-04-05, David Woodhouse
87d96850a2d7
Increase timeout for initial XDP portal session to 2s

It should still fail immediately when the portal isn't available, but
in my test VM with systemd automatically activating the portal when it's
first accessed, it can take 800ms or so. Wait for it instead of falling
back after 200ms.
/**
* @file backend-fs2.c Farstream backend for media API
* @ingroup core
*/
/* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "internal.h"
#include "backend-fs2.h"
#ifdef USE_VV
#include "backend-iface.h"
#include "debug.h"
#include "network.h"
#include "media-gst.h"
#ifdef HAVE_FARSIGHT
#include <gst/farsight/fs-conference-iface.h>
#include <gst/farsight/fs-element-added-notifier.h>
#else
#include <farstream/fs-conference.h>
#include <farstream/fs-element-added-notifier.h>
#include <farstream/fs-utils.h>
#include <gst/gststructure.h>
#endif
#if !GST_CHECK_VERSION(1,0,0)
#define gst_registry_get() gst_registry_get_default()
#endif
/** @copydoc _PurpleMediaBackendFs2Class */
typedef struct _PurpleMediaBackendFs2Class PurpleMediaBackendFs2Class;
/** @copydoc _PurpleMediaBackendFs2Private */
typedef struct _PurpleMediaBackendFs2Private PurpleMediaBackendFs2Private;
/** @copydoc _PurpleMediaBackendFs2Session */
typedef struct _PurpleMediaBackendFs2Session PurpleMediaBackendFs2Session;
/** @copydoc _PurpleMediaBackendFs2Stream */
typedef struct _PurpleMediaBackendFs2Stream PurpleMediaBackendFs2Stream;
#define PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE((obj), \
PURPLE_TYPE_MEDIA_BACKEND_FS2, PurpleMediaBackendFs2Private))
static void purple_media_backend_iface_init(PurpleMediaBackendIface *iface);
static gboolean
gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self);
static void
state_changed_cb(PurpleMedia *media, PurpleMediaState state,
gchar *sid, gchar *name, PurpleMediaBackendFs2 *self);
static void
stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
gchar *sid, gchar *name, gboolean local,
PurpleMediaBackendFs2 *self);
static gboolean purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *who,
PurpleMediaSessionType type, gboolean initiator,
const gchar *transmitter,
guint num_params, GParameter *params);
static void purple_media_backend_fs2_add_remote_candidates(
PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
GList *remote_candidates);
static gboolean purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
const gchar *sess_id);
static GList *purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
const gchar *sess_id);
static GList *purple_media_backend_fs2_get_local_candidates(
PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant);
#if GST_CHECK_VERSION(1,0,0)
static gboolean purple_media_backend_fs2_set_encryption_parameters (
PurpleMediaBackend *self, const gchar *sess_id, const gchar *cipher,
const gchar *auth, const gchar *key, gsize key_len);
static gboolean purple_media_backend_fs2_set_decryption_parameters(
PurpleMediaBackend *self, const gchar *sess_id,
const gchar *participant, const gchar *cipher,
const gchar *auth, const gchar *key, gsize key_len);
static gboolean purple_media_backend_fs2_set_require_encryption(
PurpleMediaBackend *self, const gchar *sess_id,
const gchar *participant, gboolean require_encryption);
#endif
static gboolean purple_media_backend_fs2_set_remote_codecs(
PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
GList *codecs);
static gboolean purple_media_backend_fs2_set_send_codec(
PurpleMediaBackend *self, const gchar *sess_id,
PurpleMediaCodec *codec);
static void purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
guint num_params, GParameter *params);
static const gchar **purple_media_backend_fs2_get_available_params(void);
static gboolean purple_media_backend_fs2_send_dtmf(
PurpleMediaBackend *self, const gchar *sess_id,
gchar dtmf, guint8 volume, guint16 duration);
static gboolean purple_media_backend_fs2_set_send_rtcp_mux(
PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
gboolean send_rtcp_mux);
static void remove_element(GstElement *element);
static void free_stream(PurpleMediaBackendFs2Stream *stream);
static void free_session(PurpleMediaBackendFs2Session *session);
struct _PurpleMediaBackendFs2Class
{
GObjectClass parent_class;
};
struct _PurpleMediaBackendFs2
{
GObject parent;
};
G_DEFINE_TYPE_WITH_CODE(PurpleMediaBackendFs2, purple_media_backend_fs2,
G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(
PURPLE_TYPE_MEDIA_BACKEND, purple_media_backend_iface_init));
struct _PurpleMediaBackendFs2Stream
{
PurpleMediaBackendFs2Session *session;
gchar *participant;
FsStream *stream;
#ifndef HAVE_FARSIGHT
gboolean supports_add;
#endif
GstElement *src;
GstElement *tee;
GstElement *volume;
GstElement *level;
GstElement *fakesink;
GstElement *queue;
GList *local_candidates;
GList *remote_candidates;
guint connected_cb_id;
};
struct _PurpleMediaBackendFs2Session
{
PurpleMediaBackendFs2 *backend;
gchar *id;
FsSession *session;
GstElement *src;
GstElement *tee;
GstElement *srcvalve;
GstPad *srcpad;
PurpleMediaSessionType type;
};
struct _PurpleMediaBackendFs2Private
{
PurpleMedia *media;
GstElement *confbin;
FsConference *conference;
gchar *conference_type;
#ifndef HAVE_FARSIGHT
FsElementAddedNotifier *notifier;
#endif
GHashTable *sessions;
GHashTable *participants;
GList *streams;
gdouble silence_threshold;
};
enum {
PROP_0,
PROP_CONFERENCE_TYPE,
PROP_MEDIA,
};
static void
purple_media_backend_fs2_init(PurpleMediaBackendFs2 *self)
{}
static FsCandidateType
purple_media_candidate_type_to_fs(PurpleMediaCandidateType type)
{
switch (type) {
case PURPLE_MEDIA_CANDIDATE_TYPE_HOST:
return FS_CANDIDATE_TYPE_HOST;
case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX:
return FS_CANDIDATE_TYPE_SRFLX;
case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX:
return FS_CANDIDATE_TYPE_PRFLX;
case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY:
return FS_CANDIDATE_TYPE_RELAY;
case PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST:
return FS_CANDIDATE_TYPE_MULTICAST;
}
g_return_val_if_reached(FS_CANDIDATE_TYPE_HOST);
}
static PurpleMediaCandidateType
purple_media_candidate_type_from_fs(FsCandidateType type)
{
switch (type) {
case FS_CANDIDATE_TYPE_HOST:
return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
case FS_CANDIDATE_TYPE_SRFLX:
return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
case FS_CANDIDATE_TYPE_PRFLX:
return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
case FS_CANDIDATE_TYPE_RELAY:
return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
case FS_CANDIDATE_TYPE_MULTICAST:
return PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST;
}
g_return_val_if_reached(PURPLE_MEDIA_CANDIDATE_TYPE_HOST);
}
static FsNetworkProtocol
purple_media_network_protocol_to_fs(PurpleMediaNetworkProtocol protocol)
{
switch (protocol) {
case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
return FS_NETWORK_PROTOCOL_UDP;
#if GST_CHECK_VERSION(1,0,0)
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE:
return FS_NETWORK_PROTOCOL_TCP_PASSIVE;
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE:
return FS_NETWORK_PROTOCOL_TCP_ACTIVE;
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO:
return FS_NETWORK_PROTOCOL_TCP_SO;
#endif
default:
g_return_val_if_reached(FS_NETWORK_PROTOCOL_TCP);
}
}
static PurpleMediaNetworkProtocol
purple_media_network_protocol_from_fs(FsNetworkProtocol protocol)
{
switch (protocol) {
case FS_NETWORK_PROTOCOL_UDP:
return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
#if GST_CHECK_VERSION(1,0,0)
case FS_NETWORK_PROTOCOL_TCP_PASSIVE:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE;
case FS_NETWORK_PROTOCOL_TCP_ACTIVE:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE;
case FS_NETWORK_PROTOCOL_TCP_SO:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO;
#endif
default:
g_return_val_if_reached(PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE);
}
}
#if GST_CHECK_VERSION(1,0,0)
static GstPadProbeReturn
event_probe_cb(GstPad *srcpad, GstPadProbeInfo *info, gpointer unused)
#else
static gboolean
event_probe_cb(GstPad *srcpad, GstEvent *event, gboolean release_pad)
#endif
{
#if GST_CHECK_VERSION(1,0,0)
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
#endif
if (GST_EVENT_TYPE(event) == GST_EVENT_CUSTOM_DOWNSTREAM
&& gst_event_has_name(event, "purple-unlink-tee")) {
const GstStructure *s = gst_event_get_structure(event);
gst_pad_unlink(srcpad, gst_pad_get_peer(srcpad));
#if GST_CHECK_VERSION(1,0,0)
gst_pad_remove_probe(srcpad,
g_value_get_ulong(gst_structure_get_value(s, "handler-id")));
#else
gst_pad_remove_event_probe(srcpad,
g_value_get_uint(gst_structure_get_value(s, "handler-id")));
#endif
if (g_value_get_boolean(gst_structure_get_value(s, "release-pad")))
gst_element_release_request_pad(GST_ELEMENT_PARENT(srcpad), srcpad);
#if GST_CHECK_VERSION(1,0,0)
return GST_PAD_PROBE_DROP;
#else
return FALSE;
#endif
}
#if GST_CHECK_VERSION(1,0,0)
return GST_PAD_PROBE_OK;
#else
return TRUE;
#endif
}
static void
unlink_teepad_dynamic(GstPad *srcpad, gboolean release_pad)
{
#if GST_CHECK_VERSION(1,0,0)
gulong id = gst_pad_add_probe(srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
event_probe_cb, NULL, NULL);
#else
guint id = gst_pad_add_event_probe(srcpad, G_CALLBACK(event_probe_cb), NULL);
#endif
if (GST_IS_GHOST_PAD(srcpad))
srcpad = gst_ghost_pad_get_target(GST_GHOST_PAD(srcpad));
gst_element_send_event(gst_pad_get_parent_element(srcpad),
gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM,
gst_structure_new("purple-unlink-tee",
"release-pad", G_TYPE_BOOLEAN, release_pad,
#if GST_CHECK_VERSION(1,0,0)
"handler-id", G_TYPE_ULONG, id,
#else
"handler-id", G_TYPE_UINT, id,
#endif
NULL)));
}
static void
purple_media_backend_fs2_dispose(GObject *obj)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(obj);
GList *iter = NULL;
purple_debug_info("backend-fs2", "purple_media_backend_fs2_dispose\n");
#ifndef HAVE_FARSIGHT
if (priv->notifier) {
g_object_unref(priv->notifier);
priv->notifier = NULL;
}
#endif
if (priv->confbin) {
GstElement *pipeline;
pipeline = purple_media_manager_get_pipeline(
purple_media_get_manager(priv->media));
/* All connections to media sources should be blocked before confbin is
* removed, to prevent freezing of any other simultaneously running
* media calls. */
if (priv->sessions) {
GList *sessions = g_hash_table_get_values(priv->sessions);
for (; sessions; sessions =
g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session = sessions->data;
if (session->srcpad) {
unlink_teepad_dynamic(session->srcpad, FALSE);
gst_object_unref(session->srcpad);
session->srcpad = NULL;
}
}
}
gst_element_set_locked_state(priv->confbin, TRUE);
gst_element_set_state(GST_ELEMENT(priv->confbin),
GST_STATE_NULL);
if (pipeline) {
GstBus *bus;
gst_bin_remove(GST_BIN(pipeline), priv->confbin);
bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
g_signal_handlers_disconnect_matched(G_OBJECT(bus),
G_SIGNAL_MATCH_FUNC |
G_SIGNAL_MATCH_DATA,
0, 0, 0, gst_bus_cb, obj);
gst_object_unref(bus);
} else {
purple_debug_warning("backend-fs2", "Unable to "
"properly dispose the conference. "
"Couldn't get the pipeline.\n");
}
priv->confbin = NULL;
priv->conference = NULL;
}
if (priv->sessions) {
GList *sessions = g_hash_table_get_values(priv->sessions);
for (; sessions; sessions =
g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session =
sessions->data;
if (session->session) {
g_object_unref(session->session);
session->session = NULL;
}
}
}
if (priv->participants) {
g_hash_table_destroy(priv->participants);
priv->participants = NULL;
}
for (iter = priv->streams; iter; iter = g_list_next(iter)) {
PurpleMediaBackendFs2Stream *stream = iter->data;
if (stream->stream) {
g_object_unref(stream->stream);
stream->stream = NULL;
}
}
if (priv->media) {
g_object_remove_weak_pointer(G_OBJECT(priv->media),
(gpointer*)&priv->media);
priv->media = NULL;
}
G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->dispose(obj);
}
static void
purple_media_backend_fs2_finalize(GObject *obj)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(obj);
purple_debug_info("backend-fs2", "purple_media_backend_fs2_finalize\n");
g_free(priv->conference_type);
for (; priv->streams; priv->streams =
g_list_delete_link(priv->streams, priv->streams)) {
PurpleMediaBackendFs2Stream *stream = priv->streams->data;
free_stream(stream);
}
if (priv->sessions) {
GList *sessions = g_hash_table_get_values(priv->sessions);
for (; sessions; sessions =
g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session =
sessions->data;
free_session(session);
}
g_hash_table_destroy(priv->sessions);
}
G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->finalize(obj);
}
static void
purple_media_backend_fs2_set_property(GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
PurpleMediaBackendFs2Private *priv;
g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object));
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(object);
switch (prop_id) {
case PROP_CONFERENCE_TYPE:
priv->conference_type = g_value_dup_string(value);
break;
case PROP_MEDIA:
priv->media = g_value_get_object(value);
if (priv->media == NULL)
break;
g_object_add_weak_pointer(G_OBJECT(priv->media),
(gpointer*)&priv->media);
g_signal_connect(G_OBJECT(priv->media),
"state-changed",
G_CALLBACK(state_changed_cb),
PURPLE_MEDIA_BACKEND_FS2(object));
g_signal_connect(G_OBJECT(priv->media), "stream-info",
G_CALLBACK(stream_info_cb),
PURPLE_MEDIA_BACKEND_FS2(object));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(
object, prop_id, pspec);
break;
}
}
static void
purple_media_backend_fs2_get_property(GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
PurpleMediaBackendFs2Private *priv;
g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object));
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(object);
switch (prop_id) {
case PROP_CONFERENCE_TYPE:
g_value_set_string(value, priv->conference_type);
break;
case PROP_MEDIA:
g_value_set_object(value, priv->media);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(
object, prop_id, pspec);
break;
}
}
static void
purple_media_backend_fs2_class_init(PurpleMediaBackendFs2Class *klass)
{
GObjectClass *gobject_class = (GObjectClass*)klass;
GList *features;
GList *it;
gobject_class->dispose = purple_media_backend_fs2_dispose;
gobject_class->finalize = purple_media_backend_fs2_finalize;
gobject_class->set_property = purple_media_backend_fs2_set_property;
gobject_class->get_property = purple_media_backend_fs2_get_property;
g_object_class_override_property(gobject_class, PROP_CONFERENCE_TYPE,
"conference-type");
g_object_class_override_property(gobject_class, PROP_MEDIA, "media");
g_type_class_add_private(klass, sizeof(PurpleMediaBackendFs2Private));
/* VA-API elements aren't well supported in Farstream. Ignore them. */
features = gst_registry_get_feature_list_by_plugin(gst_registry_get(),
"vaapi");
for (it = features; it; it = it->next) {
gst_plugin_feature_set_rank((GstPluginFeature *)it->data,
GST_RANK_NONE);
}
gst_plugin_feature_list_free(features);
}
static void
purple_media_backend_iface_init(PurpleMediaBackendIface *iface)
{
iface->add_stream = purple_media_backend_fs2_add_stream;
iface->add_remote_candidates =
purple_media_backend_fs2_add_remote_candidates;
iface->codecs_ready = purple_media_backend_fs2_codecs_ready;
iface->get_codecs = purple_media_backend_fs2_get_codecs;
iface->get_local_candidates =
purple_media_backend_fs2_get_local_candidates;
iface->set_remote_codecs = purple_media_backend_fs2_set_remote_codecs;
iface->set_send_codec = purple_media_backend_fs2_set_send_codec;
#if GST_CHECK_VERSION(1,0,0)
iface->set_encryption_parameters =
purple_media_backend_fs2_set_encryption_parameters;
iface->set_decryption_parameters =
purple_media_backend_fs2_set_decryption_parameters;
iface->set_require_encryption =
purple_media_backend_fs2_set_require_encryption;
#endif
iface->set_params = purple_media_backend_fs2_set_params;
iface->get_available_params = purple_media_backend_fs2_get_available_params;
iface->send_dtmf = purple_media_backend_fs2_send_dtmf;
iface->set_send_rtcp_mux = purple_media_backend_fs2_set_send_rtcp_mux;
}
static FsMediaType
session_type_to_fs_media_type(PurpleMediaSessionType type)
{
if (type & PURPLE_MEDIA_AUDIO)
return FS_MEDIA_TYPE_AUDIO;
else if (type & PURPLE_MEDIA_VIDEO)
return FS_MEDIA_TYPE_VIDEO;
#ifdef HAVE_MEDIA_APPLICATION
else if (type & PURPLE_MEDIA_APPLICATION)
return FS_MEDIA_TYPE_APPLICATION;
#endif
else
return 0;
}
static FsStreamDirection
session_type_to_fs_stream_direction(PurpleMediaSessionType type)
{
if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO ||
(type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
return FS_DIRECTION_BOTH;
else if ((type & PURPLE_MEDIA_SEND_AUDIO) ||
(type & PURPLE_MEDIA_SEND_VIDEO))
return FS_DIRECTION_SEND;
else if ((type & PURPLE_MEDIA_RECV_AUDIO) ||
(type & PURPLE_MEDIA_RECV_VIDEO))
return FS_DIRECTION_RECV;
#ifdef HAVE_MEDIA_APPLICATION
else if ((type & PURPLE_MEDIA_APPLICATION) == PURPLE_MEDIA_APPLICATION)
return FS_DIRECTION_BOTH;
else if (type & PURPLE_MEDIA_SEND_APPLICATION)
return FS_DIRECTION_SEND;
else if (type & PURPLE_MEDIA_RECV_APPLICATION)
return FS_DIRECTION_RECV;
#endif
else
return FS_DIRECTION_NONE;
}
static PurpleMediaSessionType
session_type_from_fs(FsMediaType type, FsStreamDirection direction)
{
PurpleMediaSessionType result = PURPLE_MEDIA_NONE;
if (type == FS_MEDIA_TYPE_AUDIO) {
if (direction & FS_DIRECTION_SEND)
result |= PURPLE_MEDIA_SEND_AUDIO;
if (direction & FS_DIRECTION_RECV)
result |= PURPLE_MEDIA_RECV_AUDIO;
} else if (type == FS_MEDIA_TYPE_VIDEO) {
if (direction & FS_DIRECTION_SEND)
result |= PURPLE_MEDIA_SEND_VIDEO;
if (direction & FS_DIRECTION_RECV)
result |= PURPLE_MEDIA_RECV_VIDEO;
#ifdef HAVE_MEDIA_APPLICATION
} else if (type == FS_MEDIA_TYPE_APPLICATION) {
if (direction & FS_DIRECTION_SEND)
result |= PURPLE_MEDIA_SEND_APPLICATION;
if (direction & FS_DIRECTION_RECV)
result |= PURPLE_MEDIA_RECV_APPLICATION;
#endif
}
return result;
}
static FsCandidate *
candidate_to_fs(PurpleMediaCandidate *candidate)
{
FsCandidate *fscandidate;
gchar *foundation;
guint component_id;
gchar *ip;
guint port;
gchar *base_ip;
guint base_port;
PurpleMediaNetworkProtocol proto;
guint32 priority;
PurpleMediaCandidateType type;
gchar *username;
gchar *password;
guint ttl;
if (candidate == NULL)
return NULL;
g_object_get(G_OBJECT(candidate),
"foundation", &foundation,
"component-id", &component_id,
"ip", &ip,
"port", &port,
"base-ip", &base_ip,
"base-port", &base_port,
"protocol", &proto,
"priority", &priority,
"type", &type,
"username", &username,
"password", &password,
"ttl", &ttl,
NULL);
fscandidate = fs_candidate_new(foundation,
component_id, purple_media_candidate_type_to_fs(type),
purple_media_network_protocol_to_fs(proto), ip, port);
fscandidate->base_ip = base_ip;
fscandidate->base_port = base_port;
fscandidate->priority = priority;
fscandidate->username = username;
fscandidate->password = password;
fscandidate->ttl = ttl;
g_free(foundation);
g_free(ip);
return fscandidate;
}
static GList *
candidate_list_to_fs(GList *candidates)
{
GList *new_list = NULL;
for (; candidates; candidates = g_list_next(candidates)) {
new_list = g_list_prepend(new_list,
candidate_to_fs(candidates->data));
}
new_list = g_list_reverse(new_list);
return new_list;
}
static PurpleMediaCandidate *
candidate_from_fs(FsCandidate *fscandidate)
{
PurpleMediaCandidate *candidate;
if (fscandidate == NULL)
return NULL;
candidate = purple_media_candidate_new(fscandidate->foundation,
fscandidate->component_id,
purple_media_candidate_type_from_fs(fscandidate->type),
purple_media_network_protocol_from_fs(fscandidate->proto),
fscandidate->ip, fscandidate->port);
g_object_set(candidate,
"base-ip", fscandidate->base_ip,
"base-port", fscandidate->base_port,
"priority", fscandidate->priority,
"username", fscandidate->username,
"password", fscandidate->password,
"ttl", fscandidate->ttl, NULL);
return candidate;
}
static GList *
candidate_list_from_fs(GList *candidates)
{
GList *new_list = NULL;
for (; candidates; candidates = g_list_next(candidates)) {
new_list = g_list_prepend(new_list,
candidate_from_fs(candidates->data));
}
new_list = g_list_reverse(new_list);
return new_list;
}
static FsCodec *
codec_to_fs(const PurpleMediaCodec *codec)
{
FsCodec *new_codec;
gint id;
char *encoding_name;
PurpleMediaSessionType media_type;
guint clock_rate;
guint channels;
GList *iter;
if (codec == NULL)
return NULL;
g_object_get(G_OBJECT(codec),
"id", &id,
"encoding-name", &encoding_name,
"media-type", &media_type,
"clock-rate", &clock_rate,
"channels", &channels,
"optional-params", &iter,
NULL);
new_codec = fs_codec_new(id, encoding_name,
session_type_to_fs_media_type(media_type),
clock_rate);
new_codec->channels = channels;
for (; iter; iter = g_list_next(iter)) {
PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data;
fs_codec_add_optional_parameter(new_codec,
param->key, param->value);
}
g_free(encoding_name);
return new_codec;
}
static PurpleMediaCodec *
codec_from_fs(const FsCodec *codec)
{
PurpleMediaCodec *new_codec;
GList *iter;
if (codec == NULL)
return NULL;
new_codec = purple_media_codec_new(codec->id, codec->encoding_name,
session_type_from_fs(codec->media_type,
FS_DIRECTION_BOTH), codec->clock_rate);
g_object_set(new_codec, "channels", codec->channels, NULL);
for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
FsCodecParameter *param = (FsCodecParameter*)iter->data;
purple_media_codec_add_optional_parameter(new_codec,
param->name, param->value);
}
return new_codec;
}
static GList *
codec_list_from_fs(GList *codecs)
{
GList *new_list = NULL;
for (; codecs; codecs = g_list_next(codecs)) {
new_list = g_list_prepend(new_list,
codec_from_fs(codecs->data));
}
new_list = g_list_reverse(new_list);
return new_list;
}
static GList *
codec_list_to_fs(GList *codecs)
{
GList *new_list = NULL;
for (; codecs; codecs = g_list_next(codecs)) {
new_list = g_list_prepend(new_list,
codec_to_fs(codecs->data));
}
new_list = g_list_reverse(new_list);
return new_list;
}
static PurpleMediaBackendFs2Session *
get_session(PurpleMediaBackendFs2 *self, const gchar *sess_id)
{
PurpleMediaBackendFs2Private *priv;
PurpleMediaBackendFs2Session *session = NULL;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
if (priv->sessions != NULL)
session = g_hash_table_lookup(priv->sessions, sess_id);
return session;
}
static FsParticipant *
get_participant(PurpleMediaBackendFs2 *self, const gchar *name)
{
PurpleMediaBackendFs2Private *priv;
FsParticipant *participant = NULL;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
if (priv->participants != NULL)
participant = g_hash_table_lookup(priv->participants, name);
return participant;
}
static PurpleMediaBackendFs2Stream *
get_stream(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *name)
{
PurpleMediaBackendFs2Private *priv;
GList *streams;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
streams = priv->streams;
for (; streams; streams = g_list_next(streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (purple_strequal(stream->session->id, sess_id) &&
purple_strequal(stream->participant, name))
return stream;
}
return NULL;
}
static GList *
get_streams(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *name)
{
PurpleMediaBackendFs2Private *priv;
GList *streams, *ret = NULL;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
streams = priv->streams;
for (; streams; streams = g_list_next(streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (sess_id != NULL && !purple_strequal(stream->session->id, sess_id))
continue;
else if (name != NULL && !purple_strequal(stream->participant, name))
continue;
else
ret = g_list_prepend(ret, stream);
}
ret = g_list_reverse(ret);
return ret;
}
static PurpleMediaBackendFs2Session *
get_session_from_fs_stream(PurpleMediaBackendFs2 *self, FsStream *stream)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
FsSession *fssession;
GList *values;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
g_return_val_if_fail(FS_IS_STREAM(stream), NULL);
g_object_get(stream, "session", &fssession, NULL);
values = g_hash_table_get_values(priv->sessions);
for (; values; values = g_list_delete_link(values, values)) {
PurpleMediaBackendFs2Session *session = values->data;
if (session->session == fssession) {
g_list_free(values);
g_object_unref(fssession);
return session;
}
}
g_object_unref(fssession);
return NULL;
}
static gdouble
gst_msg_db_to_percent(GstMessage *msg, gchar *value_name)
{
const GValue *list;
const GValue *value;
gdouble value_db;
gdouble percent;
list = gst_structure_get_value(gst_message_get_structure(msg), value_name);
#if GST_CHECK_VERSION(1,0,0)
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
value = g_value_array_get_nth(g_value_get_boxed(list), 0);
G_GNUC_END_IGNORE_DEPRECATIONS
#else
value = gst_value_list_get_value(list, 0);
#endif
value_db = g_value_get_double(value);
percent = pow(10, value_db / 20);
return (percent > 1.0) ? 1.0 : percent;
}
static void
purple_media_error_fs(PurpleMedia *media, const gchar *error,
const GstStructure *fs_error)
{
const gchar *error_msg = gst_structure_get_string(fs_error, "error-msg");
purple_media_error(media, "%s%s%s", error,
error_msg ? _("\n\nMessage from Farsight: ") : "",
error_msg ? error_msg : "");
}
static void
gst_handle_message_element(GstBus *bus, GstMessage *msg,
PurpleMediaBackendFs2 *self)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
static guint level_id = 0;
const GstStructure *structure = gst_message_get_structure(msg);
if (level_id == 0)
level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA);
if (gst_structure_has_name(structure, "level")) {
GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
gchar *name;
gchar *participant = NULL;
PurpleMediaBackendFs2Session *session = NULL;
gdouble percent;
if (!PURPLE_IS_MEDIA(priv->media) ||
GST_ELEMENT_PARENT(src) != priv->confbin)
return;
name = gst_element_get_name(src);
if (!strncmp(name, "sendlevel_", 10)) {
session = get_session(self, name+10);
if (priv->silence_threshold > 0) {
percent = gst_msg_db_to_percent(msg, "decay");
g_object_set(session->srcvalve,
"drop", (percent < priv->silence_threshold), NULL);
}
}
g_free(name);
if (!g_signal_has_handler_pending(priv->media, level_id, 0, FALSE))
return;
if (!session) {
GList *iter = priv->streams;
PurpleMediaBackendFs2Stream *stream;
for (; iter; iter = g_list_next(iter)) {
stream = iter->data;
if (stream->level == src) {
session = stream->session;
participant = stream->participant;
break;
}
}
}
if (!session)
return;
percent = gst_msg_db_to_percent(msg, "rms");
g_signal_emit(priv->media, level_id, 0,
session->id, participant, percent);
return;
}
if (!FS_IS_CONFERENCE(src) || !PURPLE_IS_MEDIA_BACKEND(self) ||
priv->conference != FS_CONFERENCE(src))
return;
#ifdef HAVE_FARSIGHT
if (gst_structure_has_name(structure, "farsight-error")) {
#else
if (gst_structure_has_name(structure, "farstream-error")) {
#endif
FsError error_no;
gboolean error_emitted = FALSE;
gst_structure_get_enum(structure, "error-no",
FS_TYPE_ERROR, (gint*)&error_no);
switch (error_no) {
case FS_ERROR_CONSTRUCTION:
purple_media_error_fs(priv->media,
_("Error initializing the call. "
"This probably denotes problem in "
#ifdef HAVE_FARSIGHT
"installation of GStreamer or Farsight."),
#else
"installation of GStreamer or Farstream."),
#endif
structure);
error_emitted = TRUE;
break;
case FS_ERROR_NETWORK:
purple_media_error_fs(priv->media, _("Network error."),
structure);
error_emitted = TRUE;
purple_media_end(priv->media, NULL, NULL);
break;
case FS_ERROR_NEGOTIATION_FAILED:
purple_media_error_fs(priv->media,
_("Codec negotiation failed. "
"This problem might be resolved by installing "
"more GStreamer codecs."),
structure);
error_emitted = TRUE;
purple_media_end(priv->media, NULL, NULL);
break;
case FS_ERROR_NO_CODECS:
purple_media_error(priv->media,
_("No codecs found. "
"Install some GStreamer codecs found "
"in GStreamer plugins packages."));
error_emitted = TRUE;
purple_media_end(priv->media, NULL, NULL);
break;
#ifdef HAVE_FARSIGHT
case FS_ERROR_NO_CODECS_LEFT:
purple_media_error(priv->media,
_("No codecs left. Your codec preferences "
"in fs-codecs.conf are too strict."));
error_emitted = TRUE;
purple_media_end(priv->media, NULL, NULL);
break;
case FS_ERROR_CONNECTION_FAILED:
purple_media_error(priv->media,
_("Could not connect to the remote party"));
error_emitted = TRUE;
break;
case FS_ERROR_UNKNOWN_CNAME:
/*
* Unknown CName is only a problem for the
* multicast transmitter which isn't used.
* It is also deprecated.
*/
break;
#endif
default:
purple_debug_error("backend-fs2",
#ifdef HAVE_FARSIGHT
"farsight-error: %i: %s\n",
#else
"farstream-error: %i: %s\n",
#endif
error_no,
gst_structure_get_string(structure, "error-msg"));
break;
}
if (FS_ERROR_IS_FATAL(error_no)) {
if (!error_emitted)
#ifdef HAVE_FARSIGHT
purple_media_error(priv->media,
_("A non-recoverable Farsight2 error has occurred."));
#else
purple_media_error(priv->media,
_("A non-recoverable Farstream error has occurred."));
#endif
purple_media_end(priv->media, NULL, NULL);
}
} else if (gst_structure_has_name(structure,
#ifdef HAVE_FARSIGHT
"farsight-new-local-candidate")) {
#else
"farstream-new-local-candidate")) {
#endif
const GValue *value;
FsStream *stream;
FsCandidate *local_candidate;
PurpleMediaCandidate *candidate;
FsParticipant *participant;
PurpleMediaBackendFs2Session *session;
PurpleMediaBackendFs2Stream *media_stream;
const gchar *name;
value = gst_structure_get_value(structure, "stream");
stream = g_value_get_object(value);
value = gst_structure_get_value(structure, "candidate");
local_candidate = g_value_get_boxed(value);
session = get_session_from_fs_stream(self, stream);
purple_debug_info("backend-fs2",
"got new local candidate: %s\n",
local_candidate->foundation);
g_object_get(stream, "participant", &participant, NULL);
name = g_object_get_data(G_OBJECT(participant), "purple-name");
media_stream = get_stream(self, session->id, name);
media_stream->local_candidates = g_list_append(
media_stream->local_candidates,
fs_candidate_copy(local_candidate));
candidate = candidate_from_fs(local_candidate);
g_signal_emit_by_name(self, "new-candidate",
session->id, name, candidate);
g_object_unref(candidate);
g_object_unref(participant);
} else if (gst_structure_has_name(structure,
#ifdef HAVE_FARSIGHT
"farsight-local-candidates-prepared")) {
#else
"farstream-local-candidates-prepared")) {
#endif
const GValue *value;
FsStream *stream;
FsParticipant *participant;
PurpleMediaBackendFs2Session *session;
value = gst_structure_get_value(structure, "stream");
stream = g_value_get_object(value);
session = get_session_from_fs_stream(self, stream);
g_object_get(stream, "participant", &participant, NULL);
g_signal_emit_by_name(self, "candidates-prepared",
session->id,
g_object_get_data(G_OBJECT(participant), "purple-name"));
g_object_unref(participant);
} else if (gst_structure_has_name(structure,
#ifdef HAVE_FARSIGHT
"farsight-new-active-candidate-pair")) {
#else
"farstream-new-active-candidate-pair")) {
#endif
const GValue *value;
FsStream *stream;
FsCandidate *local_candidate;
FsCandidate *remote_candidate;
FsParticipant *participant;
PurpleMediaBackendFs2Session *session;
PurpleMediaCandidate *lcandidate, *rcandidate;
value = gst_structure_get_value(structure, "stream");
stream = g_value_get_object(value);
value = gst_structure_get_value(structure, "local-candidate");
local_candidate = g_value_get_boxed(value);
value = gst_structure_get_value(structure, "remote-candidate");
remote_candidate = g_value_get_boxed(value);
g_object_get(stream, "participant", &participant, NULL);
session = get_session_from_fs_stream(self, stream);
lcandidate = candidate_from_fs(local_candidate);
rcandidate = candidate_from_fs(remote_candidate);
g_signal_emit_by_name(self, "active-candidate-pair",
session->id,
g_object_get_data(G_OBJECT(participant), "purple-name"),
lcandidate, rcandidate);
g_object_unref(participant);
g_object_unref(lcandidate);
g_object_unref(rcandidate);
} else if (gst_structure_has_name(structure,
#ifdef HAVE_FARSIGHT
"farsight-recv-codecs-changed")) {
#else
"farstream-recv-codecs-changed")) {
#endif
const GValue *value;
GList *codecs;
FsCodec *codec;
value = gst_structure_get_value(structure, "codecs");
codecs = g_value_get_boxed(value);
codec = codecs->data;
purple_debug_info("backend-fs2",
#ifdef HAVE_FARSIGHT
"farsight-recv-codecs-changed: %s\n",
#else
"farstream-recv-codecs-changed: %s\n",
#endif
codec->encoding_name);
} else if (gst_structure_has_name(structure,
#ifdef HAVE_FARSIGHT
"farsight-component-state-changed")) {
#else
"farstream-component-state-changed")) {
#endif
const GValue *value;
FsStreamState fsstate;
guint component;
const gchar *state;
value = gst_structure_get_value(structure, "state");
fsstate = g_value_get_enum(value);
value = gst_structure_get_value(structure, "component");
component = g_value_get_uint(value);
switch (fsstate) {
case FS_STREAM_STATE_FAILED:
state = "FAILED";
break;
case FS_STREAM_STATE_DISCONNECTED:
state = "DISCONNECTED";
break;
case FS_STREAM_STATE_GATHERING:
state = "GATHERING";
break;
case FS_STREAM_STATE_CONNECTING:
state = "CONNECTING";
break;
case FS_STREAM_STATE_CONNECTED:
state = "CONNECTED";
break;
case FS_STREAM_STATE_READY:
state = "READY";
break;
default:
state = "UNKNOWN";
break;
}
purple_debug_info("backend-fs2",
#ifdef HAVE_FARSIGHT
"farsight-component-state-changed: "
#else
"farstream-component-state-changed: "
#endif
"component: %u state: %s\n",
component, state);
} else if (gst_structure_has_name(structure,
#ifdef HAVE_FARSIGHT
"farsight-send-codec-changed")) {
#else
"farstream-send-codec-changed")) {
#endif
const GValue *value;
FsCodec *codec;
gchar *codec_str;
value = gst_structure_get_value(structure, "codec");
codec = g_value_get_boxed(value);
codec_str = fs_codec_to_string(codec);
purple_debug_info("backend-fs2",
#ifdef HAVE_FARSIGHT
"farsight-send-codec-changed: codec: %s\n",
#else
"farstream-send-codec-changed: codec: %s\n",
#endif
codec_str);
g_free(codec_str);
} else if (gst_structure_has_name(structure,
#ifdef HAVE_FARSIGHT
"farsight-codecs-changed")) {
#else
"farstream-codecs-changed")) {
#endif
const GValue *value;
FsSession *fssession;
GList *sessions;
value = gst_structure_get_value(structure, "session");
fssession = g_value_get_object(value);
sessions = g_hash_table_get_values(priv->sessions);
for (; sessions; sessions =
g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session = sessions->data;
gchar *session_id;
if (session->session != fssession)
continue;
session_id = g_strdup(session->id);
g_signal_emit_by_name(self, "codecs-changed",
session_id);
g_free(session_id);
g_list_free(sessions);
break;
}
}
}
static gboolean
downgrade_video_source(PurpleMediaBackendFs2 *self, GstBin *srcbin)
{
PurpleMediaBackendFs2Private *priv;
GstElement *src;
GstPad *srcpad = NULL;
GstPad *p;
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
src = gst_bin_get_by_name(srcbin, "purplevideodowngrade");
if (src) {
/* The failing source has already been downgraded. Stop here in
* order not to cause an infinite loop. */
gst_object_unref(src);
return FALSE;
}
src = gst_bin_get_by_name(srcbin, "tee");
if (!src) {
return FALSE;
}
while((p = gst_element_get_static_pad(src, "sink"))) {
if (srcpad) {
gst_object_unref(srcpad);
}
srcpad = gst_pad_get_peer(p);
gst_object_unref(src);
src = gst_pad_get_parent_element(srcpad);
gst_object_unref(p);
};
if (srcpad) {
PurpleMediaManager *manager;
GstElement *pipeline;
GstElement *dest;
GstElement *newsrc;
manager = purple_media_get_manager(priv->media);
pipeline = purple_media_manager_get_pipeline(manager);
dest = gst_pad_get_parent_element(GST_PAD_PEER(srcpad));
remove_element(src);
newsrc = gst_element_factory_make("videotestsrc",
"purplevideodowngrade");
g_object_set(newsrc, "is-live", TRUE, NULL);
gst_bin_add_many(srcbin, newsrc, NULL);
gst_element_link(newsrc, dest);
gst_element_set_state(newsrc, GST_STATE_PLAYING);
/* Pipeline with an error might not be any longer PLAYING. */
gst_element_set_state(pipeline, GST_STATE_PLAYING);
gst_object_unref(srcpad);
/* Flush the video pipeline. */
srcpad = gst_element_get_static_pad(newsrc, "src");
gst_pad_push_event(srcpad, gst_event_new_flush_start());
#if GST_CHECK_VERSION(1,0,0)
gst_pad_push_event(srcpad, gst_event_new_flush_stop(FALSE));
#else
gst_pad_push_event(srcpad, gst_event_new_flush_stop());
#endif
gst_object_unref(srcpad);
gst_object_unref(dest);
}
gst_object_unref(src);
return TRUE;
}
static void
gst_handle_message_error(GstBus *bus, GstMessage *msg,
PurpleMediaBackendFs2 *self)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg));
GstElement *lastElement = NULL;
GList *sessions;
gboolean fatal = TRUE;
GError *error = NULL;
gchar *debug_msg = NULL;
gst_message_parse_error(msg, &error, &debug_msg);
purple_debug_error("backend-fs2", "gst error %s\ndebugging: %s\n",
error->message, debug_msg);
g_error_free(error);
g_free(debug_msg);
while (element && !GST_IS_PIPELINE(element)) {
if (element == priv->confbin)
break;
lastElement = element;
element = GST_ELEMENT_PARENT(element);
}
if (!element || !GST_IS_PIPELINE(element))
return;
sessions = purple_media_get_session_ids(priv->media);
for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
PurpleMediaSessionType session_type;
if (purple_media_get_src(priv->media, sessions->data)
!= lastElement)
continue;
session_type = purple_media_get_session_type(priv->media,
sessions->data);
if (session_type & PURPLE_MEDIA_AUDIO) {
purple_media_error(priv->media,
_("Error with your microphone"));
} else if (session_type & PURPLE_MEDIA_VIDEO) {
fatal = !downgrade_video_source(self,
GST_BIN(lastElement));
if (fatal) {
purple_media_error(priv->media,
_("Error with your webcam"));
}
}
break;
}
g_list_free(sessions);
if (fatal) {
purple_media_error(priv->media, _("Conference error"));
purple_media_end(priv->media, NULL, NULL);
}
}
static gboolean
gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self)
{
switch(GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ELEMENT:
gst_handle_message_element(bus, msg, self);
break;
case GST_MESSAGE_ERROR:
gst_handle_message_error(bus, msg, self);
break;
default:
break;
}
return TRUE;
}
static void
remove_element(GstElement *element)
{
if (element) {
gst_element_set_locked_state(element, TRUE);
gst_element_set_state(element, GST_STATE_NULL);
gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
}
}
static void
state_changed_cb(PurpleMedia *media, PurpleMediaState state,
gchar *sid, gchar *name, PurpleMediaBackendFs2 *self)
{
if (state == PURPLE_MEDIA_STATE_END) {
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
if (sid && name) {
PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name);
gst_object_unref(stream->stream);
priv->streams = g_list_remove(priv->streams, stream);
remove_element(stream->src);
remove_element(stream->tee);
remove_element(stream->volume);
remove_element(stream->level);
remove_element(stream->fakesink);
remove_element(stream->queue);
free_stream(stream);
} else if (sid && !name) {
PurpleMediaBackendFs2Session *session = get_session(self, sid);
GstPad *pad;
g_object_get(session->session, "sink-pad", &pad, NULL);
gst_pad_unlink(GST_PAD_PEER(pad), pad);
gst_object_unref(pad);
gst_object_unref(session->session);
g_hash_table_remove(priv->sessions, session->id);
if (session->srcpad) {
pad = gst_pad_get_peer(session->srcpad);
if (pad) {
gst_element_remove_pad(
GST_PAD_PARENT(pad),
pad);
gst_object_unref(pad);
}
gst_object_unref(session->srcpad);
}
remove_element(session->srcvalve);
remove_element(session->tee);
free_session(session);
}
purple_media_manager_remove_output_windows(
purple_media_get_manager(media), media, sid, name);
}
}
static void
stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
gchar *sid, gchar *name, gboolean local,
PurpleMediaBackendFs2 *self)
{
if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) {
PurpleMediaBackendFs2Stream *stream =
get_stream(self, sid, name);
GError *err = NULL;
g_object_set(G_OBJECT(stream->stream), "direction",
session_type_to_fs_stream_direction(
stream->session->type), NULL);
if (stream->remote_candidates == NULL ||
purple_media_is_initiator(media, sid, name))
return;
#ifdef HAVE_FARSIGHT
fs_stream_set_remote_candidates(stream->stream,
stream->remote_candidates, &err);
#else
if (stream->supports_add)
fs_stream_add_remote_candidates(stream->stream,
stream->remote_candidates, &err);
else
fs_stream_force_remote_candidates(stream->stream,
stream->remote_candidates, &err);
#endif
if (err == NULL)
return;
purple_debug_error("backend-fs2", "Error adding "
"remote candidates: %s\n",
err->message);
g_error_free(err);
} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE ||
type == PURPLE_MEDIA_INFO_UNMUTE)) {
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
gboolean active = (type == PURPLE_MEDIA_INFO_MUTE);
GList *sessions;
if (sid == NULL)
sessions = g_hash_table_get_values(priv->sessions);
else
sessions = g_list_prepend(NULL,
get_session(self, sid));
purple_debug_info("media", "Turning mute %s\n",
active ? "on" : "off");
for (; sessions; sessions = g_list_delete_link(
sessions, sessions)) {
PurpleMediaBackendFs2Session *session =
sessions->data;
if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
gchar *name = g_strdup_printf("volume_%s",
session->id);
GstElement *volume = gst_bin_get_by_name(
GST_BIN(priv->confbin), name);
g_free(name);
g_object_set(volume, "mute", active, NULL);
}
}
} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
type == PURPLE_MEDIA_INFO_UNHOLD)) {
gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
GList *streams = get_streams(self, sid, name);
for (; streams; streams =
g_list_delete_link(streams, streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (stream->session->type & PURPLE_MEDIA_SEND_AUDIO) {
g_object_set(stream->stream, "direction",
session_type_to_fs_stream_direction(
stream->session->type & ((active) ?
~PURPLE_MEDIA_SEND_AUDIO :
PURPLE_MEDIA_AUDIO)), NULL);
}
}
} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE ||
type == PURPLE_MEDIA_INFO_UNPAUSE)) {
gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE);
GList *streams = get_streams(self, sid, name);
for (; streams; streams =
g_list_delete_link(streams, streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
g_object_set(stream->stream, "direction",
session_type_to_fs_stream_direction(
stream->session->type & ((active) ?
~PURPLE_MEDIA_SEND_VIDEO :
PURPLE_MEDIA_VIDEO)), NULL);
}
}
}
}
static gboolean
init_conference(PurpleMediaBackendFs2 *self)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
GstElement *pipeline;
GstBus *bus;
gchar *name;
#ifndef HAVE_FARSIGHT
GKeyFile *default_props;
#endif
priv->conference = FS_CONFERENCE(
gst_element_factory_make(priv->conference_type, NULL));
if (priv->conference == NULL) {
purple_debug_error("backend-fs2", "Conference == NULL\n");
return FALSE;
}
if (purple_account_get_silence_suppression(
purple_media_get_account(priv->media)))
priv->silence_threshold = purple_prefs_get_int(
"/purple/media/audio/silence_threshold") / 100.0;
else
priv->silence_threshold = 0;
pipeline = purple_media_manager_get_pipeline(
purple_media_get_manager(priv->media));
if (pipeline == NULL) {
purple_debug_error("backend-fs2",
"Couldn't retrieve pipeline.\n");
return FALSE;
}
name = g_strdup_printf("conf_%p", priv->conference);
priv->confbin = gst_bin_new(name);
if (priv->confbin == NULL) {
purple_debug_error("backend-fs2",
"Couldn't create confbin.\n");
return FALSE;
}
g_free(name);
bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
if (bus == NULL) {
purple_debug_error("backend-fs2",
"Couldn't get the pipeline's bus.\n");
return FALSE;
}
#ifndef HAVE_FARSIGHT
default_props = fs_utils_get_default_element_properties(GST_ELEMENT(priv->conference));
if (default_props != NULL) {
priv->notifier = fs_element_added_notifier_new();
fs_element_added_notifier_add(priv->notifier,
GST_BIN(priv->confbin));
fs_element_added_notifier_set_properties_from_keyfile(priv->notifier, default_props);
}
#endif
g_signal_connect(G_OBJECT(bus), "message",
G_CALLBACK(gst_bus_cb), self);
gst_object_unref(bus);
if (!gst_bin_add(GST_BIN(pipeline),
GST_ELEMENT(priv->confbin))) {
purple_debug_error("backend-fs2", "Couldn't add confbin "
"element to the pipeline\n");
return FALSE;
}
if (!gst_bin_add(GST_BIN(priv->confbin),
GST_ELEMENT(priv->conference))) {
purple_debug_error("backend-fs2", "Couldn't add conference "
"element to the confbin\n");
return FALSE;
}
if (gst_element_set_state(GST_ELEMENT(priv->confbin),
GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
purple_debug_error("backend-fs2",
"Failed to start conference.\n");
return FALSE;
}
return TRUE;
}
static void
gst_element_added_cb(FsElementAddedNotifier *self,
GstBin *bin, GstElement *element, gpointer user_data)
{
/*
* Hack to make H264 work with Gmail video.
*/
if (!strncmp(GST_ELEMENT_NAME(element), "x264", 4)) {
g_object_set(GST_OBJECT(element), "cabac", FALSE, NULL);
}
}
static gboolean
create_src(PurpleMediaBackendFs2 *self, const gchar *sess_id,
PurpleMediaSessionType type)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
PurpleMediaBackendFs2Session *session;
PurpleMediaSessionType session_type;
FsMediaType media_type = session_type_to_fs_media_type(type);
FsStreamDirection type_direction =
session_type_to_fs_stream_direction(type);
GstElement *src;
GstPad *sinkpad, *srcpad;
GstPad *ghost = NULL;
if ((type_direction & FS_DIRECTION_SEND) == 0)
return TRUE;
session_type = session_type_from_fs(
media_type, FS_DIRECTION_SEND);
src = purple_media_manager_get_element(
purple_media_get_manager(priv->media),
session_type, priv->media, sess_id, NULL);
if (!GST_IS_ELEMENT(src)) {
purple_debug_error("backend-fs2",
"Error creating src for session %s\n",
sess_id);
return FALSE;
}
session = get_session(self, sess_id);
if (session == NULL) {
purple_debug_warning("backend-fs2",
"purple_media_set_src: trying to set"
" src on non-existent session\n");
return FALSE;
}
if (session->src)
gst_object_unref(session->src);
session->src = src;
gst_element_set_locked_state(session->src, TRUE);
session->tee = gst_element_factory_make("tee", NULL);
gst_bin_add(GST_BIN(priv->confbin), session->tee);
/* This supposedly isn't necessary, but it silences some warnings */
if (GST_ELEMENT_PARENT(priv->confbin)
== GST_ELEMENT_PARENT(session->src)) {
GstPad *pad = gst_element_get_static_pad(session->tee, "sink");
ghost = gst_ghost_pad_new(NULL, pad);
gst_object_unref(pad);
gst_pad_set_active(ghost, TRUE);
gst_element_add_pad(priv->confbin, ghost);
}
gst_element_set_state(session->tee, GST_STATE_PLAYING);
gst_element_link(session->src, priv->confbin);
if (ghost)
session->srcpad = gst_pad_get_peer(ghost);
g_object_get(session->session, "sink-pad", &sinkpad, NULL);
if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
gchar *name = g_strdup_printf("volume_%s", session->id);
GstElement *level;
GstElement *volume = gst_element_factory_make("volume", name);
double input_volume = purple_prefs_get_int(
"/purple/media/audio/volume/input")/10.0;
g_free(name);
name = g_strdup_printf("sendlevel_%s", session->id);
level = gst_element_factory_make("level", name);
g_free(name);
session->srcvalve = gst_element_factory_make("valve", NULL);
gst_bin_add(GST_BIN(priv->confbin), volume);
gst_bin_add(GST_BIN(priv->confbin), level);
gst_bin_add(GST_BIN(priv->confbin), session->srcvalve);
gst_element_set_state(level, GST_STATE_PLAYING);
gst_element_set_state(volume, GST_STATE_PLAYING);
gst_element_set_state(session->srcvalve, GST_STATE_PLAYING);
gst_element_link(level, session->srcvalve);
gst_element_link(volume, level);
gst_element_link(session->tee, volume);
srcpad = gst_element_get_static_pad(session->srcvalve, "src");
g_object_set(volume, "volume", input_volume, NULL);
} else {
#if GST_CHECK_VERSION(1,0,0)
srcpad = gst_element_get_request_pad(session->tee, "src_%u");
#else
srcpad = gst_element_get_request_pad(session->tee, "src%d");
#endif
}
purple_debug_info("backend-fs2", "connecting pad: %s\n",
gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
? "success" : "failure");
gst_element_set_locked_state(session->src, FALSE);
gst_object_unref(session->src);
gst_object_unref(sinkpad);
gst_object_unref(srcpad);
purple_media_manager_create_output_window(purple_media_get_manager(
priv->media), priv->media, sess_id, NULL);
purple_debug_info("backend-fs2", "create_src: setting source "
"state to GST_STATE_PLAYING - it may hang here on win32\n");
gst_element_set_state(session->src, GST_STATE_PLAYING);
purple_debug_info("backend-fs2", "create_src: state set\n");
return TRUE;
}
static gboolean
create_session(PurpleMediaBackendFs2 *self, const gchar *sess_id,
PurpleMediaSessionType type, gboolean initiator,
const gchar *transmitter)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
PurpleMediaBackendFs2Session *session;
GError *err = NULL;
GList *codec_conf = NULL, *iter = NULL;
gchar *filename = NULL;
gboolean is_nice = purple_strequal(transmitter, "nice");
session = g_new0(PurpleMediaBackendFs2Session, 1);
session->session = fs_conference_new_session(priv->conference,
session_type_to_fs_media_type(type), &err);
#ifdef HAVE_MEDIA_APPLICATION
if (type == PURPLE_MEDIA_APPLICATION) {
GstCaps *caps;
GObject *rtpsession = NULL;
caps = gst_caps_new_empty_simple ("application/octet-stream");
fs_session_set_allowed_caps (session->session, caps, caps, NULL);
gst_caps_unref (caps);
g_object_get (session->session, "internal-session", &rtpsession, NULL);
if (rtpsession) {
g_object_set (rtpsession, "probation", 0, NULL);
g_object_unref (rtpsession);
}
}
#endif
if (err != NULL) {
purple_media_error(priv->media,
_("Error creating session: %s"),
err->message);
g_error_free(err);
g_free(session);
return FALSE;
}
filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
codec_conf = fs_codec_list_from_keyfile(filename, &err);
g_free(filename);
if (err != NULL) {
if (err->code == 4)
purple_debug_info("backend-fs2", "Couldn't read "
"fs-codec.conf: %s\n",
err->message);
else
purple_debug_error("backend-fs2", "Error reading "
"fs-codec.conf: %s\n",
err->message);
g_error_free(err);
}
/*
* Add SPEEX if the configuration file doesn't exist or
* there isn't a speex entry.
*/
for (iter = codec_conf; iter; iter = g_list_next(iter)) {
FsCodec *codec = iter->data;
if (!g_ascii_strcasecmp(codec->encoding_name, "speex"))
break;
}
if (iter == NULL) {
codec_conf = g_list_prepend(codec_conf,
fs_codec_new(FS_CODEC_ID_ANY,
"SPEEX", FS_MEDIA_TYPE_AUDIO, 8000));
codec_conf = g_list_prepend(codec_conf,
fs_codec_new(FS_CODEC_ID_ANY,
"SPEEX", FS_MEDIA_TYPE_AUDIO, 16000));
}
fs_session_set_codec_preferences(session->session, codec_conf, NULL);
fs_codec_list_destroy(codec_conf);
/*
* Removes a 5-7 second delay before
* receiving the src-pad-added signal.
* Only works for non-multicast FsRtpSessions.
*/
if (!purple_strequal(transmitter, "multicast"))
g_object_set(G_OBJECT(session->session),
"no-rtcp-timeout", 0, NULL);
/*
* Hack to make x264 work with Gmail video.
*/
if (is_nice && purple_strequal(sess_id, "google-video")) {
FsElementAddedNotifier *notifier =
fs_element_added_notifier_new();
g_signal_connect(G_OBJECT(notifier), "element-added",
G_CALLBACK(gst_element_added_cb), NULL);
fs_element_added_notifier_add(notifier,
GST_BIN(priv->conference));
}
session->id = g_strdup(sess_id);
session->backend = self;
session->type = type;
if (!priv->sessions) {
purple_debug_info("backend-fs2",
"Creating hash table for sessions\n");
priv->sessions = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
}
g_hash_table_insert(priv->sessions, g_strdup(session->id), session);
if (!create_src(self, sess_id, type)) {
purple_debug_info("backend-fs2", "Error creating the src\n");
return FALSE;
}
return TRUE;
}
static void
free_session(PurpleMediaBackendFs2Session *session)
{
g_free(session->id);
g_free(session);
}
static gboolean
create_participant(PurpleMediaBackendFs2 *self, const gchar *name)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
FsParticipant *participant;
GError *err = NULL;
participant = fs_conference_new_participant(
#ifdef HAVE_FARSIGHT
priv->conference, name, &err);
#else
priv->conference, &err);
#endif
if (err) {
purple_debug_error("backend-fs2",
"Error creating participant: %s\n",
err->message);
g_error_free(err);
return FALSE;
}
g_object_set_data_full(G_OBJECT(participant), "purple-name",
g_strdup(name), g_free);
#ifndef HAVE_FARSIGHT
if (g_object_class_find_property(G_OBJECT_GET_CLASS(participant),
"cname")) {
g_object_set(participant, "cname", name, NULL);
}
#endif
if (!priv->participants) {
purple_debug_info("backend-fs2",
"Creating hash table for participants\n");
priv->participants = g_hash_table_new_full(g_str_hash,
g_str_equal, g_free, g_object_unref);
}
g_hash_table_insert(priv->participants, g_strdup(name), participant);
return TRUE;
}
static gboolean
src_pad_added_cb_cb(PurpleMediaBackendFs2Stream *stream)
{
PurpleMediaBackendFs2Private *priv;
g_return_val_if_fail(stream != NULL, FALSE);
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(stream->session->backend);
stream->connected_cb_id = 0;
if (stream->src == NULL) {
GstElement *pipeline = purple_media_manager_get_pipeline(
purple_media_get_manager(priv->media));
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(pipeline),
GST_DEBUG_GRAPH_SHOW_ALL, "media-fail");
purple_media_error(priv->media,
_("Could not create media pipeline"));
purple_media_end(priv->media, NULL, NULL);
return FALSE;
}
purple_media_manager_create_output_window(
purple_media_get_manager(priv->media), priv->media,
stream->session->id, stream->participant);
g_signal_emit_by_name(priv->media, "state-changed",
PURPLE_MEDIA_STATE_CONNECTED,
stream->session->id, stream->participant);
return FALSE;
}
static void
src_pad_added_cb(FsStream *fsstream, GstPad *srcpad,
FsCodec *codec, PurpleMediaBackendFs2Stream *stream)
{
PurpleMediaBackendFs2Private *priv;
GstPad *sinkpad;
g_return_if_fail(FS_IS_STREAM(fsstream));
g_return_if_fail(stream != NULL);
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(stream->session->backend);
if (stream->src == NULL) {
GstElement *sink = NULL;
if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
double output_volume = purple_prefs_get_int(
"/purple/media/audio/volume/output")/10.0;
stream->queue = gst_element_factory_make("queue", NULL);
stream->volume = gst_element_factory_make("volume", NULL);
g_object_set(stream->volume, "volume", output_volume, NULL);
stream->level = gst_element_factory_make("level", NULL);
stream->src = gst_element_factory_make("audiomixer", NULL);
g_object_set(stream->src, "start-time-selection", 1, NULL);
sink = purple_media_manager_get_element(
purple_media_get_manager(priv->media),
PURPLE_MEDIA_RECV_AUDIO, priv->media,
stream->session->id,
stream->participant);
gst_bin_add(GST_BIN(priv->confbin), stream->queue);
gst_bin_add(GST_BIN(priv->confbin), stream->volume);
gst_bin_add(GST_BIN(priv->confbin), stream->level);
gst_bin_add(GST_BIN(priv->confbin), sink);
gst_element_set_state(sink, GST_STATE_PLAYING);
gst_element_set_state(stream->level, GST_STATE_PLAYING);
gst_element_set_state(stream->volume, GST_STATE_PLAYING);
gst_element_set_state(stream->queue, GST_STATE_PLAYING);
gst_element_link(stream->level, sink);
gst_element_link(stream->volume, stream->level);
gst_element_link(stream->queue, stream->volume);
sink = stream->queue;
} else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) {
#if GST_CHECK_VERSION(1,0,0)
stream->src = gst_element_factory_make("funnel", NULL);
#else
stream->src = gst_element_factory_make("fsfunnel", NULL);
#endif
sink = gst_element_factory_make("fakesink", NULL);
g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
gst_bin_add(GST_BIN(priv->confbin), sink);
gst_element_set_state(sink, GST_STATE_PLAYING);
stream->fakesink = sink;
#ifdef HAVE_MEDIA_APPLICATION
} else if (codec->media_type == FS_MEDIA_TYPE_APPLICATION) {
#if GST_CHECK_VERSION(1,0,0)
stream->src = gst_element_factory_make("funnel", NULL);
#else
stream->src = gst_element_factory_make("fsfunnel", NULL);
#endif
sink = purple_media_manager_get_element(
purple_media_get_manager(priv->media),
PURPLE_MEDIA_RECV_APPLICATION, priv->media,
stream->session->id,
stream->participant);
gst_bin_add(GST_BIN(priv->confbin), sink);
gst_element_set_state(sink, GST_STATE_PLAYING);
#endif
}
stream->tee = gst_element_factory_make("tee", NULL);
gst_bin_add_many(GST_BIN(priv->confbin),
stream->src, stream->tee, NULL);
gst_element_set_state(stream->tee, GST_STATE_PLAYING);
gst_element_set_state(stream->src, GST_STATE_PLAYING);
gst_element_link_many(stream->src, stream->tee, sink, NULL);
} else {
if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
GstElement *convert, *resample, *capsfilter;
GstPad *mixer_srcpad;
GstCaps *caps;
/* The audiomixer element requires that all input
* streams have the same rate, so resample if
* needed
*/
mixer_srcpad = gst_element_get_static_pad(stream->src, "src");
caps = gst_pad_get_current_caps(mixer_srcpad);
if (caps) {
convert = gst_element_factory_make("audioconvert", NULL);
resample = gst_element_factory_make("audioresample", NULL);
capsfilter = gst_element_factory_make("capsfilter", NULL);
gst_bin_add_many(GST_BIN(priv->confbin), convert,
resample, capsfilter, NULL);
gst_element_link_many(gst_pad_get_parent_element(srcpad),
convert, resample, capsfilter, NULL);
g_object_set(capsfilter, "caps", caps, NULL);
gst_element_set_state(convert, GST_STATE_PLAYING);
gst_element_set_state(resample, GST_STATE_PLAYING);
gst_element_set_state(capsfilter, GST_STATE_PLAYING);
srcpad = gst_element_get_static_pad(capsfilter, "src");
gst_object_unref(caps);
}
gst_object_unref(mixer_srcpad);
}
}
#if GST_CHECK_VERSION(1,0,0)
sinkpad = gst_element_get_request_pad(stream->src, "sink_%u");
#else
sinkpad = gst_element_get_request_pad(stream->src, "sink%d");
#endif
gst_pad_link(srcpad, sinkpad);
gst_object_unref(sinkpad);
stream->connected_cb_id = purple_timeout_add(0,
(GSourceFunc)src_pad_added_cb_cb, stream);
}
#ifdef HAVE_FARSIGHT
static GValueArray *
append_relay_info(GValueArray *relay_info, const gchar *ip, gint port,
const gchar *username, const gchar *password, const gchar *type)
{
GValue value;
GstStructure *turn_setup = 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,
"relay-type", G_TYPE_STRING, type,
NULL);
if (turn_setup) {
memset(&value, 0, sizeof(GValue));
g_value_init(&value, GST_TYPE_STRUCTURE);
gst_value_set_structure(&value, turn_setup);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
relay_info = g_value_array_append(relay_info, &value);
G_GNUC_END_IGNORE_DEPRECATIONS
gst_structure_free(turn_setup);
}
return relay_info;
}
#endif
static gboolean
create_stream(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *who,
PurpleMediaSessionType type, gboolean initiator,
const gchar *transmitter,
guint num_params, GParameter *params)
{
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
GError *err = NULL;
FsStream *fsstream = NULL;
const gchar *stun_ip = purple_network_get_stun_ip();
const gchar *turn_ip = purple_network_get_turn_ip();
guint _num_params = num_params;
GParameter *_params;
FsStreamDirection type_direction =
session_type_to_fs_stream_direction(type);
PurpleMediaBackendFs2Session *session;
PurpleMediaBackendFs2Stream *stream;
FsParticipant *participant;
/* check if the prpl has already specified a relay-info
we need to do this to allow them to override when using non-standard
TURN modes, like Google f.ex. */
gboolean got_turn_from_prpl = FALSE;
guint i;
#ifndef HAVE_FARSIGHT
GPtrArray *relay_info = g_ptr_array_new_full (1, (GDestroyNotify) gst_structure_free);
gboolean ret;
#endif
session = get_session(self, sess_id);
if (session == NULL) {
purple_debug_error("backend-fs2",
"Couldn't find session to create stream.\n");
return FALSE;
}
participant = get_participant(self, who);
if (participant == NULL) {
purple_debug_error("backend-fs2", "Couldn't find "
"participant to create stream.\n");
return FALSE;
}
#ifndef HAVE_FARSIGHT
fsstream = fs_session_new_stream(session->session, participant,
initiator == TRUE ? type_direction :
(type_direction & FS_DIRECTION_RECV), &err);
if (fsstream == NULL) {
if (err) {
purple_debug_error("backend-fs2",
"Error creating stream: %s\n",
err && err->message ?
err->message : "NULL");
g_error_free(err);
} else
purple_debug_error("backend-fs2",
"Error creating stream\n");
return FALSE;
}
#endif
for (i = 0 ; i < num_params ; i++) {
if (purple_strequal(params[i].name, "relay-info")) {
got_turn_from_prpl = TRUE;
break;
}
}
_params = g_new0(GParameter, num_params + 3);
memcpy(_params, params, sizeof(GParameter) * num_params);
/* set the controlling mode parameter */
_params[_num_params].name = "controlling-mode";
g_value_init(&_params[_num_params].value, G_TYPE_BOOLEAN);
g_value_set_boolean(&_params[_num_params].value, initiator);
++_num_params;
if (stun_ip) {
purple_debug_info("backend-fs2",
"Setting stun-ip on new stream: %s\n", stun_ip);
_params[_num_params].name = "stun-ip";
g_value_init(&_params[_num_params].value, G_TYPE_STRING);
g_value_set_string(&_params[_num_params].value, stun_ip);
++_num_params;
}
if (turn_ip && purple_strequal("nice", transmitter) && !got_turn_from_prpl) {
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
#ifdef HAVE_FARSIGHT
GValueArray *relay_info = g_value_array_new(0);
#endif
G_GNUC_END_IGNORE_DEPRECATIONS
gint port;
const gchar *username = purple_prefs_get_string(
"/purple/network/turn_username");
const gchar *password = purple_prefs_get_string(
"/purple/network/turn_password");
/* UDP */
port = purple_prefs_get_int("/purple/network/turn_port");
if (port > 0) {
#ifdef HAVE_FARSIGHT
relay_info = append_relay_info(relay_info, turn_ip, port, username,
password, "udp");
#else
g_ptr_array_add (relay_info,
gst_structure_new ("relay-info",
"ip", G_TYPE_STRING, turn_ip,
"port", G_TYPE_UINT, port,
"username", G_TYPE_STRING, username,
"password", G_TYPE_STRING, password,
"relay-type", G_TYPE_STRING, "udp",
NULL));
#endif
}
/* TCP */
port = purple_prefs_get_int("/purple/network/turn_port_tcp");
if (port > 0) {
#ifdef HAVE_FARSIGHT
relay_info = append_relay_info(relay_info, turn_ip, port, username,
password, "tcp");
#else
g_ptr_array_add (relay_info,
gst_structure_new ("relay-info",
"ip", G_TYPE_STRING, turn_ip,
"port", G_TYPE_UINT, port,
"username", G_TYPE_STRING, username,
"password", G_TYPE_STRING, password,
"relay-type", G_TYPE_STRING, "tcp",
NULL));
#endif
}
/* TURN over SSL is only supported by libnice for Google's "psuedo" SSL mode
at this time */
purple_debug_info("backend-fs2",
"Setting relay-info on new stream\n");
_params[_num_params].name = "relay-info";
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
#ifdef HAVE_FARSIGHT
g_value_init(&_params[_num_params].value, G_TYPE_VALUE_ARRAY);
g_value_set_boxed(&_params[_num_params].value, relay_info);
g_value_array_free(relay_info);
#else
g_value_init(&_params[_num_params].value, G_TYPE_PTR_ARRAY);
g_value_set_boxed(&_params[_num_params].value, relay_info);
#endif
G_GNUC_END_IGNORE_DEPRECATIONS
_num_params++;
}
#ifdef HAVE_FARSIGHT
fsstream = fs_session_new_stream(session->session, participant,
initiator == TRUE ? type_direction :
(type_direction & FS_DIRECTION_RECV), transmitter,
_num_params, _params, &err);
g_free(_params);
if (fsstream == NULL) {
if (err) {
purple_debug_error("backend-fs2",
"Error creating stream: %s\n",
err && err->message ?
err->message : "NULL");
g_error_free(err);
} else
purple_debug_error("backend-fs2",
"Error creating stream\n");
return FALSE;
}
#else
ret = fs_stream_set_transmitter(fsstream, transmitter,
_params, _num_params, &err);
for (i = 0 ; i < _num_params ; i++)
g_value_unset (&_params[i].value);
g_free(_params);
if (relay_info)
g_ptr_array_unref (relay_info);
if (ret == FALSE) {
purple_debug_error("backend-fs2",
"Could not set transmitter %s: %s.\n",
transmitter, err ? err->message : NULL);
g_clear_error(&err);
return FALSE;
}
#endif
stream = g_new0(PurpleMediaBackendFs2Stream, 1);
stream->participant = g_strdup(who);
stream->session = session;
stream->stream = fsstream;
#ifndef HAVE_FARSIGHT
stream->supports_add = purple_strequal(transmitter, "nice");
#endif
priv->streams = g_list_append(priv->streams, stream);
g_signal_connect(G_OBJECT(fsstream), "src-pad-added",
G_CALLBACK(src_pad_added_cb), stream);
return TRUE;
}
static void
free_stream(PurpleMediaBackendFs2Stream *stream)
{
/* Remove the connected_cb timeout */
if (stream->connected_cb_id != 0)
purple_timeout_remove(stream->connected_cb_id);
g_free(stream->participant);
if (stream->local_candidates)
fs_candidate_list_destroy(stream->local_candidates);
if (stream->remote_candidates)
fs_candidate_list_destroy(stream->remote_candidates);
g_free(stream);
}
static gboolean
purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *who,
PurpleMediaSessionType type, gboolean initiator,
const gchar *transmitter,
guint num_params, GParameter *params)
{
PurpleMediaBackendFs2 *backend = PURPLE_MEDIA_BACKEND_FS2(self);
PurpleMediaBackendFs2Private *priv =
PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(backend);
PurpleMediaBackendFs2Stream *stream;
if (priv->conference == NULL && !init_conference(backend)) {
purple_debug_error("backend-fs2",
"Error initializing the conference.\n");
return FALSE;
}
if (get_session(backend, sess_id) == NULL &&
!create_session(backend, sess_id, type,
initiator, transmitter)) {
purple_debug_error("backend-fs2",
"Error creating the session.\n");
return FALSE;
}
if (get_participant(backend, who) == NULL &&
!create_participant(backend, who)) {
purple_debug_error("backend-fs2",
"Error creating the participant.\n");
return FALSE;
}
stream = get_stream(backend, sess_id, who);
if (stream != NULL) {
FsStreamDirection type_direction =
session_type_to_fs_stream_direction(type);
if (session_type_to_fs_stream_direction(
stream->session->type) != type_direction) {
/* change direction */
g_object_set(stream->stream, "direction",
type_direction, NULL);
}
} else if (!create_stream(backend, sess_id, who, type,
initiator, transmitter, num_params, params)) {
purple_debug_error("backend-fs2",
"Error creating the stream.\n");
return FALSE;
}
return TRUE;
}
static void
purple_media_backend_fs2_add_remote_candidates(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
GList *remote_candidates)
{
PurpleMediaBackendFs2Private *priv;
PurpleMediaBackendFs2Stream *stream;
GError *err = NULL;
g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
sess_id, participant);
if (stream == NULL) {
purple_debug_error("backend-fs2",
"purple_media_add_remote_candidates: "
"couldn't find stream %s %s.\n",
sess_id ? sess_id : "(null)",
participant ? participant : "(null)");
return;
}
stream->remote_candidates = g_list_concat(stream->remote_candidates,
candidate_list_to_fs(remote_candidates));
if (purple_media_is_initiator(priv->media, sess_id, participant) ||
purple_media_accepted(
priv->media, sess_id, participant)) {
#ifdef HAVE_FARSIGHT
fs_stream_set_remote_candidates(stream->stream,
stream->remote_candidates, &err);
#else
if (stream->supports_add)
fs_stream_add_remote_candidates(stream->stream,
stream->remote_candidates, &err);
else
fs_stream_force_remote_candidates(stream->stream,
stream->remote_candidates, &err);
#endif
if (err) {
purple_debug_error("backend-fs2", "Error adding remote"
" candidates: %s\n", err->message);
g_error_free(err);
}
}
}
static gboolean
purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
const gchar *sess_id)
{
PurpleMediaBackendFs2Private *priv;
gboolean ret = FALSE;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
if (sess_id != NULL) {
PurpleMediaBackendFs2Session *session = get_session(
PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (session == NULL)
return FALSE;
if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
#ifdef HAVE_MEDIA_APPLICATION
PURPLE_MEDIA_SEND_APPLICATION |
#endif
PURPLE_MEDIA_SEND_VIDEO)) {
#ifdef HAVE_FARSIGHT
g_object_get(session->session,
"codecs-ready", &ret, NULL);
#else
GList *codecs = NULL;
g_object_get(session->session,
"codecs", &codecs, NULL);
if (codecs) {
fs_codec_list_destroy (codecs);
ret = TRUE;
}
#endif
} else
ret = TRUE;
} else {
GList *values = g_hash_table_get_values(priv->sessions);
for (; values; values = g_list_delete_link(values, values)) {
PurpleMediaBackendFs2Session *session = values->data;
if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
#ifdef HAVE_MEDIA_APPLICATION
PURPLE_MEDIA_SEND_APPLICATION |
#endif
PURPLE_MEDIA_SEND_VIDEO)) {
#ifdef HAVE_FARSIGHT
g_object_get(session->session,
"codecs-ready", &ret, NULL);
if (ret == FALSE)
break;
#else
GList *codecs = NULL;
g_object_get(session->session,
"codecs", &codecs, NULL);
if (codecs) {
fs_codec_list_destroy (codecs);
ret = TRUE;
} else {
ret = FALSE;
break;
}
#endif
} else
ret = TRUE;
}
if (values != NULL)
g_list_free(values);
}
return ret;
}
static GList *
purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
const gchar *sess_id)
{
PurpleMediaBackendFs2Session *session;
GList *fscodecs;
GList *codecs;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (session == NULL)
return NULL;
g_object_get(G_OBJECT(session->session),
"codecs", &fscodecs, NULL);
codecs = codec_list_from_fs(fscodecs);
fs_codec_list_destroy(fscodecs);
return codecs;
}
static GList *
purple_media_backend_fs2_get_local_candidates(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant)
{
PurpleMediaBackendFs2Stream *stream;
GList *candidates = NULL;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
sess_id, participant);
if (stream != NULL)
candidates = candidate_list_from_fs(
stream->local_candidates);
return candidates;
}
static gboolean
purple_media_backend_fs2_set_remote_codecs(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
GList *codecs)
{
PurpleMediaBackendFs2Stream *stream;
GList *fscodecs;
GError *err = NULL;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
sess_id, participant);
if (stream == NULL)
return FALSE;
fscodecs = codec_list_to_fs(codecs);
fs_stream_set_remote_codecs(stream->stream, fscodecs, &err);
fs_codec_list_destroy(fscodecs);
if (err) {
purple_debug_error("backend-fs2",
"Error setting remote codecs: %s\n",
err->message);
g_error_free(err);
return FALSE;
}
return TRUE;
}
#if GST_CHECK_VERSION(1,0,0)
static GstStructure *
create_fs2_srtp_structure(const gchar *cipher, const gchar *auth,
const gchar *key, gsize key_len)
{
GstStructure *result;
GstBuffer *buffer;
GstMapInfo info;
buffer = gst_buffer_new_allocate(NULL, key_len, NULL);
gst_buffer_map(buffer, &info, GST_MAP_WRITE);
memcpy(info.data, key, key_len);
gst_buffer_unmap(buffer, &info);
result = gst_structure_new("FarstreamSRTP",
"cipher", G_TYPE_STRING, cipher,
"auth", G_TYPE_STRING, auth,
"key", GST_TYPE_BUFFER, buffer,
NULL);
gst_buffer_unref(buffer);
return result;
}
static gboolean
purple_media_backend_fs2_set_encryption_parameters (PurpleMediaBackend *self,
const gchar *sess_id, const gchar *cipher, const gchar *auth,
const gchar *key, gsize key_len)
{
PurpleMediaBackendFs2Session *session;
GstStructure *srtp;
GError *err = NULL;
gboolean result;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (!session)
return FALSE;
srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
if (!srtp)
return FALSE;
result = fs_session_set_encryption_parameters(session->session, srtp,
&err);
if (!result) {
purple_debug_error("backend-fs2",
"Error setting encryption parameters: %s\n", err->message);
g_error_free(err);
}
gst_structure_free(srtp);
return result;
}
static gboolean
purple_media_backend_fs2_set_decryption_parameters (PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
const gchar *cipher, const gchar *auth,
const gchar *key, gsize key_len)
{
PurpleMediaBackendFs2Stream *stream;
GstStructure *srtp;
GError *err = NULL;
gboolean result;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), sess_id,
participant);
if (!stream)
return FALSE;
srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
if (!srtp)
return FALSE;
result = fs_stream_set_decryption_parameters(stream->stream, srtp,
&err);
if (!result) {
purple_debug_error("backend-fs2",
"Error setting decryption parameters: %s\n", err->message);
g_error_free(err);
}
gst_structure_free(srtp);
return result;
}
static gboolean
purple_media_backend_fs2_set_require_encryption(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
gboolean require_encryption)
{
PurpleMediaBackendFs2Stream *stream;
gboolean result;
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), sess_id,
participant);
if (!stream) {
return FALSE;
}
g_object_set(stream->stream, "require-encryption",
require_encryption, NULL);
return TRUE;
}
#endif /* GST 1.0+ */
static gboolean
purple_media_backend_fs2_set_send_codec(PurpleMediaBackend *self,
const gchar *sess_id, PurpleMediaCodec *codec)
{
PurpleMediaBackendFs2Session *session;
FsCodec *fscodec;
GError *err = NULL;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (session == NULL)
return FALSE;
fscodec = codec_to_fs(codec);
fs_session_set_send_codec(session->session, fscodec, &err);
fs_codec_destroy(fscodec);
if (err) {
purple_debug_error("media", "Error setting send codec\n");
g_error_free(err);
return FALSE;
}
return TRUE;
}
static const gchar **
purple_media_backend_fs2_get_available_params(void)
{
static const gchar *supported_params[] = {
"sdes-cname", "sdes-email", "sdes-location", "sdes-name", "sdes-note",
"sdes-phone", "sdes-tool", NULL
};
return supported_params;
}
static const gchar*
param_to_sdes_type(const gchar *param)
{
const gchar **supported = purple_media_backend_fs2_get_available_params();
static const gchar *sdes_types[] = {
"cname", "email", "location", "name", "note", "phone", "tool", NULL
};
guint i;
for (i = 0; supported[i] != NULL; ++i) {
if (purple_strequal(param, supported[i])) {
return sdes_types[i];
}
}
return NULL;
}
static void
purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
guint num_params, GParameter *params)
{
PurpleMediaBackendFs2Private *priv;
guint i;
#ifndef HAVE_FARSIGHT
GstStructure *sdes;
#endif
g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
if (priv->conference == NULL &&
!init_conference(PURPLE_MEDIA_BACKEND_FS2(self))) {
purple_debug_error("backend-fs2",
"Error initializing the conference.\n");
return;
}
#ifdef HAVE_FARSIGHT
for (i = 0; i != num_params; ++i) {
if (param_to_sdes_type(params[i].name)) {
g_object_set(priv->conference,
params[i].name, g_value_get_string(&params[i].value),
NULL);
}
}
#else
g_object_get(G_OBJECT(priv->conference), "sdes", &sdes, NULL);
for (i = 0; i != num_params; ++i) {
const gchar *sdes_type = param_to_sdes_type(params[i].name);
if (!sdes_type)
continue;
gst_structure_set(sdes, sdes_type,
G_TYPE_STRING, g_value_get_string(&params[i].value),
NULL);
}
g_object_set(G_OBJECT(priv->conference), "sdes", sdes, NULL);
gst_structure_free(sdes);
#endif /* HAVE_FARSIGHT */
}
static gboolean
send_dtmf_callback(gpointer userdata)
{
FsSession *session = userdata;
fs_session_stop_telephony_event(session);
return FALSE;
}
static gboolean
purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
const gchar *sess_id, gchar dtmf, guint8 volume,
guint16 duration)
{
PurpleMediaBackendFs2Session *session;
FsDTMFEvent event;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (session == NULL)
return FALSE;
/* Convert DTMF char into FsDTMFEvent enum */
switch(dtmf) {
case '0': event = FS_DTMF_EVENT_0; break;
case '1': event = FS_DTMF_EVENT_1; break;
case '2': event = FS_DTMF_EVENT_2; break;
case '3': event = FS_DTMF_EVENT_3; break;
case '4': event = FS_DTMF_EVENT_4; break;
case '5': event = FS_DTMF_EVENT_5; break;
case '6': event = FS_DTMF_EVENT_6; break;
case '7': event = FS_DTMF_EVENT_7; break;
case '8': event = FS_DTMF_EVENT_8; break;
case '9': event = FS_DTMF_EVENT_9; break;
case '*': event = FS_DTMF_EVENT_STAR; break;
case '#': event = FS_DTMF_EVENT_POUND; break;
case 'A': event = FS_DTMF_EVENT_A; break;
case 'B': event = FS_DTMF_EVENT_B; break;
case 'C': event = FS_DTMF_EVENT_C; break;
case 'D': event = FS_DTMF_EVENT_D; break;
default:
return FALSE;
}
if (!fs_session_start_telephony_event(session->session,
event, volume)) {
return FALSE;
}
if (duration <= 50) {
fs_session_stop_telephony_event(session->session);
} else {
purple_timeout_add(duration, send_dtmf_callback,
session->session);
}
return TRUE;
}
#else
GType
purple_media_backend_fs2_get_type(void)
{
return G_TYPE_NONE;
}
#endif /* USE_VV */
#ifdef USE_GSTREAMER
GstElement *
purple_media_backend_fs2_get_src(PurpleMediaBackendFs2 *self,
const gchar *sess_id)
{
#ifdef USE_VV
PurpleMediaBackendFs2Session *session = get_session(self, sess_id);
return session != NULL ? session->src : NULL;
#else
return NULL;
#endif
}
GstElement *
purple_media_backend_fs2_get_tee(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *who)
{
#ifdef USE_VV
if (sess_id != NULL && who == NULL) {
PurpleMediaBackendFs2Session *session =
get_session(self, sess_id);
return (session != NULL) ? session->tee : NULL;
} else if (sess_id != NULL && who != NULL) {
PurpleMediaBackendFs2Stream *stream =
get_stream(self, sess_id, who);
return (stream != NULL) ? stream->tee : NULL;
}
#endif /* USE_VV */
g_return_val_if_reached(NULL);
}
void
purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self,
const gchar *sess_id, double level)
{
#ifdef USE_VV
PurpleMediaBackendFs2Private *priv;
GList *sessions;
g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
priv = PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
purple_prefs_set_int("/purple/media/audio/volume/input", level);
if (sess_id == NULL)
sessions = g_hash_table_get_values(priv->sessions);
else
sessions = g_list_append(NULL, get_session(self, sess_id));
for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session = sessions->data;
if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
gchar *name = g_strdup_printf("volume_%s",
session->id);
GstElement *volume = gst_bin_get_by_name(
GST_BIN(priv->confbin), name);
g_free(name);
g_object_set(volume, "volume", level/10.0, NULL);
}
}
#endif /* USE_VV */
}
void
purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *who, double level)
{
#ifdef USE_VV
GList *streams;
g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self));
purple_prefs_set_int("/purple/media/audio/volume/output", level);
streams = get_streams(self, sess_id, who);
for (; streams; streams = g_list_delete_link(streams, streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO
&& GST_IS_ELEMENT(stream->volume)) {
g_object_set(stream->volume, "volume",
level/10.0, NULL);
}
}
#endif /* USE_VV */
}
#endif /* USE_GSTREAMER */
#ifdef USE_VV
static gboolean
purple_media_backend_fs2_set_send_rtcp_mux(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
gboolean send_rtcp_mux)
{
PurpleMediaBackendFs2Stream *stream;
g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
sess_id, participant);
if (stream != NULL &&
g_object_class_find_property (G_OBJECT_GET_CLASS (stream->stream),
"send-rtcp-mux") != NULL) {
g_object_set (stream->stream, "send-rtcp-mux", send_rtcp_mux, NULL);
return TRUE;
}
return FALSE;
}
#endif /* USE_VV */