mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-06-06 03:02:54 -04:00
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:
parent
e082e64c87
commit
902a2113de
2 changed files with 337 additions and 1 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue