From 2702c40734efa883bfd1f0cbc4f08a8fe12f1a2a Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Wed, 7 Nov 2018 19:08:06 -0800 Subject: binding: bluetooth: add MediaPlayer1 interface media events Report back MediaPlayer1 interface events for A2DP media playback Bug-AGL: SPEC-1630 Change-Id: I777f19af2de247fe676d5bede4bacbbad8ca9844 Signed-off-by: Matt Ranostay --- README.md | 25 ++++++++++ binding/bluetooth-api.c | 112 +++++++++++++++++++++++++++++++++++++++------ binding/bluetooth-api.h | 68 +++++++++++++++++++-------- binding/bluetooth-bluez.c | 26 +++++++++++ binding/bluetooth-common.h | 4 ++ binding/bluetooth-util.c | 4 +- 6 files changed, 204 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 24ea06b..e6997a2 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ To do the same for the respective device, verb, and for singular profile | Name | Description | JSON Event Data | |-------------------|------------------------------------------|-------------------------------------------| | device_changes | report on bluetooth devices | see device_changes event section | +| media | report on MediaPlayer1 events | see media event section | | agent | PIN from BlueZ agent for confirmation | see agent event section | @@ -211,6 +212,30 @@ Changed status events for a device: } +### media event + +Playing audio reporting event (not all fields will be passed in every event): + +
+{
+        "adapter": "hci0",
+        "device": "dev_D0_81_7A_5A_BC_5E",
+        "track": {
+                "title": "True Colors",
+                "duration": 228000,
+                "album": "True Colors",
+                "tracknumber": 6,
+                "artist": "Zedd",
+                "numberoftracks": 11,
+                "genre": "Dance & DJ/General"
+        },
+        "position": 5600,
+        "status": "playing"
+        "connected": false
+}
+
+ + ### agent event After pairing request agent will send event for a pincode that must be confirmed on both sides: diff --git a/binding/bluetooth-api.c b/binding/bluetooth-api.c index 7e3e1eb..0000d9b 100644 --- a/binding/bluetooth-api.c +++ b/binding/bluetooth-api.c @@ -58,6 +58,13 @@ void call_work_unlock(struct bluetooth_state *ns) g_mutex_unlock(&ns->cw_mutex); } +static void mediaplayer1_set_path(struct bluetooth_state *ns, const char *path) +{ + if (ns->mediaplayer_path) + g_free(ns->mediaplayer_path); + ns->mediaplayer_path = g_strdup(path); +} + struct call_work *call_work_lookup_unlocked( struct bluetooth_state *ns, const char *access_type, const char *type_arg, @@ -226,6 +233,9 @@ static afb_event_t get_event_from_value(struct bluetooth_state *ns, if (!g_strcmp0(value, "device_changes")) return ns->device_changes_event; + if (!g_strcmp0(value, "media")) + return ns->media_event; + if (!g_strcmp0(value, "agent")) return ns->agent_event; @@ -248,8 +258,9 @@ static void bluez_devices_signal_callback( const gchar *path = NULL; const gchar *key = NULL; json_object *jresp = NULL, *jobj; - GVariantIter *array; + GVariantIter *array = NULL; gboolean is_config, ret; + afb_event_t event = ns->device_changes_event; /* AFB_INFO("sender=%s", sender_name); AFB_INFO("object_path=%s", object_path); @@ -263,8 +274,6 @@ static void bluez_devices_signal_callback( jresp = json_object_new_object(); json_process_path(jresp, path); - json_object_object_add(jresp, "action", - json_object_new_string("added")); jobj = json_object_new_object(); @@ -293,7 +302,16 @@ static void bluez_devices_signal_callback( g_variant_iter_free(array); if (array1) { + json_object_object_add(jresp, "action", + json_object_new_string("added")); json_object_object_add(jresp, "properties", jobj); + } else if (is_mediaplayer1_interface(path) && + g_str_has_suffix(path, BLUEZ_DEFAULT_PLAYER)) { + + json_object_object_add(jresp, "connected", + json_object_new_boolean(TRUE)); + mediaplayer1_set_path(ns, path); + event = ns->media_event; } else { json_object_put(jresp); jresp = NULL; @@ -305,10 +323,20 @@ static void bluez_devices_signal_callback( g_variant_iter_free(array); jresp = json_object_new_object(); - - json_process_path(jresp, path); - json_object_object_add(jresp, "action", + json_process_path(jresp, path); + + if (is_mediaplayer1_interface(path)) { + json_object_object_add(jresp, "connected", + json_object_new_boolean(FALSE)); + mediaplayer1_set_path(ns, NULL); + event = ns->media_event; + } else if (split_length(path) == 5) { + json_object_object_add(jresp, "action", json_object_new_string("removed")); + } else { + json_object_put(jresp); + jresp = NULL; + } } else if (!g_strcmp0(signal_name, "PropertiesChanged")) { @@ -347,6 +375,33 @@ static void bluez_devices_signal_callback( json_object_put(jresp); jresp = NULL; } + + } else if (!g_strcmp0(path, BLUEZ_MEDIAPLAYER_INTERFACE)) { + int cnt = 0; + jresp = json_object_new_object(); + json_process_path(jresp, object_path); + + while (g_variant_iter_next(array, "{&sv}", &key, &var)) { + ret = mediaplayer_property_dbus2json(jresp, + key, var, &is_config, &error); + g_variant_unref(var); + if (!ret) { + //AFB_WARNING("%s property %s - %s", + // "mediaplayer", + // key, error->message); + g_clear_error(&error); + continue; + } + cnt++; + } + + // NOTE: Possible to get a changed property for something we don't care about + if (!cnt) { + json_object_put(jresp); + jresp = NULL; + } + + event = ns->media_event; } g_variant_iter_free(array); @@ -354,7 +409,7 @@ static void bluez_devices_signal_callback( } if (jresp) { - afb_event_push(ns->device_changes_event, jresp); + afb_event_push(event, jresp); jresp = NULL; } @@ -390,10 +445,13 @@ static struct bluetooth_state *bluetooth_init(GMainLoop *loop) ns->device_changes_event = afb_daemon_make_event("device_changes"); + ns->media_event = + afb_daemon_make_event("media"); ns->agent_event = afb_daemon_make_event("agent"); if (!afb_event_is_valid(ns->device_changes_event) || + !afb_event_is_valid(ns->media_event) || !afb_event_is_valid(ns->agent_event)) { AFB_ERROR("Cannot create events"); goto err_no_events; @@ -535,6 +593,24 @@ static int init(afb_api_t api) return id->rc; } +static void mediaplayer1_send_event(struct bluetooth_state *ns) +{ + gchar *player = g_strdup(ns->mediaplayer_path); + json_object *jresp = mediaplayer_properties(ns, NULL, player); + + if (!jresp) + goto out_err; + + json_process_path(jresp, player); + json_object_object_add(jresp, "connected", + json_object_new_boolean(TRUE)); + + afb_event_push(ns->media_event, jresp); + +out_err: + g_free(player); +} + static void bluetooth_subscribe_unsubscribe(afb_req_t request, gboolean unsub) { @@ -558,10 +634,14 @@ static void bluetooth_subscribe_unsubscribe(afb_req_t request, return; } - if (!unsub) + if (!unsub) { rc = afb_req_subscribe(request, event); - else + + if (!g_strcmp0(value, "media")) + mediaplayer1_send_event(ns); + } else { rc = afb_req_unsubscribe(request, event); + } if (rc != 0) { afb_req_fail_f(request, "failed", "%s error on \"value\" event \"%s\"", @@ -1043,15 +1123,19 @@ static void bluetooth_avrcp_controls(afb_req_t request) } device = return_bluez_path(request); - if (!device) { + if (device) { + /* TODO: handle multiple players per device */ + player = g_strconcat(device, "/", BLUEZ_DEFAULT_PLAYER, NULL); + g_free(device); + } else { + player = g_strdup(ns->mediaplayer_path); + } + + if (!player) { afb_req_fail(request, "failed", "No path given"); return; } - /* TODO: handle multiple players per device */ - player = g_strconcat(device, "/player0", NULL); - g_free(device); - reply = mediaplayer_call(ns, player, action, NULL, &error); if (!reply) { diff --git a/binding/bluetooth-api.h b/binding/bluetooth-api.h index 0d72afc..cd6a775 100644 --- a/binding/bluetooth-api.h +++ b/binding/bluetooth-api.h @@ -68,39 +68,56 @@ #define BLUEZ_AT_MEDIAPLAYER "mediaplayer" #define BLUEZ_DEFAULT_ADAPTER "hci0" +#define BLUEZ_DEFAULT_PLAYER "player0" struct bluetooth_state; -static inline gchar *bluez_return_adapter(const char *path) + +static inline int split_length(const char *path) { + gchar **strings = g_strsplit(path, "/", -1); + int ret = g_strv_length(strings) ; + + g_strfreev(strings); + return ret; +} + +static inline gchar *find_index(const char *path, int idx) { gchar **strings = g_strsplit(path, "/", -1); - gchar *adapter; + gchar *item = NULL; + + if (g_strv_length(strings) > idx) + item = g_strdup(strings[idx]); - if (g_strv_length(strings) < 3) { - g_strfreev(strings); - return NULL; - } - adapter = g_strdup(strings[3]); g_strfreev(strings); + return item; +} - return adapter; +static inline gchar *bluez_return_adapter(const char *path) +{ + return find_index(path, 3); } static inline gchar *bluez_return_device(const char *path) { - const char *basename; + return find_index(path, 4); +} + +static inline gboolean is_mediaplayer1_interface(const char *path) +{ + gchar *data = NULL; + gboolean ret; - basename = strrchr(path, '/'); - if (!basename) - return NULL; - basename++; + // Don't trigger on NowPlaying, Item, etc paths + if (split_length(path) != 6) + return FALSE; - /* be sure it is a bluez path with device */ - if (strncmp(basename, "dev_", 4)) - return NULL; + // TODO: allow mutiple players per device + data = find_index(path, 5); + ret = !g_strcmp0(data, BLUEZ_DEFAULT_PLAYER); + g_free(data); - /* at least one character */ - return *basename ? g_strdup(basename) : NULL; + return ret; } struct call_work *call_work_create_unlocked(struct bluetooth_state *ns, @@ -163,6 +180,14 @@ static inline gboolean agent_property_dbus2json(json_object *jprop, jprop, key, var, is_config, error); } +static inline gboolean mediaplayer_property_dbus2json(json_object *jprop, + const gchar *key, GVariant *var, gboolean *is_config, + GError **error) +{ + return bluez_property_dbus2json(BLUEZ_AT_MEDIAPLAYER, + jprop, key, var, is_config, error); +} + static inline GVariant *device_call(struct bluetooth_state *ns, const char *device, const char *method, GVariant *params, GError **error) @@ -216,6 +241,13 @@ static inline json_object *adapter_properties(struct bluetooth_state *ns, BLUEZ_AT_ADAPTER, adapter, error); } +static inline json_object *mediaplayer_properties(struct bluetooth_state *ns, + GError **error, const gchar *player) +{ + return bluez_get_properties(ns, + BLUEZ_AT_MEDIAPLAYER, player, error); +} + static inline json_object *object_properties(struct bluetooth_state *ns, GError **error) { diff --git a/binding/bluetooth-bluez.c b/binding/bluetooth-bluez.c index 824843b..1c46106 100644 --- a/binding/bluetooth-bluez.c +++ b/binding/bluetooth-bluez.c @@ -72,6 +72,26 @@ static const struct property_info device_props[] = { { }, }; +static const struct property_info mediaplayer_props[] = { + { .name = "Position", .fmt = "u", }, + { .name = "Status", .fmt = "s", }, + { + .name = "Track", + .fmt = "{sv}", + .sub = (const struct property_info []) { + { .name = "Title", .fmt = "s", }, + { .name = "Artist", .fmt = "s", }, + { .name = "Album", .fmt = "s", }, + { .name = "Genre", .fmt = "s", }, + { .name = "NumberOfTracks", .fmt = "u", }, + { .name = "TrackNumber", .fmt = "u", }, + { .name = "Duration", .fmt = "u", }, + { }, + }, + }, + { }, +}; + const struct property_info *bluez_get_property_info( const char *access_type, GError **error) { @@ -81,6 +101,8 @@ const struct property_info *bluez_get_property_info( pi = adapter_props; else if (!strcmp(access_type, BLUEZ_AT_DEVICE)) pi = device_props; + else if (!strcmp(access_type, BLUEZ_AT_MEDIAPLAYER)) + pi = mediaplayer_props; else g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, "illegal %s argument", access_type); @@ -324,6 +346,7 @@ json_object *bluez_get_properties(struct bluetooth_state *ns, gboolean is_config; if (!strcmp(access_type, BLUEZ_AT_DEVICE) || + !strcmp(access_type, BLUEZ_AT_MEDIAPLAYER) || !strcmp(access_type, BLUEZ_AT_ADAPTER)) { pi = bluez_get_property_info(access_type, error); @@ -341,6 +364,8 @@ json_object *bluez_get_properties(struct bluetooth_state *ns, if (!strcmp(access_type, BLUEZ_AT_DEVICE)) interface2 = BLUEZ_DEVICE_INTERFACE; + else if (!strcmp(access_type, BLUEZ_AT_MEDIAPLAYER)) + interface2 = BLUEZ_MEDIAPLAYER_INTERFACE; else if (!strcmp(access_type, BLUEZ_AT_ADAPTER)) interface2 = BLUEZ_ADAPTER_INTERFACE; else if (!strcmp(access_type, BLUEZ_AT_OBJECT)) @@ -365,6 +390,7 @@ json_object *bluez_get_properties(struct bluetooth_state *ns, return NULL; if (!strcmp(access_type, BLUEZ_AT_DEVICE) || + !strcmp(access_type, BLUEZ_AT_MEDIAPLAYER) || !strcmp(access_type, BLUEZ_AT_ADAPTER)) { jprop = json_object_new_object(); g_variant_get(reply, "(a{sv})", &array); diff --git a/binding/bluetooth-common.h b/binding/bluetooth-common.h index e6fb97a..68e747b 100644 --- a/binding/bluetooth-common.h +++ b/binding/bluetooth-common.h @@ -41,6 +41,7 @@ struct bluetooth_state { guint autoconnect_sub; afb_event_t device_changes_event; + afb_event_t media_event; afb_event_t agent_event; /* NOTE: single connection allowed for now */ @@ -56,6 +57,9 @@ struct bluetooth_state { guint registration_id; gchar *agent_path; gboolean agent_registered; + + /* mediaplayer */ + gchar *mediaplayer_path; }; struct init_data { diff --git a/binding/bluetooth-util.c b/binding/bluetooth-util.c index 963af82..e8a0781 100644 --- a/binding/bluetooth-util.c +++ b/binding/bluetooth-util.c @@ -1046,10 +1046,8 @@ gchar *return_bluez_path(afb_req_t request) { adapter = adapter ? adapter : BLUEZ_DEFAULT_ADAPTER; device = afb_req_value(request, "device"); - if (!device) { - afb_req_fail(request, "failed", "No device parameter"); + if (!device) return NULL; - } tmp = device; -- cgit 1.2.3-korg