/* * Copyright (C) 2015 Arun Raghavan * * * 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 #endif #include "gstavrcputil.h" #include "bluez.h" #include #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); }