From 902a2113de06b385583cbe772a98f677fefef2c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 25 Jun 2021 17:52:55 +0200 Subject: [PATCH] spa: bluez: dbus: follow BlueZ's MediaPlayer1 objects Follow MediaPlayer1 objects in spa_bt_player objects and collect the players of a device in a list in the device. Add three new events to spa_bt_device: player_{added,changed,removed}. See https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/media-api.txt --- spa/plugins/bluez5/bluez5-dbus.c | 287 ++++++++++++++++++++++++++++++- spa/plugins/bluez5/defs.h | 51 ++++++ 2 files changed, 337 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index a03eb1602..f118ae82d 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -77,6 +77,7 @@ struct spa_bt_monitor { struct spa_list device_list; struct spa_list remote_endpoint_list; struct spa_list transport_list; + struct spa_list player_list; unsigned int filters_added:1; unsigned int objects_listed:1; @@ -749,6 +750,238 @@ static void adapter_free(struct spa_bt_adapter *adapter) free(adapter); } +static void device_remove_player(struct spa_bt_player *player) +{ + if (player->device == NULL) + return; + + spa_list_remove(&player->device_link); + + spa_hook_list_call(&player->device->listener_list, + struct spa_bt_device_events, player_removed, SPA_VERSION_BT_DEVICE_EVENTS, + player); + + player->device = NULL; +} + +static void device_add_player(struct spa_bt_device *device, struct spa_bt_player *player) +{ + if (player->device == device) + return; + + device_remove_player(player); + + player->device = device; + if (player->device == NULL) + return; + + spa_list_append(&player->device->player_list, &player->device_link); + + spa_hook_list_call(&player->device->listener_list, + struct spa_bt_device_events, player_added, SPA_VERSION_BT_DEVICE_EVENTS, + player); +} + +static struct spa_bt_player *player_create(struct spa_bt_monitor *monitor, const char *path) +{ + struct spa_bt_player *p = calloc(1, sizeof(*p)); + + if (p == NULL) + return NULL; + + p->monitor = monitor; + p->path = strdup(path); + + spa_list_append(&monitor->player_list, &p->link); + + return p; +} + +static struct spa_bt_player *player_find(const struct spa_bt_monitor *monitor, const char *path) +{ + struct spa_bt_player *p; + + spa_list_for_each(p, &monitor->player_list, link) + if (spa_streq(p->path, path)) + return p; + + return NULL; +} + +static const char * const player_state_names[] = +{ + [SPA_BT_PLAYER_STATE_UNKNOWN] = "unknown", + [SPA_BT_PLAYER_STATE_PLAYING] = "playing", + [SPA_BT_PLAYER_STATE_STOPPED] = "stopped", + [SPA_BT_PLAYER_STATE_PAUSED] = "paused", + [SPA_BT_PLAYER_STATE_FORWARD_SEEK] = "forward-seek", + [SPA_BT_PLAYER_STATE_ERROR] = "error", +}; + +static enum spa_bt_player_state str_to_player_state(const char *s) +{ + size_t index; + + if (!spa_find_str_arr(player_state_names, s, strcasecmp, &index)) + return SPA_BT_PLAYER_STATE_UNKNOWN; + + return index; +} + +static const char * const player_type_names[] = +{ + [SPA_BT_PLAYER_TYPE_UNKNOWN] = "unknown", + [SPA_BT_PLAYER_TYPE_AUDIO] = "audio", + [SPA_BT_PLAYER_TYPE_VIDEO] = "video", + [SPA_BT_PLAYER_TYPE_AUDIO_BROADCASTING] = "audio broadcasting", + [SPA_BT_PLAYER_TYPE_VIDEO_BROADCASTING] = "video broadcasting", +}; + +static enum spa_bt_player_type str_to_player_type(const char *s) +{ + size_t index; + + if (!spa_find_str_arr(player_type_names, s, strcasecmp, &index)) + return SPA_BT_PLAYER_TYPE_UNKNOWN; + + return index; +} + +static void player_parse_track(struct spa_bt_player *player, DBusMessageIter *props_iter) +{ + struct spa_bt_monitor *monitor = player->monitor; + + while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { + DBusMessageIter it[2]; + const char *key; + int type; + + dbus_message_iter_recurse(props_iter, &it[0]); + dbus_message_iter_get_basic(&it[0], &key); + dbus_message_iter_next(&it[0]); + dbus_message_iter_recurse(&it[0], &it[1]); + + type = dbus_message_iter_get_arg_type(&it[1]); + + if (type == DBUS_TYPE_STRING) { + const char *value; + + dbus_message_iter_get_basic(&it[1], &value); + + spa_log_debug(monitor->log, "player %p track: %s=%s", player, key, value); + + if (spa_streq(key, "Title")) { + free(player->track.title); + player->track.title = strdup(value); + } + else if (spa_streq(key, "Artist")) { + free(player->track.artist); + player->track.artist = strdup(value); + } + else if (spa_streq(key, "Album")) { + free(player->track.album); + player->track.album = strdup(value); + } + else if (spa_streq(key, "Genre")) { + free(player->track.genre); + player->track.genre = strdup(value); + } + else if (spa_streq(key, "TrackNumber")) { + if (!spa_atou32(value, &player->track.number, 10)) + player->track.number = -1; + } + else if (spa_streq(key, "Duration")) { + if (!spa_atou64(value, &player->track.duration, 10)) + player->track.duration = -1; + } + } + + dbus_message_iter_next(props_iter); + } +} + +static int player_update_props(struct spa_bt_player *player, + DBusMessageIter *props_iter, + DBusMessageIter *invalidated_iter) +{ + struct spa_bt_monitor * const monitor = player->monitor; + + while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { + DBusMessageIter it[2]; + const char *key; + int type; + + dbus_message_iter_recurse(props_iter, &it[0]); + dbus_message_iter_get_basic(&it[0], &key); + dbus_message_iter_next(&it[0]); + dbus_message_iter_recurse(&it[0], &it[1]); + + type = dbus_message_iter_get_arg_type(&it[1]); + + if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { + const char *value; + + dbus_message_iter_get_basic(&it[1], &value); + + spa_log_debug(monitor->log, "player %p: %s=%s", player, key, value); + + if (spa_streq(key, "Status")) { + player->status = str_to_player_state(value); + } + else if (spa_streq(key, "Device")) { + struct spa_bt_device *d = spa_bt_device_find(monitor, value); + + spa_log_debug(monitor->log, "player %p: device -> %p", player, d); + + device_add_player(d, player); + } + else if (spa_streq(key, "Name")) { + free(player->name); + player->name = strdup(value); + } + else if (spa_streq(key, "Type")) { + player->type = str_to_player_type(value); + } + } + else if (type == DBUS_TYPE_ARRAY) { + if (spa_streq(key, "Track")) { + DBusMessageIter dict_iter; + + dbus_message_iter_recurse(&it[1], &dict_iter); + player_parse_track(player, &dict_iter); + } + } + + dbus_message_iter_next(props_iter); + } + + if (player->device != NULL) + spa_hook_list_call(&player->device->listener_list, + struct spa_bt_device_events, player_changed, SPA_VERSION_BT_DEVICE_EVENTS, + player); + + return 0; +} + +static void player_free(struct spa_bt_player *player) +{ + struct spa_bt_monitor *monitor = player->monitor; + + spa_log_debug(monitor->log, "%p", player); + + device_remove_player(player); + + spa_list_remove(&player->link); + + free(player->track.title); + free(player->track.artist); + free(player->track.album); + free(player->track.genre); + free(player->path); + free(player->name); + free(player); +} + struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_device *d; @@ -785,6 +1018,7 @@ static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const spa_list_init(&d->remote_endpoint_list); spa_list_init(&d->transport_list); spa_list_init(&d->codec_switch_list); + spa_list_init(&d->player_list); spa_hook_list_init(&d->listener_list); @@ -808,6 +1042,7 @@ static void device_free(struct spa_bt_device *device) struct spa_bt_remote_endpoint *ep, *tep; struct spa_bt_a2dp_codec_switch *sw; struct spa_bt_transport *t, *tt; + struct spa_bt_player *p, *tp; struct spa_bt_monitor *monitor = device->monitor; spa_log_debug(monitor->log, "%p", device); @@ -838,6 +1073,11 @@ static void device_free(struct spa_bt_device *device) spa_list_consume(sw, &device->codec_switch_list, device_link) a2dp_codec_switch_free(sw); + spa_list_for_each_safe(p, tp, &device->player_list, device_link) { + spa_assert(p->device == device); + device_remove_player(p); + } + spa_list_remove(&device->link); free(device->path); free(device->alias); @@ -3353,6 +3593,21 @@ static void interface_added(struct spa_bt_monitor *monitor, if (d) spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); } + else if (spa_streq(interface_name, BLUEZ_MEDIA_PLAYER_INTERFACE)) { + struct spa_bt_player *p; + + p = player_find(monitor, object_path); + if (p == NULL) { + p = player_create(monitor, object_path); + if (p == NULL) { + spa_log_warn(monitor->log, "can't create Bluetooth player %s: %m", + object_path); + return; + } + } + + player_update_props(p, props_iter, NULL); + } } static void interfaces_added(struct spa_bt_monitor *monitor, DBusMessageIter *arg_iter) @@ -3415,6 +3670,12 @@ static void interfaces_removed(struct spa_bt_monitor *monitor, DBusMessageIter * if (d) spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); } + } else if (spa_streq(interface_name, BLUEZ_MEDIA_PLAYER_INTERFACE)) { + struct spa_bt_player *p; + + p = player_find(monitor, object_path); + if (p != NULL) + player_free(p); } dbus_message_iter_next(&it); @@ -3515,6 +3776,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us struct spa_bt_device *d; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; + struct spa_bt_player *p; monitor->objects_listed = false; @@ -3531,6 +3793,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us device_free(d); spa_list_consume(a, &monitor->adapter_list, link) adapter_free(a); + spa_list_consume(p, &monitor->player_list, link) + player_free(p); } if (has_new_owner) { @@ -3686,7 +3950,20 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us transport_update_props(transport, &it[1], NULL); } - } + else if (spa_streq(iface, BLUEZ_MEDIA_PLAYER_INTERFACE)) { + struct spa_bt_player *p; + + p = player_find(monitor, path); + if (p == NULL) { + spa_log_warn(monitor->log, "properties changed in unknown player %s", path); + goto finish; + } + + spa_log_debug(monitor->log, "properties changed on player %s", path); + + player_update_props(p, &it[1], NULL); + } + } fail: dbus_error_free(&err); @@ -3746,6 +4023,10 @@ static void add_filters(struct spa_bt_monitor *this) "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," "arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" BLUEZ_SERVICE "'," + "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," + "arg0='" BLUEZ_MEDIA_PLAYER_INTERFACE "'", &err); this->filters_added = true; @@ -3810,6 +4091,7 @@ static int impl_clear(struct spa_handle *handle) struct spa_bt_device *d; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; + struct spa_bt_player *p; monitor = (struct spa_bt_monitor *) handle; @@ -3834,6 +4116,8 @@ static int impl_clear(struct spa_handle *handle) device_free(d); spa_list_consume(a, &monitor->adapter_list, link) adapter_free(a); + spa_list_consume(p, &monitor->player_list, link) + player_free(p); if (monitor->backend_native) { spa_bt_backend_free(monitor->backend_native); @@ -4035,6 +4319,7 @@ impl_init(const struct spa_handle_factory *factory, spa_list_init(&this->device_list); spa_list_init(&this->remote_endpoint_list); spa_list_init(&this->transport_list); + spa_list_init(&this->player_list); if ((res = parse_codec_array(this, info)) < 0) return res; diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 30d8fd97a..a9f67f0ab 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -50,6 +50,7 @@ extern "C" { #define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1" #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1" #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" +#define BLUEZ_MEDIA_PLAYER_INTERFACE BLUEZ_SERVICE ".MediaPlayer1" #define BLUEZ_INTERFACE_BATTERY_PROVIDER BLUEZ_SERVICE ".BatteryProvider1" #define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER BLUEZ_SERVICE ".BatteryProviderManager1" @@ -390,6 +391,7 @@ static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t blu struct spa_bt_a2dp_codec_switch; struct spa_bt_transport; +struct spa_bt_player; struct spa_bt_device_events { #define SPA_VERSION_BT_DEVICE_EVENTS 0 @@ -406,6 +408,12 @@ struct spa_bt_device_events { /** Device freed */ void (*destroy) (void *data); + + void (*player_added) (void *data, struct spa_bt_player *player); + + void (*player_changed) (void *data, struct spa_bt_player *player); + + void (*player_removed) (void *data, struct spa_bt_player *player); }; struct spa_bt_device { @@ -439,6 +447,7 @@ struct spa_bt_device { struct spa_list remote_endpoint_list; struct spa_list transport_list; struct spa_list codec_switch_list; + struct spa_list player_list; uint8_t battery; int has_battery; @@ -623,6 +632,48 @@ static inline enum spa_bt_transport_state spa_bt_transport_state_from_string(con return SPA_BT_TRANSPORT_STATE_IDLE; } +enum spa_bt_player_state { + SPA_BT_PLAYER_STATE_UNKNOWN, + SPA_BT_PLAYER_STATE_PLAYING, + SPA_BT_PLAYER_STATE_STOPPED, + SPA_BT_PLAYER_STATE_PAUSED, + SPA_BT_PLAYER_STATE_FORWARD_SEEK, + SPA_BT_PLAYER_STATE_ERROR, +}; + +enum spa_bt_player_type { + SPA_BT_PLAYER_TYPE_UNKNOWN, + SPA_BT_PLAYER_TYPE_AUDIO, + SPA_BT_PLAYER_TYPE_VIDEO, + SPA_BT_PLAYER_TYPE_AUDIO_BROADCASTING, + SPA_BT_PLAYER_TYPE_VIDEO_BROADCASTING, +}; + +struct spa_bt_player { + struct spa_list link; + + struct spa_bt_monitor *monitor; + + struct spa_bt_device *device; + struct spa_list device_link; + + char *name; + char *path; + + struct { + uint32_t number; + uint64_t duration; + + char *title; + char *artist; + char *album; + char *genre; + } track; + + enum spa_bt_player_state status; + enum spa_bt_player_type type; +}; + #define DEFAULT_AG_VOLUME 1.0f #define DEFAULT_RX_VOLUME 1.0f #define DEFAULT_TX_VOLUME 0.064f /* pa_sw_volume_to_linear(0.4 * PA_VOLUME_NORM) */