2015-10-06 11:53:16

by Bharat Bhusan Panda

[permalink] [raw]
Subject: [PATCH 1/3] aurdio/avrcp: Get player playlist details

Support added to read and cache player playlist details after
player registration completes.
---
profiles/audio/media.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 121 insertions(+)

diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index 69070bf..edeb66f 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -58,6 +58,7 @@
#define MEDIA_INTERFACE "org.bluez.Media1"
#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
#define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
+#define MEDIA_PLAYER_PLAYLIST_INTERFACE "org.mpris.MediaPlayer2.Playlists"

#define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */

@@ -95,6 +96,7 @@ struct media_endpoint {
struct media_player {
struct media_adapter *adapter;
struct avrcp_player *player;
+ GSList *playlists;
char *sender; /* Player DBus bus id */
char *path; /* Player object path */
GHashTable *settings; /* Player settings */
@@ -105,8 +107,10 @@ struct media_player {
char *status;
uint32_t position;
uint32_t duration;
+ uint32_t total_items;
uint8_t volume;
GTimer *timer;
+ guint playlist_id;
bool play;
bool pause;
bool next;
@@ -115,6 +119,13 @@ struct media_player {
char *name;
};

+struct media_playlist {
+ char *name;
+ char *id;
+ char *icon;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
static GSList *adapters = NULL;

static void endpoint_request_free(struct endpoint_request *request)
@@ -961,6 +972,9 @@ static void media_player_free(gpointer data)
if (mp->settings)
g_hash_table_unref(mp->settings);

+ if (mp->playlist_id > 0)
+ g_source_remove(mp->playlist_id);
+
g_timer_destroy(mp->timer);
g_free(mp->sender);
g_free(mp->path);
@@ -1271,6 +1285,111 @@ static bool previous(void *user_data)
return media_player_send(mp, "Previous");
}

+
+static void playlist_reply(DBusPendingCall *call, void *user_data)
+{
+ struct media_player *mp = user_data;
+ DBusMessage *reply;
+ DBusMessageIter array_iter, entry, struct_iter;
+ DBusError derr;
+ int ctype;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&derr);
+
+ if (dbus_set_error_from_message(&derr, reply)) {
+ error("Error: GetPlayLists method %s, %s", derr.name,
+ derr.message);
+ dbus_error_free(&derr);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &array_iter);
+ ctype = dbus_message_iter_get_arg_type(&array_iter);
+
+ if (ctype != DBUS_TYPE_ARRAY)
+ goto done;
+
+ dbus_message_iter_recurse(&array_iter, &struct_iter);
+
+ while ((ctype = dbus_message_iter_get_arg_type(&struct_iter)) !=
+ DBUS_TYPE_INVALID) {
+ struct media_playlist *playlist;
+
+ if (ctype != DBUS_TYPE_STRUCT) {
+ error("Invalid type");
+ goto done;
+ }
+
+ playlist = g_new0(struct media_playlist, 1);
+
+ dbus_message_iter_recurse(&struct_iter, &entry);
+
+ dbus_message_iter_get_basic(&entry, &playlist->id);
+ DBG("Playlists Id %s", playlist->id);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_get_basic(&entry, &playlist->name);
+ DBG("Playlist Name %s", playlist->name);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_get_basic(&entry, &playlist->icon);
+ DBG("Playlist Icon %s", playlist->icon);
+
+ /* TODO: Create Media folder with playlist information */
+
+ mp->playlists = g_slist_append(mp->playlists, playlist);
+ mp->total_items++;
+
+ dbus_message_iter_next(&struct_iter);
+ }
+
+done:
+ dbus_message_unref(reply);
+}
+
+static gboolean get_playlists(gpointer user_data)
+{
+ struct media_player *mp = user_data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+ gboolean reverse_order = FALSE;
+ uint32_t start_index = 0x0000;
+ uint32_t end_index = 0xFFFF;
+ char *ordering = "Alphabetical";
+ uint32_t max_count = (end_index - start_index);
+
+ msg = dbus_message_new_method_call(mp->sender, mp->path,
+ MEDIA_PLAYER_PLAYLIST_INTERFACE,
+ "GetPlaylists");
+
+ if (msg == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return -ENOMEM;
+ }
+
+ dbus_message_append_args(msg,
+ DBUS_TYPE_UINT32, &start_index,
+ DBUS_TYPE_UINT32, &max_count,
+ DBUS_TYPE_STRING, &ordering,
+ DBUS_TYPE_BOOLEAN, &reverse_order,
+ DBUS_TYPE_INVALID);
+
+ mp->total_items = 0;
+
+ if ((dbus_connection_send_with_reply(btd_get_dbus_connection(),
+ msg, &call, -1))) {
+ dbus_pending_call_set_notify(call, &playlist_reply, mp, NULL);
+ dbus_pending_call_unref(call);
+ dbus_message_unref(msg);
+ }
+
+ mp->playlist_id = 0;
+
+ return FALSE;
+}
+
static struct avrcp_player_cb player_cb = {
.list_settings = list_settings,
.get_setting = get_setting,
@@ -1831,6 +1950,8 @@ static DBusMessage *register_player(DBusConnection *conn, DBusMessage *msg,
return btd_error_invalid_args(msg);
}

+ mp->playlist_id = g_idle_add(get_playlists, mp);
+
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

--
1.9.1



2015-10-20 05:09:31

by Bharat Bhusan Panda

[permalink] [raw]
Subject: RE: [PATCH 1/3] aurdio/avrcp: Get player playlist details

Hi Luiz,

> -----Original Message-----
> From: [email protected] [mailto:linux-bluetooth-
> [email protected]] On Behalf Of Luiz Augusto von Dentz
> Sent: Monday, October 19, 2015 4:06 PM
> To: Bharat Bhusan Panda
> Cc: [email protected]; [email protected]
> Subject: Re: [PATCH 1/3] aurdio/avrcp: Get player playlist details
>
> Hi Bharat,
>
> On Mon, Oct 19, 2015 at 12:02 PM, Bharat Bhusan Panda
> <[email protected]> wrote:
> > Ping
> >
> >> -----Original Message-----
> >> From: [email protected] [mailto:linux-bluetooth-
> >> [email protected]] On Behalf Of Bharat Panda
> >> Sent: Tuesday, October 06, 2015 5:23 PM
> >> To: [email protected]
> >> Cc: [email protected]; Bharat Panda
> >> Subject: [PATCH 1/3] aurdio/avrcp: Get player playlist details
> >>
> >> Support added to read and cache player playlist details after player
> >> registration completes.
> >> ---
> >> profiles/audio/media.c | 121
> >> +++++++++++++++++++++++++++++++++++++++++++++++++
> >> 1 file changed, 121 insertions(+)
> >>
> >> diff --git a/profiles/audio/media.c b/profiles/audio/media.c index
> >> 69070bf..edeb66f 100644
> >> --- a/profiles/audio/media.c
> >> +++ b/profiles/audio/media.c
> >> @@ -58,6 +58,7 @@
> >> #define MEDIA_INTERFACE "org.bluez.Media1"
> >> #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
> >> #define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
> >> +#define MEDIA_PLAYER_PLAYLIST_INTERFACE
> >> "org.mpris.MediaPlayer2.Playlists"
> >>
> >> #define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */
> >>
> >> @@ -95,6 +96,7 @@ struct media_endpoint { struct media_player {
> >> struct media_adapter *adapter;
> >> struct avrcp_player *player;
> >> + GSList *playlists;
> >> char *sender; /* Player DBus bus id */
> >> char *path; /* Player object path */
> >> GHashTable *settings; /* Player settings */
> >> @@ -105,8 +107,10 @@ struct media_player {
> >> char *status;
> >> uint32_t position;
> >> uint32_t duration;
> >> + uint32_t total_items;
> >> uint8_t volume;
> >> GTimer *timer;
> >> + guint playlist_id;
> >> bool play;
> >> bool pause;
> >> bool next;
> >> @@ -115,6 +119,13 @@ struct media_player {
> >> char *name;
> >> };
> >>
> >> +struct media_playlist {
> >> + char *name;
> >> + char *id;
> >> + char *icon;
> >> + uint8_t data[0];
> >> +} __attribute__ ((packed));
> >> +
> >> static GSList *adapters = NULL;
> >>
> >> static void endpoint_request_free(struct endpoint_request *request)
> >> @@ -
> >> 961,6 +972,9 @@ static void media_player_free(gpointer data)
> >> if (mp->settings)
> >> g_hash_table_unref(mp->settings);
> >>
> >> + if (mp->playlist_id > 0)
> >> + g_source_remove(mp->playlist_id);
> >> +
> >> g_timer_destroy(mp->timer);
> >> g_free(mp->sender);
> >> g_free(mp->path);
> >> @@ -1271,6 +1285,111 @@ static bool previous(void *user_data)
> >> return media_player_send(mp, "Previous"); }
> >>
> >> +
> >> +static void playlist_reply(DBusPendingCall *call, void *user_data) {
> >> + struct media_player *mp = user_data;
> >> + DBusMessage *reply;
> >> + DBusMessageIter array_iter, entry, struct_iter;
> >> + DBusError derr;
> >> + int ctype;
> >> +
> >> + reply = dbus_pending_call_steal_reply(call);
> >> +
> >> + dbus_error_init(&derr);
> >> +
> >> + if (dbus_set_error_from_message(&derr, reply)) {
> >> + error("Error: GetPlayLists method %s, %s", derr.name,
> >> + derr.message);
> >> + dbus_error_free(&derr);
> >> + goto done;
> >> + }
> >> +
> >> + dbus_message_iter_init(reply, &array_iter);
> >> + ctype = dbus_message_iter_get_arg_type(&array_iter);
> >> +
> >> + if (ctype != DBUS_TYPE_ARRAY)
> >> + goto done;
> >> +
> >> + dbus_message_iter_recurse(&array_iter, &struct_iter);
> >> +
> >> + while ((ctype = dbus_message_iter_get_arg_type(&struct_iter)) !=
> >> + DBUS_TYPE_INVALID) {
> >> + struct media_playlist *playlist;
> >> +
> >> + if (ctype != DBUS_TYPE_STRUCT) {
> >> + error("Invalid type");
> >> + goto done;
> >> + }
> >> +
> >> + playlist = g_new0(struct media_playlist, 1);
> >> +
> >> + dbus_message_iter_recurse(&struct_iter, &entry);
> >> +
> >> + dbus_message_iter_get_basic(&entry, &playlist->id);
> >> + DBG("Playlists Id %s", playlist->id);
> >> +
> >> + dbus_message_iter_next(&entry);
> >> + dbus_message_iter_get_basic(&entry, &playlist->name);
> >> + DBG("Playlist Name %s", playlist->name);
> >> +
> >> + dbus_message_iter_next(&entry);
> >> + dbus_message_iter_get_basic(&entry, &playlist->icon);
> >> + DBG("Playlist Icon %s", playlist->icon);
> >> +
> >> + /* TODO: Create Media folder with playlist information
> >> + */
> >> +
> >> + mp->playlists = g_slist_append(mp->playlists, playlist);
> >> + mp->total_items++;
> >> +
> >> + dbus_message_iter_next(&struct_iter);
> >> + }
>
> Id expected you to subscribe to playlist changes once you complete reading
> the initial list then every time it changes you will need to refetch the list in
> case it has changed, but fill free to split it so this patch don't become to big.
>
> >> +done:
> >> + dbus_message_unref(reply);
> >> +}
> >> +
> >> +static gboolean get_playlists(gpointer user_data) {
> >> + struct media_player *mp = user_data;
> >> + DBusMessage *msg;
> >> + DBusPendingCall *call;
> >> + gboolean reverse_order = FALSE;
> >> + uint32_t start_index = 0x0000;
> >> + uint32_t end_index = 0xFFFF;
> >> + char *ordering = "Alphabetical";
> >> + uint32_t max_count = (end_index - start_index);
> >> +
> >> + msg = dbus_message_new_method_call(mp->sender, mp->path,
> >> + MEDIA_PLAYER_PLAYLIST_INTERFACE,
> >> + "GetPlaylists");
> >> +
> >> + if (msg == NULL) {
> >> + error("Couldn't allocate D-Bus message");
> >> + return -ENOMEM;
> >> + }
> >> +
> >> + dbus_message_append_args(msg,
> >> + DBUS_TYPE_UINT32, &start_index,
> >> + DBUS_TYPE_UINT32, &max_count,
> >> + DBUS_TYPE_STRING, &ordering,
> >> + DBUS_TYPE_BOOLEAN, &reverse_order,
> >> + DBUS_TYPE_INVALID);
> >> +
> >> + mp->total_items = 0;
> >> +
> >> + if ((dbus_connection_send_with_reply(btd_get_dbus_connection(),
> >> + msg, &call, -1))) {
> >> + dbus_pending_call_set_notify(call, &playlist_reply, mp,
> >> NULL);
> >> + dbus_pending_call_unref(call);
> >> + dbus_message_unref(msg);
> >> + }
>
> You need to save the DBusPendingCall call to be able to cancel the request in
> case something happens in the meantime, there is similar code already in the
> same file just look at e.g.
> media_endpoint_async_call. (note that I don't want you to copy it exactly
> but it might be a good idea to have pending request for media players but if
> is just GetPlaylist Im fine to just store it inside media_player structure).
Getplaylist and ActivatePlaylist are the methods I am planning to add for Playlist interface as of now. But Tracklist info are also needed for which there are 4 new methods on Tracklist interface. So it will be better to keep the pending call and make it somewhat similar to media_endpoint_async_call. But though the parser for these methods reply will be quite different from each other I am planning to put a switch case on the basic of method name to dbus_pending_call_set_notify for callbacks.
I will submit v2 with these changes.
>
> >> + mp->playlist_id = 0;
> >> +
> >> + return FALSE;
> >> +}
> >> +
> >> static struct avrcp_player_cb player_cb = {
> >> .list_settings = list_settings,
> >> .get_setting = get_setting,
> >> @@ -1831,6 +1950,8 @@ static DBusMessage
> >> *register_player(DBusConnection *conn, DBusMessage *msg,
> >> return btd_error_invalid_args(msg);
> >> }
> >>
> >> + mp->playlist_id = g_idle_add(get_playlists, mp);
>
> Since you won't block anymore the call to get_playlists can be done directly
> here.
>
> >> +
> >> return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); }
> >>
> >> --
> >> 1.9.1
> >>
> >> --
> >> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth"
> > in
> >> the body of a message to [email protected] More majordomo
> >> info at http://vger.kernel.org/majordomo-info.html
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe
> > linux-bluetooth" in the body of a message to [email protected]
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>
>
> --
> Luiz Augusto von Dentz
> --

Regards,
Bharat


2015-10-19 10:54:32

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [PATCH 3/3] audio/avrcp: Add support for SetBrowsedPlayer

Hi Bharat,

On Tue, Oct 6, 2015 at 2:53 PM, Bharat Panda <[email protected]> wrote:
> Support added to handle SetBrowsedPlayer command for TG role.
>
> AVCTP Browsing: Response: type 0x00 label 13 PID 0x110e
> AVRCP: SetBrowsedPlayer: len 0x000a
> Status: 0x04 (Success)
> UIDCounter: 0x0000 (0)
> Number of Items: 0x00000007 (7)
> CharsetID: 0x006a (UTF-8)
> Folder Depth: 0x00 (0)
> ---
> profiles/audio/avrcp.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++-
> profiles/audio/avrcp.h | 1 +
> profiles/audio/media.c | 7 ++++++
> 3 files changed, 66 insertions(+), 1 deletion(-)
>
> diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c
> index 44f8929..af40b59 100644
> --- a/profiles/audio/avrcp.c
> +++ b/profiles/audio/avrcp.c
> @@ -207,6 +207,15 @@ struct player_item {
> char name[0];
> } __attribute__ ((packed));
>
> +struct avrcp_set_browsed_player_rsp {
> + uint8_t status;
> + uint16_t uid_counter;
> + uint32_t num_of_items;
> + uint16_t charset_id;
> + uint8_t folder_depth;
> + uint8_t data[0];
> +} __attribute__ ((packed));
> +
> struct avrcp_server {
> struct btd_adapter *adapter;
> uint32_t tg_record_id;
> @@ -1873,6 +1882,54 @@ err_metadata:
> return AVRCP_HEADER_LENGTH + 1;
> }
>
> +static void avrcp_handle_set_browsed_player(struct avrcp *session,
> + struct avrcp_browsing_header *pdu,
> + uint8_t transaction)
> +{
> + struct avrcp_player *player;
> + struct media_player *mp;
> + uint16_t len = ntohs(pdu->param_len);
> + uint16_t player_id = 0;
> + uint8_t status;
> +
> + if (len < 1) {
> + status = AVRCP_STATUS_INVALID_PARAM;
> + goto failed;
> + }
> +
> + player_id = bt_get_be16(&pdu->params[0]);
> + player = find_tg_player(session, player_id);
> +
> + if (player) {
> + struct avrcp_set_browsed_player_rsp *resp;
> +
> + mp = player->user_data;
> + player->browsed = true;
> +
> + resp = (struct avrcp_set_browsed_player_rsp *) pdu->params;
> +
> + resp->status = AVRCP_STATUS_SUCCESS;
> + resp->uid_counter = htons(player_get_uid_counter(player));
> + resp->num_of_items = htonl(player->cb->get_total_items(mp));
> + /*
> + * MPRIS player returns a flat hierarchy playlist structure,
> + * where the folder depth will always considered to be 0.
> + */
> + resp->folder_depth = 0;
> + resp->charset_id = htons(AVRCP_CHARSET_UTF8);
> + pdu->param_len = htons(sizeof(*resp));
> + } else {
> + status = AVRCP_STATUS_INVALID_PLAYER_ID;
> + goto failed;

Id probably check with if (!player)... goto failed; to simplify the
indentation, also we may respond with No available players in case
there are no players registered, or Player Not Browsable if the player
browsing flag is not set which means it does not support playlists.

> + }
> +
> + return;
> +
> +failed:
> + pdu->params[0] = status;
> + pdu->param_len = htons(1);
> +}
> +
> static void avrcp_handle_media_player_list(struct avrcp *session,
> struct avrcp_browsing_header *pdu,
> uint32_t start_item, uint32_t end_item)
> @@ -1912,7 +1969,6 @@ static void avrcp_handle_media_player_list(struct avrcp *session,
> memcpy(&item->features, &features, sizeof(features));
>
> item->charset = htons(AVRCP_CHARSET_UTF8);
> -
> name = player->cb->get_name(player->user_data);
> namelen = strlen(name);
> item->namelen = htons(namelen);
> @@ -1989,6 +2045,7 @@ static struct browsing_pdu_handler {
> uint8_t transaction);
> } browsing_handlers[] = {
> { AVRCP_GET_FOLDER_ITEMS, avrcp_handle_get_folder_items },
> + { AVRCP_SET_BROWSED_PLAYER, avrcp_handle_set_browsed_player},
> { },
> };
>
> diff --git a/profiles/audio/avrcp.h b/profiles/audio/avrcp.h
> index 86d310c..c09b910 100644
> --- a/profiles/audio/avrcp.h
> +++ b/profiles/audio/avrcp.h
> @@ -101,6 +101,7 @@ struct avrcp_player_cb {
> bool (*pause) (void *user_data);
> bool (*next) (void *user_data);
> bool (*previous) (void *user_data);
> + uint32_t (*get_total_items) (void *user_data);
> };
>
> int avrcp_set_volume(struct btd_device *dev, uint8_t volume, bool notify);
> diff --git a/profiles/audio/media.c b/profiles/audio/media.c
> index 2df0cb2..3196b3a 100644
> --- a/profiles/audio/media.c
> +++ b/profiles/audio/media.c
> @@ -1287,6 +1287,12 @@ static bool previous(void *user_data)
> return media_player_send(mp, "Previous");
> }
>
> +static uint32_t get_total_items(void *user_data)
> +{
> + struct media_player *mp = user_data;
> +
> + return mp->total_items;
> +}
>
> static void playlist_reply(DBusPendingCall *call, void *user_data)
> {
> @@ -1409,6 +1415,7 @@ static struct avrcp_player_cb player_cb = {
> .pause = pause,
> .next = next,
> .previous = previous,
> + .get_total_items = get_total_items,
> };
>
> static void media_player_exit(DBusConnection *connection, void *user_data)
> --
> 1.9.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html



--
Luiz Augusto von Dentz

2015-10-19 10:42:49

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [PATCH 2/3] audio/avrcp: Handle PlaylistChanged signal

Hi Bharat,

On Tue, Oct 6, 2015 at 2:53 PM, Bharat Panda <[email protected]> wrote:
> Added signal handler for "PlaylistChanged" on MediaPlayer2.Playlists
> interface.
> ---
> profiles/audio/media.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 54 insertions(+)
>
> diff --git a/profiles/audio/media.c b/profiles/audio/media.c
> index edeb66f..2df0cb2 100644
> --- a/profiles/audio/media.c
> +++ b/profiles/audio/media.c
> @@ -104,6 +104,7 @@ struct media_player {
> guint watch;
> guint properties_watch;
> guint seek_watch;
> + guint playlist_watch;
> char *status;
> uint32_t position;
> uint32_t duration;
> @@ -965,6 +966,7 @@ static void media_player_free(gpointer data)
> g_dbus_remove_watch(conn, mp->watch);
> g_dbus_remove_watch(conn, mp->properties_watch);
> g_dbus_remove_watch(conn, mp->seek_watch);
> + g_dbus_remove_watch(conn, mp->playlist_watch);
>
> if (mp->track)
> g_hash_table_unref(mp->track);
> @@ -1871,6 +1873,51 @@ static gboolean position_changed(DBusConnection *connection, DBusMessage *msg,
> return TRUE;
> }
>
> +static gboolean playlist_changed(DBusConnection *connection,
> + DBusMessage *msg,
> + void *user_data)
> +{
> + struct media_player *mp = user_data;
> + DBusMessageIter iter;
> + DBusMessageIter entry;
> + char *playlist_id;
> + GSList *l;
> +
> + dbus_message_iter_init(msg, &iter);
> + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRUCT)
> + return FALSE;
> +
> + dbus_message_iter_recurse(&iter, &entry);
> +
> + if (dbus_message_iter_get_arg_type(&entry) !=
> + DBUS_TYPE_OBJECT_PATH)
> + return FALSE;
> +
> + dbus_message_iter_get_basic(&entry, &playlist_id);
> +
> + for (l = mp->playlists; l; l = l->next) {
> + struct media_playlist *playlist = l->data;
> +
> + if (g_strcmp0(playlist->id, playlist_id) == 0) {
> + dbus_message_iter_next(&entry);
> + if (dbus_message_iter_get_arg_type(&entry) !=
> + DBUS_TYPE_STRING)
> + return FALSE;
> +
> + dbus_message_iter_get_basic(&entry, &playlist->name);
> +
> + dbus_message_iter_next(&entry);
> + if (dbus_message_iter_get_arg_type(&entry) !=
> + DBUS_TYPE_STRING)
> + return FALSE;
> +
> + dbus_message_iter_get_basic(&entry, &playlist->icon);
> + }
> + }

Id split this into find_playlist since we might need to reuse it in a
few places, also in case the playlist_id don't match it is probably a
new playlist so you might need to create a entry if that happens.

> +
> + return TRUE;
> +}
> +
> static struct media_player *media_player_create(struct media_adapter *adapter,
> const char *sender,
> const char *path,
> @@ -1950,6 +1997,13 @@ static DBusMessage *register_player(DBusConnection *conn, DBusMessage *msg,
> return btd_error_invalid_args(msg);
> }
>
> + mp->playlist_watch = g_dbus_add_signal_watch(conn,
> + sender, path,
> + MEDIA_PLAYER_PLAYLIST_INTERFACE,
> + "PlaylistChanged",
> + playlist_changed,
> + mp, NULL);
> +

It seems you have in fact tracking this, so please disregard the
comments related to changed signal in the first patch.


--
Luiz Augusto von Dentz

2015-10-19 10:35:57

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [PATCH 1/3] aurdio/avrcp: Get player playlist details

Hi Bharat,

On Mon, Oct 19, 2015 at 12:02 PM, Bharat Bhusan Panda
<[email protected]> wrote:
> Ping
>
>> -----Original Message-----
>> From: [email protected] [mailto:linux-bluetooth-
>> [email protected]] On Behalf Of Bharat Panda
>> Sent: Tuesday, October 06, 2015 5:23 PM
>> To: [email protected]
>> Cc: [email protected]; Bharat Panda
>> Subject: [PATCH 1/3] aurdio/avrcp: Get player playlist details
>>
>> Support added to read and cache player playlist details after player
>> registration completes.
>> ---
>> profiles/audio/media.c | 121
>> +++++++++++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 121 insertions(+)
>>
>> diff --git a/profiles/audio/media.c b/profiles/audio/media.c index
>> 69070bf..edeb66f 100644
>> --- a/profiles/audio/media.c
>> +++ b/profiles/audio/media.c
>> @@ -58,6 +58,7 @@
>> #define MEDIA_INTERFACE "org.bluez.Media1"
>> #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
>> #define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
>> +#define MEDIA_PLAYER_PLAYLIST_INTERFACE
>> "org.mpris.MediaPlayer2.Playlists"
>>
>> #define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */
>>
>> @@ -95,6 +96,7 @@ struct media_endpoint { struct media_player {
>> struct media_adapter *adapter;
>> struct avrcp_player *player;
>> + GSList *playlists;
>> char *sender; /* Player DBus bus id */
>> char *path; /* Player object path */
>> GHashTable *settings; /* Player settings */
>> @@ -105,8 +107,10 @@ struct media_player {
>> char *status;
>> uint32_t position;
>> uint32_t duration;
>> + uint32_t total_items;
>> uint8_t volume;
>> GTimer *timer;
>> + guint playlist_id;
>> bool play;
>> bool pause;
>> bool next;
>> @@ -115,6 +119,13 @@ struct media_player {
>> char *name;
>> };
>>
>> +struct media_playlist {
>> + char *name;
>> + char *id;
>> + char *icon;
>> + uint8_t data[0];
>> +} __attribute__ ((packed));
>> +
>> static GSList *adapters = NULL;
>>
>> static void endpoint_request_free(struct endpoint_request *request) @@ -
>> 961,6 +972,9 @@ static void media_player_free(gpointer data)
>> if (mp->settings)
>> g_hash_table_unref(mp->settings);
>>
>> + if (mp->playlist_id > 0)
>> + g_source_remove(mp->playlist_id);
>> +
>> g_timer_destroy(mp->timer);
>> g_free(mp->sender);
>> g_free(mp->path);
>> @@ -1271,6 +1285,111 @@ static bool previous(void *user_data)
>> return media_player_send(mp, "Previous"); }
>>
>> +
>> +static void playlist_reply(DBusPendingCall *call, void *user_data) {
>> + struct media_player *mp = user_data;
>> + DBusMessage *reply;
>> + DBusMessageIter array_iter, entry, struct_iter;
>> + DBusError derr;
>> + int ctype;
>> +
>> + reply = dbus_pending_call_steal_reply(call);
>> +
>> + dbus_error_init(&derr);
>> +
>> + if (dbus_set_error_from_message(&derr, reply)) {
>> + error("Error: GetPlayLists method %s, %s", derr.name,
>> + derr.message);
>> + dbus_error_free(&derr);
>> + goto done;
>> + }
>> +
>> + dbus_message_iter_init(reply, &array_iter);
>> + ctype = dbus_message_iter_get_arg_type(&array_iter);
>> +
>> + if (ctype != DBUS_TYPE_ARRAY)
>> + goto done;
>> +
>> + dbus_message_iter_recurse(&array_iter, &struct_iter);
>> +
>> + while ((ctype = dbus_message_iter_get_arg_type(&struct_iter)) !=
>> + DBUS_TYPE_INVALID) {
>> + struct media_playlist *playlist;
>> +
>> + if (ctype != DBUS_TYPE_STRUCT) {
>> + error("Invalid type");
>> + goto done;
>> + }
>> +
>> + playlist = g_new0(struct media_playlist, 1);
>> +
>> + dbus_message_iter_recurse(&struct_iter, &entry);
>> +
>> + dbus_message_iter_get_basic(&entry, &playlist->id);
>> + DBG("Playlists Id %s", playlist->id);
>> +
>> + dbus_message_iter_next(&entry);
>> + dbus_message_iter_get_basic(&entry, &playlist->name);
>> + DBG("Playlist Name %s", playlist->name);
>> +
>> + dbus_message_iter_next(&entry);
>> + dbus_message_iter_get_basic(&entry, &playlist->icon);
>> + DBG("Playlist Icon %s", playlist->icon);
>> +
>> + /* TODO: Create Media folder with playlist information */
>> +
>> + mp->playlists = g_slist_append(mp->playlists, playlist);
>> + mp->total_items++;
>> +
>> + dbus_message_iter_next(&struct_iter);
>> + }

Id expected you to subscribe to playlist changes once you complete
reading the initial list then every time it changes you will need to
refetch the list in case it has changed, but fill free to split it so
this patch don't become to big.

>> +done:
>> + dbus_message_unref(reply);
>> +}
>> +
>> +static gboolean get_playlists(gpointer user_data) {
>> + struct media_player *mp = user_data;
>> + DBusMessage *msg;
>> + DBusPendingCall *call;
>> + gboolean reverse_order = FALSE;
>> + uint32_t start_index = 0x0000;
>> + uint32_t end_index = 0xFFFF;
>> + char *ordering = "Alphabetical";
>> + uint32_t max_count = (end_index - start_index);
>> +
>> + msg = dbus_message_new_method_call(mp->sender, mp->path,
>> + MEDIA_PLAYER_PLAYLIST_INTERFACE,
>> + "GetPlaylists");
>> +
>> + if (msg == NULL) {
>> + error("Couldn't allocate D-Bus message");
>> + return -ENOMEM;
>> + }
>> +
>> + dbus_message_append_args(msg,
>> + DBUS_TYPE_UINT32, &start_index,
>> + DBUS_TYPE_UINT32, &max_count,
>> + DBUS_TYPE_STRING, &ordering,
>> + DBUS_TYPE_BOOLEAN, &reverse_order,
>> + DBUS_TYPE_INVALID);
>> +
>> + mp->total_items = 0;
>> +
>> + if ((dbus_connection_send_with_reply(btd_get_dbus_connection(),
>> + msg, &call, -1))) {
>> + dbus_pending_call_set_notify(call, &playlist_reply, mp,
>> NULL);
>> + dbus_pending_call_unref(call);
>> + dbus_message_unref(msg);
>> + }

You need to save the DBusPendingCall call to be able to cancel the
request in case something happens in the meantime, there is similar
code already in the same file just look at e.g.
media_endpoint_async_call. (note that I don't want you to copy it
exactly but it might be a good idea to have pending request for media
players but if is just GetPlaylist Im fine to just store it inside
media_player structure).

>> + mp->playlist_id = 0;
>> +
>> + return FALSE;
>> +}
>> +
>> static struct avrcp_player_cb player_cb = {
>> .list_settings = list_settings,
>> .get_setting = get_setting,
>> @@ -1831,6 +1950,8 @@ static DBusMessage
>> *register_player(DBusConnection *conn, DBusMessage *msg,
>> return btd_error_invalid_args(msg);
>> }
>>
>> + mp->playlist_id = g_idle_add(get_playlists, mp);

Since you won't block anymore the call to get_playlists can be done
directly here.

>> +
>> return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); }
>>
>> --
>> 1.9.1
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth"
> in
>> the body of a message to [email protected] More majordomo
>> info at http://vger.kernel.org/majordomo-info.html
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html



--
Luiz Augusto von Dentz

2015-10-19 09:02:14

by Bharat Bhusan Panda

[permalink] [raw]
Subject: RE: [PATCH 1/3] aurdio/avrcp: Get player playlist details

Ping

> -----Original Message-----
> From: [email protected] [mailto:linux-bluetooth-
> [email protected]] On Behalf Of Bharat Panda
> Sent: Tuesday, October 06, 2015 5:23 PM
> To: [email protected]
> Cc: [email protected]; Bharat Panda
> Subject: [PATCH 1/3] aurdio/avrcp: Get player playlist details
>
> Support added to read and cache player playlist details after player
> registration completes.
> ---
> profiles/audio/media.c | 121
> +++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 121 insertions(+)
>
> diff --git a/profiles/audio/media.c b/profiles/audio/media.c index
> 69070bf..edeb66f 100644
> --- a/profiles/audio/media.c
> +++ b/profiles/audio/media.c
> @@ -58,6 +58,7 @@
> #define MEDIA_INTERFACE "org.bluez.Media1"
> #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
> #define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
> +#define MEDIA_PLAYER_PLAYLIST_INTERFACE
> "org.mpris.MediaPlayer2.Playlists"
>
> #define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */
>
> @@ -95,6 +96,7 @@ struct media_endpoint { struct media_player {
> struct media_adapter *adapter;
> struct avrcp_player *player;
> + GSList *playlists;
> char *sender; /* Player DBus bus id */
> char *path; /* Player object path */
> GHashTable *settings; /* Player settings */
> @@ -105,8 +107,10 @@ struct media_player {
> char *status;
> uint32_t position;
> uint32_t duration;
> + uint32_t total_items;
> uint8_t volume;
> GTimer *timer;
> + guint playlist_id;
> bool play;
> bool pause;
> bool next;
> @@ -115,6 +119,13 @@ struct media_player {
> char *name;
> };
>
> +struct media_playlist {
> + char *name;
> + char *id;
> + char *icon;
> + uint8_t data[0];
> +} __attribute__ ((packed));
> +
> static GSList *adapters = NULL;
>
> static void endpoint_request_free(struct endpoint_request *request) @@ -
> 961,6 +972,9 @@ static void media_player_free(gpointer data)
> if (mp->settings)
> g_hash_table_unref(mp->settings);
>
> + if (mp->playlist_id > 0)
> + g_source_remove(mp->playlist_id);
> +
> g_timer_destroy(mp->timer);
> g_free(mp->sender);
> g_free(mp->path);
> @@ -1271,6 +1285,111 @@ static bool previous(void *user_data)
> return media_player_send(mp, "Previous"); }
>
> +
> +static void playlist_reply(DBusPendingCall *call, void *user_data) {
> + struct media_player *mp = user_data;
> + DBusMessage *reply;
> + DBusMessageIter array_iter, entry, struct_iter;
> + DBusError derr;
> + int ctype;
> +
> + reply = dbus_pending_call_steal_reply(call);
> +
> + dbus_error_init(&derr);
> +
> + if (dbus_set_error_from_message(&derr, reply)) {
> + error("Error: GetPlayLists method %s, %s", derr.name,
> + derr.message);
> + dbus_error_free(&derr);
> + goto done;
> + }
> +
> + dbus_message_iter_init(reply, &array_iter);
> + ctype = dbus_message_iter_get_arg_type(&array_iter);
> +
> + if (ctype != DBUS_TYPE_ARRAY)
> + goto done;
> +
> + dbus_message_iter_recurse(&array_iter, &struct_iter);
> +
> + while ((ctype = dbus_message_iter_get_arg_type(&struct_iter)) !=
> + DBUS_TYPE_INVALID) {
> + struct media_playlist *playlist;
> +
> + if (ctype != DBUS_TYPE_STRUCT) {
> + error("Invalid type");
> + goto done;
> + }
> +
> + playlist = g_new0(struct media_playlist, 1);
> +
> + dbus_message_iter_recurse(&struct_iter, &entry);
> +
> + dbus_message_iter_get_basic(&entry, &playlist->id);
> + DBG("Playlists Id %s", playlist->id);
> +
> + dbus_message_iter_next(&entry);
> + dbus_message_iter_get_basic(&entry, &playlist->name);
> + DBG("Playlist Name %s", playlist->name);
> +
> + dbus_message_iter_next(&entry);
> + dbus_message_iter_get_basic(&entry, &playlist->icon);
> + DBG("Playlist Icon %s", playlist->icon);
> +
> + /* TODO: Create Media folder with playlist information */
> +
> + mp->playlists = g_slist_append(mp->playlists, playlist);
> + mp->total_items++;
> +
> + dbus_message_iter_next(&struct_iter);
> + }
> +
> +done:
> + dbus_message_unref(reply);
> +}
> +
> +static gboolean get_playlists(gpointer user_data) {
> + struct media_player *mp = user_data;
> + DBusMessage *msg;
> + DBusPendingCall *call;
> + gboolean reverse_order = FALSE;
> + uint32_t start_index = 0x0000;
> + uint32_t end_index = 0xFFFF;
> + char *ordering = "Alphabetical";
> + uint32_t max_count = (end_index - start_index);
> +
> + msg = dbus_message_new_method_call(mp->sender, mp->path,
> + MEDIA_PLAYER_PLAYLIST_INTERFACE,
> + "GetPlaylists");
> +
> + if (msg == NULL) {
> + error("Couldn't allocate D-Bus message");
> + return -ENOMEM;
> + }
> +
> + dbus_message_append_args(msg,
> + DBUS_TYPE_UINT32, &start_index,
> + DBUS_TYPE_UINT32, &max_count,
> + DBUS_TYPE_STRING, &ordering,
> + DBUS_TYPE_BOOLEAN, &reverse_order,
> + DBUS_TYPE_INVALID);
> +
> + mp->total_items = 0;
> +
> + if ((dbus_connection_send_with_reply(btd_get_dbus_connection(),
> + msg, &call, -1))) {
> + dbus_pending_call_set_notify(call, &playlist_reply, mp,
> NULL);
> + dbus_pending_call_unref(call);
> + dbus_message_unref(msg);
> + }
> +
> + mp->playlist_id = 0;
> +
> + return FALSE;
> +}
> +
> static struct avrcp_player_cb player_cb = {
> .list_settings = list_settings,
> .get_setting = get_setting,
> @@ -1831,6 +1950,8 @@ static DBusMessage
> *register_player(DBusConnection *conn, DBusMessage *msg,
> return btd_error_invalid_args(msg);
> }
>
> + mp->playlist_id = g_idle_add(get_playlists, mp);
> +
> return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); }
>
> --
> 1.9.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth"
in
> the body of a message to [email protected] More majordomo
> info at http://vger.kernel.org/majordomo-info.html


2015-10-06 11:53:18

by Bharat Bhusan Panda

[permalink] [raw]
Subject: [PATCH 3/3] audio/avrcp: Add support for SetBrowsedPlayer

Support added to handle SetBrowsedPlayer command for TG role.

AVCTP Browsing: Response: type 0x00 label 13 PID 0x110e
AVRCP: SetBrowsedPlayer: len 0x000a
Status: 0x04 (Success)
UIDCounter: 0x0000 (0)
Number of Items: 0x00000007 (7)
CharsetID: 0x006a (UTF-8)
Folder Depth: 0x00 (0)
---
profiles/audio/avrcp.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++-
profiles/audio/avrcp.h | 1 +
profiles/audio/media.c | 7 ++++++
3 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c
index 44f8929..af40b59 100644
--- a/profiles/audio/avrcp.c
+++ b/profiles/audio/avrcp.c
@@ -207,6 +207,15 @@ struct player_item {
char name[0];
} __attribute__ ((packed));

+struct avrcp_set_browsed_player_rsp {
+ uint8_t status;
+ uint16_t uid_counter;
+ uint32_t num_of_items;
+ uint16_t charset_id;
+ uint8_t folder_depth;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
struct avrcp_server {
struct btd_adapter *adapter;
uint32_t tg_record_id;
@@ -1873,6 +1882,54 @@ err_metadata:
return AVRCP_HEADER_LENGTH + 1;
}

+static void avrcp_handle_set_browsed_player(struct avrcp *session,
+ struct avrcp_browsing_header *pdu,
+ uint8_t transaction)
+{
+ struct avrcp_player *player;
+ struct media_player *mp;
+ uint16_t len = ntohs(pdu->param_len);
+ uint16_t player_id = 0;
+ uint8_t status;
+
+ if (len < 1) {
+ status = AVRCP_STATUS_INVALID_PARAM;
+ goto failed;
+ }
+
+ player_id = bt_get_be16(&pdu->params[0]);
+ player = find_tg_player(session, player_id);
+
+ if (player) {
+ struct avrcp_set_browsed_player_rsp *resp;
+
+ mp = player->user_data;
+ player->browsed = true;
+
+ resp = (struct avrcp_set_browsed_player_rsp *) pdu->params;
+
+ resp->status = AVRCP_STATUS_SUCCESS;
+ resp->uid_counter = htons(player_get_uid_counter(player));
+ resp->num_of_items = htonl(player->cb->get_total_items(mp));
+ /*
+ * MPRIS player returns a flat hierarchy playlist structure,
+ * where the folder depth will always considered to be 0.
+ */
+ resp->folder_depth = 0;
+ resp->charset_id = htons(AVRCP_CHARSET_UTF8);
+ pdu->param_len = htons(sizeof(*resp));
+ } else {
+ status = AVRCP_STATUS_INVALID_PLAYER_ID;
+ goto failed;
+ }
+
+ return;
+
+failed:
+ pdu->params[0] = status;
+ pdu->param_len = htons(1);
+}
+
static void avrcp_handle_media_player_list(struct avrcp *session,
struct avrcp_browsing_header *pdu,
uint32_t start_item, uint32_t end_item)
@@ -1912,7 +1969,6 @@ static void avrcp_handle_media_player_list(struct avrcp *session,
memcpy(&item->features, &features, sizeof(features));

item->charset = htons(AVRCP_CHARSET_UTF8);
-
name = player->cb->get_name(player->user_data);
namelen = strlen(name);
item->namelen = htons(namelen);
@@ -1989,6 +2045,7 @@ static struct browsing_pdu_handler {
uint8_t transaction);
} browsing_handlers[] = {
{ AVRCP_GET_FOLDER_ITEMS, avrcp_handle_get_folder_items },
+ { AVRCP_SET_BROWSED_PLAYER, avrcp_handle_set_browsed_player},
{ },
};

diff --git a/profiles/audio/avrcp.h b/profiles/audio/avrcp.h
index 86d310c..c09b910 100644
--- a/profiles/audio/avrcp.h
+++ b/profiles/audio/avrcp.h
@@ -101,6 +101,7 @@ struct avrcp_player_cb {
bool (*pause) (void *user_data);
bool (*next) (void *user_data);
bool (*previous) (void *user_data);
+ uint32_t (*get_total_items) (void *user_data);
};

int avrcp_set_volume(struct btd_device *dev, uint8_t volume, bool notify);
diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index 2df0cb2..3196b3a 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -1287,6 +1287,12 @@ static bool previous(void *user_data)
return media_player_send(mp, "Previous");
}

+static uint32_t get_total_items(void *user_data)
+{
+ struct media_player *mp = user_data;
+
+ return mp->total_items;
+}

static void playlist_reply(DBusPendingCall *call, void *user_data)
{
@@ -1409,6 +1415,7 @@ static struct avrcp_player_cb player_cb = {
.pause = pause,
.next = next,
.previous = previous,
+ .get_total_items = get_total_items,
};

static void media_player_exit(DBusConnection *connection, void *user_data)
--
1.9.1


2015-10-06 11:53:17

by Bharat Bhusan Panda

[permalink] [raw]
Subject: [PATCH 2/3] audio/avrcp: Handle PlaylistChanged signal

Added signal handler for "PlaylistChanged" on MediaPlayer2.Playlists
interface.
---
profiles/audio/media.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)

diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index edeb66f..2df0cb2 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -104,6 +104,7 @@ struct media_player {
guint watch;
guint properties_watch;
guint seek_watch;
+ guint playlist_watch;
char *status;
uint32_t position;
uint32_t duration;
@@ -965,6 +966,7 @@ static void media_player_free(gpointer data)
g_dbus_remove_watch(conn, mp->watch);
g_dbus_remove_watch(conn, mp->properties_watch);
g_dbus_remove_watch(conn, mp->seek_watch);
+ g_dbus_remove_watch(conn, mp->playlist_watch);

if (mp->track)
g_hash_table_unref(mp->track);
@@ -1871,6 +1873,51 @@ static gboolean position_changed(DBusConnection *connection, DBusMessage *msg,
return TRUE;
}

+static gboolean playlist_changed(DBusConnection *connection,
+ DBusMessage *msg,
+ void *user_data)
+{
+ struct media_player *mp = user_data;
+ DBusMessageIter iter;
+ DBusMessageIter entry;
+ char *playlist_id;
+ GSList *l;
+
+ dbus_message_iter_init(msg, &iter);
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRUCT)
+ return FALSE;
+
+ dbus_message_iter_recurse(&iter, &entry);
+
+ if (dbus_message_iter_get_arg_type(&entry) !=
+ DBUS_TYPE_OBJECT_PATH)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&entry, &playlist_id);
+
+ for (l = mp->playlists; l; l = l->next) {
+ struct media_playlist *playlist = l->data;
+
+ if (g_strcmp0(playlist->id, playlist_id) == 0) {
+ dbus_message_iter_next(&entry);
+ if (dbus_message_iter_get_arg_type(&entry) !=
+ DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&entry, &playlist->name);
+
+ dbus_message_iter_next(&entry);
+ if (dbus_message_iter_get_arg_type(&entry) !=
+ DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&entry, &playlist->icon);
+ }
+ }
+
+ return TRUE;
+}
+
static struct media_player *media_player_create(struct media_adapter *adapter,
const char *sender,
const char *path,
@@ -1950,6 +1997,13 @@ static DBusMessage *register_player(DBusConnection *conn, DBusMessage *msg,
return btd_error_invalid_args(msg);
}

+ mp->playlist_watch = g_dbus_add_signal_watch(conn,
+ sender, path,
+ MEDIA_PLAYER_PLAYLIST_INTERFACE,
+ "PlaylistChanged",
+ playlist_changed,
+ mp, NULL);
+
mp->playlist_id = g_idle_add(get_playlists, mp);

return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
--
1.9.1