1 /*
2  * Copyright (C) 2015 Arun Raghavan <git@arunraghavan.net>
3  *
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 
24 #include "gstavrcputil.h"
25 #include "bluez.h"
26 
27 #include <gio/gio.h>
28 
29 #define BLUEZ_NAME "org.bluez"
30 #define BLUEZ_PATH "/"
31 #define BLUEZ_MEDIA_PLAYER_IFACE BLUEZ_NAME ".MediaPlayer1"
32 
33 struct _GstAvrcpConnection
34 {
35   GMainContext *context;
36   GMainLoop *mainloop;
37   GThread *thread;
38 
39   gchar *dev_path;
40   GDBusObjectManager *manager;
41   BluezMediaPlayer1 *player;
42 
43   GstAvrcpMetadataCb cb;
44   gpointer user_data;
45   GDestroyNotify user_data_free_cb;
46 };
47 
48 static const char *
tag_from_property(const char * name)49 tag_from_property (const char *name)
50 {
51   if (g_str_equal (name, "Title"))
52     return GST_TAG_TITLE;
53   else if (g_str_equal (name, "Artist"))
54     return GST_TAG_ARTIST;
55   else if (g_str_equal (name, "Album"))
56     return GST_TAG_ALBUM;
57   else if (g_str_equal (name, "Genre"))
58     return GST_TAG_GENRE;
59   else if (g_str_equal (name, "NumberOfTracks"))
60     return GST_TAG_TRACK_COUNT;
61   else if (g_str_equal (name, "TrackNumber"))
62     return GST_TAG_TRACK_NUMBER;
63   else if (g_str_equal (name, "Duration"))
64     return GST_TAG_DURATION;
65   else
66     return NULL;
67 }
68 
69 static GstTagList *
tag_list_from_variant(GVariant * properties,gboolean track)70 tag_list_from_variant (GVariant * properties, gboolean track)
71 {
72   const gchar *name, *s;
73   GVariant *value;
74   GVariantIter *iter;
75   GstTagList *taglist = NULL;
76 
77   iter = g_variant_iter_new (properties);
78 
79   if (track)
80     taglist = gst_tag_list_new_empty ();
81 
82   /* The properties are in two levels -- at the top level we have the position
83    * and the 'track'. The 'track' is another level of {sv} so we recurse one
84    * level to pick up the actual track data. We get the taglist from the
85    * recursive call, and ignore the position for now. */
86 
87   while (g_variant_iter_next (iter, "{&sv}", &name, &value)) {
88     if (!track && g_str_equal (name, "Track")) {
89       /* Top level property */
90       taglist = tag_list_from_variant (value, TRUE);
91 
92     } else if (track) {
93       /* If we get here, we are in the recursive call and we're dealing with
94        * properties under "Track" */
95       GType type;
96       const gchar *tag;
97       guint i;
98       guint64 i64;
99 
100       tag = tag_from_property (name);
101       if (!tag)
102         goto next;
103 
104       type = gst_tag_get_type (tag);
105 
106       switch (type) {
107         case G_TYPE_STRING:
108           s = g_variant_get_string (value, NULL);
109           if (s && s[0] != '\0')
110             gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL);
111           break;
112 
113         case G_TYPE_UINT:
114           i = g_variant_get_uint32 (value);
115           if (i > 0)
116             gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, i, NULL);
117           break;
118 
119         case G_TYPE_UINT64:
120           /* If we're here, the tag is 'duration' */
121           i64 = g_variant_get_uint32 (value);
122           if (i64 > 0 && i64 != (guint32) (-1)) {
123             gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag,
124                 i64 * GST_MSECOND, NULL);
125           }
126           break;
127 
128         default:
129           GST_WARNING ("Unknown property: %s", name);
130           break;
131       }
132     }
133 
134   next:
135     g_variant_unref (value);
136   }
137 
138   g_variant_iter_free (iter);
139 
140   if (taglist && gst_tag_list_is_empty (taglist)) {
141     gst_tag_list_unref (taglist);
142     taglist = NULL;
143   }
144 
145   return taglist;
146 }
147 
148 static void
player_property_changed_cb(GDBusProxy * proxy,GVariant * properties,GStrv invalid,gpointer user_data)149 player_property_changed_cb (GDBusProxy * proxy, GVariant * properties,
150     GStrv invalid, gpointer user_data)
151 {
152   GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data;
153   GstTagList *taglist;
154 
155   taglist = tag_list_from_variant (properties, FALSE);
156 
157   if (taglist)
158     avrcp->cb (avrcp, taglist, avrcp->user_data);
159 }
160 
161 static GstTagList *
player_get_taglist(BluezMediaPlayer1 * player)162 player_get_taglist (BluezMediaPlayer1 * player)
163 {
164   GstTagList *taglist = NULL;
165   GVariant *track;
166 
167   track = bluez_media_player1_get_track (player);
168   if (track)
169     taglist = tag_list_from_variant (track, TRUE);
170 
171   return taglist;
172 }
173 
174 static void
gst_avrcp_connection_set_player(GstAvrcpConnection * avrcp,BluezMediaPlayer1 * player)175 gst_avrcp_connection_set_player (GstAvrcpConnection * avrcp,
176     BluezMediaPlayer1 * player)
177 {
178   GstTagList *taglist;
179 
180   if (avrcp->player)
181     g_object_unref (avrcp->player);
182 
183   if (!player) {
184     avrcp->player = NULL;
185     return;
186   }
187 
188   avrcp->player = g_object_ref (player);
189 
190   g_signal_connect (player, "g-properties-changed",
191       G_CALLBACK (player_property_changed_cb), avrcp);
192 
193   taglist = player_get_taglist (avrcp->player);
194 
195   if (taglist)
196     avrcp->cb (avrcp, taglist, avrcp->user_data);
197 }
198 
199 static BluezMediaPlayer1 *
media_player_from_dbus_object(GDBusObject * object)200 media_player_from_dbus_object (GDBusObject * object)
201 {
202   return (BluezMediaPlayer1 *) g_dbus_object_get_interface (object,
203       BLUEZ_MEDIA_PLAYER_IFACE);
204 }
205 
206 static GType
manager_proxy_type_func(GDBusObjectManagerClient * manager,const gchar * object_path,const gchar * interface_name,gpointer user_data)207 manager_proxy_type_func (GDBusObjectManagerClient * manager,
208     const gchar * object_path, const gchar * interface_name, gpointer user_data)
209 {
210   if (!interface_name)
211     return G_TYPE_DBUS_OBJECT_PROXY;
212 
213   if (g_str_equal (interface_name, BLUEZ_MEDIA_PLAYER_IFACE))
214     return BLUEZ_TYPE_MEDIA_PLAYER1_PROXY;
215 
216   return G_TYPE_DBUS_PROXY;
217 }
218 
219 static void
manager_object_added_cb(GDBusObjectManager * manager,GDBusObject * object,gpointer user_data)220 manager_object_added_cb (GDBusObjectManager * manager,
221     GDBusObject * object, gpointer user_data)
222 {
223   GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data;
224   BluezMediaPlayer1 *player;
225 
226   if (!(player = media_player_from_dbus_object (object)))
227     return;
228 
229   gst_avrcp_connection_set_player (avrcp, player);
230 }
231 
232 static void
manager_object_removed_cb(GDBusObjectManager * manager,GDBusObject * object,gpointer user_data)233 manager_object_removed_cb (GDBusObjectManager * manager,
234     GDBusObject * object, gpointer user_data)
235 {
236   GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data;
237   BluezMediaPlayer1 *player;
238 
239   if (!(player = media_player_from_dbus_object (object)))
240     return;
241 
242   if (player == avrcp->player)
243     gst_avrcp_connection_set_player (avrcp, NULL);
244 }
245 
246 static void
manager_ready_cb(GObject * object,GAsyncResult * res,gpointer user_data)247 manager_ready_cb (GObject * object, GAsyncResult * res, gpointer user_data)
248 {
249   GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data;
250   GList *objects, *i;
251   GError *err = NULL;
252 
253   avrcp->manager = g_dbus_object_manager_client_new_for_bus_finish (res, &err);
254   if (!avrcp->manager) {
255     GST_WARNING ("Could not create ObjectManager proxy: %s", err->message);
256     g_error_free (err);
257     return;
258   }
259 
260   g_signal_connect (avrcp->manager, "object-added",
261       G_CALLBACK (manager_object_added_cb), avrcp);
262   g_signal_connect (avrcp->manager, "object-removed",
263       G_CALLBACK (manager_object_removed_cb), avrcp);
264 
265   objects = g_dbus_object_manager_get_objects (avrcp->manager);
266 
267   for (i = objects; i; i = i->next) {
268     BluezMediaPlayer1 *player =
269         media_player_from_dbus_object (G_DBUS_OBJECT (i->data));
270 
271     if (player && g_str_equal (avrcp->dev_path,
272             bluez_media_player1_get_device (player))) {
273       gst_avrcp_connection_set_player (avrcp, player);
274       break;
275     }
276   }
277 
278   g_list_free_full (objects, g_object_unref);
279 }
280 
281 GstAvrcpConnection *
gst_avrcp_connection_new(const gchar * dev_path,GstAvrcpMetadataCb cb,gpointer user_data,GDestroyNotify user_data_free_cb)282 gst_avrcp_connection_new (const gchar * dev_path, GstAvrcpMetadataCb cb,
283     gpointer user_data, GDestroyNotify user_data_free_cb)
284 {
285   GstAvrcpConnection *avrcp;
286 
287   avrcp = g_new0 (GstAvrcpConnection, 1);
288 
289   avrcp->cb = cb;
290   avrcp->user_data = user_data;
291   avrcp->user_data_free_cb = user_data_free_cb;
292 
293   avrcp->context = g_main_context_new ();
294   avrcp->mainloop = g_main_loop_new (avrcp->context, FALSE);
295 
296   avrcp->dev_path = g_strdup (dev_path);
297 
298   g_main_context_push_thread_default (avrcp->context);
299 
300   g_dbus_object_manager_client_new_for_bus (G_BUS_TYPE_SYSTEM,
301       G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, BLUEZ_NAME, BLUEZ_PATH,
302       manager_proxy_type_func, NULL, NULL, NULL, manager_ready_cb, avrcp);
303 
304   g_main_context_pop_thread_default (avrcp->context);
305 
306   avrcp->thread = g_thread_new ("gstavrcp", (GThreadFunc) g_main_loop_run,
307       avrcp->mainloop);
308 
309   return avrcp;
310 }
311 
312 void
gst_avrcp_connection_free(GstAvrcpConnection * avrcp)313 gst_avrcp_connection_free (GstAvrcpConnection * avrcp)
314 {
315   g_main_loop_quit (avrcp->mainloop);
316   g_main_loop_unref (avrcp->mainloop);
317 
318   g_main_context_unref (avrcp->context);
319 
320   g_thread_join (avrcp->thread);
321 
322   if (avrcp->player)
323     g_object_unref (avrcp->player);
324 
325   if (avrcp->manager)
326     g_object_unref (avrcp->manager);
327 
328   if (avrcp->user_data_free_cb)
329     avrcp->user_data_free_cb (avrcp->user_data);
330 
331   g_free (avrcp->dev_path);
332   g_free (avrcp);
333 }
334