/** * @file media.c Media API * @ingroup core */ /* purple * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include #include "internal.h" #include "account.h" #include "media.h" #include "mediamanager.h" #include "network.h" #include "debug.h" #ifdef USE_GSTREAMER #include "marshallers.h" #include "media-gst.h" #endif #ifdef USE_VV #include #include /** @copydoc _PurpleMediaSession */ typedef struct _PurpleMediaSession PurpleMediaSession; /** @copydoc _PurpleMediaStream */ typedef struct _PurpleMediaStream PurpleMediaStream; /** @copydoc _PurpleMediaClass */ typedef struct _PurpleMediaClass PurpleMediaClass; /** @copydoc _PurpleMediaPrivate */ typedef struct _PurpleMediaPrivate PurpleMediaPrivate; /** @copydoc _PurpleMediaCandidateClass */ typedef struct _PurpleMediaCandidateClass PurpleMediaCandidateClass; /** @copydoc _PurpleMediaCandidatePrivate */ typedef struct _PurpleMediaCandidatePrivate PurpleMediaCandidatePrivate; /** @copydoc _PurpleMediaCodecClass */ typedef struct _PurpleMediaCodecClass PurpleMediaCodecClass; /** @copydoc _PurpleMediaCodecPrivate */ typedef struct _PurpleMediaCodecPrivate PurpleMediaCodecPrivate; /** The media class */ struct _PurpleMediaClass { GObjectClass parent_class; /**< The parent class. */ }; /** The media class's private data */ struct _PurpleMedia { GObject parent; /**< The parent of this object. */ PurpleMediaPrivate *priv; /**< The private data of this object. */ }; struct _PurpleMediaSession { gchar *id; PurpleMedia *media; GstElement *src; GstElement *tee; FsSession *session; PurpleMediaSessionType type; gboolean initiator; }; struct _PurpleMediaStream { PurpleMediaSession *session; gchar *participant; FsStream *stream; GstElement *src; GstElement *tee; GstElement *volume; GstElement *level; GList *local_candidates; GList *remote_candidates; gboolean initiator; gboolean accepted; gboolean candidates_prepared; gboolean held; gboolean paused; GList *active_local_candidates; GList *active_remote_candidates; guint connected_cb_id; }; #endif struct _PurpleMediaPrivate { #ifdef USE_VV PurpleMediaManager *manager; PurpleAccount *account; FsConference *conference; gboolean initiator; gpointer prpl_data; GHashTable *sessions; /* PurpleMediaSession table */ GHashTable *participants; /* FsParticipant table */ GList *streams; /* PurpleMediaStream table */ GstElement *confbin; #else gpointer dummy; #endif }; #ifdef USE_VV #define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate)) #define PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidatePrivate)) #define PURPLE_MEDIA_CODEC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodecPrivate)) static void purple_media_class_init (PurpleMediaClass *klass); static void purple_media_init (PurpleMedia *media); static void purple_media_dispose (GObject *object); static void purple_media_finalize (GObject *object); static void purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void purple_media_new_local_candidate_cb(FsStream *stream, FsCandidate *local_candidate, PurpleMediaSession *session); static void purple_media_candidates_prepared_cb(FsStream *stream, PurpleMediaSession *session); static void purple_media_candidate_pair_established_cb(FsStream *stream, FsCandidate *native_candidate, FsCandidate *remote_candidate, PurpleMediaSession *session); static gboolean media_bus_call(GstBus *bus, GstMessage *msg, PurpleMedia *media); static GObjectClass *parent_class = NULL; enum { S_ERROR, CANDIDATES_PREPARED, CODECS_CHANGED, LEVEL, NEW_CANDIDATE, STATE_CHANGED, STREAM_INFO, LAST_SIGNAL }; static guint purple_media_signals[LAST_SIGNAL] = {0}; enum { PROP_0, PROP_MANAGER, PROP_ACCOUNT, PROP_CONFERENCE, PROP_INITIATOR, PROP_PRPL_DATA, }; #endif /* * PurpleMediaElementType */ GType purple_media_session_type_get_type() { static GType type = 0; if (type == 0) { static const GFlagsValue values[] = { { PURPLE_MEDIA_NONE, "PURPLE_MEDIA_NONE", "none" }, { PURPLE_MEDIA_RECV_AUDIO, "PURPLE_MEDIA_RECV_AUDIO", "recv-audio" }, { PURPLE_MEDIA_SEND_AUDIO, "PURPLE_MEDIA_SEND_AUDIO", "send-audio" }, { PURPLE_MEDIA_RECV_VIDEO, "PURPLE_MEDIA_RECV_VIDEO", "recv-video" }, { PURPLE_MEDIA_SEND_VIDEO, "PURPLE_MEDIA_SEND_VIDEO", "send-audio" }, { PURPLE_MEDIA_AUDIO, "PURPLE_MEDIA_AUDIO", "audio" }, { PURPLE_MEDIA_VIDEO, "PURPLE_MEDIA_VIDEO", "video" }, { 0, NULL, NULL } }; type = g_flags_register_static( "PurpleMediaSessionType", values); } return type; } GType purple_media_get_type() { #ifdef USE_VV static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof(PurpleMediaClass), NULL, NULL, (GClassInitFunc) purple_media_class_init, NULL, NULL, sizeof(PurpleMedia), 0, (GInstanceInitFunc) purple_media_init, NULL }; type = g_type_register_static(G_TYPE_OBJECT, "PurpleMedia", &info, 0); } return type; #else return G_TYPE_NONE; #endif } GType purple_media_state_changed_get_type() { static GType type = 0; if (type == 0) { static const GEnumValue values[] = { { PURPLE_MEDIA_STATE_NEW, "PURPLE_MEDIA_STATE_NEW", "new" }, { PURPLE_MEDIA_STATE_CONNECTED, "PURPLE_MEDIA_STATE_CONNECTED", "connected" }, { PURPLE_MEDIA_STATE_END, "PURPLE_MEDIA_STATE_END", "end" }, { 0, NULL, NULL } }; type = g_enum_register_static("PurpleMediaState", values); } return type; } GType purple_media_info_type_get_type() { static GType type = 0; if (type == 0) { static const GEnumValue values[] = { { PURPLE_MEDIA_INFO_HANGUP, "PURPLE_MEDIA_INFO_HANGUP", "hangup" }, { PURPLE_MEDIA_INFO_ACCEPT, "PURPLE_MEDIA_INFO_ACCEPT", "accept" }, { PURPLE_MEDIA_INFO_REJECT, "PURPLE_MEDIA_INFO_REJECT", "reject" }, { PURPLE_MEDIA_INFO_MUTE, "PURPLE_MEDIA_INFO_MUTE", "mute" }, { PURPLE_MEDIA_INFO_UNMUTE, "PURPLE_MEDIA_INFO_UNMUTE", "unmute" }, { PURPLE_MEDIA_INFO_PAUSE, "PURPLE_MEDIA_INFO_PAUSE", "pause" }, { PURPLE_MEDIA_INFO_UNPAUSE, "PURPLE_MEDIA_INFO_UNPAUSE", "unpause" }, { PURPLE_MEDIA_INFO_HOLD, "PURPLE_MEDIA_INFO_HOLD", "hold" }, { PURPLE_MEDIA_INFO_UNHOLD, "PURPLE_MEDIA_INFO_UNHOLD", "unhold" }, { 0, NULL, NULL } }; type = g_enum_register_static("PurpleMediaInfoType", values); } return type; } #ifdef USE_VV static void purple_media_class_init (PurpleMediaClass *klass) { GObjectClass *gobject_class = (GObjectClass*)klass; parent_class = g_type_class_peek_parent(klass); gobject_class->dispose = purple_media_dispose; gobject_class->finalize = purple_media_finalize; gobject_class->set_property = purple_media_set_property; gobject_class->get_property = purple_media_get_property; g_object_class_install_property(gobject_class, PROP_MANAGER, g_param_spec_object("manager", "Purple Media Manager", "The media manager that contains this media session.", PURPLE_TYPE_MEDIA_MANAGER, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_ACCOUNT, g_param_spec_pointer("account", "PurpleAccount", "The account this media session is on.", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_CONFERENCE, g_param_spec_object("conference", "Farsight conference", "The FsConference associated with this media.", FS_TYPE_CONFERENCE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_INITIATOR, g_param_spec_boolean("initiator", "initiator", "If the local user initiated the conference.", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_PRPL_DATA, g_param_spec_pointer("prpl-data", "gpointer", "Data the prpl plugin set on the media session.", G_PARAM_READWRITE)); purple_media_signals[S_ERROR] = g_signal_new("error", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, purple_smarshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); purple_media_signals[CODECS_CHANGED] = g_signal_new("codecs-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); purple_media_signals[LEVEL] = g_signal_new("level", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, purple_smarshal_VOID__STRING_STRING_DOUBLE, G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE); purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, purple_smarshal_VOID__POINTER_POINTER_OBJECT, G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_POINTER, PURPLE_TYPE_MEDIA_CANDIDATE); purple_media_signals[STATE_CHANGED] = g_signal_new("state-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, purple_smarshal_VOID__ENUM_STRING_STRING, G_TYPE_NONE, 3, PURPLE_MEDIA_TYPE_STATE, G_TYPE_STRING, G_TYPE_STRING); purple_media_signals[STREAM_INFO] = g_signal_new("stream-info", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, purple_smarshal_VOID__ENUM_STRING_STRING_BOOLEAN, G_TYPE_NONE, 4, PURPLE_MEDIA_TYPE_INFO_TYPE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); g_type_class_add_private(klass, sizeof(PurpleMediaPrivate)); } static void purple_media_init (PurpleMedia *media) { media->priv = PURPLE_MEDIA_GET_PRIVATE(media); memset(media->priv, 0, sizeof(*media->priv)); } static void purple_media_stream_free(PurpleMediaStream *stream) { if (stream == NULL) return; /* Remove the connected_cb timeout */ if (stream->connected_cb_id != 0) purple_timeout_remove(stream->connected_cb_id); g_free(stream->participant); if (stream->local_candidates) fs_candidate_list_destroy(stream->local_candidates); if (stream->remote_candidates) fs_candidate_list_destroy(stream->remote_candidates); if (stream->active_local_candidates) fs_candidate_list_destroy(stream->active_local_candidates); if (stream->active_remote_candidates) fs_candidate_list_destroy(stream->active_remote_candidates); g_free(stream); } static void purple_media_session_free(PurpleMediaSession *session) { if (session == NULL) return; g_free(session->id); g_free(session); } static void purple_media_dispose(GObject *media) { PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media); GList *iter = NULL; purple_debug_info("media","purple_media_dispose\n"); purple_media_manager_remove_media(priv->manager, PURPLE_MEDIA(media)); if (priv->confbin) { gst_element_set_locked_state(priv->confbin, TRUE); gst_element_set_state(GST_ELEMENT(priv->confbin), GST_STATE_NULL); gst_bin_remove(GST_BIN(purple_media_manager_get_pipeline( priv->manager)), priv->confbin); priv->confbin = NULL; priv->conference = NULL; } for (iter = priv->streams; iter; iter = g_list_next(iter)) { PurpleMediaStream *stream = iter->data; if (stream->stream) { g_object_unref(stream->stream); stream->stream = NULL; } } if (priv->sessions) { GList *sessions = g_hash_table_get_values(priv->sessions); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { PurpleMediaSession *session = sessions->data; if (session->session) { g_object_unref(session->session); session->session = NULL; } } } if (priv->participants) { GList *participants = g_hash_table_get_values(priv->participants); for (; participants; participants = g_list_delete_link(participants, participants)) g_object_unref(participants->data); } if (priv->manager) { GstElement *pipeline = purple_media_manager_get_pipeline( priv->manager); GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); g_signal_handlers_disconnect_matched(G_OBJECT(bus), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, 0, media_bus_call, media); gst_object_unref(bus); g_object_unref(priv->manager); priv->manager = NULL; } G_OBJECT_CLASS(parent_class)->dispose(media); } static void purple_media_finalize(GObject *media) { PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media); purple_debug_info("media","purple_media_finalize\n"); for (; priv->streams; priv->streams = g_list_delete_link(priv->streams, priv->streams)) purple_media_stream_free(priv->streams->data); if (priv->sessions) { GList *sessions = g_hash_table_get_values(priv->sessions); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { purple_media_session_free(sessions->data); } g_hash_table_destroy(priv->sessions); } G_OBJECT_CLASS(parent_class)->finalize(media); } static void purple_media_setup_pipeline(PurpleMedia *media) { GstBus *bus; gchar *name; GstElement *pipeline; if (media->priv->conference == NULL || media->priv->manager == NULL) return; pipeline = purple_media_manager_get_pipeline(media->priv->manager); name = g_strdup_printf("conf_%p", media->priv->conference); media->priv->confbin = gst_bin_new(name); g_free(name); bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); g_signal_connect(G_OBJECT(bus), "message", G_CALLBACK(media_bus_call), media); gst_object_unref(bus); gst_bin_add(GST_BIN(pipeline), media->priv->confbin); gst_bin_add(GST_BIN(media->priv->confbin), GST_ELEMENT(media->priv->conference)); gst_element_set_state(GST_ELEMENT(media->priv->confbin), GST_STATE_PLAYING); } static void purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { PurpleMedia *media; g_return_if_fail(PURPLE_IS_MEDIA(object)); media = PURPLE_MEDIA(object); switch (prop_id) { case PROP_MANAGER: media->priv->manager = g_value_get_object(value); g_object_ref(media->priv->manager); purple_media_setup_pipeline(media); break; case PROP_ACCOUNT: media->priv->account = g_value_get_pointer(value); break; case PROP_CONFERENCE: { if (media->priv->conference) gst_object_unref(media->priv->conference); media->priv->conference = g_value_get_object(value); gst_object_ref(media->priv->conference); purple_media_setup_pipeline(media); break; } case PROP_INITIATOR: media->priv->initiator = g_value_get_boolean(value); break; case PROP_PRPL_DATA: media->priv->prpl_data = g_value_get_pointer(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { PurpleMedia *media; g_return_if_fail(PURPLE_IS_MEDIA(object)); media = PURPLE_MEDIA(object); switch (prop_id) { case PROP_MANAGER: g_value_set_object(value, media->priv->manager); break; case PROP_ACCOUNT: g_value_set_pointer(value, media->priv->account); break; case PROP_CONFERENCE: g_value_set_object(value, media->priv->conference); break; case PROP_INITIATOR: g_value_set_boolean(value, media->priv->initiator); break; case PROP_PRPL_DATA: g_value_set_pointer(value, media->priv->prpl_data); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } #endif /* * PurpleMediaCandidateType */ GType purple_media_candidate_type_get_type() { static GType type = 0; if (type == 0) { static const GEnumValue values[] = { { PURPLE_MEDIA_CANDIDATE_TYPE_HOST, "PURPLE_MEDIA_CANDIDATE_TYPE_HOST", "host" }, { PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX, "PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX", "srflx" }, { PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX, "PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX", "prflx" }, { PURPLE_MEDIA_CANDIDATE_TYPE_RELAY, "PPURPLE_MEDIA_CANDIDATE_TYPE_RELAY", "relay" }, { PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST, "PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST", "multicast" }, { 0, NULL, NULL } }; type = g_enum_register_static("PurpleMediaCandidateType", values); } return type; } /* * PurpleMediaNetworkProtocol */ GType purple_media_network_protocol_get_type() { static GType type = 0; if (type == 0) { static const GEnumValue values[] = { { PURPLE_MEDIA_NETWORK_PROTOCOL_UDP, "PURPLE_MEDIA_NETWORK_PROTOCOL_UDP", "udp" }, { PURPLE_MEDIA_NETWORK_PROTOCOL_TCP, "PURPLE_MEDIA_NETWORK_PROTOCOL_TCP", "tcp" }, { 0, NULL, NULL } }; type = g_enum_register_static("PurpleMediaNetworkProtocol", values); } return type; } /* * PurpleMediaCandidate */ struct _PurpleMediaCandidateClass { GObjectClass parent_class; }; struct _PurpleMediaCandidate { GObject parent; }; #ifdef USE_VV struct _PurpleMediaCandidatePrivate { gchar *foundation; guint component_id; gchar *ip; guint16 port; gchar *base_ip; guint16 base_port; PurpleMediaNetworkProtocol proto; guint32 priority; PurpleMediaCandidateType type; gchar *username; gchar *password; guint ttl; }; enum { PROP_CANDIDATE_0, PROP_FOUNDATION, PROP_COMPONENT_ID, PROP_IP, PROP_PORT, PROP_BASE_IP, PROP_BASE_PORT, PROP_PROTOCOL, PROP_PRIORITY, PROP_TYPE, PROP_USERNAME, PROP_PASSWORD, PROP_TTL, }; static void purple_media_candidate_init(PurpleMediaCandidate *info) { PurpleMediaCandidatePrivate *priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(info); priv->foundation = NULL; priv->component_id = 0; priv->ip = NULL; priv->port = 0; priv->base_ip = NULL; priv->proto = PURPLE_MEDIA_NETWORK_PROTOCOL_UDP; priv->priority = 0; priv->type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST; priv->username = NULL; priv->password = NULL; priv->ttl = 0; } static void purple_media_candidate_finalize(GObject *info) { PurpleMediaCandidatePrivate *priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(info); g_free(priv->foundation); g_free(priv->ip); g_free(priv->base_ip); g_free(priv->username); g_free(priv->password); } static void purple_media_candidate_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { PurpleMediaCandidatePrivate *priv; g_return_if_fail(PURPLE_IS_MEDIA_CANDIDATE(object)); priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(object); switch (prop_id) { case PROP_FOUNDATION: g_free(priv->foundation); priv->foundation = g_value_dup_string(value); break; case PROP_COMPONENT_ID: priv->component_id = g_value_get_uint(value); break; case PROP_IP: g_free(priv->ip); priv->ip = g_value_dup_string(value); break; case PROP_PORT: priv->port = g_value_get_uint(value); break; case PROP_BASE_IP: g_free(priv->base_ip); priv->base_ip = g_value_dup_string(value); break; case PROP_BASE_PORT: priv->base_port = g_value_get_uint(value); break; case PROP_PROTOCOL: priv->proto = g_value_get_enum(value); break; case PROP_PRIORITY: priv->priority = g_value_get_uint(value); break; case PROP_TYPE: priv->type = g_value_get_enum(value); break; case PROP_USERNAME: g_free(priv->username); priv->username = g_value_dup_string(value); break; case PROP_PASSWORD: g_free(priv->password); priv->password = g_value_dup_string(value); break; case PROP_TTL: priv->ttl = g_value_get_uint(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec); break; } } static void purple_media_candidate_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { PurpleMediaCandidatePrivate *priv; g_return_if_fail(PURPLE_IS_MEDIA_CANDIDATE(object)); priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(object); switch (prop_id) { case PROP_FOUNDATION: g_value_set_string(value, priv->foundation); break; case PROP_COMPONENT_ID: g_value_set_uint(value, priv->component_id); break; case PROP_IP: g_value_set_string(value, priv->ip); break; case PROP_PORT: g_value_set_uint(value, priv->port); break; case PROP_BASE_IP: g_value_set_string(value, priv->base_ip); break; case PROP_BASE_PORT: g_value_set_uint(value, priv->base_port); break; case PROP_PROTOCOL: g_value_set_enum(value, priv->proto); break; case PROP_PRIORITY: g_value_set_uint(value, priv->priority); break; case PROP_TYPE: g_value_set_enum(value, priv->type); break; case PROP_USERNAME: g_value_set_string(value, priv->username); break; case PROP_PASSWORD: g_value_set_string(value, priv->password); break; case PROP_TTL: g_value_set_uint(value, priv->ttl); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec); break; } } static void purple_media_candidate_class_init(PurpleMediaCandidateClass *klass) { GObjectClass *gobject_class = (GObjectClass*)klass; gobject_class->finalize = purple_media_candidate_finalize; gobject_class->set_property = purple_media_candidate_set_property; gobject_class->get_property = purple_media_candidate_get_property; g_object_class_install_property(gobject_class, PROP_FOUNDATION, g_param_spec_string("foundation", "Foundation", "The foundation of the candidate.", NULL, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_COMPONENT_ID, g_param_spec_uint("component-id", "Component ID", "The component id of the candidate.", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_IP, g_param_spec_string("ip", "IP Address", "The IP address of the candidate.", NULL, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_PORT, g_param_spec_uint("port", "Port", "The port of the candidate.", 0, G_MAXUINT16, 0, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_BASE_IP, g_param_spec_string("base-ip", "Base IP", "The internal IP address of the candidate.", NULL, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_BASE_PORT, g_param_spec_uint("base-port", "Base Port", "The internal port of the candidate.", 0, G_MAXUINT16, 0, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_PROTOCOL, g_param_spec_enum("protocol", "Protocol", "The protocol of the candidate.", PURPLE_TYPE_MEDIA_NETWORK_PROTOCOL, PURPLE_MEDIA_NETWORK_PROTOCOL_UDP, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_PRIORITY, g_param_spec_uint("priority", "Priority", "The priority of the candidate.", 0, G_MAXUINT32, 0, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_TYPE, g_param_spec_enum("type", "Type", "The type of the candidate.", PURPLE_TYPE_MEDIA_CANDIDATE_TYPE, PURPLE_MEDIA_CANDIDATE_TYPE_HOST, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_USERNAME, g_param_spec_string("username", "Username", "The username used to connect to the candidate.", NULL, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_PASSWORD, g_param_spec_string("password", "Password", "The password use to connect to the candidate.", NULL, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_TTL, g_param_spec_uint("ttl", "TTL", "The TTL of the candidate.", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); g_type_class_add_private(klass, sizeof(PurpleMediaCandidatePrivate)); } G_DEFINE_TYPE(PurpleMediaCandidate, purple_media_candidate, G_TYPE_OBJECT); #else GType purple_media_candidate_get_type() { return G_TYPE_NONE; } #endif PurpleMediaCandidate * purple_media_candidate_new(const gchar *foundation, guint component_id, PurpleMediaCandidateType type, PurpleMediaNetworkProtocol proto, const gchar *ip, guint port) { return g_object_new(PURPLE_TYPE_MEDIA_CANDIDATE, "foundation", foundation, "component-id", component_id, "type", type, "protocol", proto, "ip", ip, "port", port, NULL); } static PurpleMediaCandidate * purple_media_candidate_copy(PurpleMediaCandidate *candidate) { #ifdef USE_VV PurpleMediaCandidatePrivate *priv; PurpleMediaCandidate *new_candidate; if (candidate == NULL) return NULL; priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(candidate); new_candidate = purple_media_candidate_new(priv->foundation, priv->component_id, priv->type, priv->proto, priv->ip, priv->port); g_object_set(new_candidate, "base-ip", priv->base_ip, "base-port", priv->base_port, "priority", priv->priority, "username", priv->username, "password", priv->password, "ttl", priv->ttl, NULL); return new_candidate; #else return NULL; #endif } #ifdef USE_VV static FsCandidate * purple_media_candidate_to_fs(PurpleMediaCandidate *candidate) { PurpleMediaCandidatePrivate *priv; FsCandidate *fscandidate; if (candidate == NULL) return NULL; priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(candidate); fscandidate = fs_candidate_new(priv->foundation, priv->component_id, priv->type, priv->proto, priv->ip, priv->port); fscandidate->base_ip = g_strdup(priv->base_ip); fscandidate->base_port = priv->base_port; fscandidate->priority = priv->priority; fscandidate->username = g_strdup(priv->username); fscandidate->password = g_strdup(priv->password); fscandidate->ttl = priv->ttl; return fscandidate; } static PurpleMediaCandidate * purple_media_candidate_from_fs(FsCandidate *fscandidate) { PurpleMediaCandidate *candidate; if (fscandidate == NULL) return NULL; candidate = purple_media_candidate_new(fscandidate->foundation, fscandidate->component_id, fscandidate->type, fscandidate->proto, fscandidate->ip, fscandidate->port); g_object_set(candidate, "base-ip", fscandidate->base_ip, "base-port", fscandidate->base_port, "priority", fscandidate->priority, "username", fscandidate->username, "password", fscandidate->password, "ttl", fscandidate->ttl, NULL); return candidate; } static GList * purple_media_candidate_list_from_fs(GList *candidates) { GList *new_list = NULL; for (; candidates; candidates = g_list_next(candidates)) { new_list = g_list_prepend(new_list, purple_media_candidate_from_fs( candidates->data)); } new_list = g_list_reverse(new_list); return new_list; } static GList * purple_media_candidate_list_to_fs(GList *candidates) { GList *new_list = NULL; for (; candidates; candidates = g_list_next(candidates)) { new_list = g_list_prepend(new_list, purple_media_candidate_to_fs( candidates->data)); } new_list = g_list_reverse(new_list); return new_list; } #endif GList * purple_media_candidate_list_copy(GList *candidates) { GList *new_list = NULL; for (; candidates; candidates = g_list_next(candidates)) { new_list = g_list_prepend(new_list, purple_media_candidate_copy(candidates->data)); } new_list = g_list_reverse(new_list); return new_list; } void purple_media_candidate_list_free(GList *candidates) { for (; candidates; candidates = g_list_delete_link(candidates, candidates)) { g_object_unref(candidates->data); } } gchar * purple_media_candidate_get_foundation(PurpleMediaCandidate *candidate) { gchar *foundation; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); g_object_get(candidate, "foundation", &foundation, NULL); return foundation; } guint purple_media_candidate_get_component_id(PurpleMediaCandidate *candidate) { guint component_id; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); g_object_get(candidate, "component-id", &component_id, NULL); return component_id; } gchar * purple_media_candidate_get_ip(PurpleMediaCandidate *candidate) { gchar *ip; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); g_object_get(candidate, "ip", &ip, NULL); return ip; } guint16 purple_media_candidate_get_port(PurpleMediaCandidate *candidate) { guint port; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); g_object_get(candidate, "port", &port, NULL); return port; } gchar * purple_media_candidate_get_base_ip(PurpleMediaCandidate *candidate) { gchar *base_ip; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); g_object_get(candidate, "base-ip", &base_ip, NULL); return base_ip; } guint16 purple_media_candidate_get_base_port(PurpleMediaCandidate *candidate) { guint base_port; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); g_object_get(candidate, "base_port", &base_port, NULL); return base_port; } PurpleMediaNetworkProtocol purple_media_candidate_get_protocol(PurpleMediaCandidate *candidate) { PurpleMediaNetworkProtocol protocol; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), PURPLE_MEDIA_NETWORK_PROTOCOL_UDP); g_object_get(candidate, "protocol", &protocol, NULL); return protocol; } guint32 purple_media_candidate_get_priority(PurpleMediaCandidate *candidate) { guint priority; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); g_object_get(candidate, "priority", &priority, NULL); return priority; } PurpleMediaCandidateType purple_media_candidate_get_candidate_type(PurpleMediaCandidate *candidate) { PurpleMediaCandidateType type; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), PURPLE_MEDIA_CANDIDATE_TYPE_HOST); g_object_get(candidate, "type", &type, NULL); return type; } gchar * purple_media_candidate_get_username(PurpleMediaCandidate *candidate) { gchar *username; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); g_object_get(candidate, "username", &username, NULL); return username; } gchar * purple_media_candidate_get_password(PurpleMediaCandidate *candidate) { gchar *password; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); g_object_get(candidate, "password", &password, NULL); return password; } guint purple_media_candidate_get_ttl(PurpleMediaCandidate *candidate) { guint ttl; g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); g_object_get(candidate, "ttl", &ttl, NULL); return ttl; } #ifdef USE_VV static FsMediaType purple_media_to_fs_media_type(PurpleMediaSessionType type) { if (type & PURPLE_MEDIA_AUDIO) return FS_MEDIA_TYPE_AUDIO; else if (type & PURPLE_MEDIA_VIDEO) return FS_MEDIA_TYPE_VIDEO; else return 0; } static FsStreamDirection purple_media_to_fs_stream_direction(PurpleMediaSessionType type) { if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO || (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO) return FS_DIRECTION_BOTH; else if ((type & PURPLE_MEDIA_SEND_AUDIO) || (type & PURPLE_MEDIA_SEND_VIDEO)) return FS_DIRECTION_SEND; else if ((type & PURPLE_MEDIA_RECV_AUDIO) || (type & PURPLE_MEDIA_RECV_VIDEO)) return FS_DIRECTION_RECV; else return FS_DIRECTION_NONE; } static PurpleMediaSessionType purple_media_from_fs(FsMediaType type, FsStreamDirection direction) { PurpleMediaSessionType result = PURPLE_MEDIA_NONE; if (type == FS_MEDIA_TYPE_AUDIO) { if (direction & FS_DIRECTION_SEND) result |= PURPLE_MEDIA_SEND_AUDIO; if (direction & FS_DIRECTION_RECV) result |= PURPLE_MEDIA_RECV_AUDIO; } else if (type == FS_MEDIA_TYPE_VIDEO) { if (direction & FS_DIRECTION_SEND) result |= PURPLE_MEDIA_SEND_VIDEO; if (direction & FS_DIRECTION_RECV) result |= PURPLE_MEDIA_RECV_VIDEO; } return result; } #endif /* * PurpleMediaCodec */ struct _PurpleMediaCodecClass { GObjectClass parent_class; }; struct _PurpleMediaCodec { GObject parent; }; #ifdef USE_VV struct _PurpleMediaCodecPrivate { gint id; char *encoding_name; PurpleMediaSessionType media_type; guint clock_rate; guint channels; GList *optional_params; }; enum { PROP_CODEC_0, PROP_ID, PROP_ENCODING_NAME, PROP_MEDIA_TYPE, PROP_CLOCK_RATE, PROP_CHANNELS, PROP_OPTIONAL_PARAMS, }; static void purple_media_codec_init(PurpleMediaCodec *info) { PurpleMediaCodecPrivate *priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(info); priv->encoding_name = NULL; priv->optional_params = NULL; } static void purple_media_codec_finalize(GObject *info) { PurpleMediaCodecPrivate *priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(info); g_free(priv->encoding_name); for (; priv->optional_params; priv->optional_params = g_list_delete_link(priv->optional_params, priv->optional_params)) { g_free(priv->optional_params->data); } } static void purple_media_codec_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { PurpleMediaCodecPrivate *priv; g_return_if_fail(PURPLE_IS_MEDIA_CODEC(object)); priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(object); switch (prop_id) { case PROP_ID: priv->id = g_value_get_uint(value); break; case PROP_ENCODING_NAME: g_free(priv->encoding_name); priv->encoding_name = g_value_dup_string(value); break; case PROP_MEDIA_TYPE: priv->media_type = g_value_get_flags(value); break; case PROP_CLOCK_RATE: priv->clock_rate = g_value_get_uint(value); break; case PROP_CHANNELS: priv->channels = g_value_get_uint(value); break; case PROP_OPTIONAL_PARAMS: priv->optional_params = g_value_get_pointer(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec); break; } } static void purple_media_codec_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { PurpleMediaCodecPrivate *priv; g_return_if_fail(PURPLE_IS_MEDIA_CODEC(object)); priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(object); switch (prop_id) { case PROP_ID: g_value_set_uint(value, priv->id); break; case PROP_ENCODING_NAME: g_value_set_string(value, priv->encoding_name); break; case PROP_MEDIA_TYPE: g_value_set_flags(value, priv->media_type); break; case PROP_CLOCK_RATE: g_value_set_uint(value, priv->clock_rate); break; case PROP_CHANNELS: g_value_set_uint(value, priv->channels); break; case PROP_OPTIONAL_PARAMS: g_value_set_pointer(value, priv->optional_params); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec); break; } } static void purple_media_codec_class_init(PurpleMediaCodecClass *klass) { GObjectClass *gobject_class = (GObjectClass*)klass; gobject_class->finalize = purple_media_codec_finalize; gobject_class->set_property = purple_media_codec_set_property; gobject_class->get_property = purple_media_codec_get_property; g_object_class_install_property(gobject_class, PROP_ID, g_param_spec_uint("id", "ID", "The numeric identifier of the codec.", 0, G_MAXUINT, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_ENCODING_NAME, g_param_spec_string("encoding-name", "Encoding Name", "The name of the codec.", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_MEDIA_TYPE, g_param_spec_flags("media-type", "Media Type", "Whether this is an audio of video codec.", PURPLE_TYPE_MEDIA_SESSION_TYPE, PURPLE_MEDIA_NONE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_CLOCK_RATE, g_param_spec_uint("clock-rate", "Create Callback", "The function called to create this element.", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_CHANNELS, g_param_spec_uint("channels", "Channels", "The number of channels in this codec.", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_OPTIONAL_PARAMS, g_param_spec_pointer("optional-params", "Optional Params", "A list of optional parameters for the codec.", G_PARAM_READWRITE)); g_type_class_add_private(klass, sizeof(PurpleMediaCodecPrivate)); } G_DEFINE_TYPE(PurpleMediaCodec, purple_media_codec, G_TYPE_OBJECT); #else GType purple_media_codec_get_type() { return G_TYPE_NONE; } #endif guint purple_media_codec_get_id(PurpleMediaCodec *codec) { guint id; g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0); g_object_get(codec, "id", &id, NULL); return id; } gchar * purple_media_codec_get_encoding_name(PurpleMediaCodec *codec) { gchar *name; g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), NULL); g_object_get(codec, "encoding-name", &name, NULL); return name; } guint purple_media_codec_get_clock_rate(PurpleMediaCodec *codec) { guint clock_rate; g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0); g_object_get(codec, "clock-rate", &clock_rate, NULL); return clock_rate; } guint purple_media_codec_get_channels(PurpleMediaCodec *codec) { guint channels; g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0); g_object_get(codec, "channels", &channels, NULL); return channels; } GList * purple_media_codec_get_optional_parameters(PurpleMediaCodec *codec) { GList *optional_params; g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), NULL); g_object_get(codec, "optional-params", &optional_params, NULL); return optional_params; } void purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec, const gchar *name, const gchar *value) { #ifdef USE_VV PurpleMediaCodecPrivate *priv; PurpleKeyValuePair *new_param; g_return_if_fail(codec != NULL); g_return_if_fail(name != NULL && value != NULL); priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); new_param = g_new0(PurpleKeyValuePair, 1); new_param->key = g_strdup(name); new_param->value = g_strdup(value); priv->optional_params = g_list_append( priv->optional_params, new_param); #endif } void purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec, PurpleKeyValuePair *param) { #ifdef USE_VV PurpleMediaCodecPrivate *priv; g_return_if_fail(codec != NULL && param != NULL); priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); g_free(param->key); g_free(param->value); g_free(param); priv->optional_params = g_list_remove(priv->optional_params, param); #endif } PurpleKeyValuePair * purple_media_codec_get_optional_parameter(PurpleMediaCodec *codec, const gchar *name, const gchar *value) { #ifdef USE_VV PurpleMediaCodecPrivate *priv; GList *iter; g_return_val_if_fail(codec != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); for (iter = priv->optional_params; iter; iter = g_list_next(iter)) { PurpleKeyValuePair *param = iter->data; if (!g_ascii_strcasecmp(param->key, name) && (value == NULL || !g_ascii_strcasecmp(param->value, value))) return param; } #endif return NULL; } PurpleMediaCodec * purple_media_codec_new(int id, const char *encoding_name, PurpleMediaSessionType media_type, guint clock_rate) { PurpleMediaCodec *codec = g_object_new(PURPLE_TYPE_MEDIA_CODEC, "id", id, "encoding_name", encoding_name, "media_type", media_type, "clock-rate", clock_rate, NULL); return codec; } static PurpleMediaCodec * purple_media_codec_copy(PurpleMediaCodec *codec) { #ifdef USE_VV PurpleMediaCodecPrivate *priv; PurpleMediaCodec *new_codec; GList *iter; if (codec == NULL) return NULL; priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); new_codec = purple_media_codec_new(priv->id, priv->encoding_name, priv->media_type, priv->clock_rate); g_object_set(codec, "channels", priv->channels, NULL); for (iter = priv->optional_params; iter; iter = g_list_next(iter)) { PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data; purple_media_codec_add_optional_parameter(new_codec, param->key, param->value); } return new_codec; #else return NULL; #endif } #ifdef USE_VV static FsCodec * purple_media_codec_to_fs(const PurpleMediaCodec *codec) { PurpleMediaCodecPrivate *priv; FsCodec *new_codec; GList *iter; if (codec == NULL) return NULL; priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); new_codec = fs_codec_new(priv->id, priv->encoding_name, purple_media_to_fs_media_type(priv->media_type), priv->clock_rate); new_codec->channels = priv->channels; for (iter = priv->optional_params; iter; iter = g_list_next(iter)) { PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data; fs_codec_add_optional_parameter(new_codec, param->key, param->value); } return new_codec; } static PurpleMediaCodec * purple_media_codec_from_fs(const FsCodec *codec) { PurpleMediaCodec *new_codec; GList *iter; if (codec == NULL) return NULL; new_codec = purple_media_codec_new(codec->id, codec->encoding_name, purple_media_from_fs(codec->media_type, FS_DIRECTION_BOTH), codec->clock_rate); g_object_set(new_codec, "channels", codec->channels, NULL); for (iter = codec->optional_params; iter; iter = g_list_next(iter)) { FsCodecParameter *param = (FsCodecParameter*)iter->data; purple_media_codec_add_optional_parameter(new_codec, param->name, param->value); } return new_codec; } #endif gchar * purple_media_codec_to_string(const PurpleMediaCodec *codec) { #ifdef USE_VV FsCodec *fscodec = purple_media_codec_to_fs(codec); gchar *str = fs_codec_to_string(fscodec); fs_codec_destroy(fscodec); return str; #else return g_strdup(""); #endif } #ifdef USE_VV static GList * purple_media_codec_list_from_fs(GList *codecs) { GList *new_list = NULL; for (; codecs; codecs = g_list_next(codecs)) { new_list = g_list_prepend(new_list, purple_media_codec_from_fs( codecs->data)); } new_list = g_list_reverse(new_list); return new_list; } static GList * purple_media_codec_list_to_fs(GList *codecs) { GList *new_list = NULL; for (; codecs; codecs = g_list_next(codecs)) { new_list = g_list_prepend(new_list, purple_media_codec_to_fs( codecs->data)); } new_list = g_list_reverse(new_list); return new_list; } #endif GList * purple_media_codec_list_copy(GList *codecs) { GList *new_list = NULL; for (; codecs; codecs = g_list_next(codecs)) { new_list = g_list_prepend(new_list, purple_media_codec_copy(codecs->data)); } new_list = g_list_reverse(new_list); return new_list; } void purple_media_codec_list_free(GList *codecs) { for (; codecs; codecs = g_list_delete_link(codecs, codecs)) { g_object_unref(codecs->data); } } #ifdef USE_VV static PurpleMediaSession* purple_media_get_session(PurpleMedia *media, const gchar *sess_id) { g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); return (PurpleMediaSession*) (media->priv->sessions) ? g_hash_table_lookup(media->priv->sessions, sess_id) : NULL; } static FsParticipant* purple_media_get_participant(PurpleMedia *media, const gchar *name) { g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); return (FsParticipant*) (media->priv->participants) ? g_hash_table_lookup(media->priv->participants, name) : NULL; } static PurpleMediaStream* purple_media_get_stream(PurpleMedia *media, const gchar *session, const gchar *participant) { GList *streams; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); streams = media->priv->streams; for (; streams; streams = g_list_next(streams)) { PurpleMediaStream *stream = streams->data; if (!strcmp(stream->session->id, session) && !strcmp(stream->participant, participant)) return stream; } return NULL; } static GList * purple_media_get_streams(PurpleMedia *media, const gchar *session, const gchar *participant) { GList *streams; GList *ret = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); streams = media->priv->streams; for (; streams; streams = g_list_next(streams)) { PurpleMediaStream *stream = streams->data; if ((session == NULL || !strcmp(stream->session->id, session)) && (participant == NULL || !strcmp(stream->participant, participant))) ret = g_list_append(ret, stream); } return ret; } static void purple_media_add_session(PurpleMedia *media, PurpleMediaSession *session) { g_return_if_fail(PURPLE_IS_MEDIA(media)); g_return_if_fail(session != NULL); if (!media->priv->sessions) { purple_debug_info("media", "Creating hash table for sessions\n"); media->priv->sessions = g_hash_table_new(g_str_hash, g_str_equal); } g_hash_table_insert(media->priv->sessions, g_strdup(session->id), session); } static gboolean purple_media_remove_session(PurpleMedia *media, PurpleMediaSession *session) { g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); return g_hash_table_remove(media->priv->sessions, session->id); } static FsParticipant * purple_media_add_participant(PurpleMedia *media, const gchar *name) { FsParticipant *participant; GError *err = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); participant = purple_media_get_participant(media, name); if (participant) return participant; participant = fs_conference_new_participant(media->priv->conference, (gchar*)name, &err); if (err) { purple_debug_error("media", "Error creating participant: %s\n", err->message); g_error_free(err); return NULL; } if (!media->priv->participants) { purple_debug_info("media", "Creating hash table for participants\n"); media->priv->participants = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); } g_hash_table_insert(media->priv->participants, g_strdup(name), participant); return participant; } static PurpleMediaStream * purple_media_insert_stream(PurpleMediaSession *session, const gchar *name, FsStream *stream) { PurpleMediaStream *media_stream; g_return_val_if_fail(session != NULL, NULL); media_stream = g_new0(PurpleMediaStream, 1); media_stream->stream = stream; media_stream->participant = g_strdup(name); media_stream->session = session; session->media->priv->streams = g_list_append(session->media->priv->streams, media_stream); return media_stream; } static void purple_media_insert_local_candidate(PurpleMediaSession *session, const gchar *name, FsCandidate *candidate) { PurpleMediaStream *stream; g_return_if_fail(session != NULL); stream = purple_media_get_stream(session->media, session->id, name); stream->local_candidates = g_list_append(stream->local_candidates, candidate); } #endif GList * purple_media_get_session_ids(PurpleMedia *media) { #ifdef USE_VV g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); return media->priv->sessions != NULL ? g_hash_table_get_keys(media->priv->sessions) : NULL; #else return NULL; #endif } #ifdef USE_VV static void purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src) { PurpleMediaSession *session; GstPad *sinkpad; GstPad *srcpad; g_return_if_fail(PURPLE_IS_MEDIA(media)); g_return_if_fail(GST_IS_ELEMENT(src)); session = purple_media_get_session(media, sess_id); if (session == NULL) { purple_debug_warning("media", "purple_media_set_src: trying" " to set src on non-existent session\n"); return; } if (session->src) gst_object_unref(session->src); session->src = src; gst_element_set_locked_state(session->src, TRUE); session->tee = gst_element_factory_make("tee", NULL); gst_bin_add(GST_BIN(session->media->priv->confbin), session->tee); /* This supposedly isn't necessary, but it silences some warnings */ if (GST_ELEMENT_PARENT(session->media->priv->confbin) == GST_ELEMENT_PARENT(session->src)) { GstPad *pad = gst_element_get_static_pad(session->tee, "sink"); GstPad *ghost = gst_ghost_pad_new(NULL, pad); gst_object_unref(pad); gst_pad_set_active(ghost, TRUE); gst_element_add_pad(session->media->priv->confbin, ghost); } gst_element_set_state(session->tee, GST_STATE_PLAYING); gst_element_link(session->src, session->media->priv->confbin); g_object_get(session->session, "sink-pad", &sinkpad, NULL); if (session->type & PURPLE_MEDIA_SEND_AUDIO) { gchar *name = g_strdup_printf("volume_%s", session->id); GstElement *level; GstElement *volume = gst_element_factory_make("volume", name); double input_volume = purple_prefs_get_int( "/purple/media/audio/volume/input")/10.0; g_free(name); name = g_strdup_printf("sendlevel_%s", session->id); level = gst_element_factory_make("level", name); g_free(name); gst_bin_add(GST_BIN(session->media->priv->confbin), volume); gst_bin_add(GST_BIN(session->media->priv->confbin), level); gst_element_set_state(level, GST_STATE_PLAYING); gst_element_set_state(volume, GST_STATE_PLAYING); gst_element_link(volume, level); gst_element_link(session->tee, volume); srcpad = gst_element_get_static_pad(level, "src"); g_object_set(volume, "volume", input_volume, NULL); } else { srcpad = gst_element_get_request_pad(session->tee, "src%d"); } purple_debug_info("media", "connecting pad: %s\n", gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK ? "success" : "failure"); gst_element_set_locked_state(session->src, FALSE); gst_object_unref(session->src); } #endif #ifdef USE_GSTREAMER GstElement * purple_media_get_src(PurpleMedia *media, const gchar *sess_id) { #ifdef USE_VV PurpleMediaSession *session; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); session = purple_media_get_session(media, sess_id); return (session != NULL) ? session->src : NULL; #else return NULL; #endif } #endif /* USE_GSTREAMER */ #ifdef USE_VV static PurpleMediaSession * purple_media_session_from_fs_stream(PurpleMedia *media, FsStream *stream) { FsSession *fssession; GList *values; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); g_return_val_if_fail(FS_IS_STREAM(stream), NULL); g_object_get(stream, "session", &fssession, NULL); values = g_hash_table_get_values(media->priv->sessions); for (; values; values = g_list_delete_link(values, values)) { PurpleMediaSession *session = values->data; if (session->session == fssession) { g_list_free(values); g_object_unref(fssession); return session; } } g_object_unref(fssession); return NULL; } static gboolean media_bus_call(GstBus *bus, GstMessage *msg, PurpleMedia *media) { switch(GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_ELEMENT: { if (g_signal_has_handler_pending(media, purple_media_signals[LEVEL], 0, FALSE) && gst_structure_has_name( gst_message_get_structure(msg), "level")) { GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); gchar *name; gchar *participant = NULL; PurpleMediaSession *session = NULL; gdouble rms_db; gdouble percent; const GValue *list; const GValue *value; if (!PURPLE_IS_MEDIA(media) || GST_ELEMENT_PARENT(src) != media->priv->confbin) break; name = gst_element_get_name(src); if (!strncmp(name, "sendlevel_", 10)) { session = purple_media_get_session( media, name+10); } else { GList *iter = media->priv->streams; for (; iter; iter = g_list_next(iter)) { PurpleMediaStream *stream = iter->data; if (stream->level == src) { session = stream->session; participant = stream->participant; break; } } } g_free(name); if (!session) break; list = gst_structure_get_value( gst_message_get_structure(msg), "rms"); value = gst_value_list_get_value(list, 0); rms_db = g_value_get_double(value); percent = pow(10, rms_db / 20) * 5; if(percent > 1.0) percent = 1.0; g_signal_emit(media, purple_media_signals[LEVEL], 0, session->id, participant, percent); break; } if (!FS_IS_CONFERENCE(GST_MESSAGE_SRC(msg)) || !PURPLE_IS_MEDIA(media) || media->priv->conference != FS_CONFERENCE(GST_MESSAGE_SRC(msg))) break; if (gst_structure_has_name(msg->structure, "farsight-error")) { FsError error_no; gst_structure_get_enum(msg->structure, "error-no", FS_TYPE_ERROR, (gint*)&error_no); switch (error_no) { case FS_ERROR_NO_CODECS: purple_media_error(media, _("No codecs found. Install some GStreamer codecs found in GStreamer plugins packages.")); purple_media_end(media, NULL, NULL); break; case FS_ERROR_NO_CODECS_LEFT: purple_media_error(media, _("No codecs left. Your codec preferences in fs-codecs.conf are too strict.")); purple_media_end(media, NULL, NULL); break; case FS_ERROR_UNKNOWN_CNAME: /* * Unknown CName is only a problem for the * multicast transmitter which isn't used. * It is also deprecated. */ break; default: purple_debug_error("media", "farsight-error: %i: %s\n", error_no, gst_structure_get_string(msg->structure, "error-msg")); break; } if (FS_ERROR_IS_FATAL(error_no)) { purple_media_error(media, _("A non-recoverable Farsight2 error has occurred.")); purple_media_end(media, NULL, NULL); } } else if (gst_structure_has_name(msg->structure, "farsight-new-local-candidate")) { FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "candidate")); PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); purple_media_new_local_candidate_cb(stream, local_candidate, session); } else if (gst_structure_has_name(msg->structure, "farsight-local-candidates-prepared")) { FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); purple_media_candidates_prepared_cb(stream, session); } else if (gst_structure_has_name(msg->structure, "farsight-new-active-candidate-pair")) { FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "local-candidate")); FsCandidate *remote_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "remote-candidate")); PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); purple_media_candidate_pair_established_cb(stream, local_candidate, remote_candidate, session); } else if (gst_structure_has_name(msg->structure, "farsight-recv-codecs-changed")) { GList *codecs = g_value_get_boxed(gst_structure_get_value(msg->structure, "codecs")); FsCodec *codec = codecs->data; purple_debug_info("media", "farsight-recv-codecs-changed: %s\n", codec->encoding_name); } else if (gst_structure_has_name(msg->structure, "farsight-component-state-changed")) { FsStreamState fsstate = g_value_get_enum(gst_structure_get_value(msg->structure, "state")); guint component = g_value_get_uint(gst_structure_get_value(msg->structure, "component")); const gchar *state; switch (fsstate) { case FS_STREAM_STATE_FAILED: state = "FAILED"; break; case FS_STREAM_STATE_DISCONNECTED: state = "DISCONNECTED"; break; case FS_STREAM_STATE_GATHERING: state = "GATHERING"; break; case FS_STREAM_STATE_CONNECTING: state = "CONNECTING"; break; case FS_STREAM_STATE_CONNECTED: state = "CONNECTED"; break; case FS_STREAM_STATE_READY: state = "READY"; break; default: state = "UNKNOWN"; break; } purple_debug_info("media", "farsight-component-state-changed: component: %u state: %s\n", component, state); } else if (gst_structure_has_name(msg->structure, "farsight-send-codec-changed")) { FsCodec *codec = g_value_get_boxed(gst_structure_get_value(msg->structure, "codec")); gchar *codec_str = fs_codec_to_string(codec); purple_debug_info("media", "farsight-send-codec-changed: codec: %s\n", codec_str); g_free(codec_str); } else if (gst_structure_has_name(msg->structure, "farsight-codecs-changed")) { GList *sessions = g_hash_table_get_values(PURPLE_MEDIA(media)->priv->sessions); FsSession *fssession = g_value_get_object(gst_structure_get_value(msg->structure, "session")); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { PurpleMediaSession *session = sessions->data; if (session->session == fssession) { gchar *session_id = g_strdup(session->id); g_signal_emit(media, purple_media_signals[CODECS_CHANGED], 0, session_id); g_free(session_id); g_list_free(sessions); break; } } } break; } case GST_MESSAGE_ERROR: { GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg)); GstElement *lastElement = NULL; while (!GST_IS_PIPELINE(element)) { if (element == media->priv->confbin) { purple_media_error(media, _("Conference error")); purple_media_end(media, NULL, NULL); break; } lastElement = element; element = GST_ELEMENT_PARENT(element); } if (GST_IS_PIPELINE(element)) { GList *sessions = g_hash_table_get_values(media->priv->sessions); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { PurpleMediaSession *session = sessions->data; if (session->src == lastElement) { if (session->type & PURPLE_MEDIA_AUDIO) purple_media_error(media, _("Error with your microphone")); else purple_media_error(media, _("Error with your webcam")); purple_media_end(media, NULL, NULL); break; } } g_list_free(sessions); } } default: break; } return TRUE; } #endif PurpleAccount * purple_media_get_account(PurpleMedia *media) { #ifdef USE_VV PurpleAccount *account; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); g_object_get(G_OBJECT(media), "account", &account, NULL); return account; #else return NULL; #endif } gpointer purple_media_get_prpl_data(PurpleMedia *media) { #ifdef USE_VV gpointer prpl_data; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); g_object_get(G_OBJECT(media), "prpl-data", &prpl_data, NULL); return prpl_data; #else return NULL; #endif } void purple_media_set_prpl_data(PurpleMedia *media, gpointer prpl_data) { #ifdef USE_VV g_return_if_fail(PURPLE_IS_MEDIA(media)); g_object_set(G_OBJECT(media), "prpl-data", prpl_data, NULL); #endif } void purple_media_error(PurpleMedia *media, const gchar *error, ...) { #ifdef USE_VV va_list args; gchar *message; g_return_if_fail(PURPLE_IS_MEDIA(media)); va_start(args, error); message = g_strdup_vprintf(error, args); va_end(args); purple_debug_error("media", "%s\n", message); g_signal_emit(media, purple_media_signals[S_ERROR], 0, message); g_free(message); #endif } void purple_media_end(PurpleMedia *media, const gchar *session_id, const gchar *participant) { #ifdef USE_VV g_return_if_fail(PURPLE_IS_MEDIA(media)); if (session_id == NULL && participant == NULL) { g_signal_emit(media, purple_media_signals[STATE_CHANGED], 0, PURPLE_MEDIA_STATE_END, NULL, NULL); g_object_unref(media); } #endif } void purple_media_stream_info(PurpleMedia *media, PurpleMediaInfoType type, const gchar *session_id, const gchar *participant, gboolean local) { #ifdef USE_VV g_return_if_fail(PURPLE_IS_MEDIA(media)); if (type == PURPLE_MEDIA_INFO_ACCEPT) { GList *streams; g_return_if_fail(PURPLE_IS_MEDIA(media)); streams = purple_media_get_streams(media, session_id, participant); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; g_object_set(G_OBJECT(stream->stream), "direction", purple_media_to_fs_stream_direction( stream->session->type), NULL); stream->accepted = TRUE; if (stream->remote_candidates != NULL) { GError *err = NULL; fs_stream_set_remote_candidates(stream->stream, stream->remote_candidates, &err); if (err) { purple_debug_error("media", "Error adding remote" " candidates: %s\n", err->message); g_error_free(err); } } } } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE || type == PURPLE_MEDIA_INFO_UNMUTE)) { GList *sessions; gboolean active = (type == PURPLE_MEDIA_INFO_MUTE); g_return_if_fail(PURPLE_IS_MEDIA(media)); if (session_id == NULL) sessions = g_hash_table_get_values( media->priv->sessions); else sessions = g_list_prepend(NULL, purple_media_get_session( media, session_id)); purple_debug_info("media", "Turning mute %s\n", active ? "on" : "off"); for (; sessions; sessions = g_list_delete_link( sessions, sessions)) { PurpleMediaSession *session = sessions->data; if (session->type & PURPLE_MEDIA_SEND_AUDIO) { gchar *name = g_strdup_printf("volume_%s", session->id); GstElement *volume = gst_bin_get_by_name( GST_BIN(session->media-> priv->confbin), name); g_free(name); g_object_set(volume, "mute", active, NULL); } } } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE || type == PURPLE_MEDIA_INFO_UNPAUSE)) { gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE); GList *streams = purple_media_get_streams(media, session_id, participant); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) { stream->paused = active; if (!stream->held) g_object_set(stream->stream, "direction", purple_media_to_fs_stream_direction( stream->session->type & ((active) ? ~PURPLE_MEDIA_SEND_VIDEO : PURPLE_MEDIA_VIDEO)), NULL); } } } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD || type == PURPLE_MEDIA_INFO_UNHOLD)) { GList *streams; gboolean active = (type == PURPLE_MEDIA_INFO_HOLD); g_return_if_fail(PURPLE_IS_MEDIA(media)); streams = purple_media_get_streams(media, session_id, participant); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; stream->held = active; if (stream->session->type & PURPLE_MEDIA_VIDEO) { FsStreamDirection direction; direction = ((active) ? ~PURPLE_MEDIA_VIDEO : PURPLE_MEDIA_VIDEO); if (!active && stream->paused) direction &= ~PURPLE_MEDIA_SEND_VIDEO; g_object_set(stream->stream, "direction", purple_media_to_fs_stream_direction( stream->session->type & direction), NULL); } else if (stream->session->type & PURPLE_MEDIA_AUDIO) { g_object_set(stream->stream, "direction", purple_media_to_fs_stream_direction( stream->session->type & ((active) ? ~PURPLE_MEDIA_AUDIO : PURPLE_MEDIA_AUDIO)), NULL); } } } g_signal_emit(media, purple_media_signals[STREAM_INFO], 0, type, session_id, participant, local); if (type == PURPLE_MEDIA_INFO_HANGUP || type == PURPLE_MEDIA_INFO_REJECT) { purple_media_end(media, session_id, participant); } #endif } #ifdef USE_VV static void purple_media_new_local_candidate_cb(FsStream *stream, FsCandidate *local_candidate, PurpleMediaSession *session) { gchar *name; FsParticipant *participant; PurpleMediaCandidate *candidate; g_return_if_fail(FS_IS_STREAM(stream)); g_return_if_fail(session != NULL); purple_debug_info("media", "got new local candidate: %s\n", local_candidate->foundation); g_object_get(stream, "participant", &participant, NULL); g_object_get(participant, "cname", &name, NULL); g_object_unref(participant); purple_media_insert_local_candidate(session, name, fs_candidate_copy(local_candidate)); candidate = purple_media_candidate_from_fs(local_candidate); g_signal_emit(session->media, purple_media_signals[NEW_CANDIDATE], 0, session->id, name, candidate); g_object_unref(candidate); g_free(name); } static void purple_media_candidates_prepared_cb(FsStream *stream, PurpleMediaSession *session) { gchar *name; FsParticipant *participant; PurpleMediaStream *stream_data; g_return_if_fail(FS_IS_STREAM(stream)); g_return_if_fail(session != NULL); g_object_get(stream, "participant", &participant, NULL); g_object_get(participant, "cname", &name, NULL); g_object_unref(participant); stream_data = purple_media_get_stream(session->media, session->id, name); stream_data->candidates_prepared = TRUE; g_signal_emit(session->media, purple_media_signals[CANDIDATES_PREPARED], 0, session->id, name); g_free(name); } /* callback called when a pair of transport candidates (local and remote) * has been established */ static void purple_media_candidate_pair_established_cb(FsStream *fsstream, FsCandidate *native_candidate, FsCandidate *remote_candidate, PurpleMediaSession *session) { gchar *name; FsParticipant *participant; PurpleMediaStream *stream; GList *iter; g_return_if_fail(FS_IS_STREAM(fsstream)); g_return_if_fail(session != NULL); g_object_get(fsstream, "participant", &participant, NULL); g_object_get(participant, "cname", &name, NULL); g_object_unref(participant); stream = purple_media_get_stream(session->media, session->id, name); iter = stream->active_local_candidates; for(; iter; iter = g_list_next(iter)) { FsCandidate *c = iter->data; if (native_candidate->component_id == c->component_id) { fs_candidate_destroy(c); stream->active_local_candidates = g_list_delete_link(iter, iter); stream->active_local_candidates = g_list_prepend( stream->active_local_candidates, fs_candidate_copy(native_candidate)); break; } } if (iter == NULL) stream->active_local_candidates = g_list_prepend( stream->active_local_candidates, fs_candidate_copy(native_candidate)); iter = stream->active_remote_candidates; for(; iter; iter = g_list_next(iter)) { FsCandidate *c = iter->data; if (native_candidate->component_id == c->component_id) { fs_candidate_destroy(c); stream->active_remote_candidates = g_list_delete_link(iter, iter); stream->active_remote_candidates = g_list_prepend( stream->active_remote_candidates, fs_candidate_copy(remote_candidate)); break; } } if (iter == NULL) stream->active_remote_candidates = g_list_prepend( stream->active_remote_candidates, fs_candidate_copy(remote_candidate)); purple_debug_info("media", "candidate pair established\n"); } static gboolean purple_media_connected_cb(PurpleMediaStream *stream) { g_return_val_if_fail(stream != NULL, FALSE); stream->connected_cb_id = 0; purple_media_manager_create_output_window( stream->session->media->priv->manager, stream->session->media, stream->session->id, stream->participant); g_signal_emit(stream->session->media, purple_media_signals[STATE_CHANGED], 0, PURPLE_MEDIA_STATE_CONNECTED, stream->session->id, stream->participant); return FALSE; } static void purple_media_src_pad_added_cb(FsStream *fsstream, GstPad *srcpad, FsCodec *codec, PurpleMediaStream *stream) { PurpleMediaPrivate *priv; GstPad *sinkpad; g_return_if_fail(FS_IS_STREAM(fsstream)); g_return_if_fail(stream != NULL); priv = stream->session->media->priv; if (stream->src == NULL) { GstElement *sink = NULL; if (codec->media_type == FS_MEDIA_TYPE_AUDIO) { GstElement *queue = NULL; double output_volume = purple_prefs_get_int( "/purple/media/audio/volume/output")/10.0; /* * Should this instead be: * audioconvert ! audioresample ! liveadder ! * audioresample ! audioconvert ! realsink */ queue = gst_element_factory_make("queue", NULL); stream->volume = gst_element_factory_make( "volume", NULL); g_object_set(stream->volume, "volume", output_volume, NULL); stream->level = gst_element_factory_make( "level", NULL); stream->src = gst_element_factory_make( "liveadder", NULL); sink = purple_media_manager_get_element(priv->manager, PURPLE_MEDIA_RECV_AUDIO, stream->session->media, stream->session->id, stream->participant); gst_bin_add(GST_BIN(priv->confbin), queue); gst_bin_add(GST_BIN(priv->confbin), stream->volume); gst_bin_add(GST_BIN(priv->confbin), stream->level); gst_bin_add(GST_BIN(priv->confbin), sink); gst_element_set_state(sink, GST_STATE_PLAYING); gst_element_set_state(stream->level, GST_STATE_PLAYING); gst_element_set_state(stream->volume, GST_STATE_PLAYING); gst_element_set_state(queue, GST_STATE_PLAYING); gst_element_link(stream->level, sink); gst_element_link(stream->volume, stream->level); gst_element_link(queue, stream->volume); sink = queue; } else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) { stream->src = gst_element_factory_make( "fsfunnel", NULL); sink = gst_element_factory_make( "fakesink", NULL); g_object_set(G_OBJECT(sink), "async", FALSE, NULL); gst_bin_add(GST_BIN(priv->confbin), sink); gst_element_set_state(sink, GST_STATE_PLAYING); } stream->tee = gst_element_factory_make("tee", NULL); gst_bin_add_many(GST_BIN(priv->confbin), stream->src, stream->tee, NULL); gst_element_set_state(stream->tee, GST_STATE_PLAYING); gst_element_set_state(stream->src, GST_STATE_PLAYING); gst_element_link_many(stream->src, stream->tee, sink, NULL); } sinkpad = gst_element_get_request_pad(stream->src, "sink%d"); gst_pad_link(srcpad, sinkpad); gst_object_unref(sinkpad); stream->connected_cb_id = purple_timeout_add(0, (GSourceFunc)purple_media_connected_cb, stream); } static void purple_media_element_added_cb(FsElementAddedNotifier *self, GstBin *bin, GstElement *element, gpointer user_data) { /* * Hack to make H264 work with Gmail video. */ if (!strncmp(GST_ELEMENT_NAME(element), "x264", 4)) { g_object_set(GST_OBJECT(element), "cabac", FALSE, NULL); } } #endif /* USE_VV */ gboolean purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who, PurpleMediaSessionType type, gboolean initiator, const gchar *transmitter, guint num_params, GParameter *params) { #ifdef USE_VV PurpleMediaSession *session; FsParticipant *participant = NULL; PurpleMediaStream *stream = NULL; FsMediaType media_type = purple_media_to_fs_media_type(type); FsStreamDirection type_direction = purple_media_to_fs_stream_direction(type); gboolean is_nice = !strcmp(transmitter, "nice"); g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); session = purple_media_get_session(media, sess_id); if (!session) { GError *err = NULL; GList *codec_conf = NULL, *iter = NULL; gchar *filename = NULL; PurpleMediaSessionType session_type; GstElement *src = NULL; session = g_new0(PurpleMediaSession, 1); session->session = fs_conference_new_session( media->priv->conference, media_type, &err); if (err != NULL) { purple_media_error(media, _("Error creating session: %s"), err->message); g_error_free(err); g_free(session); return FALSE; } filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL); codec_conf = fs_codec_list_from_keyfile(filename, &err); g_free(filename); if (err != NULL) { if (err->code == 4) purple_debug_info("media", "Couldn't read " "fs-codec.conf: %s\n", err->message); else purple_debug_error("media", "Error reading " "fs-codec.conf: %s\n", err->message); g_error_free(err); } /* * Add SPEEX if the configuration file doesn't exist or * there isn't a speex entry. */ for (iter = codec_conf; iter; iter = g_list_next(iter)) { FsCodec *codec = iter->data; if (!g_ascii_strcasecmp(codec->encoding_name, "speex")) break; } if (iter == NULL) { codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY, "SPEEX", FS_MEDIA_TYPE_AUDIO, 8000)); codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY, "SPEEX", FS_MEDIA_TYPE_AUDIO, 16000)); } fs_session_set_codec_preferences(session->session, codec_conf, NULL); /* * Removes a 5-7 second delay before * receiving the src-pad-added signal. * Only works for non-multicast FsRtpSessions. */ if (is_nice || !strcmp(transmitter, "rawudp")) g_object_set(G_OBJECT(session->session), "no-rtcp-timeout", 0, NULL); /* * Hack to make x264 work with Gmail video. */ if (is_nice && !strcmp(sess_id, "google-video")) { FsElementAddedNotifier *notifier = fs_element_added_notifier_new(); g_signal_connect(G_OBJECT(notifier), "element-added", G_CALLBACK(purple_media_element_added_cb), stream); fs_element_added_notifier_add(notifier, GST_BIN(media->priv->conference)); } fs_codec_list_destroy(codec_conf); session->id = g_strdup(sess_id); session->media = media; session->type = type; session->initiator = initiator; purple_media_add_session(media, session); g_signal_emit(media, purple_media_signals[STATE_CHANGED], 0, PURPLE_MEDIA_STATE_NEW, session->id, NULL); if (type_direction & FS_DIRECTION_SEND) { session_type = purple_media_from_fs(media_type, FS_DIRECTION_SEND); src = purple_media_manager_get_element( media->priv->manager, session_type, media, session->id, who); if (!GST_IS_ELEMENT(src)) { purple_debug_error("media", "Error creating src for session %s\n", session->id); purple_media_end(media, session->id, NULL); return FALSE; } purple_media_set_src(media, session->id, src); gst_element_set_state(session->src, GST_STATE_PLAYING); purple_media_manager_create_output_window( media->priv->manager, session->media, session->id, NULL); } } if (!(participant = purple_media_add_participant(media, who))) { purple_media_remove_session(media, session); g_free(session); return FALSE; } else { g_signal_emit(media, purple_media_signals[STATE_CHANGED], 0, PURPLE_MEDIA_STATE_NEW, NULL, who); } stream = purple_media_get_stream(media, sess_id, who); if (!stream) { GError *err = NULL; FsStream *fsstream = NULL; const gchar *stun_ip = purple_network_get_stun_ip(); const gchar *turn_ip = purple_network_get_turn_ip(); if (stun_ip || turn_ip) { guint new_num_params = (stun_ip && is_nice) && turn_ip ? num_params + 2 : num_params + 1; guint next_param_index = num_params; GParameter *param = g_new0(GParameter, new_num_params); memcpy(param, params, sizeof(GParameter) * num_params); if (stun_ip) { purple_debug_info("media", "setting property stun-ip on new stream: %s\n", stun_ip); param[next_param_index].name = "stun-ip"; g_value_init(¶m[next_param_index].value, G_TYPE_STRING); g_value_set_string(¶m[next_param_index].value, stun_ip); next_param_index++; } if (turn_ip && is_nice) { GValueArray *relay_info = g_value_array_new(0); GValue value; gint turn_port = purple_prefs_get_int("/purple/network/turn_port"); const gchar *username = purple_prefs_get_string("/purple/network/turn_username"); const gchar *password = purple_prefs_get_string("/purple/network/turn_password"); GstStructure *turn_setup = gst_structure_new("relay-info", "ip", G_TYPE_STRING, turn_ip, "port", G_TYPE_UINT, turn_port, "username", G_TYPE_STRING, username, "password", G_TYPE_STRING, password, NULL); if (turn_setup) { memset(&value, 0, sizeof(GValue)); g_value_init(&value, GST_TYPE_STRUCTURE); gst_value_set_structure(&value, turn_setup); relay_info = g_value_array_append(relay_info, &value); gst_structure_free(turn_setup); purple_debug_info("media", "setting property relay-info on new stream\n"); param[next_param_index].name = "relay-info"; g_value_init(¶m[next_param_index].value, G_TYPE_VALUE_ARRAY); g_value_set_boxed(¶m[next_param_index].value, relay_info); g_value_array_free(relay_info); } else { purple_debug_error("media", "Error relay info"); g_object_unref(participant); g_hash_table_remove(media->priv->participants, who); purple_media_remove_session(media, session); g_free(session); return FALSE; } } fsstream = fs_session_new_stream(session->session, participant, type_direction & FS_DIRECTION_RECV, transmitter, new_num_params, param, &err); g_free(param); } else { fsstream = fs_session_new_stream(session->session, participant, type_direction & FS_DIRECTION_RECV, transmitter, num_params, params, &err); } if (fsstream == NULL) { purple_debug_error("media", "Error creating stream: %s\n", err && err->message ? err->message : "NULL"); if (err) g_error_free(err); g_object_unref(participant); g_hash_table_remove(media->priv->participants, who); purple_media_remove_session(media, session); g_free(session); return FALSE; } stream = purple_media_insert_stream(session, who, fsstream); stream->initiator = initiator; /* callback for source pad added (new stream source ready) */ g_signal_connect(G_OBJECT(fsstream), "src-pad-added", G_CALLBACK(purple_media_src_pad_added_cb), stream); g_signal_emit(media, purple_media_signals[STATE_CHANGED], 0, PURPLE_MEDIA_STATE_NEW, session->id, who); } else { if (purple_media_to_fs_stream_direction(stream->session->type) != type_direction) { /* change direction */ g_object_set(stream->stream, "direction", type_direction, NULL); } } return TRUE; #else return FALSE; #endif /* USE_VV */ } PurpleMediaManager * purple_media_get_manager(PurpleMedia *media) { PurpleMediaManager *ret; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); g_object_get(media, "manager", &ret, NULL); return ret; } PurpleMediaSessionType purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id) { #ifdef USE_VV PurpleMediaSession *session; g_return_val_if_fail(PURPLE_IS_MEDIA(media), PURPLE_MEDIA_NONE); session = purple_media_get_session(media, sess_id); return session->type; #else return PURPLE_MEDIA_NONE; #endif } /* XXX: Should wait until codecs-ready is TRUE before using this function */ GList * purple_media_get_codecs(PurpleMedia *media, const gchar *sess_id) { #ifdef USE_VV GList *fscodecs; GList *codecs; PurpleMediaSession *session; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); session = purple_media_get_session(media, sess_id); if (session == NULL) return NULL; g_object_get(G_OBJECT(session->session), "codecs", &fscodecs, NULL); codecs = purple_media_codec_list_from_fs(fscodecs); fs_codec_list_destroy(fscodecs); return codecs; #else return NULL; #endif } GList * purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *participant) { #ifdef USE_VV PurpleMediaStream *stream; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); stream = purple_media_get_stream(media, sess_id, participant); return stream ? purple_media_candidate_list_from_fs( stream->local_candidates) : NULL; #else return NULL; #endif } void purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *participant, GList *remote_candidates) { #ifdef USE_VV PurpleMediaStream *stream; GError *err = NULL; g_return_if_fail(PURPLE_IS_MEDIA(media)); stream = purple_media_get_stream(media, sess_id, participant); if (stream == NULL) { purple_debug_error("media", "purple_media_add_remote_candidates: " "couldn't find stream %s %s.\n", sess_id ? sess_id : "(null)", participant ? participant : "(null)"); return; } stream->remote_candidates = g_list_concat(stream->remote_candidates, purple_media_candidate_list_to_fs(remote_candidates)); if (stream->accepted == TRUE) { fs_stream_set_remote_candidates(stream->stream, stream->remote_candidates, &err); if (err) { purple_debug_error("media", "Error adding remote" " candidates: %s\n", err->message); g_error_free(err); } } #endif } #if 0 /* * These two functions aren't being used and I'd rather not lock in the API * until they are needed. If they ever are. */ GList * purple_media_get_active_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *participant) { #ifdef USE_VV PurpleMediaStream *stream; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); stream = purple_media_get_stream(media, sess_id, participant); return purple_media_candidate_list_from_fs( stream->active_local_candidates); #else return NULL; #endif } GList * purple_media_get_active_remote_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *participant) { #ifdef USE_VV PurpleMediaStream *stream; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); stream = purple_media_get_stream(media, sess_id, participant); return purple_media_candidate_list_from_fs( stream->active_remote_candidates); #else return NULL; #endif } #endif gboolean purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id, const gchar *participant, GList *codecs) { #ifdef USE_VV PurpleMediaStream *stream; FsStream *fsstream; GList *fscodecs; GError *err = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); stream = purple_media_get_stream(media, sess_id, participant); if (stream == NULL) return FALSE; fsstream = stream->stream; fscodecs = purple_media_codec_list_to_fs(codecs); fs_stream_set_remote_codecs(fsstream, fscodecs, &err); fs_codec_list_destroy(fscodecs); if (err) { purple_debug_error("media", "Error setting remote codecs: %s\n", err->message); g_error_free(err); return FALSE; } return TRUE; #else return FALSE; #endif } gboolean purple_media_candidates_prepared(PurpleMedia *media, const gchar *session_id, const gchar *participant) { #ifdef USE_VV GList *streams; gboolean prepared = TRUE; g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); streams = purple_media_get_streams(media, session_id, participant); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; if (stream->candidates_prepared == FALSE) { g_list_free(streams); prepared = FALSE; break; } } return prepared; #else return FALSE; #endif } gboolean purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, PurpleMediaCodec *codec) { #ifdef USE_VV PurpleMediaSession *session; FsCodec *fscodec; GError *err = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); session = purple_media_get_session(media, sess_id); if (session != NULL) return FALSE; fscodec = purple_media_codec_to_fs(codec); fs_session_set_send_codec(session->session, fscodec, &err); fs_codec_destroy(fscodec); if (err) { purple_debug_error("media", "Error setting send codec\n"); g_error_free(err); return FALSE; } return TRUE; #else return FALSE; #endif } gboolean purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id) { #ifdef USE_VV gboolean ret; g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); if (sess_id != NULL) { PurpleMediaSession *session; session = purple_media_get_session(media, sess_id); if (session == NULL) return FALSE; if (session->type & (PURPLE_MEDIA_SEND_AUDIO | PURPLE_MEDIA_SEND_VIDEO)) g_object_get(session->session, "codecs-ready", &ret, NULL); else ret = TRUE; } else { GList *values = g_hash_table_get_values(media->priv->sessions); for (; values; values = g_list_delete_link(values, values)) { PurpleMediaSession *session = values->data; if (session->type & (PURPLE_MEDIA_SEND_AUDIO | PURPLE_MEDIA_SEND_VIDEO)) g_object_get(session->session, "codecs-ready", &ret, NULL); else ret = TRUE; if (ret == FALSE) break; } if (values != NULL) g_list_free(values); } return ret; #else return FALSE; #endif } gboolean purple_media_is_initiator(PurpleMedia *media, const gchar *sess_id, const gchar *participant) { #ifdef USE_VV g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); if (sess_id == NULL && participant == NULL) return media->priv->initiator; else if (sess_id != NULL && participant == NULL) { PurpleMediaSession *session = purple_media_get_session(media, sess_id); return session != NULL ? session->initiator : FALSE; } else if (sess_id != NULL && participant != NULL) { PurpleMediaStream *stream = purple_media_get_stream( media, sess_id, participant); return stream != NULL ? stream->initiator : FALSE; } #endif return FALSE; } gboolean purple_media_accepted(PurpleMedia *media, const gchar *sess_id, const gchar *participant) { #ifdef USE_VV gboolean accepted = TRUE; g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); if (sess_id == NULL && participant == NULL) { GList *streams = media->priv->streams; for (; streams; streams = g_list_next(streams)) { PurpleMediaStream *stream = streams->data; if (stream->accepted == FALSE) { accepted = FALSE; break; } } } else if (sess_id != NULL && participant == NULL) { GList *streams = purple_media_get_streams( media, sess_id, NULL); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; if (stream->accepted == FALSE) { g_list_free(streams); accepted = FALSE; break; } } } else if (sess_id != NULL && participant != NULL) { PurpleMediaStream *stream = purple_media_get_stream( media, sess_id, participant); if (stream == NULL || stream->accepted == FALSE) accepted = FALSE; } return accepted; #else return FALSE; #endif } void purple_media_set_input_volume(PurpleMedia *media, const gchar *session_id, double level) { #ifdef USE_VV GList *sessions; g_return_if_fail(PURPLE_IS_MEDIA(media)); purple_prefs_set_int("/purple/media/audio/volume/input", level); if (session_id == NULL) sessions = g_hash_table_get_values(media->priv->sessions); else sessions = g_list_append(NULL, purple_media_get_session(media, session_id)); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { PurpleMediaSession *session = sessions->data; if (session->type & PURPLE_MEDIA_SEND_AUDIO) { gchar *name = g_strdup_printf("volume_%s", session->id); GstElement *volume = gst_bin_get_by_name( GST_BIN(session->media->priv->confbin), name); g_free(name); g_object_set(volume, "volume", level/10.0, NULL); } } #endif } void purple_media_set_output_volume(PurpleMedia *media, const gchar *session_id, const gchar *participant, double level) { #ifdef USE_VV GList *streams; g_return_if_fail(PURPLE_IS_MEDIA(media)); purple_prefs_set_int("/purple/media/audio/volume/output", level); streams = purple_media_get_streams(media, session_id, participant); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO && GST_IS_ELEMENT(stream->volume)) { g_object_set(stream->volume, "volume", level/10.0, NULL); } } #endif } gulong purple_media_set_output_window(PurpleMedia *media, const gchar *session_id, const gchar *participant, gulong window_id) { #ifdef USE_VV g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); return purple_media_manager_set_output_window(media->priv->manager, media, session_id, participant, window_id); #else return 0; #endif } void purple_media_remove_output_windows(PurpleMedia *media) { #ifdef USE_VV GList *iter = media->priv->streams; for (; iter; iter = g_list_next(iter)) { PurpleMediaStream *stream = iter->data; purple_media_manager_remove_output_windows( media->priv->manager, media, stream->session->id, stream->participant); } iter = purple_media_get_session_ids(media); for (; iter; iter = g_list_delete_link(iter, iter)) { gchar *session_name = iter->data; purple_media_manager_remove_output_windows( media->priv->manager, media, session_name, NULL); } #endif } #ifdef USE_GSTREAMER GstElement * purple_media_get_tee(PurpleMedia *media, const gchar *session_id, const gchar *participant) { #ifdef USE_VV g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); if (session_id != NULL && participant == NULL) { PurpleMediaSession *session = purple_media_get_session(media, session_id); return (session != NULL) ? session->tee : NULL; } else if (session_id != NULL && participant != NULL) { PurpleMediaStream *stream = purple_media_get_stream(media, session_id, participant); return (stream != NULL) ? stream->tee : NULL; } g_return_val_if_reached(NULL); #else return NULL; #endif } #endif /* USE_GSTREAMER */