blob: efce32dd425d147a4948ecc055bf3e2cef62552c [file] [log] [blame]
/*
* Copyright (C) 2015 Arun Raghavan <git@arunraghavan.net>
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "gstavrcputil.h"
#include "bluez.h"
#include <gio/gio.h>
#define BLUEZ_NAME "org.bluez"
#define BLUEZ_PATH "/"
#define BLUEZ_MEDIA_PLAYER_IFACE BLUEZ_NAME ".MediaPlayer1"
struct _GstAvrcpConnection
{
GMainContext *context;
GMainLoop *mainloop;
GThread *thread;
gchar *dev_path;
GDBusObjectManager *manager;
BluezMediaPlayer1 *player;
GstAvrcpMetadataCb cb;
gpointer user_data;
GDestroyNotify user_data_free_cb;
};
static const char *
tag_from_property (const char *name)
{
if (g_str_equal (name, "Title"))
return GST_TAG_TITLE;
else if (g_str_equal (name, "Artist"))
return GST_TAG_ARTIST;
else if (g_str_equal (name, "Album"))
return GST_TAG_ALBUM;
else if (g_str_equal (name, "Genre"))
return GST_TAG_GENRE;
else if (g_str_equal (name, "NumberOfTracks"))
return GST_TAG_TRACK_COUNT;
else if (g_str_equal (name, "TrackNumber"))
return GST_TAG_TRACK_NUMBER;
else if (g_str_equal (name, "Duration"))
return GST_TAG_DURATION;
else
return NULL;
}
static GstTagList *
tag_list_from_variant (GVariant * properties, gboolean track)
{
const gchar *name, *s;
GVariant *value;
GVariantIter *iter;
GstTagList *taglist = NULL;
iter = g_variant_iter_new (properties);
if (track)
taglist = gst_tag_list_new_empty ();
/* The properties are in two levels -- at the top level we have the position
* and the 'track'. The 'track' is another level of {sv} so we recurse one
* level to pick up the actual track data. We get the taglist from the
* recursive call, and ignore the position for now. */
while (g_variant_iter_next (iter, "{&sv}", &name, &value)) {
if (!track && g_str_equal (name, "Track")) {
/* Top level property */
taglist = tag_list_from_variant (value, TRUE);
} else if (track) {
/* If we get here, we are in the recursive call and we're dealing with
* properties under "Track" */
GType type;
const gchar *tag;
guint i;
guint64 i64;
tag = tag_from_property (name);
if (!tag)
goto next;
type = gst_tag_get_type (tag);
switch (type) {
case G_TYPE_STRING:
s = g_variant_get_string (value, NULL);
if (s && s[0] != '\0')
gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL);
break;
case G_TYPE_UINT:
i = g_variant_get_uint32 (value);
if (i > 0)
gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, i, NULL);
break;
case G_TYPE_UINT64:
/* If we're here, the tag is 'duration' */
i64 = g_variant_get_uint32 (value);
if (i64 > 0 && i64 != (guint32) (-1)) {
gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag,
i64 * GST_MSECOND, NULL);
}
break;
default:
GST_WARNING ("Unknown property: %s", name);
break;
}
}
next:
g_variant_unref (value);
}
g_variant_iter_free (iter);
if (taglist && gst_tag_list_is_empty (taglist)) {
gst_tag_list_unref (taglist);
taglist = NULL;
}
return taglist;
}
static void
player_property_changed_cb (GDBusProxy * proxy, GVariant * properties,
GStrv invalid, gpointer user_data)
{
GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data;
GstTagList *taglist;
taglist = tag_list_from_variant (properties, FALSE);
if (taglist)
avrcp->cb (avrcp, taglist, avrcp->user_data);
}
static GstTagList *
player_get_taglist (BluezMediaPlayer1 * player)
{
GstTagList *taglist = NULL;
GVariant *track;
track = bluez_media_player1_get_track (player);
if (track)
taglist = tag_list_from_variant (track, TRUE);
return taglist;
}
static void
gst_avrcp_connection_set_player (GstAvrcpConnection * avrcp,
BluezMediaPlayer1 * player)
{
GstTagList *taglist;
if (avrcp->player)
g_object_unref (avrcp->player);
if (!player) {
avrcp->player = NULL;
return;
}
avrcp->player = g_object_ref (player);
g_signal_connect (player, "g-properties-changed",
G_CALLBACK (player_property_changed_cb), avrcp);
taglist = player_get_taglist (avrcp->player);
if (taglist)
avrcp->cb (avrcp, taglist, avrcp->user_data);
}
static BluezMediaPlayer1 *
media_player_from_dbus_object (GDBusObject * object)
{
return (BluezMediaPlayer1 *) g_dbus_object_get_interface (object,
BLUEZ_MEDIA_PLAYER_IFACE);
}
static GType
manager_proxy_type_func (GDBusObjectManagerClient * manager,
const gchar * object_path, const gchar * interface_name, gpointer user_data)
{
if (!interface_name)
return G_TYPE_DBUS_OBJECT_PROXY;
if (g_str_equal (interface_name, BLUEZ_MEDIA_PLAYER_IFACE))
return BLUEZ_TYPE_MEDIA_PLAYER1_PROXY;
return G_TYPE_DBUS_PROXY;
}
static void
manager_object_added_cb (GDBusObjectManager * manager,
GDBusObject * object, gpointer user_data)
{
GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data;
BluezMediaPlayer1 *player;
if (!(player = media_player_from_dbus_object (object)))
return;
gst_avrcp_connection_set_player (avrcp, player);
}
static void
manager_object_removed_cb (GDBusObjectManager * manager,
GDBusObject * object, gpointer user_data)
{
GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data;
BluezMediaPlayer1 *player;
if (!(player = media_player_from_dbus_object (object)))
return;
if (player == avrcp->player)
gst_avrcp_connection_set_player (avrcp, NULL);
}
static void
manager_ready_cb (GObject * object, GAsyncResult * res, gpointer user_data)
{
GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data;
GList *objects, *i;
GError *err = NULL;
avrcp->manager = g_dbus_object_manager_client_new_for_bus_finish (res, &err);
if (!avrcp->manager) {
GST_WARNING ("Could not create ObjectManager proxy: %s", err->message);
g_error_free (err);
return;
}
g_signal_connect (avrcp->manager, "object-added",
G_CALLBACK (manager_object_added_cb), avrcp);
g_signal_connect (avrcp->manager, "object-removed",
G_CALLBACK (manager_object_removed_cb), avrcp);
objects = g_dbus_object_manager_get_objects (avrcp->manager);
for (i = objects; i; i = i->next) {
BluezMediaPlayer1 *player =
media_player_from_dbus_object (G_DBUS_OBJECT (i->data));
if (player && g_str_equal (avrcp->dev_path,
bluez_media_player1_get_device (player))) {
gst_avrcp_connection_set_player (avrcp, player);
break;
}
}
g_list_free_full (objects, g_object_unref);
}
GstAvrcpConnection *
gst_avrcp_connection_new (const gchar * dev_path, GstAvrcpMetadataCb cb,
gpointer user_data, GDestroyNotify user_data_free_cb)
{
GstAvrcpConnection *avrcp;
avrcp = g_new0 (GstAvrcpConnection, 1);
avrcp->cb = cb;
avrcp->user_data = user_data;
avrcp->user_data_free_cb = user_data_free_cb;
avrcp->context = g_main_context_new ();
avrcp->mainloop = g_main_loop_new (avrcp->context, FALSE);
avrcp->dev_path = g_strdup (dev_path);
g_main_context_push_thread_default (avrcp->context);
g_dbus_object_manager_client_new_for_bus (G_BUS_TYPE_SYSTEM,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, BLUEZ_NAME, BLUEZ_PATH,
manager_proxy_type_func, NULL, NULL, NULL, manager_ready_cb, avrcp);
g_main_context_pop_thread_default (avrcp->context);
avrcp->thread = g_thread_new ("gstavrcp", (GThreadFunc) g_main_loop_run,
avrcp->mainloop);
return avrcp;
}
void
gst_avrcp_connection_free (GstAvrcpConnection * avrcp)
{
g_main_loop_quit (avrcp->mainloop);
g_main_loop_unref (avrcp->mainloop);
g_main_context_unref (avrcp->context);
g_thread_join (avrcp->thread);
if (avrcp->player)
g_object_unref (avrcp->player);
if (avrcp->manager)
g_object_unref (avrcp->manager);
if (avrcp->user_data_free_cb)
avrcp->user_data_free_cb (avrcp->user_data);
g_free (avrcp->dev_path);
g_free (avrcp);
}