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
This commit is contained in:
Barnabás Pőcze 2021-06-25 17:52:55 +02:00
parent e082e64c87
commit 902a2113de
2 changed files with 337 additions and 1 deletions

View file

@ -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;

View file

@ -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) */